From 2861947057f3d79e76a8264aa088532562652ab5 Mon Sep 17 00:00:00 2001 From: Reese Norris Date: Tue, 9 Apr 2024 11:26:15 -0700 Subject: [PATCH] implement "Fsinn" plane info request --- processor.go | 3 + processor_defs.go | 32 +++++++ protocol/plane_info_request_fsinn.go | 48 ++++++++++ protocol/plane_info_request_fsinn_test.go | 107 ++++++++++++++++++++++ 4 files changed, 190 insertions(+) create mode 100644 protocol/plane_info_request_fsinn.go create mode 100644 protocol/plane_info_request_fsinn_test.go diff --git a/processor.go b/processor.go index 79e519b..c7486ea 100644 --- a/processor.go +++ b/processor.go @@ -97,6 +97,9 @@ func GetProcessor(rawPacket string) (Processor, error) { if fields[2] == "PIR" { return PlaneInfoRequestProcessor, nil } + if fields[2] == "FSIPIR" { + return PlaneInfoRequestFsinnProcessor, nil + } if fields[2] == "PI" && len(fields) > 3 && fields[3] == "GEN" { return PlaneInfoResponseProcessor, nil } diff --git a/processor_defs.go b/processor_defs.go index cf404ee..ad3caa4 100644 --- a/processor_defs.go +++ b/processor_defs.go @@ -193,6 +193,38 @@ func PlaneInfoRequestProcessor(client *FSDClient, rawPacket string) *ProcessorRe return result } +func PlaneInfoRequestFsinnProcessor(client *FSDClient, rawPacket string) *ProcessorResult { + // Parse & validate packet + pdu, err := protocol.ParsePlaneInfoRequestFsinnPDU(rawPacket) + if err != nil { + var fsdError *protocol.FSDError + result := NewProcessorResult() + if errors.As(err, &fsdError) { + result.AddReply(fsdError.Serialize()) + } + result.Disconnect(true) + return result + } + + // Check for valid source callsign + if pdu.From != client.Callsign { + result := NewProcessorResult() + result.AddReply(protocol.NewGenericFSDError(protocol.PDUSourceInvalidError).Serialize()) + result.Disconnect(true) + return result + } + + mail := NewMail(client) + mail.SetType(MailTypeDirect) + mail.AddRecipient(pdu.To) + mail.AddPacket(rawPacket) + + result := NewProcessorResult() + result.AddMail(*mail) + + return result +} + func PlaneInfoResponseProcessor(client *FSDClient, rawPacket string) *ProcessorResult { // Parse & validate packet pdu, err := protocol.ParsePlaneInfoResponsePDU(rawPacket) diff --git a/protocol/plane_info_request_fsinn.go b/protocol/plane_info_request_fsinn.go new file mode 100644 index 0000000..4d28009 --- /dev/null +++ b/protocol/plane_info_request_fsinn.go @@ -0,0 +1,48 @@ +package protocol + +import ( + "fmt" + "strings" +) + +type PlaneInfoRequestFsinnPDU struct { + From string `validate:"required,alphanum,max=7"` + To string `validate:"required,alphanum,max=7"` + AirlineICAO string `validate:"alphanum,max=4"` + AircraftICAO string `validate:"alphanum,max=4"` + AircraftICAOCombinedType string `validate:"alphanum,max=4"` + SendMModel string `validate:"max=128"` +} + +func (p *PlaneInfoRequestFsinnPDU) Serialize() string { + return fmt.Sprintf("#SB%s:%s:FSIPIR:0:%s:%s:::::%s:%s%s", p.From, p.To, p.AirlineICAO, p.AircraftICAO, p.AircraftICAOCombinedType, p.SendMModel, PacketDelimeter) +} + +func ParsePlaneInfoRequestFsinnPDU(rawPacket string) (*PlaneInfoRequestFsinnPDU, error) { + rawPacket = strings.TrimSuffix(rawPacket, PacketDelimeter) + rawPacket = strings.TrimPrefix(rawPacket, "#SB") + fields := strings.Split(rawPacket, Delimeter) + if len(fields) != 12 { + return nil, NewGenericFSDError(SyntaxError) + } + + if fields[2] != "FSIPIR" { + return nil, NewGenericFSDError(SyntaxError) + } + + pdu := &PlaneInfoRequestFsinnPDU{ + From: fields[0], + To: fields[1], + AirlineICAO: fields[4], + AircraftICAO: fields[5], + AircraftICAOCombinedType: fields[10], + SendMModel: fields[11], + } + + err := V.Struct(pdu) + if err != nil { + return nil, NewGenericFSDError(SyntaxError) + } + + return pdu, nil +} diff --git a/protocol/plane_info_request_fsinn_test.go b/protocol/plane_info_request_fsinn_test.go new file mode 100644 index 0000000..b6f6291 --- /dev/null +++ b/protocol/plane_info_request_fsinn_test.go @@ -0,0 +1,107 @@ +package protocol + +import ( + "github.com/go-playground/validator/v10" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestParsePlaneInfoRequestFsinnPDU(t *testing.T) { + V = validator.New(validator.WithRequiredStructEnabled()) + + tests := []struct { + name string + packet string + want *PlaneInfoRequestFsinnPDU + wantErr error + }{ + { + "Valid full request", + "#SBPILOT:ATC:FSIPIR:0:BAW:7478:::::B744:British Airways Boeing 747-400\r\n", + &PlaneInfoRequestFsinnPDU{ + From: "PILOT", + To: "ATC", + AirlineICAO: "BAW", + AircraftICAO: "7478", + AircraftICAOCombinedType: "B744", + SendMModel: "British Airways Boeing 747-400", + }, + nil, + }, + { + "Invalid From", + "#SB12345678:ATC:FSIPIR:0:UAL:A320:::::A20N:United Airbus A320neo\r\n", + nil, + NewGenericFSDError(SyntaxError), + }, + { + "Invalid To", + "#SBPILOT:CONTROLLER123:FSIPIR:0:LUF:B77W:::::B77W:Lufthansa Boeing 777\r\n", + nil, + NewGenericFSDError(SyntaxError), + }, + { + "Extra delimiter", + "#SBPILOT:ATC:FSIPIR:0:DLH:::A343:::A343:Lufthansa Airbus A340-300\r\n", + nil, + NewGenericFSDError(SyntaxError), + }, + { + "Missing ICAO code", + "#SBPILOT:ATC:FSIPIR:0::7378:::::B738:Ryanair Boeing 737-800\r\n", + nil, + NewGenericFSDError(SyntaxError), + }, + { + "Invalid PDU type", + "#SBPILOT:ATC:FOOBAR:0:SWR:A333:::::A333:Swiss Airbus A330-300\r\n", + nil, + NewGenericFSDError(SyntaxError), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + // Perform the parsing + result, err := ParsePlaneInfoRequestFsinnPDU(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 TestPlaneInfoRequestFsinnPDU_Serialize(t *testing.T) { + tests := []struct { + name string + pdu *PlaneInfoRequestFsinnPDU + wantStr string + }{ + { + "Valid Serialize", + &PlaneInfoRequestFsinnPDU{ + From: "PILOT", + To: "ATC", + AirlineICAO: "AAA", + AircraftICAO: "A320", + AircraftICAOCombinedType: "A20N", + SendMModel: "Airbus A320neo", + }, + "#SBPILOT:ATC:FSIPIR:0:AAA:A320:::::A20N:Airbus A320neo\r\n", + }, + // You can add more cases if necessary, for example empty values, very long strings, etc + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + assert.Equal(t, tc.wantStr, tc.pdu.Serialize()) + }) + } +}