forked from warthog618/sms
-
Notifications
You must be signed in to change notification settings - Fork 0
/
validityperiod.go
274 lines (248 loc) · 6.77 KB
/
validityperiod.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
// SPDX-License-Identifier: MIT
//
// Copyright © 2018 Kent Gibson <[email protected]>.
package tpdu
import (
"time"
"github.com/warthog618/sms/encoding/bcd"
)
// ValidityPeriod represents the validity period as defined in 3GPP TS 34.040
// Section 9.2.3.12.
type ValidityPeriod struct {
Format ValidityPeriodFormat
Time Timestamp // for VpfAbsolute
Duration time.Duration // for VpfRelative and VpfEnhanced
EFI byte // enhanced functionality indicator - first octet of enhanced format
}
// EnhancedFormat extracts the format field from the EFI.
func EnhancedFormat(efi byte) EnhancedValidityPeriodFormat {
return EnhancedValidityPeriodFormat(efi & 0x07)
}
// SetAbsolute sets the validity period to an absolute time.
func (v *ValidityPeriod) SetAbsolute(t Timestamp) {
v.Format = VpfAbsolute
v.Duration = 0
v.Time = t
v.EFI = 0
}
// SetRelative sets the validity period to a relative time.
func (v *ValidityPeriod) SetRelative(d time.Duration) {
v.Format = VpfRelative
v.Duration = d
v.Time = Timestamp{}
v.EFI = 0
}
// SetEnhanced sets the validity period to an enhnaced format as determined
// from the functionality identifier (efi).
func (v *ValidityPeriod) SetEnhanced(d time.Duration, efi byte) {
v.Format = VpfEnhanced
v.Duration = d
v.Time = Timestamp{}
v.EFI = efi
}
// MarshalBinary marshals a ValidityPeriod.
func (v *ValidityPeriod) MarshalBinary() ([]byte, error) {
switch v.Format {
case VpfAbsolute:
return v.Time.MarshalBinary()
case VpfEnhanced:
evpf := EnhancedFormat(v.EFI)
if evpf > EvpfRelativeHHMMSS {
return nil, EncodeError("fi", ErrInvalid)
}
dst := make([]byte, 7)
dst[0] = v.EFI
switch evpf {
case EvpfRelative:
dst[1] = durationToRelative(v.Duration)
case EvpfRelativeSeconds:
secs := v.Duration / time.Second
if secs > 255 {
secs = 255
}
dst[1] = byte(secs)
case EvpfRelativeHHMMSS:
f := []int{int(v.Duration.Hours()) % 100, int(v.Duration.Minutes()) % 60, int(v.Duration.Seconds()) % 60}
for i, tf := range f {
t, err := bcd.Encode(tf)
// this should never trip, as the encoded values should always be valid, but just in case...
if err != nil {
return nil, EncodeError("enhanced", err)
}
dst[i+1] = t
}
}
return dst, nil
case VpfRelative:
t := durationToRelative(v.Duration)
return []byte{t}, nil
case VpfNotPresent:
return nil, nil
}
return nil, EncodeError("vpf", ErrInvalid)
}
// UnmarshalBinary unmarshals a ValidityPeriod stored in the given format.
//
// Returns the number of bytes read from the src, and any error detected during
// the unmarshalling.
func (v *ValidityPeriod) UnmarshalBinary(src []byte, vpf ValidityPeriodFormat) (int, error) {
v.Format = VpfNotPresent
switch vpf {
case VpfAbsolute:
t := Timestamp{}
err := t.UnmarshalBinary(src)
if err == nil {
v.Time = t
v.Format = vpf
}
return 7, err
case VpfEnhanced:
used, err := v.unmarshalVPEnhanced(src)
if err == nil {
v.Format = vpf
}
return used, err
case VpfRelative:
if len(src) < 1 {
return 0, ErrUnderflow
}
v.Duration = relativeToDuration(src[0])
v.Format = vpf
return 1, nil
case VpfNotPresent:
return 0, nil
}
return 0, NewDecodeError("vpf", 0, ErrInvalid)
}
func (v *ValidityPeriod) unmarshalVPEnhanced(src []byte) (int, error) {
if len(src) < 7 {
return 0, ErrUnderflow
}
efi := src[0]
evpf := EnhancedValidityPeriodFormat(efi & 0x7)
used := 0
d := time.Duration(0)
switch evpf {
case EvpfNotPresent:
case EvpfRelative:
d = relativeToDuration(src[1])
used = 1
case EvpfRelativeSeconds:
d = time.Second * time.Duration(src[1])
used = 1
case EvpfRelativeHHMMSS:
i := make([]int, 3)
var err error
for idx := 0; idx < 3; idx++ {
i[idx], err = bcd.Decode(src[idx+1])
if err != nil {
return 4, NewDecodeError("enhanced", 1, err)
}
}
d = time.Duration(i[0])*time.Hour + time.Duration(i[1])*time.Minute + time.Duration(i[2])*time.Second
used = 3
default:
return 7, NewDecodeError("enhanced", 0, ErrInvalid)
}
for i := used + 1; i < 7; i++ {
if src[i] != 0 {
return used + 1, NewDecodeError("enhanced", i, ErrNonZero)
}
}
v.EFI = efi
v.Duration = d
return 7, nil
}
// ValidityPeriodFormat identifies the format of the ValidityPeriod when encoded to binary.
type ValidityPeriodFormat byte
const (
// VpfNotPresent indicates no VP is present.
VpfNotPresent ValidityPeriodFormat = iota
// VpfEnhanced indicates the VP is stored in enhanced format as per 3GPP TS
// 23.038 Section 9.2.3.12.3.
VpfEnhanced
// VpfRelative indicates the VP is stored in relative format as per 3GPP TS
// 23.038 Section 9.2.3.12.1.
VpfRelative
// VpfAbsolute indicates the VP is stored in absolute format as per 3GPP TS
// 23.038 Section 9.2.3.12.2. The absolute format is the same format as the
// SCTS.
VpfAbsolute
)
// EnhancedValidityPeriodFormat identifies the subformat of the ValidityPeriod
// when encoded to binary in enhanced format, as per 3GPP TS 23.038 Section
// 9.2.3.12.3
type EnhancedValidityPeriodFormat byte
const (
// EvpfNotPresent indicates no VP is present.
EvpfNotPresent EnhancedValidityPeriodFormat = iota
// EvpfRelative indicates the VP is stored in relative format as per 3GPP
// TS 23.038 Section 9.2.3.12.1.
EvpfRelative
// EvpfRelativeSeconds indicates the VP is stored in relative format as an
// integer number of seconds, from 0 to 255.
EvpfRelativeSeconds
// EvpfRelativeHHMMSS indicates the VP is stored in relative format as a
// period of hours, minutes and seconds in semioctet format as per SCTS
// time.
EvpfRelativeHHMMSS
// All other values currently reserved.
)
func durationToRelative(d time.Duration) byte {
switch {
case d < time.Hour*12:
t := byte(d / (time.Minute * 5))
if t > 1 {
t--
}
return t
case d < time.Hour*24:
return 119 + byte(d/(time.Minute*30))
case d < time.Hour*24*30:
return 166 + byte(d/(time.Hour*24))
case d < time.Hour*24*7*63:
return 192 + byte(d/(time.Hour*24*7))
default:
return 255
}
}
func relativeToDuration(t byte) time.Duration {
switch {
case t < 144:
return time.Minute * 5 * time.Duration(t+1)
case t < 168:
return time.Minute * 30 * time.Duration(t-119)
case t < 197:
return time.Hour * 24 * time.Duration(t-166)
default:
return time.Hour * 24 * 7 * time.Duration(t-192)
}
}
func (vpf ValidityPeriodFormat) String() string {
switch vpf {
default:
return "Unknown"
case VpfNotPresent:
return "Not Present"
case VpfAbsolute:
return "Absolute"
case VpfRelative:
return "Relative"
case VpfEnhanced:
return "Enhanced"
}
}
func (evpf EnhancedValidityPeriodFormat) String() string {
switch evpf {
default:
return "Unknown"
case EvpfNotPresent:
return "Not Present"
case EvpfRelative:
return "Relative"
case EvpfRelativeSeconds:
return "Relative Seconds"
case EvpfRelativeHHMMSS:
return "Relative HHMMSS"
}
}