Another checkpoint moving to proto defined packets

This commit is contained in:
Daniel Pupius
2025-04-21 18:27:55 -07:00
parent 86380cccf9
commit f580249162
8 changed files with 280 additions and 281 deletions

View File

@@ -6,7 +6,8 @@ import (
"strings"
"testing"
pb "meshstream/proto/generated/meshtastic"
meshtreampb "meshstream/generated/meshstream"
pb "meshstream/generated/meshtastic"
"google.golang.org/protobuf/proto"
)
@@ -127,38 +128,35 @@ func TestDecodeMessageWithMapPayload(t *testing.T) {
}
// Create a topic info structure for a map topic
topicInfo := &TopicInfo{
topicInfo := &meshtreampb.TopicInfo{
FullTopic: "msh/US/bayarea/2/map/LongFast/!1234abcd",
RegionPath: "US/bayarea",
Version: "2",
Format: "map",
Channel: "LongFast",
UserID: "!1234abcd",
UserId: "!1234abcd",
}
// Call the actual DecodeMessage function we want to test
decodedPacket := DecodeMessage(data, topicInfo)
decodedData := DecodeMessage(data, topicInfo)
// Check that the decoding was successful
if decodedPacket.DecodeError != nil {
t.Errorf("Expected successful decoding, but got error: %v", decodedPacket.DecodeError)
if decodedData.DecodeError != "" {
t.Errorf("Expected successful decoding, but got error: %v", decodedData.DecodeError)
}
// Verify the decoded packet has the expected format
if decodedPacket.PortNum != pb.PortNum_MAP_REPORT_APP {
t.Errorf("Expected PortNum to be MAP_REPORT_APP, got %s", decodedPacket.PortNum)
if decodedData.PortNum != pb.PortNum_MAP_REPORT_APP {
t.Errorf("Expected PortNum to be MAP_REPORT_APP, got %s", decodedData.PortNum)
}
// These fields are no longer used
// Only verify that key metadata was correctly extracted
// Verify that key metadata was correctly extracted
if decodedPacket.From == 0 {
if decodedData.From == 0 {
t.Error("Expected From field to be non-zero")
}
// Format the output and check it contains expected components
formattedOutput := FormatTopicAndPacket(topicInfo, decodedPacket)
formattedOutput := FormatTopicAndPacket(topicInfo, decodedData)
// Print out the formatted output to debug
t.Logf("Formatted output: %s", formattedOutput)

View File

@@ -6,13 +6,13 @@ import (
"google.golang.org/protobuf/proto"
mesh "meshstream/proto/generated"
pb "meshstream/proto/generated/meshtastic"
meshtreampb "meshstream/generated/meshstream"
pb "meshstream/generated/meshtastic"
)
// ParseTopic parses a Meshtastic MQTT topic into its components
func ParseTopic(topic string) (*mesh.TopicInfo, error) {
info := &mesh.TopicInfo{
func ParseTopic(topic string) (*meshtreampb.TopicInfo, error) {
info := &meshtreampb.TopicInfo{
FullTopic: topic,
}
@@ -75,78 +75,78 @@ func DecodeEncodedMessage(payload []byte) (*pb.ServiceEnvelope, error) {
return &serviceEnvelope, nil
}
// DecodeMessage creates a DecodedPacket from a binary encoded message
func DecodeMessage(payload []byte, topicInfo *mesh.TopicInfo) *mesh.DecodedPacket {
decoded := &mesh.DecodedPacket{}
// DecodeMessage creates a Data object from a binary encoded message
func DecodeMessage(payload []byte, topicInfo *meshtreampb.TopicInfo) *meshtreampb.Data {
data := &meshtreampb.Data{}
// First decode the envelope
envelope, err := DecodeEncodedMessage(payload)
if err != nil {
decoded.DecodeError = err.Error()
return decoded
data.DecodeError = err.Error()
return data
}
// Extract envelope fields
decoded.ChannelId = envelope.GetChannelId()
decoded.GatewayId = envelope.GetGatewayId()
data.ChannelId = envelope.GetChannelId()
data.GatewayId = envelope.GetGatewayId()
// Extract mesh packet fields if available
packet := envelope.GetPacket()
if packet == nil {
decoded.DecodeError = "no mesh packet in envelope"
return decoded
data.DecodeError = "no mesh packet in envelope"
return data
}
// Extract mesh packet fields
decoded.Id = packet.GetId()
decoded.From = packet.GetFrom()
decoded.To = packet.GetTo()
decoded.HopLimit = packet.GetHopLimit()
decoded.HopStart = packet.GetHopStart()
decoded.WantAck = packet.GetWantAck()
decoded.Priority = packet.GetPriority().String()
decoded.ViaMqtt = packet.GetViaMqtt()
decoded.NextHop = packet.GetNextHop()
decoded.RelayNode = packet.GetRelayNode()
data.Id = packet.GetId()
data.From = packet.GetFrom()
data.To = packet.GetTo()
data.HopLimit = packet.GetHopLimit()
data.HopStart = packet.GetHopStart()
data.WantAck = packet.GetWantAck()
data.Priority = packet.GetPriority().String()
data.ViaMqtt = packet.GetViaMqtt()
data.NextHop = packet.GetNextHop()
data.RelayNode = packet.GetRelayNode()
// Process the payload
if packet.GetDecoded() != nil {
// Packet has already been decoded
decodeDataPayload(decoded, packet.GetDecoded())
decodeDataPayload(data, packet.GetDecoded())
} else if packet.GetEncrypted() != nil {
// Packet is encrypted, try to decrypt it
decodeEncryptedPayload(decoded, packet.GetEncrypted(), envelope.GetChannelId(), packet.GetId(), packet.GetFrom())
decodeEncryptedPayload(data, packet.GetEncrypted(), envelope.GetChannelId(), packet.GetId(), packet.GetFrom())
} else {
decoded.DecodeError = "packet has no payload"
data.DecodeError = "packet has no payload"
}
return decoded
return data
}
// decodeDataPayload extracts information from a Data message
func decodeDataPayload(decoded *mesh.DecodedPacket, data *pb.Data) {
func decodeDataPayload(data *meshtreampb.Data, pbData *pb.Data) {
// Extract data fields
decoded.PortNum = data.GetPortnum()
decoded.RequestId = data.GetRequestId()
decoded.ReplyId = data.GetReplyId()
decoded.Emoji = data.GetEmoji()
decoded.Dest = data.GetDest()
decoded.Source = data.GetSource()
decoded.WantResponse = data.GetWantResponse()
data.PortNum = pbData.GetPortnum()
data.RequestId = pbData.GetRequestId()
data.ReplyId = pbData.GetReplyId()
data.Emoji = pbData.GetEmoji()
data.Dest = pbData.GetDest()
data.Source = pbData.GetSource()
data.WantResponse = pbData.GetWantResponse()
// Process the payload based on port type
payload := data.GetPayload()
payload := pbData.GetPayload()
switch data.GetPortnum() {
switch pbData.GetPortnum() {
case pb.PortNum_TEXT_MESSAGE_APP:
// Text message - store as string
decoded.Payload = &mesh.DecodedPacket_TextMessage{
data.Payload = &meshtreampb.Data_TextMessage{
TextMessage: string(payload),
}
case pb.PortNum_TEXT_MESSAGE_COMPRESSED_APP:
// Compressed text - store the raw bytes
decoded.Payload = &mesh.DecodedPacket_CompressedText{
data.Payload = &meshtreampb.Data_CompressedText{
CompressedText: payload,
}
@@ -154,9 +154,9 @@ func decodeDataPayload(decoded *mesh.DecodedPacket, data *pb.Data) {
// Position data
var position pb.Position
if err := proto.Unmarshal(payload, &position); err != nil {
decoded.DecodeError = fmt.Sprintf("failed to unmarshal Position data: %v", err)
data.DecodeError = fmt.Sprintf("failed to unmarshal Position data: %v", err)
} else {
decoded.Payload = &mesh.DecodedPacket_Position{
data.Payload = &meshtreampb.Data_Position{
Position: &position,
}
}
@@ -165,9 +165,9 @@ func decodeDataPayload(decoded *mesh.DecodedPacket, data *pb.Data) {
// Node information
var user pb.User
if err := proto.Unmarshal(payload, &user); err != nil {
decoded.DecodeError = fmt.Sprintf("failed to unmarshal User data: %v", err)
data.DecodeError = fmt.Sprintf("failed to unmarshal User data: %v", err)
} else {
decoded.Payload = &mesh.DecodedPacket_NodeInfo{
data.Payload = &meshtreampb.Data_NodeInfo{
NodeInfo: &user,
}
}
@@ -176,9 +176,9 @@ func decodeDataPayload(decoded *mesh.DecodedPacket, data *pb.Data) {
// Telemetry data
var telemetry pb.Telemetry
if err := proto.Unmarshal(payload, &telemetry); err != nil {
decoded.DecodeError = fmt.Sprintf("failed to unmarshal Telemetry data: %v", err)
data.DecodeError = fmt.Sprintf("failed to unmarshal Telemetry data: %v", err)
} else {
decoded.Payload = &mesh.DecodedPacket_Telemetry{
data.Payload = &meshtreampb.Data_Telemetry{
Telemetry: &telemetry,
}
}
@@ -187,9 +187,9 @@ func decodeDataPayload(decoded *mesh.DecodedPacket, data *pb.Data) {
// Waypoint data
var waypoint pb.Waypoint
if err := proto.Unmarshal(payload, &waypoint); err != nil {
decoded.DecodeError = fmt.Sprintf("failed to unmarshal Waypoint data: %v", err)
data.DecodeError = fmt.Sprintf("failed to unmarshal Waypoint data: %v", err)
} else {
decoded.Payload = &mesh.DecodedPacket_Waypoint{
data.Payload = &meshtreampb.Data_Waypoint{
Waypoint: &waypoint,
}
}
@@ -198,9 +198,9 @@ func decodeDataPayload(decoded *mesh.DecodedPacket, data *pb.Data) {
// Map report data
var mapReport pb.MapReport
if err := proto.Unmarshal(payload, &mapReport); err != nil {
decoded.DecodeError = fmt.Sprintf("failed to unmarshal MapReport data: %v", err)
data.DecodeError = fmt.Sprintf("failed to unmarshal MapReport data: %v", err)
} else {
decoded.Payload = &mesh.DecodedPacket_MapReport{
data.Payload = &meshtreampb.Data_MapReport{
MapReport: &mapReport,
}
}
@@ -209,9 +209,9 @@ func decodeDataPayload(decoded *mesh.DecodedPacket, data *pb.Data) {
// Traceroute data
var routeDiscovery pb.RouteDiscovery
if err := proto.Unmarshal(payload, &routeDiscovery); err != nil {
decoded.DecodeError = fmt.Sprintf("failed to unmarshal RouteDiscovery data: %v", err)
data.DecodeError = fmt.Sprintf("failed to unmarshal RouteDiscovery data: %v", err)
} else {
decoded.Payload = &mesh.DecodedPacket_RouteDiscovery{
data.Payload = &meshtreampb.Data_RouteDiscovery{
RouteDiscovery: &routeDiscovery,
}
}
@@ -220,54 +220,98 @@ func decodeDataPayload(decoded *mesh.DecodedPacket, data *pb.Data) {
// Neighbor information data
var neighborInfo pb.NeighborInfo
if err := proto.Unmarshal(payload, &neighborInfo); err != nil {
decoded.DecodeError = fmt.Sprintf("failed to unmarshal NeighborInfo data: %v", err)
data.DecodeError = fmt.Sprintf("failed to unmarshal NeighborInfo data: %v", err)
} else {
decoded.Payload = &mesh.DecodedPacket_NeighborInfo{
data.Payload = &meshtreampb.Data_NeighborInfo{
NeighborInfo: &neighborInfo,
}
}
case pb.PortNum_REMOTE_HARDWARE_APP:
// Remote hardware data
var hardware pb.HardwareMessage
if err := proto.Unmarshal(payload, &hardware); err != nil {
data.DecodeError = fmt.Sprintf("failed to unmarshal HardwareMessage data: %v", err)
} else {
data.Payload = &meshtreampb.Data_RemoteHardware{
RemoteHardware: &hardware,
}
}
case pb.PortNum_ROUTING_APP:
// Routing data
var routing pb.Routing
if err := proto.Unmarshal(payload, &routing); err != nil {
data.DecodeError = fmt.Sprintf("failed to unmarshal Routing data: %v", err)
} else {
data.Payload = &meshtreampb.Data_Routing{
Routing: &routing,
}
}
case pb.PortNum_ADMIN_APP:
// Admin data
var admin pb.AdminMessage
if err := proto.Unmarshal(payload, &admin); err != nil {
data.DecodeError = fmt.Sprintf("failed to unmarshal AdminMessage data: %v", err)
} else {
data.Payload = &meshtreampb.Data_Admin{
Admin: &admin,
}
}
case pb.PortNum_PAXCOUNTER_APP:
// Paxcount data
var paxcount pb.Paxcount
if err := proto.Unmarshal(payload, &paxcount); err != nil {
data.DecodeError = fmt.Sprintf("failed to unmarshal Paxcount data: %v", err)
} else {
data.Payload = &meshtreampb.Data_Paxcounter{
Paxcounter: &paxcount,
}
}
default:
// For other types, just store the raw bytes
decoded.Payload = &mesh.DecodedPacket_BinaryData{
data.Payload = &meshtreampb.Data_BinaryData{
BinaryData: payload,
}
}
}
// decodeEncryptedPayload tries to decrypt and decode encrypted payloads
func decodeEncryptedPayload(decoded *mesh.DecodedPacket, encrypted []byte, channelId string, packetId, fromNode uint32) {
func decodeEncryptedPayload(data *meshtreampb.Data, encrypted []byte, channelId string, packetId, fromNode uint32) {
// Attempt to decrypt the payload using the channel key
if channelId == "" {
decoded.DecodeError = "encrypted packet has no channel ID"
data.DecodeError = "encrypted packet has no channel ID"
return
}
channelKey := GetChannelKey(channelId)
decrypted, err := XOR(encrypted, channelKey, packetId, fromNode)
if err != nil {
decoded.DecodeError = fmt.Sprintf("failed to decrypt payload: %v", err)
data.DecodeError = fmt.Sprintf("failed to decrypt payload: %v", err)
return
}
// Try to parse as a Data message
var data pb.Data
if err := proto.Unmarshal(decrypted, &data); err != nil {
var pbData pb.Data
if err := proto.Unmarshal(decrypted, &pbData); err != nil {
// If we can't parse as Data, check if it's ASCII text
if IsASCII(decrypted) {
decoded.PortNum = pb.PortNum_TEXT_MESSAGE_APP
decoded.Payload = &mesh.DecodedPacket_TextMessage{
data.PortNum = pb.PortNum_TEXT_MESSAGE_APP
data.Payload = &meshtreampb.Data_TextMessage{
TextMessage: string(decrypted),
}
} else {
decoded.DecodeError = fmt.Sprintf("failed to parse decrypted data: %v", err)
decoded.Payload = &mesh.DecodedPacket_BinaryData{
data.DecodeError = fmt.Sprintf("failed to parse decrypted data: %v", err)
data.Payload = &meshtreampb.Data_BinaryData{
BinaryData: decrypted,
}
}
} else {
// Successfully decoded the payload
decodeDataPayload(decoded, &data)
decodeDataPayload(data, &pbData)
}
}

View File

@@ -1,5 +1,87 @@
package decoder
// This file has been intentionally left empty.
// Removed formatter functions as they are no longer needed.
// We now use protobuf serialization via protojson for formatting.
import (
"fmt"
"strings"
meshtreampb "meshstream/generated/meshstream"
)
// FormatTopicAndPacket creates a human-readable representation of a topic and packet
// for debugging purposes.
func FormatTopicAndPacket(topic *meshtreampb.TopicInfo, data *meshtreampb.Data) string {
var sb strings.Builder
// Topic information
sb.WriteString("===== Topic Info =====\n")
sb.WriteString(fmt.Sprintf("Full Topic: %s\n", topic.FullTopic))
sb.WriteString(fmt.Sprintf("Region Path: %s\n", topic.RegionPath))
sb.WriteString(fmt.Sprintf("Version: %s\n", topic.Version))
sb.WriteString(fmt.Sprintf("Format: %s\n", topic.Format))
sb.WriteString(fmt.Sprintf("Channel: %s\n", topic.Channel))
sb.WriteString(fmt.Sprintf("User ID: %s\n", topic.UserId))
// Packet information
sb.WriteString("\n===== Packet Info =====\n")
// Check if we have a decode error
if data.DecodeError != "" {
sb.WriteString(fmt.Sprintf("ERROR: %s\n", data.DecodeError))
return sb.String()
}
// Basic packet information
sb.WriteString(fmt.Sprintf("ID: %d\n", data.Id))
sb.WriteString(fmt.Sprintf("From: %d\n", data.From))
sb.WriteString(fmt.Sprintf("To: %d\n", data.To))
sb.WriteString(fmt.Sprintf("Channel ID: %s\n", data.ChannelId))
sb.WriteString(fmt.Sprintf("Gateway ID: %s\n", data.GatewayId))
sb.WriteString(fmt.Sprintf("Port: %s\n", data.PortNum.String()))
sb.WriteString(fmt.Sprintf("Hop Limit: %d\n", data.HopLimit))
sb.WriteString(fmt.Sprintf("Request ID: %d\n", data.RequestId))
// Payload type-specific information
sb.WriteString("\n===== Payload Info =====\n")
switch data.Payload.(type) {
case *meshtreampb.Data_TextMessage:
sb.WriteString(fmt.Sprintf("Type: Text Message\nContent: %s\n", data.GetTextMessage()))
case *meshtreampb.Data_Position:
pos := data.GetPosition()
lat := float64(pos.GetLatitudeI()) / 10000000.0
lon := float64(pos.GetLongitudeI()) / 10000000.0
sb.WriteString(fmt.Sprintf("Type: Position\nLatitude: %.6f\nLongitude: %.6f\nAltitude: %d\n",
lat, lon, pos.GetAltitude()))
case *meshtreampb.Data_Telemetry:
telemetry := data.GetTelemetry()
sb.WriteString("Type: Telemetry\n")
if telemetry.GetEnvironmentMetrics() != nil {
env := telemetry.GetEnvironmentMetrics()
sb.WriteString(fmt.Sprintf("Environment: Temp %.1f°C, Rel Humidity %.1f%%\n",
env.GetTemperature(), env.GetRelativeHumidity()))
}
if telemetry.GetDeviceMetrics() != nil {
dev := telemetry.GetDeviceMetrics()
sb.WriteString(fmt.Sprintf("Device: Battery %d%%, Voltage %.1fV\n",
dev.GetBatteryLevel(), dev.GetVoltage()))
}
case *meshtreampb.Data_NodeInfo:
user := data.GetNodeInfo()
sb.WriteString(fmt.Sprintf("Type: User Info\nID: %s\nLongName: %s\nShortName: %s\n",
user.GetId(), user.GetLongName(), user.GetShortName()))
case *meshtreampb.Data_MapReport:
sb.WriteString(fmt.Sprintf("Type: Map Report\n"))
case *meshtreampb.Data_BinaryData:
sb.WriteString(fmt.Sprintf("Type: Binary Data\nLength: %d bytes\n", len(data.GetBinaryData())))
default:
sb.WriteString("Type: Unknown\n")
}
return sb.String()
}