package jt808 import ( "encoding/hex" "testing" "lingniu-vehicle-ingest/go/vehicle-gateway/internal/envelope" ) const sampleLocationFrame = "7E020000320133077954250001000000000048000301D2C4C707376139000A00E6004F26063016235701040001900C2504000000000202000030011F31010F867E" const real2019LocationFrame = "7E02004046010000000001489413506007CB00000000004C000301D276AB06FBFF4B000D014E00E826070122092214040000000017020000010400013361030201402504000000012A02000030011D310112EA0402008000C57E" func TestExtractFramesUnescapesAndParsesLocationWithTotalMileage(t *testing.T) { stream, err := hex.DecodeString(sampleLocationFrame + "7E02000000") if err != nil { t.Fatal(err) } frames, remainder, err := ExtractFrames(stream) if err != nil { t.Fatalf("ExtractFrames() error = %v", err) } if len(frames) != 1 { t.Fatalf("expected one complete frame, got %d", len(frames)) } if hex.EncodeToString(remainder) != "7e02000000" { t.Fatalf("expected partial remainder, got %s", hex.EncodeToString(remainder)) } env, err := ParseFrame(frames[0], 1782745114999, "115.231.168.135:43625") if err != nil { t.Fatalf("ParseFrame() error = %v", err) } if env.Protocol != envelope.ProtocolJT808 { t.Fatalf("protocol = %q", env.Protocol) } if env.MessageID != "0x0200" { t.Fatalf("message id = %q", env.MessageID) } if env.Phone != "013307795425" { t.Fatalf("phone = %q", env.Phone) } if env.Sequence != 1 { t.Fatalf("sequence = %d", env.Sequence) } assertFloatField(t, env, envelope.FieldLatitude, 30.590151) assertFloatField(t, env, envelope.FieldLongitude, 121.069881) assertFloatField(t, env, envelope.FieldSpeedKMH, 23.0) assertFloatField(t, env, envelope.FieldTotalMileageKM, 10241.2) if env.Fields["direction_deg"] != uint16(79) { t.Fatalf("direction = %#v", env.Fields["direction_deg"]) } location, ok := env.Parsed["location"].(map[string]any) if !ok { t.Fatalf("parsed location missing: %#v", env.Parsed) } additional, ok := location["additional"].([]map[string]any) if !ok || len(additional) == 0 { t.Fatalf("additional fields missing: %#v", location["additional"]) } if additional[0]["id"] != "0x01" || additional[0]["value_hex"] != "0001900C" { t.Fatalf("unexpected first additional item: %#v", additional[0]) } } func TestParseFrameSupportsJT8082019VersionedHeader(t *testing.T) { stream, err := hex.DecodeString(real2019LocationFrame) if err != nil { t.Fatal(err) } frames, remainder, err := ExtractFrames(stream) if err != nil { t.Fatalf("ExtractFrames() error = %v", err) } if len(frames) != 1 || len(remainder) != 0 { t.Fatalf("unexpected split result frames=%d remainder=%s", len(frames), hex.EncodeToString(remainder)) } env, err := ParseFrame(frames[0], 1782914967713, "115.231.168.135:43625") if err != nil { t.Fatalf("ParseFrame() error = %v", err) } if env.MessageID != "0x0200" { t.Fatalf("message id = %q", env.MessageID) } if env.Phone != "14894135060" { t.Fatalf("phone = %q", env.Phone) } if env.Sequence != 1995 { t.Fatalf("sequence = %d", env.Sequence) } assertFloatField(t, env, envelope.FieldLatitude, 30.570155) assertFloatField(t, env, envelope.FieldLongitude, 117.178187) assertFloatField(t, env, envelope.FieldSpeedKMH, 33.4) assertFloatField(t, env, envelope.FieldTotalMileageKM, 7868.9) if env.Fields["altitude_m"] != uint16(13) { t.Fatalf("altitude = %#v", env.Fields["altitude_m"]) } if env.Fields["direction_deg"] != uint16(232) { t.Fatalf("direction = %#v", env.Fields["direction_deg"]) } header, ok := env.Parsed["header"].(map[string]any) if !ok { t.Fatalf("parsed header missing: %#v", env.Parsed) } if header["versioned"] != true || header["protocol_version"] != byte(1) { t.Fatalf("unexpected versioned header: %#v", header) } if header["phone_bcd_hex"] != "00000000014894135060" { t.Fatalf("phone bcd = %#v", header["phone_bcd_hex"]) } } func TestExtractFramesHandlesEscapedPayload(t *testing.T) { payload := []byte{0x02, 0x00, 0x00, 0x02, 0x01, 0x33, 0x07, 0x79, 0x54, 0x25, 0x00, 0x01, 0x7e, 0x7d} frame := append([]byte{0x7e}, escape(append(payload, checksum(payload)))...) frame = append(frame, 0x7e) frames, remainder, err := ExtractFrames(frame) if err != nil { t.Fatalf("ExtractFrames() error = %v", err) } if len(frames) != 1 || len(remainder) != 0 { t.Fatalf("unexpected split result frames=%d remainder=%s", len(frames), hex.EncodeToString(remainder)) } if hex.EncodeToString(frames[0][:len(payload)]) != hex.EncodeToString(payload) { t.Fatalf("frame was not unescaped: %s", hex.EncodeToString(frames[0])) } } func assertFloatField(t *testing.T, env envelope.FrameEnvelope, key string, want float64) { t.Helper() got, ok := env.Fields[key].(float64) if !ok { t.Fatalf("field %s missing or not float64: %#v", key, env.Fields[key]) } if got != want { t.Fatalf("field %s = %v, want %v", key, got, want) } } func escape(payload []byte) []byte { var out []byte for _, value := range payload { switch value { case 0x7e: out = append(out, 0x7d, 0x02) case 0x7d: out = append(out, 0x7d, 0x01) default: out = append(out, value) } } return out }