From ff75b3c269efa24022da5cea0ef5552be163f838 Mon Sep 17 00:00:00 2001 From: Arvo Heinonen Date: Mon, 23 Jun 2025 10:38:23 +0300 Subject: [PATCH 1/4] Store TP-VP as []byte in smsSubmit This is to accommodate other TP-VP formats that can take a variable amount of bytes. --- sms/sms.go | 4 ++-- sms/sms_submit.go | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/sms/sms.go b/sms/sms.go index cf21d4e..ecce363 100644 --- a/sms/sms.go +++ b/sms/sms.go @@ -152,7 +152,7 @@ func (s *Message) encodeSubmit(buf *bytes.Buffer) (n int, err error) { switch s.VPFormat { case ValidityPeriodFormats.Relative: - sms.ValidityPeriod = byte(s.VP.Octet()) + sms.ValidityPeriod = []byte{s.VP.Octet()} case ValidityPeriodFormats.Absolute, ValidityPeriodFormats.Enhanced: return 0, ErrNonRelative } @@ -287,7 +287,7 @@ func (s *Message) decodeSubmit(data []byte) (n int, err error) { s.Encoding = Encoding(sms.DataCodingScheme) if s.VPFormat != ValidityPeriodFormats.FieldNotPresent { - s.VP.ReadFrom(sms.ValidityPeriod) + s.VP.ReadFrom(sms.ValidityPeriod[0]) } err = s.decodeUserData(sms.UserData, sms.UserDataLength) return n, err diff --git a/sms/sms_submit.go b/sms/sms_submit.go index aabdfec..0a90ea3 100644 --- a/sms/sms_submit.go +++ b/sms/sms_submit.go @@ -18,7 +18,7 @@ type smsSubmit struct { DestinationAddress []byte ProtocolIdentifier byte DataCodingScheme byte - ValidityPeriod byte + ValidityPeriod []byte UserDataLength byte UserData []byte } @@ -45,7 +45,7 @@ func (s *smsSubmit) Bytes() []byte { buf.WriteByte(s.ProtocolIdentifier) buf.WriteByte(s.DataCodingScheme) if ValidityPeriodFormat(s.ValidityPeriodFormat) != ValidityPeriodFormats.FieldNotPresent { - buf.WriteByte(s.ValidityPeriod) + buf.Write(s.ValidityPeriod) } buf.WriteByte(s.UserDataLength) buf.Write(s.UserData) @@ -106,8 +106,9 @@ func (s *smsSubmit) FromBytes(octets []byte) (n int, err error) { //nolint:funle return } if ValidityPeriodFormat(s.ValidityPeriodFormat) != ValidityPeriodFormats.FieldNotPresent { - s.ValidityPeriod, err = buf.ReadByte() - n++ + s.ValidityPeriod = make([]byte, 1) + off, err = io.ReadFull(buf, s.ValidityPeriod) + n += off if err != nil { return } From 618283ec2b39ee95027b675008b42915987fa7d5 Mon Sep 17 00:00:00 2001 From: Arvo Heinonen Date: Mon, 23 Jun 2025 10:47:17 +0300 Subject: [PATCH 2/4] Rename ValidityPeriod to RelativeValidityPeriod Define ValidityPeriod as a type alias to RelativeValidityPeriod for backward compatibility. --- README.md | 2 +- at.go | 2 +- sms/sms.go | 2 +- sms/sms_test.go | 4 ++-- sms/validity_period.go | 19 +++++++++++-------- 5 files changed, 16 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 8bc96f2..209b505 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ smsSubmitGsm7 := Message{ Type: MessageTypes.Submit, Address: "+79261234567", ServiceCenterAddress: "+79262000331", - VP: ValidityPeriod(time.Hour * 24 * 4), + VP: RelativeValidityPeriod(time.Hour * 24 * 4), VPFormat: ValidityPeriodFormats.Relative, } n, octets, err := smsSubmitGsm7.PDU() diff --git a/at.go b/at.go index cdcb773..ac4a8b7 100644 --- a/at.go +++ b/at.go @@ -438,7 +438,7 @@ func (d *Device) SendSMS(text string, address sms.PhoneNumber) (err error) { Encoding: sms.Encodings.Gsm7Bit, Address: address, VPFormat: sms.ValidityPeriodFormats.Relative, - VP: sms.ValidityPeriod(24 * time.Hour * 4), + VP: sms.RelativeValidityPeriod(24 * time.Hour * 4), } if !pdu.Is7BitEncodable(text) { diff --git a/sms/sms.go b/sms/sms.go index ecce363..0ce556c 100644 --- a/sms/sms.go +++ b/sms/sms.go @@ -26,7 +26,7 @@ var ( type Message struct { Type MessageType Encoding Encoding - VP ValidityPeriod + VP RelativeValidityPeriod VPFormat ValidityPeriodFormat ServiceCenterTime Timestamp DischargeTime Timestamp diff --git a/sms/sms_test.go b/sms/sms_test.go index 9430a29..9e9c7aa 100644 --- a/sms/sms_test.go +++ b/sms/sms_test.go @@ -61,7 +61,7 @@ var ( Type: MessageTypes.Submit, Address: "+79269965690", ServiceCenterAddress: "+79168999100", - VP: ValidityPeriod(time.Hour * 24 * 4), + VP: RelativeValidityPeriod(time.Hour * 24 * 4), VPFormat: ValidityPeriodFormats.Relative, } smsSubmitGsm7 = Message{ @@ -70,7 +70,7 @@ var ( Type: MessageTypes.Submit, Address: "+79269965690", ServiceCenterAddress: "+79262000331", - VP: ValidityPeriod(time.Hour * 24 * 4), + VP: RelativeValidityPeriod(time.Hour * 24 * 4), VPFormat: ValidityPeriodFormats.Relative, } smsSubmitGsm7_EnhancedTpVp = Message{ diff --git a/sms/validity_period.go b/sms/validity_period.go index d4d3f86..bd9821e 100644 --- a/sms/validity_period.go +++ b/sms/validity_period.go @@ -16,11 +16,14 @@ var ValidityPeriodFormats = struct { 0x00, 0x02, 0x01, 0x03, } -// ValidityPeriod represents the validity period of message. -type ValidityPeriod time.Duration +// Relative validity period (3GPP TS 23.040 9.2.3.12.1) +type RelativeValidityPeriod time.Duration + +// Type alias for backwards compatibility +type ValidityPeriod = RelativeValidityPeriod // Octet return a one-byte representation of the validity period. -func (v ValidityPeriod) Octet() byte { +func (v RelativeValidityPeriod) Octet() byte { switch d := time.Duration(v); { case d/time.Minute < 5: return 0x00 @@ -41,15 +44,15 @@ func (v ValidityPeriod) Octet() byte { } // ReadFrom reads the validity period form the given byte. -func (v *ValidityPeriod) ReadFrom(oct byte) { +func (v *RelativeValidityPeriod) ReadFrom(oct byte) { switch n := time.Duration(oct); { case n >= 0 && n <= 143: - *v = ValidityPeriod(5 * time.Minute * n) + *v = RelativeValidityPeriod(5 * time.Minute * n) case n >= 144 && n <= 167: - *v = ValidityPeriod(12*time.Hour + 30*time.Minute*(n-143)) + *v = RelativeValidityPeriod(12*time.Hour + 30*time.Minute*(n-143)) case n >= 168 && n <= 196: - *v = ValidityPeriod(24 * time.Hour * (n - 166)) + *v = RelativeValidityPeriod(24 * time.Hour * (n - 166)) case n >= 197 && n <= 255: - *v = ValidityPeriod(7 * 24 * time.Hour * (n - 192)) + *v = RelativeValidityPeriod(7 * 24 * time.Hour * (n - 192)) } } From 887787321b5dd28b4c48b2e9c78fa4501b4cee19 Mon Sep 17 00:00:00 2001 From: Arvo Heinonen Date: Mon, 23 Jun 2025 13:35:16 +0300 Subject: [PATCH 3/4] Implement AbsoluteValidityPeriod support --- sms/sms.go | 29 +++++++++++++++++---------- sms/sms_submit.go | 20 +++++++++++++++---- sms/sms_test.go | 45 +++++++++++++++++++++++++++++++++++++++++- sms/validity_period.go | 3 +++ 4 files changed, 82 insertions(+), 15 deletions(-) diff --git a/sms/sms.go b/sms/sms.go index 0ce556c..7cc0b9f 100644 --- a/sms/sms.go +++ b/sms/sms.go @@ -15,7 +15,8 @@ var ( ErrUnknownEncoding = errors.New("sms: unsupported encoding") ErrUnknownMessageType = errors.New("sms: unsupported message type") ErrIncorrectSize = errors.New("sms: decoded incorrect size of field") - ErrNonRelative = errors.New("sms: non-relative validity period support is not implemented yet") + ErrEnhancedVpfNotSupported = errors.New("sms: enhanced validity period format is not implemented yet") + ErrUnknownVpf = errors.New("sms: unknown validity period format") ErrIncorrectUserDataHeaderLength = errors.New("sms: incorrect user data header length ") ErrUnsupportedTypeOfNumber = errors.New("sms: unsupported type-of-number") ) @@ -27,6 +28,7 @@ type Message struct { Type MessageType Encoding Encoding VP RelativeValidityPeriod + AbsoluteVP AbsoluteValidityPeriod VPFormat ValidityPeriodFormat ServiceCenterTime Timestamp DischargeTime Timestamp @@ -151,10 +153,14 @@ func (s *Message) encodeSubmit(buf *bytes.Buffer) (n int, err error) { sms.DataCodingScheme = byte(s.Encoding) switch s.VPFormat { + case ValidityPeriodFormats.FieldNotPresent: + sms.ValidityPeriod = make([]byte, 0) case ValidityPeriodFormats.Relative: sms.ValidityPeriod = []byte{s.VP.Octet()} - case ValidityPeriodFormats.Absolute, ValidityPeriodFormats.Enhanced: - return 0, ErrNonRelative + case ValidityPeriodFormats.Absolute: + sms.ValidityPeriod = s.AbsoluteVP.PDU() + case ValidityPeriodFormats.Enhanced: + return 0, ErrEnhancedVpfNotSupported } sms.UserData, sms.UserDataLength, err = s.encodedUserData() @@ -272,11 +278,17 @@ func (s *Message) decodeSubmit(data []byte) (n int, err error) { } s.RejectDuplicates = sms.RejectDuplicates - switch ValidityPeriodFormat(sms.ValidityPeriodFormat) { - case ValidityPeriodFormats.Absolute, ValidityPeriodFormats.Enhanced: - return n, ErrNonRelative + s.VPFormat = ValidityPeriodFormat(sms.ValidityPeriodFormat) + switch s.VPFormat { + case ValidityPeriodFormats.FieldNotPresent: + case ValidityPeriodFormats.Absolute: + s.AbsoluteVP.ReadFrom(sms.ValidityPeriod) + case ValidityPeriodFormats.Relative: + s.VP.ReadFrom(sms.ValidityPeriod[0]) + case ValidityPeriodFormats.Enhanced: + return n, ErrEnhancedVpfNotSupported default: - s.VPFormat = ValidityPeriodFormat(sms.ValidityPeriodFormat) + return n, ErrUnknownVpf } s.MessageReference = sms.MessageReference @@ -286,9 +298,6 @@ func (s *Message) decodeSubmit(data []byte) (n int, err error) { s.Address.ReadFrom(sms.DestinationAddress[1:]) s.Encoding = Encoding(sms.DataCodingScheme) - if s.VPFormat != ValidityPeriodFormats.FieldNotPresent { - s.VP.ReadFrom(sms.ValidityPeriod[0]) - } err = s.decodeUserData(sms.UserData, sms.UserDataLength) return n, err } diff --git a/sms/sms_submit.go b/sms/sms_submit.go index 0a90ea3..a00bc1a 100644 --- a/sms/sms_submit.go +++ b/sms/sms_submit.go @@ -44,9 +44,7 @@ func (s *smsSubmit) Bytes() []byte { buf.Write(s.DestinationAddress) buf.WriteByte(s.ProtocolIdentifier) buf.WriteByte(s.DataCodingScheme) - if ValidityPeriodFormat(s.ValidityPeriodFormat) != ValidityPeriodFormats.FieldNotPresent { - buf.Write(s.ValidityPeriod) - } + buf.Write(s.ValidityPeriod) buf.WriteByte(s.UserDataLength) buf.Write(s.UserData) return buf.Bytes() @@ -105,14 +103,28 @@ func (s *smsSubmit) FromBytes(octets []byte) (n int, err error) { //nolint:funle if err != nil { return } - if ValidityPeriodFormat(s.ValidityPeriodFormat) != ValidityPeriodFormats.FieldNotPresent { + + switch ValidityPeriodFormat(s.ValidityPeriodFormat) { + case ValidityPeriodFormats.FieldNotPresent: + s.ValidityPeriod = make([]byte, 0) + case ValidityPeriodFormats.Relative: s.ValidityPeriod = make([]byte, 1) off, err = io.ReadFull(buf, s.ValidityPeriod) n += off if err != nil { return } + case ValidityPeriodFormats.Absolute: + s.ValidityPeriod = make([]byte, 7) + off, err = io.ReadFull(buf, s.ValidityPeriod) + n += off + if err != nil { + return + } + default: + return } + s.UserDataLength, err = buf.ReadByte() n++ if err != nil { diff --git a/sms/sms_test.go b/sms/sms_test.go index 9e9c7aa..8cea2db 100644 --- a/sms/sms_test.go +++ b/sms/sms_test.go @@ -24,6 +24,10 @@ var ( pduSubmitGsm7 = "07919762020033F111000B919762995696F00000AA066379180E8200" pduSubmitGsm7_EnhancedTpVp = "05915155010009010891515511110000420300000000001e547" + "47a0e9a36a72074780e9a81e6e5f1db4d9e83e86f103b6d2f03" + pduSubmitGsm7_AbsoluteTpVp = "059151550100190008915155111100001010103295953246cfb" + + "a1ce42cc3c3ecf2bc0c32cbd36537790eba87dd74101d9d9e83a6cd29485c36bfe565900c068" + + "bb560b1162c0692cd74b59cae960355a9c3554d47ab01" + pduDeliverGsm7_2 = "0791551010010201040D91551699296568F80011719022124215293DD4B71C5E26BF" + "41D3E6145476D3E5E573BD0C82BF40B59A2D96CBE564351BCE8603A164319D8CA6ABD540E432482673C172AED82DE502" @@ -73,6 +77,15 @@ var ( VP: RelativeValidityPeriod(time.Hour * 24 * 4), VPFormat: ValidityPeriodFormats.Relative, } + smsSubmitGsm7_AbsoluteTpVp = Message{ + Text: "Our Nepalese friends want this SMS before 2001-01-01 23:59:59 UTC+5:45", + Encoding: Encodings.Gsm7Bit, + Type: MessageTypes.Submit, + Address: "+15551111", + ServiceCenterAddress: "+15551000", + AbsoluteVP: AbsoluteValidityPeriod(parseTimestamp("2001-01-01T23:59:59+05:45")), + VPFormat: ValidityPeriodFormats.Absolute, + } smsSubmitGsm7_EnhancedTpVp = Message{ Text: "This SMS has 3 seconds to live", Encoding: Encodings.Gsm7Bit, @@ -101,6 +114,14 @@ func parseTimestamp(timetamp string) Timestamp { return Timestamp(date) } +func asBytes(str string) []byte { + bytes, err := util.Bytes(str) + if err != nil { + panic(err) + } + return bytes +} + func TestSmsDeliverReadFromUCS2(t *testing.T) { t.Parallel() @@ -185,6 +206,18 @@ func TestSmsSubmitReadFromGsm7(t *testing.T) { assert.Equal(t, smsSubmitGsm7, msg) } +func TestSmsSubmitReadFromGsm7_AbsoluteTpVp(t *testing.T) { + t.Parallel() + + var msg Message + data, err := util.Bytes(pduSubmitGsm7_AbsoluteTpVp) + require.NoError(t, err) + n, err := msg.ReadFrom(data) + require.NoError(t, err) + assert.Equal(t, n, len(data)) + assert.Equal(t, smsSubmitGsm7_AbsoluteTpVp, msg) +} + func TestSmsSubmitReadFromGsm7_EnhancedTpVp(t *testing.T) { t.Parallel() @@ -192,7 +225,7 @@ func TestSmsSubmitReadFromGsm7_EnhancedTpVp(t *testing.T) { data, err := util.Bytes(pduSubmitGsm7_EnhancedTpVp) require.NoError(t, err) _, err = msg.ReadFrom(data) - assert.Equal(t, err, ErrNonRelative) + assert.Equal(t, err, ErrEnhancedVpfNotSupported) } func TestSmsSubmitPduUCS2(t *testing.T) { @@ -217,6 +250,16 @@ func TestSmsSubmitPduGsm7(t *testing.T) { assert.Equal(t, data, octets) } +func TestSmsSubmitPduGsm7_AbsoluteTpVp(t *testing.T) { + t.Parallel() + + n, octets, err := smsSubmitGsm7_AbsoluteTpVp.PDU() + require.NoError(t, err) + data := asBytes(pduSubmitGsm7_AbsoluteTpVp) + assert.Equal(t, len(data) - 6, n) + assert.Equal(t, data, octets) +} + func TestSmsStatusReport(t *testing.T) { t.Parallel() diff --git a/sms/validity_period.go b/sms/validity_period.go index bd9821e..a8163dc 100644 --- a/sms/validity_period.go +++ b/sms/validity_period.go @@ -16,6 +16,9 @@ var ValidityPeriodFormats = struct { 0x00, 0x02, 0x01, 0x03, } +// Absolute validity period (3GPP TS 23.040 9.2.3.12.2) +type AbsoluteValidityPeriod = Timestamp + // Relative validity period (3GPP TS 23.040 9.2.3.12.1) type RelativeValidityPeriod time.Duration From 8af6aa534e5c320f55740d4ef1c094ba7bf87c7e Mon Sep 17 00:00:00 2001 From: Arvo Heinonen Date: Mon, 23 Jun 2025 16:08:18 +0300 Subject: [PATCH 4/4] Add partial enhanced SMS validity period support * Implement enhanced relative VP format support * Implement enhanced "relative integer" VP format support where "relative integer" refers to the unnamed format denoted by 0b010 in 3GPP TS 23.040 9.2.3.12.3. * Leave enhanced "relative semi-octet" format unsupported where "relative semi-octet" refers to the unnamed format denoted by 0b011 in 3GPP TS 23.040 9.2.3.12.3. --- sms/sms.go | 14 ++++++-- sms/sms_submit.go | 2 ++ sms/sms_test.go | 62 ++++++++++++++++++++++++++++++-- sms/validity_period.go | 81 +++++++++++++++++++++++++++++++++++++++++- 4 files changed, 153 insertions(+), 6 deletions(-) diff --git a/sms/sms.go b/sms/sms.go index 7cc0b9f..458e025 100644 --- a/sms/sms.go +++ b/sms/sms.go @@ -15,7 +15,8 @@ var ( ErrUnknownEncoding = errors.New("sms: unsupported encoding") ErrUnknownMessageType = errors.New("sms: unsupported message type") ErrIncorrectSize = errors.New("sms: decoded incorrect size of field") - ErrEnhancedVpfNotSupported = errors.New("sms: enhanced validity period format is not implemented yet") + ErrLongEnhancedVpNotSupported = errors.New("sms: extended functionality indicator for enhanced validity period is not supported") + ErrUnknownEnhancedVpReservedBits = errors.New("sms: unknown reserved bits for enhanced validity period were set") ErrUnknownVpf = errors.New("sms: unknown validity period format") ErrIncorrectUserDataHeaderLength = errors.New("sms: incorrect user data header length ") ErrUnsupportedTypeOfNumber = errors.New("sms: unsupported type-of-number") @@ -29,6 +30,7 @@ type Message struct { Encoding Encoding VP RelativeValidityPeriod AbsoluteVP AbsoluteValidityPeriod + EnhancedVP EnhancedValidityPeriod VPFormat ValidityPeriodFormat ServiceCenterTime Timestamp DischargeTime Timestamp @@ -160,7 +162,10 @@ func (s *Message) encodeSubmit(buf *bytes.Buffer) (n int, err error) { case ValidityPeriodFormats.Absolute: sms.ValidityPeriod = s.AbsoluteVP.PDU() case ValidityPeriodFormats.Enhanced: - return 0, ErrEnhancedVpfNotSupported + sms.ValidityPeriod, err = s.EnhancedVP.PDU() + if err != nil { + return 0, err + } } sms.UserData, sms.UserDataLength, err = s.encodedUserData() @@ -286,7 +291,10 @@ func (s *Message) decodeSubmit(data []byte) (n int, err error) { case ValidityPeriodFormats.Relative: s.VP.ReadFrom(sms.ValidityPeriod[0]) case ValidityPeriodFormats.Enhanced: - return n, ErrEnhancedVpfNotSupported + err = s.EnhancedVP.ReadFrom(sms.ValidityPeriod) + if err != nil { + return n, err + } default: return n, ErrUnknownVpf } diff --git a/sms/sms_submit.go b/sms/sms_submit.go index a00bc1a..2ad6af0 100644 --- a/sms/sms_submit.go +++ b/sms/sms_submit.go @@ -115,6 +115,8 @@ func (s *smsSubmit) FromBytes(octets []byte) (n int, err error) { //nolint:funle return } case ValidityPeriodFormats.Absolute: + fallthrough + case ValidityPeriodFormats.Enhanced: s.ValidityPeriod = make([]byte, 7) off, err = io.ReadFull(buf, s.ValidityPeriod) n += off diff --git a/sms/sms_test.go b/sms/sms_test.go index 8cea2db..d8ff12c 100644 --- a/sms/sms_test.go +++ b/sms/sms_test.go @@ -24,6 +24,8 @@ var ( pduSubmitGsm7 = "07919762020033F111000B919762995696F00000AA066379180E8200" pduSubmitGsm7_EnhancedTpVp = "05915155010009010891515511110000420300000000001e547" + "47a0e9a36a72074780e9a81e6e5f1db4d9e83e86f103b6d2f03" + pduSubmitGsm7_EnhancedTpVp2 = "05915155020009000891515522220000010000000000001f54" + + "747a0e9a36a7a0f41c640fb3d36490f92d07c940edb4bb4e2fcf1b" pduSubmitGsm7_AbsoluteTpVp = "059151550100190008915155111100001010103295953246cfb" + "a1ce42cc3c3ecf2bc0c32cbd36537790eba87dd74101d9d9e83a6cd29485c36bfe565900c068" + "bb560b1162c0692cd74b59cae960355a9c3554d47ab01" @@ -92,6 +94,28 @@ var ( Type: MessageTypes.Submit, Address: "+15551111", ServiceCenterAddress: "+15551000", + VPFormat: ValidityPeriodFormats.Enhanced, + EnhancedVP: EnhancedValidityPeriod{ + ExtensionBit: false, + SingleShotSm: true, + EnhancedFormat: EnhancedValidityPeriodFormats.RelativeInteger, + RelativeIntegerVP: 3, + }, + MessageReference: 1, + } + smsSubmitGsm7_EnhancedTpVp2 = Message{ + Text: "This SMS is valid for 2 minutes", + Encoding: Encodings.Gsm7Bit, + Type: MessageTypes.Submit, + Address: "+15552222", + ServiceCenterAddress: "+15552000", + VPFormat: ValidityPeriodFormats.Enhanced, + EnhancedVP: EnhancedValidityPeriod{ + ExtensionBit: false, + SingleShotSm: false, + EnhancedFormat: EnhancedValidityPeriodFormats.Relative, + RelativeVP: 0, + }, } smsReport = Message{ Type: MessageTypes.StatusReport, @@ -224,8 +248,22 @@ func TestSmsSubmitReadFromGsm7_EnhancedTpVp(t *testing.T) { var msg Message data, err := util.Bytes(pduSubmitGsm7_EnhancedTpVp) require.NoError(t, err) - _, err = msg.ReadFrom(data) - assert.Equal(t, err, ErrEnhancedVpfNotSupported) + n, err := msg.ReadFrom(data) + require.NoError(t, err) + assert.Equal(t, n, len(data)) + assert.Equal(t, smsSubmitGsm7_EnhancedTpVp, msg) +} + +func TestSmsSubmitReadFromGsm7_EnhancedTpVp2(t *testing.T) { + t.Parallel() + + var msg Message + data, err := util.Bytes(pduSubmitGsm7_EnhancedTpVp2) + require.NoError(t, err) + n, err := msg.ReadFrom(data) + require.NoError(t, err) + assert.Equal(t, n, len(data)) + assert.Equal(t, smsSubmitGsm7_EnhancedTpVp2, msg) } func TestSmsSubmitPduUCS2(t *testing.T) { @@ -260,6 +298,26 @@ func TestSmsSubmitPduGsm7_AbsoluteTpVp(t *testing.T) { assert.Equal(t, data, octets) } +func TestSmsSubmitPduGsm7_EnhancedTpVp(t *testing.T) { + t.Parallel() + + n, octets, err := smsSubmitGsm7_EnhancedTpVp.PDU() + require.NoError(t, err) + data := asBytes(pduSubmitGsm7_EnhancedTpVp) + assert.Equal(t, len(data) - 6, n) + assert.Equal(t, data, octets) +} + +func TestSmsSubmitPduGsm7_EnhancedTpVp2(t *testing.T) { + t.Parallel() + + n, octets, err := smsSubmitGsm7_EnhancedTpVp2.PDU() + require.NoError(t, err) + data := asBytes(pduSubmitGsm7_EnhancedTpVp2) + assert.Equal(t, len(data) - 6, n) + assert.Equal(t, data, octets) +} + func TestSmsStatusReport(t *testing.T) { t.Parallel() diff --git a/sms/validity_period.go b/sms/validity_period.go index a8163dc..e95acd7 100644 --- a/sms/validity_period.go +++ b/sms/validity_period.go @@ -1,6 +1,9 @@ package sms -import "time" +import ( + "fmt" + "time" +) // ValidityPeriodFormat represents the format of message's validity period. type ValidityPeriodFormat byte @@ -16,6 +19,29 @@ var ValidityPeriodFormats = struct { 0x00, 0x02, 0x01, 0x03, } +type EnhancedValidityPeriodFormat byte + +var EnhancedValidityPeriodFormats = struct { + NotPresent EnhancedValidityPeriodFormat + Relative EnhancedValidityPeriodFormat + RelativeInteger EnhancedValidityPeriodFormat + RelativeSemiOctet EnhancedValidityPeriodFormat +}{ + 0x00, 0x01, 0x02, 0x03, +} + +// Enhanced "0b010" validity period format (3GPP TS 23.040 9.2.3.12.3) +type RelativeIntegerValidityPeriod byte + +// Enhanced validity period (3GPP TS 23.040 9.2.3.12.3) +type EnhancedValidityPeriod struct { + ExtensionBit bool + SingleShotSm bool + EnhancedFormat EnhancedValidityPeriodFormat + RelativeVP RelativeValidityPeriod + RelativeIntegerVP RelativeIntegerValidityPeriod +} + // Absolute validity period (3GPP TS 23.040 9.2.3.12.2) type AbsoluteValidityPeriod = Timestamp @@ -59,3 +85,56 @@ func (v *RelativeValidityPeriod) ReadFrom(oct byte) { *v = RelativeValidityPeriod(7 * 24 * time.Hour * (n - 192)) } } + +func (v *EnhancedValidityPeriod) PDU() ([]byte, error) { + if v.ExtensionBit { + return nil, ErrLongEnhancedVpNotSupported + } + + pdu := make([]byte, 7) + pdu[0] = 0b0000_0000 + if v.SingleShotSm { + pdu[0] |= 0b0100_0000 + } + + pdu[0] |= byte(v.EnhancedFormat) & 0b0000_0111 + switch v.EnhancedFormat { + case EnhancedValidityPeriodFormats.NotPresent: + case EnhancedValidityPeriodFormats.Relative: + pdu[1] = v.RelativeVP.Octet() + case EnhancedValidityPeriodFormats.RelativeInteger: + pdu[1] = byte(v.RelativeIntegerVP) + default: + return nil, fmt.Errorf("%w: Enhanced Type(0x%x)", ErrUnknownVpf, v.EnhancedFormat) + } + return pdu, nil +} + +func (v *EnhancedValidityPeriod) ReadFrom(octets []byte) error { + if len(octets) != 7 { + return ErrIncorrectSize + } + + v.ExtensionBit = (octets[0] & 0b1000_0000) != 0 + v.SingleShotSm = (octets[0] & 0b0100_0000) != 0 + v.EnhancedFormat = EnhancedValidityPeriodFormat(octets[0] & 0b0111) + + reservedBits := (octets[0] & 0b0011_1000) != 0 + if reservedBits { + return ErrUnknownEnhancedVpReservedBits + } + if v.ExtensionBit { + return ErrLongEnhancedVpNotSupported + } + + switch v.EnhancedFormat { + case EnhancedValidityPeriodFormats.NotPresent: + case EnhancedValidityPeriodFormats.Relative: + v.RelativeVP.ReadFrom(octets[1]) + case EnhancedValidityPeriodFormats.RelativeInteger: + v.RelativeIntegerVP = RelativeIntegerValidityPeriod(octets[1]) + default: + return fmt.Errorf("%w: Enhanced Type(0x%x)", ErrUnknownVpf, v.EnhancedFormat) + } + return nil +}