From 0c92ace2fa28ffe283c459c87b8fe9ac4994bc44 Mon Sep 17 00:00:00 2001 From: Daniel Pupius Date: Sun, 20 Apr 2025 15:32:44 -0700 Subject: [PATCH] Separate decoding and formatting into dedicated files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Created new formatter.go file with all formatting functions - Replaced deprecated FormatData with improved FormatDataMessage - Updated main.go to separate decoding and formatting steps - Created specialized formatting functions for different message types - Better separation of concerns for improved maintainability 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- decoder/formatter.go | 130 ++++++++++++++++++++++++++----------------- main.go | 23 +++++++- 2 files changed, 101 insertions(+), 52 deletions(-) diff --git a/decoder/formatter.go b/decoder/formatter.go index ff47f4d..3350c0c 100644 --- a/decoder/formatter.go +++ b/decoder/formatter.go @@ -430,65 +430,69 @@ func FormatWaypointMessage(waypoint *pb.Waypoint) string { return builder.String() } -// FormatData formats a Data message (deprecated - use FormatPayload instead) -func FormatData(data *pb.Data) string { +// FormatDataMessage formats a Data message +func FormatDataMessage(data *pb.Data) string { if data == nil { return "Error: nil data" } - decoded := &DecodedPacket{ - PortNum: data.GetPortnum(), - RequestID: data.GetRequestId(), - ReplyID: data.GetReplyId(), - Emoji: data.GetEmoji(), - Dest: data.GetDest(), - Source: data.GetSource(), - WantResponse: data.GetWantResponse(), - RawData: data, - } + var builder strings.Builder + builder.WriteString(fmt.Sprintf("Data (Port: %s):\n", data.GetPortnum())) - // Process payload based on type + // Format payload based on port type payload := data.GetPayload() switch data.GetPortnum() { case pb.PortNum_TEXT_MESSAGE_APP: - decoded.Payload = string(payload) + builder.WriteString(fmt.Sprintf(" Text Message: %s\n", string(payload))) + + case pb.PortNum_TEXT_MESSAGE_COMPRESSED_APP: + builder.WriteString(fmt.Sprintf(" Compressed Text Message (%d bytes): %x\n", len(payload), payload)) + case pb.PortNum_TELEMETRY_APP: var telemetry pb.Telemetry if err := proto.Unmarshal(payload, &telemetry); err == nil { - decoded.Payload = &telemetry + builder.WriteString(FormatTelemetryMessage(&telemetry)) } else { - decoded.Payload = payload + builder.WriteString(fmt.Sprintf(" Failed to parse telemetry: %v\n", err)) + builder.WriteString(fmt.Sprintf(" Raw bytes (%d): %x\n", len(payload), payload)) } + case pb.PortNum_POSITION_APP: var position pb.Position if err := proto.Unmarshal(payload, &position); err == nil { - decoded.Payload = &position + builder.WriteString(FormatPositionMessage(&position)) } else { - decoded.Payload = payload + builder.WriteString(fmt.Sprintf(" Failed to parse position: %v\n", err)) + builder.WriteString(fmt.Sprintf(" Raw bytes (%d): %x\n", len(payload), payload)) } + case pb.PortNum_NODEINFO_APP: var user pb.User if err := proto.Unmarshal(payload, &user); err == nil { - decoded.Payload = &user + builder.WriteString(FormatNodeInfoMessage(&user)) } else { - decoded.Payload = payload + builder.WriteString(fmt.Sprintf(" Failed to parse node info: %v\n", err)) + builder.WriteString(fmt.Sprintf(" Raw bytes (%d): %x\n", len(payload), payload)) } + case pb.PortNum_WAYPOINT_APP: var waypoint pb.Waypoint if err := proto.Unmarshal(payload, &waypoint); err == nil { - decoded.Payload = &waypoint + builder.WriteString(FormatWaypointMessage(&waypoint)) } else { - decoded.Payload = payload + builder.WriteString(fmt.Sprintf(" Failed to parse waypoint: %v\n", err)) + builder.WriteString(fmt.Sprintf(" Raw bytes (%d): %x\n", len(payload), payload)) } + default: - decoded.Payload = payload + // For unknown types, show raw binary data + builder.WriteString(fmt.Sprintf(" Binary data (%d bytes): %x\n", len(payload), payload)) + // If it looks like ASCII text, also show as text + if IsASCII(payload) { + builder.WriteString(fmt.Sprintf(" As text: %s\n", string(payload))) + } } - // Format with our new formatter - var builder strings.Builder - builder.WriteString(fmt.Sprintf("Data (Port: %s):\n", data.GetPortnum())) - builder.WriteString(FormatPayload(decoded.Payload, decoded.PortNum)) - // Show additional Data fields if data.GetRequestId() != 0 { builder.WriteString(fmt.Sprintf(" Request ID: %d\n", data.GetRequestId())) @@ -692,7 +696,7 @@ func FormatServiceEnvelope(envelope *pb.ServiceEnvelope) string { if packet.GetDecoded() != nil { // For already decoded packets, use our specialized data formatter builder.WriteString("\n") - builder.WriteString(FormatData(packet.GetDecoded())) + builder.WriteString(FormatDataMessage(packet.GetDecoded())) } else if packet.GetEncrypted() != nil { // Encrypted payload information @@ -731,7 +735,7 @@ func FormatServiceEnvelope(envelope *pb.ServiceEnvelope) string { // Successfully decoded the decrypted payload into a Data message // Use our specialized data formatter to show the message details builder.WriteString("\n") - builder.WriteString(indent(FormatData(&data), " ")) + builder.WriteString(indent(FormatDataMessage(&data), " ")) } else { // If we couldn't parse as Data, try to interpret as text if IsASCII(decrypted) { @@ -800,8 +804,8 @@ func FormatJSONMessage(jsonData map[string]interface{}) string { return builder.String() } -// FormatMessage formats a decoded message based on its format -func FormatMessage(topicInfo *TopicInfo, payload []byte) string { +// FormatTopicAndPacket formats both topic information and a decoded packet +func FormatTopicAndPacket(topicInfo *TopicInfo, decodedPacket *DecodedPacket) string { var builder strings.Builder // Display topic information @@ -815,25 +819,51 @@ func FormatMessage(topicInfo *TopicInfo, payload []byte) string { } builder.WriteString("\n") - // Decode and format based on the format - if topicInfo.Format == "e" { - // Encoded protobuf message - use our new decoder - decodedPacket := DecodeMessage(payload, topicInfo) - builder.WriteString(FormatDecodedPacket(decodedPacket)) - } else if topicInfo.Format == "json" { - // JSON message - jsonData, err := DecodeJSONMessage(payload) - if err != nil { - builder.WriteString(fmt.Sprintf("Error decoding JSON message: %v\n", err)) - builder.WriteString(fmt.Sprintf("Raw Data: %s\n", string(payload))) - } else { - builder.WriteString(FormatJSONMessage(jsonData)) - } - } else { - // Unknown format - builder.WriteString(fmt.Sprintf("Unsupported format: %s\n", topicInfo.Format)) - builder.WriteString(fmt.Sprintf("Raw Data (%d bytes): %x\n", len(payload), payload)) + // Format the decoded packet + builder.WriteString(FormatDecodedPacket(decodedPacket)) + + return builder.String() +} + +// FormatTopicAndJSONData formats both topic information and decoded JSON data +func FormatTopicAndJSONData(topicInfo *TopicInfo, jsonData map[string]interface{}) string { + var builder strings.Builder + + // Display topic information + builder.WriteString(fmt.Sprintf("Topic: %s\n", topicInfo.FullTopic)) + builder.WriteString(fmt.Sprintf("Region Path: %s\n", topicInfo.RegionPath)) + builder.WriteString(fmt.Sprintf("Version: %s\n", topicInfo.Version)) + builder.WriteString(fmt.Sprintf("Format: %s\n", topicInfo.Format)) + builder.WriteString(fmt.Sprintf("Channel: %s\n", topicInfo.Channel)) + if topicInfo.UserID != "" { + builder.WriteString(fmt.Sprintf("User ID: %s\n", topicInfo.UserID)) } + builder.WriteString("\n") + + // Format the JSON data + builder.WriteString(FormatJSONMessage(jsonData)) + + return builder.String() +} + +// FormatTopicAndRawData formats topic information and raw data for unsupported formats +func FormatTopicAndRawData(topicInfo *TopicInfo, payload []byte) string { + var builder strings.Builder + + // Display topic information + builder.WriteString(fmt.Sprintf("Topic: %s\n", topicInfo.FullTopic)) + builder.WriteString(fmt.Sprintf("Region Path: %s\n", topicInfo.RegionPath)) + builder.WriteString(fmt.Sprintf("Version: %s\n", topicInfo.Version)) + builder.WriteString(fmt.Sprintf("Format: %s\n", topicInfo.Format)) + builder.WriteString(fmt.Sprintf("Channel: %s\n", topicInfo.Channel)) + if topicInfo.UserID != "" { + builder.WriteString(fmt.Sprintf("User ID: %s\n", topicInfo.UserID)) + } + builder.WriteString("\n") + + // Display raw data + builder.WriteString(fmt.Sprintf("Unsupported format: %s\n", topicInfo.Format)) + builder.WriteString(fmt.Sprintf("Raw Data (%d bytes): %x\n", len(payload), payload)) return builder.String() } \ No newline at end of file diff --git a/main.go b/main.go index 9787bee..b7f30c1 100644 --- a/main.go +++ b/main.go @@ -31,8 +31,27 @@ var messagePubHandler mqtt.MessageHandler = func(client mqtt.Client, msg mqtt.Me fmt.Printf("Raw topic: %s\n", msg.Topic()) fmt.Printf("Raw payload: %x\n", msg.Payload()) } else { - // Format and print the message - formattedOutput := decoder.FormatMessage(topicInfo, msg.Payload()) + // First decode the message based on its format + var formattedOutput string + if topicInfo.Format == "e" { + // Binary encoded protobuf message + decodedPacket := decoder.DecodeMessage(msg.Payload(), topicInfo) + formattedOutput = decoder.FormatTopicAndPacket(topicInfo, decodedPacket) + } else if topicInfo.Format == "json" { + // JSON format message + jsonData, err := decoder.DecodeJSONMessage(msg.Payload()) + if err != nil { + fmt.Printf("Error decoding JSON message: %v\n", err) + formattedOutput = decoder.FormatTopicAndRawData(topicInfo, msg.Payload()) + } else { + formattedOutput = decoder.FormatTopicAndJSONData(topicInfo, jsonData) + } + } else { + // Unsupported format + formattedOutput = decoder.FormatTopicAndRawData(topicInfo, msg.Payload()) + } + + // Print the formatted output fmt.Println(formattedOutput) }