master
go 450 lines 15.3 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 //go:build cgo && ibm_mq
4
5 package pcf
6
7 import (
8 "fmt"
9 "strings"
10
11 "github.com/ibm-messaging/mq-golang/v5/ibmmq"
12 )
13
14 // sendPCFCommand sends a PCF command using IBM library and returns the response parameters
15 func (c *Client) sendPCFCommand(command int32, params []*ibmmq.PCFParameter) ([]*ibmmq.PCFParameter, error) {
16 // Handle nil params as empty slice
17 if params == nil {
18 params = []*ibmmq.PCFParameter{}
19 }
20
21 // Build PCF message using IBM library
22 cfh := ibmmq.NewMQCFH()
23 // cfh.Version defaults to MQCFH_VERSION_1 which is correct
24 cfh.Type = ibmmq.MQCFT_COMMAND // Regular PCF command
25 cfh.Command = command
26 cfh.ParameterCount = int32(len(params))
27
28 // Build parameter bytes first
29 var paramBytes []byte
30 for _, param := range params {
31 paramBytes = append(paramBytes, param.Bytes()...)
32 }
33
34 // CRITICAL: Put CFH header at front of message (not after)
35 msgData := append(cfh.Bytes(), paramBytes...)
36
37 // Create message descriptor - CRITICAL: Format must be "MQADMIN" string
38 md := ibmmq.NewMQMD()
39 md.MsgType = ibmmq.MQMT_REQUEST
40 md.Format = "MQADMIN" // CRITICAL: Must be string "MQADMIN", not constant
41 md.ReplyToQ = c.replyQueueName
42 md.ReplyToQMgr = c.config.QueueManager
43 md.CodedCharSetId = 1208 // UTF-8 - CRITICAL: Required for PCF commands
44
45 // Create put message options
46 pmo := ibmmq.NewMQPMO()
47 pmo.Options = ibmmq.MQPMO_NEW_MSG_ID | ibmmq.MQPMO_NEW_CORREL_ID
48
49 // Put message to command queue
50 err := c.cmdQueue.Put(md, pmo, msgData)
51 if err != nil {
52 return nil, fmt.Errorf("failed to put PCF command: %w", err)
53 }
54
55 // Log in structured format
56 paramStr := ""
57 for i, p := range params {
58 if i > 0 {
59 paramStr += ","
60 }
61 paramStr += fmt.Sprintf("%s(%d)", mqParameterToString(p.Parameter), p.Parameter)
62 }
63 c.protocol.Debugf("MQPUT %s(%d) params=[%s] msgId=%X bytes=%d",
64 mqcmdToString(command), command, paramStr, md.MsgId, len(msgData))
65
66 // Get reply - PCF replies use request's MsgId as their CorrelId
67 return c.getPCFReply(md.MsgId)
68 }
69
70 // getPCFReply gets the PCF reply from the reply queue - handles multi-message responses
71 // The correlId parameter should be the request's MsgId (PCF standard behavior)
72 func (c *Client) getPCFReply(correlId []byte) ([]*ibmmq.PCFParameter, error) {
73 // Create get message options - matching the Prometheus collector pattern
74 gmo := ibmmq.NewMQGMO()
75 gmo.Options = ibmmq.MQGMO_WAIT | ibmmq.MQGMO_CONVERT | ibmmq.MQGMO_FAIL_IF_QUIESCING
76 gmo.WaitInterval = 5000 // 5 seconds
77 gmo.MatchOptions = ibmmq.MQMO_MATCH_CORREL_ID // CRITICAL: Must set match options
78
79 var allParams []*ibmmq.PCFParameter
80 bufferSize := 65536 // 64KB initial size
81
82 // Loop to handle multi-message responses
83 for {
84 // Create new message descriptor for each get
85 md := ibmmq.NewMQMD()
86 // PCF replies use request's MsgId as their CorrelId
87 copy(md.CorrelId[:], correlId)
88
89 buffer := make([]byte, bufferSize)
90
91 // Get the reply
92 datalen, err := c.replyQueue.Get(md, gmo, buffer)
93 if err != nil {
94 // Check if buffer too small
95 if mqErr, ok := err.(*ibmmq.MQReturn); ok && mqErr.MQRC == ibmmq.MQRC_DATA_LENGTH_ERROR {
96 // Double the buffer size and retry with same message
97 bufferSize *= 2
98 c.protocol.Debugf("buffer too small, increasing to %d bytes and retrying", bufferSize)
99 continue
100 }
101 return nil, fmt.Errorf("failed to get PCF reply: %w", err)
102 }
103
104 // Read PCF header first for logging
105 tempCfh, _ := ibmmq.ReadPCFHeader(buffer[:datalen])
106 if tempCfh != nil {
107 // Convert codes to readable names for logging
108 compCodeStr := ibmmq.MQItoString("CC", int(tempCfh.CompCode))
109 reasonStr := mqReasonString(tempCfh.Reason)
110
111 c.protocol.Debugf("MQGET Command=%s(%d) Type=%s CompCode=%s(%d) Reason=%s(%d) Params=%d Control=%s bytes=%d",
112 mqcmdToString(tempCfh.Command), tempCfh.Command, mqPCFTypeToString(tempCfh.Type),
113 compCodeStr, tempCfh.CompCode, reasonStr, tempCfh.Reason, tempCfh.ParameterCount, mqPCFControlToString(tempCfh.Control), datalen)
114 }
115
116 // Read PCF header to check control field
117 cfh, _ := ibmmq.ReadPCFHeader(buffer[:datalen])
118 if cfh == nil {
119 return nil, fmt.Errorf("failed to read PCF header from response")
120 }
121
122 // Parse this message's parameters
123 params, err := c.parsePCFResponseInternal(buffer[:datalen])
124 if err != nil {
125 // In multi-message responses, log the error but continue reading
126 if pcfErr, ok := err.(*PCFError); ok {
127 key := fmt.Sprintf("pcf_command_%d_reason_%d", cfh.Command, pcfErr.Code)
128 c.warnOnce(key, "PCF command %s(%d) failed: %s", mqcmdToString(cfh.Command), cfh.Command, pcfErr.Message)
129 } else {
130 key := fmt.Sprintf("pcf_command_%d_parse", cfh.Command)
131 c.warnOnce(key, "Error in multi-message response for %s(%d): %v", mqcmdToString(cfh.Command), cfh.Command, err)
132 }
133
134 // Check if this is the last message even with error
135 if cfh.Control == ibmmq.MQCFC_LAST {
136 c.protocol.Debugf("received last PCF message (with error), total parameters: %d", len(allParams))
137 break
138 }
139 // Continue to next message
140 c.protocol.Debugf("MQGET multi-message response detected, continuing despite error")
141 continue
142 }
143
144 // Accumulate parameters from successful messages
145 allParams = append(allParams, params...)
146
147 // Check if this is the last message
148 if cfh.Control == ibmmq.MQCFC_LAST {
149 c.protocol.Debugf("received last PCF message, total parameters: %d", len(allParams))
150 break
151 }
152 // Continue loop for MQCFC_NOT_LAST
153 c.protocol.Debugf("MQGET multi-message response detected, continuing to read next message")
154 }
155
156 return allParams, nil
157 }
158
159 // readPCFParameters reads PCF parameters from data using IBM library
160 // This function is used when we know the expected parameter count from the CFH
161 func (c *Client) readPCFParameters(data []byte, count int) ([]*ibmmq.PCFParameter, error) {
162 var params []*ibmmq.PCFParameter
163 offset := 0
164
165 for i := 0; i < count && offset < len(data); i++ {
166 param, bytesRead := ibmmq.ReadPCFParameter(data[offset:])
167 if param == nil || bytesRead == 0 {
168 return nil, fmt.Errorf("invalid parameter %d at offset %d: nil parameter or zero bytes read", i, offset)
169 }
170
171 params = append(params, param)
172 offset += bytesRead
173 }
174
175 c.protocol.Debugf("Read %d PCF parameters from %d bytes (expected %d parameters)", len(params), len(data), count)
176 return params, nil
177 }
178
179 // convertPCFParametersToAttrs converts IBM PCFParameter array to the expected attrs format
180 // This preserves compatibility with existing smart function code
181 func convertPCFParametersToAttrs(params []*ibmmq.PCFParameter) map[int32]interface{} {
182 attrs := make(map[int32]interface{})
183
184 for _, param := range params {
185 switch param.Type {
186 case ibmmq.MQCFT_INTEGER:
187 if len(param.Int64Value) > 0 {
188 attrs[param.Parameter] = int32(param.Int64Value[0])
189 }
190 case ibmmq.MQCFT_INTEGER64:
191 if len(param.Int64Value) > 0 {
192 attrs[param.Parameter] = param.Int64Value[0]
193 }
194 case ibmmq.MQCFT_STRING:
195 if len(param.String) > 0 {
196 attrs[param.Parameter] = param.String[0]
197 }
198 case ibmmq.MQCFT_INTEGER_LIST:
199 if len(param.Int64Value) > 0 {
200 // Convert to int32 slice
201 intList := make([]int32, len(param.Int64Value))
202 for i, v := range param.Int64Value {
203 intList[i] = int32(v)
204 }
205 attrs[param.Parameter] = intList
206 }
207 case ibmmq.MQCFT_STRING_LIST:
208 if len(param.String) > 0 {
209 attrs[param.Parameter] = param.String
210 }
211 }
212 }
213
214 return attrs
215 }
216
217 // parsePCFResponse provides compatibility bridge for smart functions
218 // This version takes []byte response data (used by list_parser.go)
219 func (c *Client) parsePCFResponse(data []byte, context string) (map[int32]interface{}, error) {
220 // Parse the raw PCF response data into IBM library parameters
221 params, err := c.parsePCFResponseInternal(data)
222 if err != nil {
223 return nil, err
224 }
225
226 // Convert to expected attrs format for smart functions
227 attrs := convertPCFParametersToAttrs(params)
228 return attrs, nil
229 }
230
231 // parsePCFResponseFromParams provides compatibility bridge for smart functions
232 // This version takes []*ibmmq.PCFParameter response data (from sendPCFCommand)
233 func (c *Client) parsePCFResponseFromParams(params []*ibmmq.PCFParameter, context string) (map[int32]interface{}, error) {
234 // Convert IBM library parameters to expected attrs format for smart functions
235 attrs := convertPCFParametersToAttrs(params)
236 return attrs, nil
237 }
238
239 // parseChannelListResponseFromParams provides compatibility bridge for smart functions
240 // This version takes []*ibmmq.PCFParameter response data (from sendPCFCommand)
241 func (c *Client) parseChannelListResponseFromParams(params []*ibmmq.PCFParameter) *ChannelListResult {
242 result := &ChannelListResult{
243 Channels: []string{},
244 ErrorCounts: make(map[int32]int),
245 ErrorChannels: make(map[int32][]string),
246 }
247
248 // Convert each parameter set (assuming it's a multi-object response)
249 for _, param := range params {
250 if param.Type == ibmmq.MQCFT_STRING && param.Parameter == ibmmq.MQCACH_CHANNEL_NAME {
251 if len(param.String) > 0 {
252 channelName := strings.TrimSpace(param.String[0])
253 if channelName != "" {
254 result.Channels = append(result.Channels, channelName)
255 }
256 }
257 }
258 }
259
260 return result
261 }
262
263 // parseChannelInfoFromParams extracts channel names and types from PCF response
264 func (c *Client) parseChannelInfoFromParams(params []*ibmmq.PCFParameter) []ChannelInfo {
265 var channels []ChannelInfo
266 var currentChannel *ChannelInfo
267
268 // Process parameters - they come in groups per channel
269 for _, param := range params {
270 switch param.Parameter {
271 case ibmmq.MQCACH_CHANNEL_NAME:
272 // Start of new channel data
273 if param.Type == ibmmq.MQCFT_STRING && len(param.String) > 0 {
274 channelName := strings.TrimSpace(param.String[0])
275 if channelName != "" {
276 // Save previous channel if exists
277 if currentChannel != nil {
278 channels = append(channels, *currentChannel)
279 }
280 // Start new channel
281 currentChannel = &ChannelInfo{
282 Name: channelName,
283 Type: 0, // Will be filled by MQIACH_CHANNEL_TYPE
284 }
285 }
286 }
287 case ibmmq.MQIACH_CHANNEL_TYPE:
288 // Channel type for current channel
289 if currentChannel != nil && param.Type == ibmmq.MQCFT_INTEGER && len(param.Int64Value) > 0 {
290 currentChannel.Type = ChannelType(param.Int64Value[0])
291 }
292 }
293 }
294
295 // Don't forget the last channel
296 if currentChannel != nil {
297 channels = append(channels, *currentChannel)
298 }
299
300 return channels
301 }
302
303 // parseListenerListResponseFromParams provides compatibility bridge for listener discovery
304 // This version takes []*ibmmq.PCFParameter response data (from sendPCFCommand)
305 func (c *Client) parseListenerListResponseFromParams(params []*ibmmq.PCFParameter) *ListenerListResult {
306 result := &ListenerListResult{
307 Listeners: []string{},
308 ErrorCounts: make(map[int32]int),
309 }
310
311 // Convert each parameter set (assuming it's a multi-object response)
312 for _, param := range params {
313 if param.Type == ibmmq.MQCFT_STRING && param.Parameter == ibmmq.MQCACH_LISTENER_NAME {
314 if len(param.String) > 0 {
315 listenerName := strings.TrimSpace(param.String[0])
316 if listenerName != "" {
317 result.Listeners = append(result.Listeners, listenerName)
318 }
319 }
320 }
321 }
322
323 return result
324 }
325
326 // parsePCFResponseInternal handles the actual parsing of PCF response data using IBM library
327 // This implements proper PCF message parsing using the official IBM MQ Go library API
328 func (c *Client) parsePCFResponseInternal(data []byte) ([]*ibmmq.PCFParameter, error) {
329 var params []*ibmmq.PCFParameter
330
331 if len(data) == 0 {
332 return params, nil
333 }
334
335 // Read PCF header using IBM library function
336 cfh, offset := ibmmq.ReadPCFHeader(data)
337 if cfh == nil {
338 return nil, fmt.Errorf("failed to read PCF header: invalid header")
339 }
340
341 // Check for MQ errors in the response
342 if cfh.CompCode != ibmmq.MQCC_OK {
343 // Convert codes to readable names
344 compCodeStr := ibmmq.MQItoString("CC", int(cfh.CompCode))
345 reasonStr := mqReasonString(cfh.Reason)
346
347 return nil, &PCFError{
348 Code: cfh.Reason,
349 Message: fmt.Sprintf("MQ error in PCF response: CompCode=%s Reason=%s",
350 compCodeStr, reasonStr),
351 }
352 }
353
354 // Parse parameters using IBM library functions
355 for i := int32(0); i < cfh.ParameterCount && offset < len(data); i++ {
356 param, bytesRead := ibmmq.ReadPCFParameter(data[offset:])
357 if param == nil || bytesRead == 0 {
358 c.protocol.Warningf("MQMSG: invalid PCF parameter %d at offset %d: nil parameter or zero bytes read", i, offset)
359 break
360 }
361
362 switch param.Type {
363 case ibmmq.MQCFT_INTEGER:
364 if len(param.Int64Value) > 0 {
365 // Use PCFValueToString to get meaningful names for integer values
366 valueStr := ibmmq.PCFValueToString(param.Parameter, param.Int64Value[0])
367 c.protocol.Debugf("MQMSG #%d %s(%d) type=%s = %s (%d) offset=%d bytes=%d",
368 i+1, mqParameterToString(param.Parameter), param.Parameter, mqPCFTypeToString(param.Type),
369 valueStr, param.Int64Value[0], offset, bytesRead)
370 }
371 case ibmmq.MQCFT_STRING:
372 if len(param.String) > 0 {
373 c.protocol.Debugf("MQMSG #%d %s(%d) type=%s = '%s' offset=%d bytes=%d",
374 i+1, mqParameterToString(param.Parameter), param.Parameter, mqPCFTypeToString(param.Type),
375 strings.TrimSpace(param.String[0]), offset, bytesRead)
376 }
377 case ibmmq.MQCFT_INTEGER_LIST:
378 c.protocol.Debugf("MQMSG #%d %s(%d) type=%s = [%d values] offset=%d bytes=%d",
379 i+1, mqParameterToString(param.Parameter), param.Parameter, mqPCFTypeToString(param.Type),
380 len(param.Int64Value), offset, bytesRead)
381 case ibmmq.MQCFT_STRING_LIST:
382 c.protocol.Debugf("MQMSG #%d %s(%d) type=%s = [%d strings] offset=%d bytes=%d",
383 i+1, mqParameterToString(param.Parameter), param.Parameter, mqPCFTypeToString(param.Type),
384 len(param.String), offset, bytesRead)
385 case ibmmq.MQCFT_INTEGER64:
386 if len(param.Int64Value) > 0 {
387 // For 64-bit integers, still use PCFValueToString but note it might not have mappings
388 valueStr := ibmmq.PCFValueToString(param.Parameter, param.Int64Value[0])
389 c.protocol.Debugf("MQMSG #%d %s(%d) type=%s = %s (%d) offset=%d bytes=%d",
390 i+1, mqParameterToString(param.Parameter), param.Parameter, mqPCFTypeToString(param.Type),
391 valueStr, param.Int64Value[0], offset, bytesRead)
392 }
393 default:
394 c.protocol.Debugf("MQMSG #%d %s(%d) type=%s offset=%d bytes=%d",
395 i+1, mqParameterToString(param.Parameter), param.Parameter, mqPCFTypeToString(param.Type),
396 offset, bytesRead)
397 }
398
399 params = append(params, param)
400 offset += bytesRead
401 }
402
403 return params, nil
404 }
405
406 // Helper functions for building PCF parameters
407
408 // buildStringParameter creates a string PCF parameter
409 func buildStringParameter(attr int32, value string) *ibmmq.PCFParameter {
410 param := &ibmmq.PCFParameter{
411 Type: ibmmq.MQCFT_STRING,
412 Parameter: attr,
413 String: []string{value},
414 }
415 return param
416 }
417
418 // buildIntParameter creates an integer PCF parameter
419 func buildIntParameter(attr int32, value int32) *ibmmq.PCFParameter {
420 param := &ibmmq.PCFParameter{
421 Type: ibmmq.MQCFT_INTEGER,
422 Parameter: attr,
423 Int64Value: []int64{int64(value)},
424 }
425 return param
426 }
427
428 // buildStringListParameter creates a string list PCF parameter
429 func buildStringListParameter(attr int32, values []string) *ibmmq.PCFParameter {
430 param := &ibmmq.PCFParameter{
431 Type: ibmmq.MQCFT_STRING_LIST,
432 Parameter: attr,
433 String: values,
434 }
435 return param
436 }
437
438 // buildIntListParameter creates an integer list PCF parameter
439 func buildIntListParameter(attr int32, values []int32) *ibmmq.PCFParameter {
440 int64Values := make([]int64, len(values))
441 for i, v := range values {
442 int64Values[i] = int64(v)
443 }
444 param := &ibmmq.PCFParameter{
445 Type: ibmmq.MQCFT_INTEGER_LIST,
446 Parameter: attr,
447 Int64Value: int64Values,
448 }
449 return param
450 }