From ed3eee721f51741ff59f4735a5ff15971a73e28c Mon Sep 17 00:00:00 2001 From: Reese Norris Date: Tue, 9 Apr 2024 15:14:09 -0700 Subject: [PATCH] add metar request/response to protocol --- protocol/metar_request.go | 43 ++++++++++++ protocol/metar_request_test.go | 96 ++++++++++++++++++++++++++ protocol/metar_response.go | 39 +++++++++++ protocol/metar_response_test.go | 115 ++++++++++++++++++++++++++++++++ 4 files changed, 293 insertions(+) create mode 100644 protocol/metar_request.go create mode 100644 protocol/metar_request_test.go create mode 100644 protocol/metar_response.go create mode 100644 protocol/metar_response_test.go diff --git a/protocol/metar_request.go b/protocol/metar_request.go new file mode 100644 index 0000000..1be32e7 --- /dev/null +++ b/protocol/metar_request.go @@ -0,0 +1,43 @@ +package protocol + +import ( + "fmt" + "strings" +) + +type MetarRequestPDU struct { + From string `validate:"required,alphanum,max=7"` + To string `validate:"required,alphanum,max=7"` + Station string `validate:"required,alphanum,max=4"` +} + +func (p *MetarRequestPDU) Serialize() string { + return fmt.Sprintf("$AX%s:%s:METAR:%s%s", p.From, p.To, p.Station, PacketDelimeter) +} + +func ParseMetarRequestPDU(rawPacket string) (*MetarRequestPDU, error) { + rawPacket = strings.TrimSuffix(rawPacket, PacketDelimeter) + rawPacket = strings.TrimPrefix(rawPacket, "$AX") + fields := strings.Split(rawPacket, Delimeter) + + if len(fields) != 4 { + return nil, NewGenericFSDError(SyntaxError) + } + + if fields[2] != "METAR" { + return nil, NewGenericFSDError(SyntaxError) + } + + pdu := MetarRequestPDU{ + From: fields[0], + To: fields[1], + Station: fields[3], + } + + err := V.Struct(pdu) + if err != nil { + return nil, NewGenericFSDError(SyntaxError) + } + + return &pdu, nil +} diff --git a/protocol/metar_request_test.go b/protocol/metar_request_test.go new file mode 100644 index 0000000..29e1807 --- /dev/null +++ b/protocol/metar_request_test.go @@ -0,0 +1,96 @@ +package protocol + +import ( + "github.com/go-playground/validator/v10" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestMetarRequestPDU_SerializationAndParsing(t *testing.T) { + V = validator.New(validator.WithRequiredStructEnabled()) + + tests := []struct { + name string + pduInstance *MetarRequestPDU + rawPacket string + expectedError error + }{ + { + "Valid MetarRequestPDU", + &MetarRequestPDU{ + From: "PILOT1", + To: "ATC01", + Station: "KJFK", + }, + "$AXPILOT1:ATC01:METAR:KJFK\r\n", + nil, + }, + { + "Invalid From (too long)", + nil, + "$AXPILOT123:ATC01:METAR:KJFK\r\n", + NewGenericFSDError(SyntaxError), + }, + { + "Invalid To (not alphanumeric)", + nil, + "$AXPILOT1:AT*C1:METAR:KJFK\r\n", + NewGenericFSDError(SyntaxError), + }, + { + "Invalid Station (too long)", + nil, + "$AXPILOT1:ATC01:METAR:KJFKKK\r\n", + NewGenericFSDError(SyntaxError), + }, + { + "Missing Station", + nil, + "$AXPILOT1:ATC01:METAR:\r\n", + NewGenericFSDError(SyntaxError), + }, + { + "Invalid Command", + nil, + "$AXPILOT1:ATC01:NOTAM:KJFK\r\n", + NewGenericFSDError(SyntaxError), + }, + { + "Extra fields", + nil, + "$AXPILOT1:ATC01:METAR:KJFK:EXTRA\r\n", + NewGenericFSDError(SyntaxError), + }, + { + "Missing Delimiters", + nil, + "PILOT1ATC01METARKJFK\r\n", + NewGenericFSDError(SyntaxError), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + if tc.pduInstance != nil { + // Test serialization + serialized := tc.pduInstance.Serialize() + assert.Equal(t, tc.rawPacket, serialized, "serialization should match expected output") + } + + // Perform parsing + result, err := ParseMetarRequestPDU(tc.rawPacket) + + // Check the error + if tc.expectedError != nil { + assert.EqualError(t, err, tc.expectedError.Error(), "errors should match expected output for case '%s'", tc.name) + } else { + assert.NoError(t, err, "no error should occur for case '%s'", tc.name) + } + + // Verify the result + if tc.pduInstance != nil { + assert.Equal(t, tc.pduInstance, result, "parsed result should match expected PDU for case '%s'", tc.name) + } + }) + } +} diff --git a/protocol/metar_response.go b/protocol/metar_response.go new file mode 100644 index 0000000..2b91880 --- /dev/null +++ b/protocol/metar_response.go @@ -0,0 +1,39 @@ +package protocol + +import ( + "fmt" + "strings" +) + +type MetarResponsePDU struct { + From string `validate:"required,alphanum,max=7"` + To string `validate:"required,alphanum,max=7"` + Metar string `validate:"required,max=256"` +} + +func (p *MetarResponsePDU) Serialize() string { + return fmt.Sprintf("$AR%s:%s:%s%s", p.From, p.To, p.Metar, PacketDelimeter) +} + +func ParseMetarResponsePDU(rawPacket string) (*MetarResponsePDU, error) { + rawPacket = strings.TrimSuffix(rawPacket, PacketDelimeter) + rawPacket = strings.TrimPrefix(rawPacket, "$AR") + fields := strings.SplitN(rawPacket, Delimeter, 3) + + if len(fields) != 3 { + return nil, NewGenericFSDError(SyntaxError) + } + + pdu := MetarResponsePDU{ + From: fields[0], + To: fields[1], + Metar: fields[2], + } + + err := V.Struct(pdu) + if err != nil { + return nil, NewGenericFSDError(SyntaxError) + } + + return &pdu, nil +} diff --git a/protocol/metar_response_test.go b/protocol/metar_response_test.go new file mode 100644 index 0000000..0f31ef1 --- /dev/null +++ b/protocol/metar_response_test.go @@ -0,0 +1,115 @@ +package protocol + +import ( + "github.com/go-playground/validator/v10" + "github.com/stretchr/testify/assert" + "strings" + "testing" +) + +func TestParseMetarResponsePDU(t *testing.T) { + V = validator.New(validator.WithRequiredStructEnabled()) + + tests := []struct { + name string + packet string + want *MetarResponsePDU + wantErr error + }{ + { + "Valid - Standard Metar", + "$ARSERVER:CLIENT:KSEE 091847Z 25007KT 10SM SKC 24/04 A3006\r\n", + &MetarResponsePDU{ + From: "SERVER", + To: "CLIENT", + Metar: "KSEE 091847Z 25007KT 10SM SKC 24/04 A3006", + }, + nil, + }, + { + "Valid - colons in metar", + "$ARSERVER:CLIENT:KSEE 091847Z::: 25007KT 10::SM SKC 24/:04 A3006\r\n", + &MetarResponsePDU{ + From: "SERVER", + To: "CLIENT", + Metar: "KSEE 091847Z::: 25007KT 10::SM SKC 24/:04 A3006", + }, + nil, + }, + { + "Missing To field", + "$ARSERVER::KSEE 091847Z 25007KT 10SM SKC 24/04 A3006\r\n", + nil, + NewGenericFSDError(SyntaxError), + }, + { + "From Field too long", + "$ARSERVERTOLONG:CLIENT:KSEE 091847Z 25007KT 10SM SKC 24/04 A3006\r\n", + nil, + NewGenericFSDError(SyntaxError), + }, + { + "Metar too long", + "$ARSERVER:CLIENT:" + strings.Repeat("A", 257) + "\r\n", + nil, + NewGenericFSDError(SyntaxError), + }, + { + "Incomplete packet format", + "$ARSERVER:CLIENT\r\n", + nil, + NewGenericFSDError(SyntaxError), + }, + { + "Empty metar field", + "$ARSERVER:CLIENT:\r\n", + nil, + NewGenericFSDError(SyntaxError), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + // Perform the parsing + result, err := ParseMetarResponsePDU(tc.packet) + + // Check the error + if tc.wantErr != nil { + assert.EqualError(t, err, tc.wantErr.Error()) + } else { + assert.NoError(t, err) + } + + // Verify the result + assert.Equal(t, tc.want, result) + }) + } +} + +func TestMetarResponsePDU_Serialize(t *testing.T) { + tests := []struct { + name string + pdu MetarResponsePDU + want string + }{ + { + "Standard Metar", + MetarResponsePDU{ + From: "SERVER", + To: "CLIENT", + Metar: "KSEE 091847Z 25007KT 10SM SKC 24/04 A3006", + }, + "$ARSERVER:CLIENT:KSEE 091847Z 25007KT 10SM SKC 24/04 A3006\r\n", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + // Perform the serialization + got := tc.pdu.Serialize() + + // Verify the result + assert.Equal(t, tc.want, got) + }) + } +}