You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							362 lines
						
					
					
						
							12 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							362 lines
						
					
					
						
							12 KiB
						
					
					
				
								package protocol
							 | 
						|
								
							 | 
						|
								import (
							 | 
						|
									"context"
							 | 
						|
									"encoding/binary"
							 | 
						|
									"net"
							 | 
						|
									"time"
							 | 
						|
								)
							 | 
						|
								
							 | 
						|
								// Kafka Protocol Error Codes
							 | 
						|
								// Based on Apache Kafka protocol specification
							 | 
						|
								const (
							 | 
						|
									// Success
							 | 
						|
									ErrorCodeNone int16 = 0
							 | 
						|
								
							 | 
						|
									// General server errors
							 | 
						|
									ErrorCodeUnknownServerError           int16 = -1
							 | 
						|
									ErrorCodeOffsetOutOfRange             int16 = 1
							 | 
						|
									ErrorCodeCorruptMessage               int16 = 3 // Also UNKNOWN_TOPIC_OR_PARTITION
							 | 
						|
									ErrorCodeUnknownTopicOrPartition      int16 = 3
							 | 
						|
									ErrorCodeInvalidFetchSize             int16 = 4
							 | 
						|
									ErrorCodeLeaderNotAvailable           int16 = 5
							 | 
						|
									ErrorCodeNotLeaderOrFollower          int16 = 6 // Formerly NOT_LEADER_FOR_PARTITION
							 | 
						|
									ErrorCodeRequestTimedOut              int16 = 7
							 | 
						|
									ErrorCodeBrokerNotAvailable           int16 = 8
							 | 
						|
									ErrorCodeReplicaNotAvailable          int16 = 9
							 | 
						|
									ErrorCodeMessageTooLarge              int16 = 10
							 | 
						|
									ErrorCodeStaleControllerEpoch         int16 = 11
							 | 
						|
									ErrorCodeOffsetMetadataTooLarge       int16 = 12
							 | 
						|
									ErrorCodeNetworkException             int16 = 13
							 | 
						|
									ErrorCodeOffsetLoadInProgress         int16 = 14
							 | 
						|
									ErrorCodeGroupLoadInProgress          int16 = 15
							 | 
						|
									ErrorCodeNotCoordinatorForGroup       int16 = 16
							 | 
						|
									ErrorCodeNotCoordinatorForTransaction int16 = 17
							 | 
						|
								
							 | 
						|
									// Consumer group coordination errors
							 | 
						|
									ErrorCodeIllegalGeneration          int16 = 22
							 | 
						|
									ErrorCodeInconsistentGroupProtocol  int16 = 23
							 | 
						|
									ErrorCodeInvalidGroupID             int16 = 24
							 | 
						|
									ErrorCodeUnknownMemberID            int16 = 25
							 | 
						|
									ErrorCodeInvalidSessionTimeout      int16 = 26
							 | 
						|
									ErrorCodeRebalanceInProgress        int16 = 27
							 | 
						|
									ErrorCodeInvalidCommitOffsetSize    int16 = 28
							 | 
						|
									ErrorCodeTopicAuthorizationFailed   int16 = 29
							 | 
						|
									ErrorCodeGroupAuthorizationFailed   int16 = 30
							 | 
						|
									ErrorCodeClusterAuthorizationFailed int16 = 31
							 | 
						|
									ErrorCodeInvalidTimestamp           int16 = 32
							 | 
						|
									ErrorCodeUnsupportedSASLMechanism   int16 = 33
							 | 
						|
									ErrorCodeIllegalSASLState           int16 = 34
							 | 
						|
									ErrorCodeUnsupportedVersion         int16 = 35
							 | 
						|
								
							 | 
						|
									// Topic management errors
							 | 
						|
									ErrorCodeTopicAlreadyExists        int16 = 36
							 | 
						|
									ErrorCodeInvalidPartitions         int16 = 37
							 | 
						|
									ErrorCodeInvalidReplicationFactor  int16 = 38
							 | 
						|
									ErrorCodeInvalidReplicaAssignment  int16 = 39
							 | 
						|
									ErrorCodeInvalidConfig             int16 = 40
							 | 
						|
									ErrorCodeNotController             int16 = 41
							 | 
						|
									ErrorCodeInvalidRecord             int16 = 42
							 | 
						|
									ErrorCodePolicyViolation           int16 = 43
							 | 
						|
									ErrorCodeOutOfOrderSequenceNumber  int16 = 44
							 | 
						|
									ErrorCodeDuplicateSequenceNumber   int16 = 45
							 | 
						|
									ErrorCodeInvalidProducerEpoch      int16 = 46
							 | 
						|
									ErrorCodeInvalidTxnState           int16 = 47
							 | 
						|
									ErrorCodeInvalidProducerIDMapping  int16 = 48
							 | 
						|
									ErrorCodeInvalidTransactionTimeout int16 = 49
							 | 
						|
									ErrorCodeConcurrentTransactions    int16 = 50
							 | 
						|
								
							 | 
						|
									// Connection and timeout errors
							 | 
						|
									ErrorCodeConnectionRefused int16 = 60 // Custom for connection issues
							 | 
						|
									ErrorCodeConnectionTimeout int16 = 61 // Custom for connection timeouts
							 | 
						|
									ErrorCodeReadTimeout       int16 = 62 // Custom for read timeouts
							 | 
						|
									ErrorCodeWriteTimeout      int16 = 63 // Custom for write timeouts
							 | 
						|
								
							 | 
						|
									// Consumer group specific errors
							 | 
						|
									ErrorCodeMemberIDRequired     int16 = 79
							 | 
						|
									ErrorCodeFencedInstanceID     int16 = 82
							 | 
						|
									ErrorCodeGroupMaxSizeReached  int16 = 84
							 | 
						|
									ErrorCodeUnstableOffsetCommit int16 = 95
							 | 
						|
								)
							 | 
						|
								
							 | 
						|
								// ErrorInfo contains metadata about a Kafka error
							 | 
						|
								type ErrorInfo struct {
							 | 
						|
									Code        int16
							 | 
						|
									Name        string
							 | 
						|
									Description string
							 | 
						|
									Retriable   bool
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// KafkaErrors maps error codes to their metadata
							 | 
						|
								var KafkaErrors = map[int16]ErrorInfo{
							 | 
						|
									ErrorCodeNone: {
							 | 
						|
										Code: ErrorCodeNone, Name: "NONE", Description: "No error", Retriable: false,
							 | 
						|
									},
							 | 
						|
									ErrorCodeUnknownServerError: {
							 | 
						|
										Code: ErrorCodeUnknownServerError, Name: "UNKNOWN_SERVER_ERROR",
							 | 
						|
										Description: "Unknown server error", Retriable: true,
							 | 
						|
									},
							 | 
						|
									ErrorCodeOffsetOutOfRange: {
							 | 
						|
										Code: ErrorCodeOffsetOutOfRange, Name: "OFFSET_OUT_OF_RANGE",
							 | 
						|
										Description: "Offset out of range", Retriable: false,
							 | 
						|
									},
							 | 
						|
									ErrorCodeUnknownTopicOrPartition: {
							 | 
						|
										Code: ErrorCodeUnknownTopicOrPartition, Name: "UNKNOWN_TOPIC_OR_PARTITION",
							 | 
						|
										Description: "Topic or partition does not exist", Retriable: false,
							 | 
						|
									},
							 | 
						|
									ErrorCodeInvalidFetchSize: {
							 | 
						|
										Code: ErrorCodeInvalidFetchSize, Name: "INVALID_FETCH_SIZE",
							 | 
						|
										Description: "Invalid fetch size", Retriable: false,
							 | 
						|
									},
							 | 
						|
									ErrorCodeLeaderNotAvailable: {
							 | 
						|
										Code: ErrorCodeLeaderNotAvailable, Name: "LEADER_NOT_AVAILABLE",
							 | 
						|
										Description: "Leader not available", Retriable: true,
							 | 
						|
									},
							 | 
						|
									ErrorCodeNotLeaderOrFollower: {
							 | 
						|
										Code: ErrorCodeNotLeaderOrFollower, Name: "NOT_LEADER_OR_FOLLOWER",
							 | 
						|
										Description: "Not leader or follower", Retriable: true,
							 | 
						|
									},
							 | 
						|
									ErrorCodeRequestTimedOut: {
							 | 
						|
										Code: ErrorCodeRequestTimedOut, Name: "REQUEST_TIMED_OUT",
							 | 
						|
										Description: "Request timed out", Retriable: true,
							 | 
						|
									},
							 | 
						|
									ErrorCodeBrokerNotAvailable: {
							 | 
						|
										Code: ErrorCodeBrokerNotAvailable, Name: "BROKER_NOT_AVAILABLE",
							 | 
						|
										Description: "Broker not available", Retriable: true,
							 | 
						|
									},
							 | 
						|
									ErrorCodeMessageTooLarge: {
							 | 
						|
										Code: ErrorCodeMessageTooLarge, Name: "MESSAGE_TOO_LARGE",
							 | 
						|
										Description: "Message size exceeds limit", Retriable: false,
							 | 
						|
									},
							 | 
						|
									ErrorCodeOffsetMetadataTooLarge: {
							 | 
						|
										Code: ErrorCodeOffsetMetadataTooLarge, Name: "OFFSET_METADATA_TOO_LARGE",
							 | 
						|
										Description: "Offset metadata too large", Retriable: false,
							 | 
						|
									},
							 | 
						|
									ErrorCodeNetworkException: {
							 | 
						|
										Code: ErrorCodeNetworkException, Name: "NETWORK_EXCEPTION",
							 | 
						|
										Description: "Network error", Retriable: true,
							 | 
						|
									},
							 | 
						|
									ErrorCodeOffsetLoadInProgress: {
							 | 
						|
										Code: ErrorCodeOffsetLoadInProgress, Name: "OFFSET_LOAD_IN_PROGRESS",
							 | 
						|
										Description: "Offset load in progress", Retriable: true,
							 | 
						|
									},
							 | 
						|
									ErrorCodeNotCoordinatorForGroup: {
							 | 
						|
										Code: ErrorCodeNotCoordinatorForGroup, Name: "NOT_COORDINATOR_FOR_GROUP",
							 | 
						|
										Description: "Not coordinator for group", Retriable: true,
							 | 
						|
									},
							 | 
						|
									ErrorCodeInvalidGroupID: {
							 | 
						|
										Code: ErrorCodeInvalidGroupID, Name: "INVALID_GROUP_ID",
							 | 
						|
										Description: "Invalid group ID", Retriable: false,
							 | 
						|
									},
							 | 
						|
									ErrorCodeUnknownMemberID: {
							 | 
						|
										Code: ErrorCodeUnknownMemberID, Name: "UNKNOWN_MEMBER_ID",
							 | 
						|
										Description: "Unknown member ID", Retriable: false,
							 | 
						|
									},
							 | 
						|
									ErrorCodeInvalidSessionTimeout: {
							 | 
						|
										Code: ErrorCodeInvalidSessionTimeout, Name: "INVALID_SESSION_TIMEOUT",
							 | 
						|
										Description: "Invalid session timeout", Retriable: false,
							 | 
						|
									},
							 | 
						|
									ErrorCodeRebalanceInProgress: {
							 | 
						|
										Code: ErrorCodeRebalanceInProgress, Name: "REBALANCE_IN_PROGRESS",
							 | 
						|
										Description: "Group rebalance in progress", Retriable: true,
							 | 
						|
									},
							 | 
						|
									ErrorCodeInvalidCommitOffsetSize: {
							 | 
						|
										Code: ErrorCodeInvalidCommitOffsetSize, Name: "INVALID_COMMIT_OFFSET_SIZE",
							 | 
						|
										Description: "Invalid commit offset size", Retriable: false,
							 | 
						|
									},
							 | 
						|
									ErrorCodeTopicAuthorizationFailed: {
							 | 
						|
										Code: ErrorCodeTopicAuthorizationFailed, Name: "TOPIC_AUTHORIZATION_FAILED",
							 | 
						|
										Description: "Topic authorization failed", Retriable: false,
							 | 
						|
									},
							 | 
						|
									ErrorCodeGroupAuthorizationFailed: {
							 | 
						|
										Code: ErrorCodeGroupAuthorizationFailed, Name: "GROUP_AUTHORIZATION_FAILED",
							 | 
						|
										Description: "Group authorization failed", Retriable: false,
							 | 
						|
									},
							 | 
						|
									ErrorCodeUnsupportedVersion: {
							 | 
						|
										Code: ErrorCodeUnsupportedVersion, Name: "UNSUPPORTED_VERSION",
							 | 
						|
										Description: "Unsupported version", Retriable: false,
							 | 
						|
									},
							 | 
						|
									ErrorCodeTopicAlreadyExists: {
							 | 
						|
										Code: ErrorCodeTopicAlreadyExists, Name: "TOPIC_ALREADY_EXISTS",
							 | 
						|
										Description: "Topic already exists", Retriable: false,
							 | 
						|
									},
							 | 
						|
									ErrorCodeInvalidPartitions: {
							 | 
						|
										Code: ErrorCodeInvalidPartitions, Name: "INVALID_PARTITIONS",
							 | 
						|
										Description: "Invalid number of partitions", Retriable: false,
							 | 
						|
									},
							 | 
						|
									ErrorCodeInvalidReplicationFactor: {
							 | 
						|
										Code: ErrorCodeInvalidReplicationFactor, Name: "INVALID_REPLICATION_FACTOR",
							 | 
						|
										Description: "Invalid replication factor", Retriable: false,
							 | 
						|
									},
							 | 
						|
									ErrorCodeInvalidRecord: {
							 | 
						|
										Code: ErrorCodeInvalidRecord, Name: "INVALID_RECORD",
							 | 
						|
										Description: "Invalid record", Retriable: false,
							 | 
						|
									},
							 | 
						|
									ErrorCodeConnectionRefused: {
							 | 
						|
										Code: ErrorCodeConnectionRefused, Name: "CONNECTION_REFUSED",
							 | 
						|
										Description: "Connection refused", Retriable: true,
							 | 
						|
									},
							 | 
						|
									ErrorCodeConnectionTimeout: {
							 | 
						|
										Code: ErrorCodeConnectionTimeout, Name: "CONNECTION_TIMEOUT",
							 | 
						|
										Description: "Connection timeout", Retriable: true,
							 | 
						|
									},
							 | 
						|
									ErrorCodeReadTimeout: {
							 | 
						|
										Code: ErrorCodeReadTimeout, Name: "READ_TIMEOUT",
							 | 
						|
										Description: "Read operation timeout", Retriable: true,
							 | 
						|
									},
							 | 
						|
									ErrorCodeWriteTimeout: {
							 | 
						|
										Code: ErrorCodeWriteTimeout, Name: "WRITE_TIMEOUT",
							 | 
						|
										Description: "Write operation timeout", Retriable: true,
							 | 
						|
									},
							 | 
						|
									ErrorCodeIllegalGeneration: {
							 | 
						|
										Code: ErrorCodeIllegalGeneration, Name: "ILLEGAL_GENERATION",
							 | 
						|
										Description: "Illegal generation", Retriable: false,
							 | 
						|
									},
							 | 
						|
									ErrorCodeInconsistentGroupProtocol: {
							 | 
						|
										Code: ErrorCodeInconsistentGroupProtocol, Name: "INCONSISTENT_GROUP_PROTOCOL",
							 | 
						|
										Description: "Inconsistent group protocol", Retriable: false,
							 | 
						|
									},
							 | 
						|
									ErrorCodeMemberIDRequired: {
							 | 
						|
										Code: ErrorCodeMemberIDRequired, Name: "MEMBER_ID_REQUIRED",
							 | 
						|
										Description: "Member ID required", Retriable: false,
							 | 
						|
									},
							 | 
						|
									ErrorCodeFencedInstanceID: {
							 | 
						|
										Code: ErrorCodeFencedInstanceID, Name: "FENCED_INSTANCE_ID",
							 | 
						|
										Description: "Instance ID fenced", Retriable: false,
							 | 
						|
									},
							 | 
						|
									ErrorCodeGroupMaxSizeReached: {
							 | 
						|
										Code: ErrorCodeGroupMaxSizeReached, Name: "GROUP_MAX_SIZE_REACHED",
							 | 
						|
										Description: "Group max size reached", Retriable: false,
							 | 
						|
									},
							 | 
						|
									ErrorCodeUnstableOffsetCommit: {
							 | 
						|
										Code: ErrorCodeUnstableOffsetCommit, Name: "UNSTABLE_OFFSET_COMMIT",
							 | 
						|
										Description: "Offset commit during rebalance", Retriable: true,
							 | 
						|
									},
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// GetErrorInfo returns error information for the given error code
							 | 
						|
								func GetErrorInfo(code int16) ErrorInfo {
							 | 
						|
									if info, exists := KafkaErrors[code]; exists {
							 | 
						|
										return info
							 | 
						|
									}
							 | 
						|
									return ErrorInfo{
							 | 
						|
										Code: code, Name: "UNKNOWN", Description: "Unknown error code", Retriable: false,
							 | 
						|
									}
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// IsRetriableError returns true if the error is retriable
							 | 
						|
								func IsRetriableError(code int16) bool {
							 | 
						|
									return GetErrorInfo(code).Retriable
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// BuildErrorResponse builds a standard Kafka error response
							 | 
						|
								func BuildErrorResponse(correlationID uint32, errorCode int16) []byte {
							 | 
						|
									response := make([]byte, 0, 8)
							 | 
						|
								
							 | 
						|
									// NOTE: Correlation ID is handled by writeResponseWithCorrelationID
							 | 
						|
									// Do NOT include it in the response body
							 | 
						|
								
							 | 
						|
									// Error code (2 bytes)
							 | 
						|
									errorCodeBytes := make([]byte, 2)
							 | 
						|
									binary.BigEndian.PutUint16(errorCodeBytes, uint16(errorCode))
							 | 
						|
									response = append(response, errorCodeBytes...)
							 | 
						|
								
							 | 
						|
									return response
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// BuildErrorResponseWithMessage builds a Kafka error response with error message
							 | 
						|
								func BuildErrorResponseWithMessage(correlationID uint32, errorCode int16, message string) []byte {
							 | 
						|
									response := BuildErrorResponse(correlationID, errorCode)
							 | 
						|
								
							 | 
						|
									// Error message (2 bytes length + message)
							 | 
						|
									if message == "" {
							 | 
						|
										response = append(response, 0xFF, 0xFF) // Null string
							 | 
						|
									} else {
							 | 
						|
										messageLen := uint16(len(message))
							 | 
						|
										messageLenBytes := make([]byte, 2)
							 | 
						|
										binary.BigEndian.PutUint16(messageLenBytes, messageLen)
							 | 
						|
										response = append(response, messageLenBytes...)
							 | 
						|
										response = append(response, []byte(message)...)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return response
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// ClassifyNetworkError classifies network errors into appropriate Kafka error codes
							 | 
						|
								func ClassifyNetworkError(err error) int16 {
							 | 
						|
									if err == nil {
							 | 
						|
										return ErrorCodeNone
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Check for network errors
							 | 
						|
									if netErr, ok := err.(net.Error); ok {
							 | 
						|
										if netErr.Timeout() {
							 | 
						|
											return ErrorCodeRequestTimedOut
							 | 
						|
										}
							 | 
						|
										return ErrorCodeNetworkException
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Check for specific error types
							 | 
						|
									switch err.Error() {
							 | 
						|
									case "connection refused":
							 | 
						|
										return ErrorCodeConnectionRefused
							 | 
						|
									case "connection timeout":
							 | 
						|
										return ErrorCodeConnectionTimeout
							 | 
						|
									default:
							 | 
						|
										return ErrorCodeUnknownServerError
							 | 
						|
									}
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// TimeoutConfig holds timeout configuration for connections and operations
							 | 
						|
								type TimeoutConfig struct {
							 | 
						|
									ConnectionTimeout time.Duration // Timeout for establishing connections
							 | 
						|
									ReadTimeout       time.Duration // Timeout for read operations
							 | 
						|
									WriteTimeout      time.Duration // Timeout for write operations
							 | 
						|
									RequestTimeout    time.Duration // Overall request timeout
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// DefaultTimeoutConfig returns default timeout configuration
							 | 
						|
								func DefaultTimeoutConfig() TimeoutConfig {
							 | 
						|
									return TimeoutConfig{
							 | 
						|
										ConnectionTimeout: 30 * time.Second,
							 | 
						|
										ReadTimeout:       10 * time.Second,
							 | 
						|
										WriteTimeout:      10 * time.Second,
							 | 
						|
										RequestTimeout:    30 * time.Second,
							 | 
						|
									}
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// HandleTimeoutError handles timeout errors and returns appropriate error code
							 | 
						|
								func HandleTimeoutError(err error, operation string) int16 {
							 | 
						|
									if err == nil {
							 | 
						|
										return ErrorCodeNone
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Handle context timeout errors
							 | 
						|
									if err == context.DeadlineExceeded {
							 | 
						|
										switch operation {
							 | 
						|
										case "read":
							 | 
						|
											return ErrorCodeReadTimeout
							 | 
						|
										case "write":
							 | 
						|
											return ErrorCodeWriteTimeout
							 | 
						|
										case "connect":
							 | 
						|
											return ErrorCodeConnectionTimeout
							 | 
						|
										default:
							 | 
						|
											return ErrorCodeRequestTimedOut
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
							 | 
						|
										switch operation {
							 | 
						|
										case "read":
							 | 
						|
											return ErrorCodeReadTimeout
							 | 
						|
										case "write":
							 | 
						|
											return ErrorCodeWriteTimeout
							 | 
						|
										case "connect":
							 | 
						|
											return ErrorCodeConnectionTimeout
							 | 
						|
										default:
							 | 
						|
											return ErrorCodeRequestTimedOut
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return ClassifyNetworkError(err)
							 | 
						|
								}
							 |