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.
		
		
		
		
		
			
		
			
				
					
					
						
							447 lines
						
					
					
						
							12 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							447 lines
						
					
					
						
							12 KiB
						
					
					
				
								package protocol
							 | 
						|
								
							 | 
						|
								import (
							 | 
						|
									"encoding/binary"
							 | 
						|
									"fmt"
							 | 
						|
								)
							 | 
						|
								
							 | 
						|
								// handleDescribeGroups handles DescribeGroups API (key 15)
							 | 
						|
								func (h *Handler) handleDescribeGroups(correlationID uint32, apiVersion uint16, requestBody []byte) ([]byte, error) {
							 | 
						|
								
							 | 
						|
									// Parse request
							 | 
						|
									request, err := h.parseDescribeGroupsRequest(requestBody, apiVersion)
							 | 
						|
									if err != nil {
							 | 
						|
										return nil, fmt.Errorf("parse DescribeGroups request: %w", err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Build response
							 | 
						|
									response := DescribeGroupsResponse{
							 | 
						|
										ThrottleTimeMs: 0,
							 | 
						|
										Groups:         make([]DescribeGroupsGroup, 0, len(request.GroupIDs)),
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Get group information for each requested group
							 | 
						|
									for _, groupID := range request.GroupIDs {
							 | 
						|
										group := h.describeGroup(groupID)
							 | 
						|
										response.Groups = append(response.Groups, group)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return h.buildDescribeGroupsResponse(response, correlationID, apiVersion), nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// handleListGroups handles ListGroups API (key 16)
							 | 
						|
								func (h *Handler) handleListGroups(correlationID uint32, apiVersion uint16, requestBody []byte) ([]byte, error) {
							 | 
						|
								
							 | 
						|
									// Parse request (ListGroups has minimal request structure)
							 | 
						|
									request, err := h.parseListGroupsRequest(requestBody, apiVersion)
							 | 
						|
									if err != nil {
							 | 
						|
										return nil, fmt.Errorf("parse ListGroups request: %w", err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Build response
							 | 
						|
									response := ListGroupsResponse{
							 | 
						|
										ThrottleTimeMs: 0,
							 | 
						|
										ErrorCode:      0,
							 | 
						|
										Groups:         h.listAllGroups(request.StatesFilter),
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return h.buildListGroupsResponse(response, correlationID, apiVersion), nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// describeGroup gets detailed information about a specific group
							 | 
						|
								func (h *Handler) describeGroup(groupID string) DescribeGroupsGroup {
							 | 
						|
									// Get group information from coordinator
							 | 
						|
									if h.groupCoordinator == nil {
							 | 
						|
										return DescribeGroupsGroup{
							 | 
						|
											ErrorCode: 15, // GROUP_COORDINATOR_NOT_AVAILABLE
							 | 
						|
											GroupID:   groupID,
							 | 
						|
											State:     "Dead",
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									group := h.groupCoordinator.GetGroup(groupID)
							 | 
						|
									if group == nil {
							 | 
						|
										return DescribeGroupsGroup{
							 | 
						|
											ErrorCode:    25, // UNKNOWN_GROUP_ID
							 | 
						|
											GroupID:      groupID,
							 | 
						|
											State:        "Dead",
							 | 
						|
											ProtocolType: "",
							 | 
						|
											Protocol:     "",
							 | 
						|
											Members:      []DescribeGroupsMember{},
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Convert group to response format
							 | 
						|
									members := make([]DescribeGroupsMember, 0, len(group.Members))
							 | 
						|
									for memberID, member := range group.Members {
							 | 
						|
										// Convert assignment to bytes (simplified)
							 | 
						|
										var assignmentBytes []byte
							 | 
						|
										if len(member.Assignment) > 0 {
							 | 
						|
											// In a real implementation, this would serialize the assignment properly
							 | 
						|
											assignmentBytes = []byte(fmt.Sprintf("assignment:%d", len(member.Assignment)))
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										members = append(members, DescribeGroupsMember{
							 | 
						|
											MemberID:         memberID,
							 | 
						|
											GroupInstanceID:  member.GroupInstanceID, // Now supports static membership
							 | 
						|
											ClientID:         member.ClientID,
							 | 
						|
											ClientHost:       member.ClientHost,
							 | 
						|
											MemberMetadata:   member.Metadata,
							 | 
						|
											MemberAssignment: assignmentBytes,
							 | 
						|
										})
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Convert group state to string
							 | 
						|
									var stateStr string
							 | 
						|
									switch group.State {
							 | 
						|
									case 0: // Assuming 0 is Empty
							 | 
						|
										stateStr = "Empty"
							 | 
						|
									case 1: // Assuming 1 is PreparingRebalance
							 | 
						|
										stateStr = "PreparingRebalance"
							 | 
						|
									case 2: // Assuming 2 is CompletingRebalance
							 | 
						|
										stateStr = "CompletingRebalance"
							 | 
						|
									case 3: // Assuming 3 is Stable
							 | 
						|
										stateStr = "Stable"
							 | 
						|
									default:
							 | 
						|
										stateStr = "Dead"
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return DescribeGroupsGroup{
							 | 
						|
										ErrorCode:     0,
							 | 
						|
										GroupID:       groupID,
							 | 
						|
										State:         stateStr,
							 | 
						|
										ProtocolType:  "consumer", // Default protocol type
							 | 
						|
										Protocol:      group.Protocol,
							 | 
						|
										Members:       members,
							 | 
						|
										AuthorizedOps: []int32{}, // Empty for now
							 | 
						|
									}
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// listAllGroups gets a list of all consumer groups
							 | 
						|
								func (h *Handler) listAllGroups(statesFilter []string) []ListGroupsGroup {
							 | 
						|
									if h.groupCoordinator == nil {
							 | 
						|
										return []ListGroupsGroup{}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									allGroupIDs := h.groupCoordinator.ListGroups()
							 | 
						|
									groups := make([]ListGroupsGroup, 0, len(allGroupIDs))
							 | 
						|
								
							 | 
						|
									for _, groupID := range allGroupIDs {
							 | 
						|
										// Get the full group details
							 | 
						|
										group := h.groupCoordinator.GetGroup(groupID)
							 | 
						|
										if group == nil {
							 | 
						|
											continue
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										// Convert group state to string
							 | 
						|
										var stateStr string
							 | 
						|
										switch group.State {
							 | 
						|
										case 0:
							 | 
						|
											stateStr = "Empty"
							 | 
						|
										case 1:
							 | 
						|
											stateStr = "PreparingRebalance"
							 | 
						|
										case 2:
							 | 
						|
											stateStr = "CompletingRebalance"
							 | 
						|
										case 3:
							 | 
						|
											stateStr = "Stable"
							 | 
						|
										default:
							 | 
						|
											stateStr = "Dead"
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										// Apply state filter if provided
							 | 
						|
										if len(statesFilter) > 0 {
							 | 
						|
											matchesFilter := false
							 | 
						|
											for _, state := range statesFilter {
							 | 
						|
												if stateStr == state {
							 | 
						|
													matchesFilter = true
							 | 
						|
													break
							 | 
						|
												}
							 | 
						|
											}
							 | 
						|
											if !matchesFilter {
							 | 
						|
												continue
							 | 
						|
											}
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										groups = append(groups, ListGroupsGroup{
							 | 
						|
											GroupID:      group.ID,
							 | 
						|
											ProtocolType: "consumer", // Default protocol type
							 | 
						|
											GroupState:   stateStr,
							 | 
						|
										})
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return groups
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// Request/Response structures
							 | 
						|
								
							 | 
						|
								type DescribeGroupsRequest struct {
							 | 
						|
									GroupIDs             []string
							 | 
						|
									IncludeAuthorizedOps bool
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								type DescribeGroupsResponse struct {
							 | 
						|
									ThrottleTimeMs int32
							 | 
						|
									Groups         []DescribeGroupsGroup
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								type DescribeGroupsGroup struct {
							 | 
						|
									ErrorCode     int16
							 | 
						|
									GroupID       string
							 | 
						|
									State         string
							 | 
						|
									ProtocolType  string
							 | 
						|
									Protocol      string
							 | 
						|
									Members       []DescribeGroupsMember
							 | 
						|
									AuthorizedOps []int32
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								type DescribeGroupsMember struct {
							 | 
						|
									MemberID         string
							 | 
						|
									GroupInstanceID  *string
							 | 
						|
									ClientID         string
							 | 
						|
									ClientHost       string
							 | 
						|
									MemberMetadata   []byte
							 | 
						|
									MemberAssignment []byte
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								type ListGroupsRequest struct {
							 | 
						|
									StatesFilter []string
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								type ListGroupsResponse struct {
							 | 
						|
									ThrottleTimeMs int32
							 | 
						|
									ErrorCode      int16
							 | 
						|
									Groups         []ListGroupsGroup
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								type ListGroupsGroup struct {
							 | 
						|
									GroupID      string
							 | 
						|
									ProtocolType string
							 | 
						|
									GroupState   string
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// Parsing functions
							 | 
						|
								
							 | 
						|
								func (h *Handler) parseDescribeGroupsRequest(data []byte, apiVersion uint16) (*DescribeGroupsRequest, error) {
							 | 
						|
									offset := 0
							 | 
						|
									request := &DescribeGroupsRequest{}
							 | 
						|
								
							 | 
						|
									// Skip client_id if present (depends on version)
							 | 
						|
									if len(data) < 4 {
							 | 
						|
										return nil, fmt.Errorf("request too short")
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Group IDs array
							 | 
						|
									groupCount := binary.BigEndian.Uint32(data[offset : offset+4])
							 | 
						|
									offset += 4
							 | 
						|
								
							 | 
						|
									request.GroupIDs = make([]string, groupCount)
							 | 
						|
									for i := uint32(0); i < groupCount; i++ {
							 | 
						|
										if offset+2 > len(data) {
							 | 
						|
											return nil, fmt.Errorf("invalid group ID at index %d", i)
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										groupIDLen := binary.BigEndian.Uint16(data[offset : offset+2])
							 | 
						|
										offset += 2
							 | 
						|
								
							 | 
						|
										if offset+int(groupIDLen) > len(data) {
							 | 
						|
											return nil, fmt.Errorf("group ID too long at index %d", i)
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										request.GroupIDs[i] = string(data[offset : offset+int(groupIDLen)])
							 | 
						|
										offset += int(groupIDLen)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Include authorized operations (v3+)
							 | 
						|
									if apiVersion >= 3 && offset < len(data) {
							 | 
						|
										request.IncludeAuthorizedOps = data[offset] != 0
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return request, nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (h *Handler) parseListGroupsRequest(data []byte, apiVersion uint16) (*ListGroupsRequest, error) {
							 | 
						|
									request := &ListGroupsRequest{}
							 | 
						|
								
							 | 
						|
									// ListGroups v4+ includes states filter
							 | 
						|
									if apiVersion >= 4 && len(data) >= 4 {
							 | 
						|
										offset := 0
							 | 
						|
										statesCount := binary.BigEndian.Uint32(data[offset : offset+4])
							 | 
						|
										offset += 4
							 | 
						|
								
							 | 
						|
										if statesCount > 0 {
							 | 
						|
											request.StatesFilter = make([]string, statesCount)
							 | 
						|
											for i := uint32(0); i < statesCount; i++ {
							 | 
						|
												if offset+2 > len(data) {
							 | 
						|
													break
							 | 
						|
												}
							 | 
						|
								
							 | 
						|
												stateLen := binary.BigEndian.Uint16(data[offset : offset+2])
							 | 
						|
												offset += 2
							 | 
						|
								
							 | 
						|
												if offset+int(stateLen) > len(data) {
							 | 
						|
													break
							 | 
						|
												}
							 | 
						|
								
							 | 
						|
												request.StatesFilter[i] = string(data[offset : offset+int(stateLen)])
							 | 
						|
												offset += int(stateLen)
							 | 
						|
											}
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return request, nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// Response building functions
							 | 
						|
								
							 | 
						|
								func (h *Handler) buildDescribeGroupsResponse(response DescribeGroupsResponse, correlationID uint32, apiVersion uint16) []byte {
							 | 
						|
									buf := make([]byte, 0, 1024)
							 | 
						|
								
							 | 
						|
									// Correlation ID
							 | 
						|
									correlationIDBytes := make([]byte, 4)
							 | 
						|
									binary.BigEndian.PutUint32(correlationIDBytes, correlationID)
							 | 
						|
									buf = append(buf, correlationIDBytes...)
							 | 
						|
								
							 | 
						|
									// Throttle time (v1+)
							 | 
						|
									if apiVersion >= 1 {
							 | 
						|
										throttleBytes := make([]byte, 4)
							 | 
						|
										binary.BigEndian.PutUint32(throttleBytes, uint32(response.ThrottleTimeMs))
							 | 
						|
										buf = append(buf, throttleBytes...)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Groups array
							 | 
						|
									groupCountBytes := make([]byte, 4)
							 | 
						|
									binary.BigEndian.PutUint32(groupCountBytes, uint32(len(response.Groups)))
							 | 
						|
									buf = append(buf, groupCountBytes...)
							 | 
						|
								
							 | 
						|
									for _, group := range response.Groups {
							 | 
						|
										// Error code
							 | 
						|
										buf = append(buf, byte(group.ErrorCode>>8), byte(group.ErrorCode))
							 | 
						|
								
							 | 
						|
										// Group ID
							 | 
						|
										groupIDLen := uint16(len(group.GroupID))
							 | 
						|
										buf = append(buf, byte(groupIDLen>>8), byte(groupIDLen))
							 | 
						|
										buf = append(buf, []byte(group.GroupID)...)
							 | 
						|
								
							 | 
						|
										// State
							 | 
						|
										stateLen := uint16(len(group.State))
							 | 
						|
										buf = append(buf, byte(stateLen>>8), byte(stateLen))
							 | 
						|
										buf = append(buf, []byte(group.State)...)
							 | 
						|
								
							 | 
						|
										// Protocol type
							 | 
						|
										protocolTypeLen := uint16(len(group.ProtocolType))
							 | 
						|
										buf = append(buf, byte(protocolTypeLen>>8), byte(protocolTypeLen))
							 | 
						|
										buf = append(buf, []byte(group.ProtocolType)...)
							 | 
						|
								
							 | 
						|
										// Protocol
							 | 
						|
										protocolLen := uint16(len(group.Protocol))
							 | 
						|
										buf = append(buf, byte(protocolLen>>8), byte(protocolLen))
							 | 
						|
										buf = append(buf, []byte(group.Protocol)...)
							 | 
						|
								
							 | 
						|
										// Members array
							 | 
						|
										memberCountBytes := make([]byte, 4)
							 | 
						|
										binary.BigEndian.PutUint32(memberCountBytes, uint32(len(group.Members)))
							 | 
						|
										buf = append(buf, memberCountBytes...)
							 | 
						|
								
							 | 
						|
										for _, member := range group.Members {
							 | 
						|
											// Member ID
							 | 
						|
											memberIDLen := uint16(len(member.MemberID))
							 | 
						|
											buf = append(buf, byte(memberIDLen>>8), byte(memberIDLen))
							 | 
						|
											buf = append(buf, []byte(member.MemberID)...)
							 | 
						|
								
							 | 
						|
											// Group instance ID (v4+, nullable)
							 | 
						|
											if apiVersion >= 4 {
							 | 
						|
												if member.GroupInstanceID != nil {
							 | 
						|
													instanceIDLen := uint16(len(*member.GroupInstanceID))
							 | 
						|
													buf = append(buf, byte(instanceIDLen>>8), byte(instanceIDLen))
							 | 
						|
													buf = append(buf, []byte(*member.GroupInstanceID)...)
							 | 
						|
												} else {
							 | 
						|
													buf = append(buf, 0xFF, 0xFF) // null
							 | 
						|
												}
							 | 
						|
											}
							 | 
						|
								
							 | 
						|
											// Client ID
							 | 
						|
											clientIDLen := uint16(len(member.ClientID))
							 | 
						|
											buf = append(buf, byte(clientIDLen>>8), byte(clientIDLen))
							 | 
						|
											buf = append(buf, []byte(member.ClientID)...)
							 | 
						|
								
							 | 
						|
											// Client host
							 | 
						|
											clientHostLen := uint16(len(member.ClientHost))
							 | 
						|
											buf = append(buf, byte(clientHostLen>>8), byte(clientHostLen))
							 | 
						|
											buf = append(buf, []byte(member.ClientHost)...)
							 | 
						|
								
							 | 
						|
											// Member metadata
							 | 
						|
											metadataLen := uint32(len(member.MemberMetadata))
							 | 
						|
											metadataLenBytes := make([]byte, 4)
							 | 
						|
											binary.BigEndian.PutUint32(metadataLenBytes, metadataLen)
							 | 
						|
											buf = append(buf, metadataLenBytes...)
							 | 
						|
											buf = append(buf, member.MemberMetadata...)
							 | 
						|
								
							 | 
						|
											// Member assignment
							 | 
						|
											assignmentLen := uint32(len(member.MemberAssignment))
							 | 
						|
											assignmentLenBytes := make([]byte, 4)
							 | 
						|
											binary.BigEndian.PutUint32(assignmentLenBytes, assignmentLen)
							 | 
						|
											buf = append(buf, assignmentLenBytes...)
							 | 
						|
											buf = append(buf, member.MemberAssignment...)
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										// Authorized operations (v3+)
							 | 
						|
										if apiVersion >= 3 {
							 | 
						|
											opsCountBytes := make([]byte, 4)
							 | 
						|
											binary.BigEndian.PutUint32(opsCountBytes, uint32(len(group.AuthorizedOps)))
							 | 
						|
											buf = append(buf, opsCountBytes...)
							 | 
						|
								
							 | 
						|
											for _, op := range group.AuthorizedOps {
							 | 
						|
												opBytes := make([]byte, 4)
							 | 
						|
												binary.BigEndian.PutUint32(opBytes, uint32(op))
							 | 
						|
												buf = append(buf, opBytes...)
							 | 
						|
											}
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return buf
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (h *Handler) buildListGroupsResponse(response ListGroupsResponse, correlationID uint32, apiVersion uint16) []byte {
							 | 
						|
									buf := make([]byte, 0, 512)
							 | 
						|
								
							 | 
						|
									// Correlation ID
							 | 
						|
									correlationIDBytes := make([]byte, 4)
							 | 
						|
									binary.BigEndian.PutUint32(correlationIDBytes, correlationID)
							 | 
						|
									buf = append(buf, correlationIDBytes...)
							 | 
						|
								
							 | 
						|
									// Throttle time (v1+)
							 | 
						|
									if apiVersion >= 1 {
							 | 
						|
										throttleBytes := make([]byte, 4)
							 | 
						|
										binary.BigEndian.PutUint32(throttleBytes, uint32(response.ThrottleTimeMs))
							 | 
						|
										buf = append(buf, throttleBytes...)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Error code
							 | 
						|
									buf = append(buf, byte(response.ErrorCode>>8), byte(response.ErrorCode))
							 | 
						|
								
							 | 
						|
									// Groups array
							 | 
						|
									groupCountBytes := make([]byte, 4)
							 | 
						|
									binary.BigEndian.PutUint32(groupCountBytes, uint32(len(response.Groups)))
							 | 
						|
									buf = append(buf, groupCountBytes...)
							 | 
						|
								
							 | 
						|
									for _, group := range response.Groups {
							 | 
						|
										// Group ID
							 | 
						|
										groupIDLen := uint16(len(group.GroupID))
							 | 
						|
										buf = append(buf, byte(groupIDLen>>8), byte(groupIDLen))
							 | 
						|
										buf = append(buf, []byte(group.GroupID)...)
							 | 
						|
								
							 | 
						|
										// Protocol type
							 | 
						|
										protocolTypeLen := uint16(len(group.ProtocolType))
							 | 
						|
										buf = append(buf, byte(protocolTypeLen>>8), byte(protocolTypeLen))
							 | 
						|
										buf = append(buf, []byte(group.ProtocolType)...)
							 | 
						|
								
							 | 
						|
										// Group state (v4+)
							 | 
						|
										if apiVersion >= 4 {
							 | 
						|
											groupStateLen := uint16(len(group.GroupState))
							 | 
						|
											buf = append(buf, byte(groupStateLen>>8), byte(groupStateLen))
							 | 
						|
											buf = append(buf, []byte(group.GroupState)...)
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return buf
							 | 
						|
								}
							 |