diff --git a/sds/command.go b/sds/command.go index 0f0fae9..194453f 100644 --- a/sds/command.go +++ b/sds/command.go @@ -1,7 +1,11 @@ package sds import ( + "context" "fmt" + "regexp" + "strconv" + "strings" "github.com/ftl/tetra-pei/tetra" ) @@ -16,17 +20,50 @@ func (f EncoderFunc) Encode() ([]byte, int) { return f() } +type RequesterFunc func(context.Context, string) ([]string, error) + const ( - // SwitchToSDSTL is a short-cut for selecting the SDS-TL AI service according to [PEI] 6.14.6 + // CRLF line ending for AT commands + CRLF = "\x0d\x0a" + // CtrlZ line ending for PDUs + CtrlZ = "\x1a" + + // SwitchToSDSTL is a short-cut for selecting the SDS-TL AI service with ISSI addressing and E2EE according to [PEI] 6.14.6 SwitchToSDSTL = "AT+CTSDS=12,0,0,0,1" - // SwitchToStatus is a short-cut for selecting the status AI service according to [PEI] 6.14.6 + // SwitchToStatus is a short-cut for selecting the status AI service with ISSI addresssing according to [PEI] 6.14.6 SwitchToStatus = "AT+CTSDS=13,0" ) // SendMessage according to [PEI] 6.13.2 func SendMessage(destination tetra.Identity, sds Encoder) string { - pdu := make([]byte, 0, 2000) // TODO use the maximum size allowed + pdu := make([]byte, 0, 256) pduBits := 0 pdu, pduBits = sds.Encode(pdu, pduBits) - return fmt.Sprintf("AT+CMGS=%s,%d\x0d\x0a%s\x1a", destination, pduBits, tetra.BinaryToHex(pdu)) + return fmt.Sprintf("AT+CMGS=%s,%d"+CRLF+"%s"+CtrlZ, destination, pduBits, tetra.BinaryToHex(pdu)) +} + +var sendMessageDescription = regexp.MustCompile(`^\+CMGS: .+\(\d*-(\d*)\)$`) + +// RequestMaxMessagePDUBits uses the given RequesterFunc to find out how many bits a message PDU may have (see [PEI] 6.13.2). +func RequestMaxMessagePDUBits(ctx context.Context, requester RequesterFunc) (int, error) { + responses, err := requester(ctx, "AT+CMGS=?") + if err != nil { + return 0, err + } + if len(responses) < 1 { + return 0, fmt.Errorf("no response received") + } + response := strings.ToUpper(strings.TrimSpace(responses[0])) + parts := sendMessageDescription.FindStringSubmatch(response) + + if len(parts) != 2 { + return 0, fmt.Errorf("unexpected response: %s", responses[0]) + } + + result, err := strconv.Atoi(parts[1]) + if err != nil { + return 0, err + } + + return result, nil } diff --git a/sds/command_test.go b/sds/command_test.go index bc13b40..8f31058 100644 --- a/sds/command_test.go +++ b/sds/command_test.go @@ -1 +1,45 @@ package sds + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestRequestMaxPDUBits(t *testing.T) { + tt := []struct { + desc string + response []string + expected int + invalid bool + }{ + { + desc: "empty", + invalid: true, + }, + { + desc: "happy path", + response: []string{ + "+CMGS: (0-16777214,00000001-10231638316777214,1-255,0-999999999999999999999999),(8-1184)", + "", + "OK", + }, + expected: 1184, + }, + } + for _, tc := range tt { + t.Run(tc.desc, func(t *testing.T) { + requester := func(_ context.Context, _ string) ([]string, error) { + return tc.response, nil + } + actual, err := RequestMaxMessagePDUBits(context.Background(), requester) + if tc.invalid { + assert.Error(t, err) + } else { + assert.NoError(t, err) + assert.Equal(t, tc.expected, actual) + } + }) + } +}