1package nbf
2
3import (
4	"bytes"
5	"encoding/binary"
6	"fmt"
7	"strconv"
8	"time"
9	"unicode/utf16"
10)
11
12// predefmessages/1: inbox
13// predefmessages/3: outbox
14
15type msgInfo struct {
16	// Filename information
17	Seq          uint32
18	Timestamp    uint32
19	MultipartSeq uint16
20	Flags        uint16
21	PartNo       uint8
22	PartTotal    uint8
23	Peer         string
24}
25
26// ParseFilename decomposes the filename of messages found in NBF archives.
27// 00001DFC: sequence number of message
28// 3CEAC364: Dos timestamp (seconds since 01 Jan 1980, 32-bit integer)
29// 00B7: 16-bit multipart sequence number (identical for parts of the same message)
30// 2010: 1st byte 0x20 for sms, 0x10 for mms
31// 00500000:
32// 00302000: for multipart: 2 out of 3.
33// 00000000: zero
34// 00000000: zero
35// 000000000: zero (9 digits)
36// 36300XXXXXXX : 12 digit number (7 digit in old format)
37// 0000007C : a checksum ?
38func parseNBFFilename(filename string) (inf msgInfo, err error) {
39	s := filename
40	if len(s) < 80 {
41		return inf, fmt.Errorf("too short")
42	}
43	s, inf.Seq, err = getUint32(s)
44	if err != nil {
45		return
46	}
47	s, inf.Timestamp, err = getUint32(s)
48	if err != nil {
49		return
50	}
51	s, n, err := getUint32(s)
52	if err != nil {
53		return
54	}
55	inf.MultipartSeq = uint16(n >> 16)
56	inf.Flags = uint16(n)
57	s = s[8:] // skip
58	s, n, err = getUint32(s)
59	if err != nil {
60		return
61	}
62	inf.PartNo = uint8(n >> 12)
63	inf.PartTotal = uint8(n >> 20)
64	s = s[25:] // skip
65	if len(s) == 12+8 {
66		inf.Peer = string(s[:12])
67	} else {
68		inf.Peer = string(s[:7])
69	}
70	return inf, nil
71}
72
73func getUint32(s string) (rest string, n uint32, err error) {
74	x, err := strconv.ParseUint(s[:8], 16, 32)
75	return s[8:], uint32(x), err
76}
77
78const (
79	FLAGS_SMS = 0x2000
80	FLAGS_MMS = 0x1000
81)
82
83func DosTime(stamp uint32) time.Time {
84	t := time.Unix(int64(stamp), 0)
85	// Add 10 years
86	t = t.Add(3652 * 24 * time.Hour)
87	return t
88}
89
90// A big-endian interpretation of the binary format.
91type rawMessage struct {
92	Filename string // DEBUG
93
94	Peer  string
95	Text  string
96	Peers []string
97	// From PDU
98	Msg message
99}
100
101type message interface {
102	UserData() string
103}
104
105// SMS encoding.
106// Inspired by libgammu's libgammu/phone/nokia/dct4s40/6510/6510file.c
107
108// Structure: all integers are big-endian
109// u16 u16 u32 u32(size)
110// [82]byte (zero)
111// [41]uint16 (NUL-terminated peer name)
112// PDU (offset is 0xb0)
113// 65 unknown bytes
114// 0001 0003 size(uint16) [size/2]uint16 (NUL-terminated text)
115// 02 size(uint16) + NUL-terminated [size]byte (SMS center)
116// 04 0001 002b size(uint16) + [size]byte (NUL-terminated UTF16BE) (peer)
117// [23]byte unknown data
118
119func parseMessage(s []byte) (m rawMessage, err error) {
120	// peer (fixed offset 0x5e)
121	var runes []uint16
122	for off := 0x5e; s[off]|s[off+1] != 0; off += 2 {
123		runes = append(runes, binary.BigEndian.Uint16(s[off:off+2]))
124	}
125	peer := string(utf16.Decode(runes))
126
127	// PDU frame starts at 0xb0
128	// incoming PDU frame:
129	// * NN 91 <NN/2 bytes> (NN : number of BCD digits, little endian)
130	//   source number, padded with 0xf halfbyte.
131	// * 00 FF (data format, GSM 03.40 section 9.2.3.10)
132	// * YY MM DD HH MM SS ZZ (BCD date time, little endian)
133	// * NN <NN septets> (NN : number of packed 7-bit data)
134	// received SMS: 04 0b 91
135	pdu := s[0xb0:]
136	msgType := pdu[0]
137	if msgType == 0x8c {
138		err = fmt.Errorf("MMS is not supported")
139		return
140	}
141	var msg message
142	switch msgType & 3 {
143	case 0: // SMS-DELIVER
144		var n int
145		var err error
146		msg, n, err = parseDeliverMessage(pdu)
147		if err != nil {
148			return rawMessage{}, err
149		}
150		pdu = pdu[n:]
151	case 1: // SMS-SUBMIT
152		var n int
153		var err error
154		msg, n, err = parseSubmitMessage(pdu)
155		if err != nil {
156			return rawMessage{}, err
157		}
158		pdu = pdu[n:]
159	case 2: // SMS-COMMAND
160		return rawMessage{}, fmt.Errorf("unsupported message type SMS-COMMAND")
161	case 3: // reserved
162		panic("invalid message type 3")
163	}
164	// END of PDU.
165	if len(pdu) == 0 {
166		return rawMessage{Peer: peer, Msg: msg}, nil
167	}
168	if len(pdu) < 72 {
169		return rawMessage{}, fmt.Errorf("truncated message")
170	}
171	pdu = pdu[65:]
172	length := int(pdu[5])
173	pdu = pdu[6:]
174	text := make([]rune, length/2)
175	for i := range text {
176		text[i] = rune(binary.BigEndian.Uint16(pdu[2*i : 2*i+2]))
177	}
178
179	m = rawMessage{
180		Peer: peer,
181		Text: string(text),
182		Msg:  msg,
183	}
184
185	// peers at the end.
186	if msgType&3 == 0 {
187		return m, nil
188	}
189	data := pdu[length:]
190	getStringAfter := func(pattern []byte) string {
191		idx := bytes.Index(data, pattern)
192		if idx < 0 {
193			return ""
194		}
195		length := binary.BigEndian.Uint16(data[idx+len(pattern):]) / 2
196		s := data[idx+len(pattern)+2:]
197		text := make([]rune, length)
198		for i := 0; i < int(length); i++ {
199			text[i] = rune(binary.BigEndian.Uint16(s[2*i : 2*i+2]))
200		}
201		data = s[2*length:]
202		if len(text) > 0 && text[len(text)-1] == 0 {
203			text = text[:len(text)-1]
204		}
205		return string(text)
206	}
207	idx := 0
208	for len(data) > 0 {
209		number := getStringAfter([]byte{4, 0, 1, byte(idx), 0x2b})
210		if number == "" {
211			break
212		}
213		name := getStringAfter([]byte{0x2c})
214		m.Peers = append(m.Peers, fmt.Sprintf("%s <%s>", number, name))
215		idx++
216	}
217
218	return m, nil
219}
220
221// Parsing of DELIVER-MESSAGE
222
223// A deliverMessage represents the contents of a SMS-DELIVER message
224// as per GSM 03.40 TPDU specification.
225type deliverMessage struct {
226	MsgType  byte
227	MoreMsg  bool // true encoded as zero
228	FromAddr string
229	Protocol byte
230	// Coding byte
231	Compressed bool
232	Unicode    bool
233	SMSCStamp  time.Time
234
235	userData
236}
237
238type userData struct {
239	RawData []byte // UCS-2 encoded text, unpacked 7-bit data.
240
241	// Concatenated SMS
242	Concat            bool
243	Ref, Part, NParts int
244
245	SingleShift byte
246}
247
248func (msg userData) Text(uni bool) string {
249	if uni {
250		runes := make([]uint16, len(msg.RawData)/2)
251		for i := range runes {
252			hi, lo := msg.RawData[2*i], msg.RawData[2*i+1]
253			runes[i] = uint16(hi)<<8 | uint16(lo)
254		}
255		return string(utf16.Decode(runes))
256	} else {
257		if msg.SingleShift > 0 && msg.RawData[0] == 0x1b {
258			// FIXME: actually implement single shift table.
259			return translateSMS(msg.RawData[1:], &basicSMSset)
260		}
261		return translateSMS(msg.RawData, &basicSMSset)
262	}
263}
264
265func (msg deliverMessage) UserData() string {
266	return msg.userData.Text(msg.Unicode)
267}
268
269func parseDeliverMessage(s []byte) (msg deliverMessage, size int, err error) {
270	p := s
271	msg.MsgType = p[0] & 3    // TP-MTI
272	msg.MoreMsg = p[0]&4 == 0 // TP-MMS
273	hasUDH := p[0]&0x40 != 0  // TP-UDHI
274	addrLen := int(p[1])
275	msg.FromAddr, err = parseAddress(p[1 : 3+(addrLen+1)/2])
276	if err != nil {
277		return
278	}
279	size += 3 + (addrLen+1)/2
280	p = s[size:]
281
282	// Format
283	format := p[1]
284	msg.Compressed = format&0x20 != 0
285	msg.Unicode = format&8 != 0
286
287	// Date time
288	msg.SMSCStamp = parseDateTime(p[2:9])
289	size += 2 + 7
290	p = s[size:]
291
292	// Payload
293	var udsize int
294	msg.userData, udsize = parseUserData(p, msg.Unicode, hasUDH)
295	size += udsize
296	return
297}
298
299// A submitMessage represents the contents of a SMS-DELIVER message
300// as per GSM 03.40 TPDU specification.
301type submitMessage struct {
302	MsgType  byte
303	RefID    byte
304	ToAddr   string
305	Protocol byte
306	// Coding byte
307	Compressed bool
308	Unicode    bool
309
310	userData
311}
312
313func (msg submitMessage) UserData() string {
314	return msg.userData.Text(msg.Unicode)
315}
316
317func parseSubmitMessage(s []byte) (msg submitMessage, size int, err error) {
318	p := s
319	msg.MsgType = p[0] & 3 // TP-MTI
320	hasVP := p[0] >> 2 & 3
321	hasUDH := p[0]&0x40 != 0 // TP-UDHI
322	msg.RefID = p[1]
323	addrLen := int(p[2])
324	msg.ToAddr, err = parseAddress(p[2 : 4+(addrLen+1)/2])
325	if err != nil {
326		return
327	}
328	size += 4 + (addrLen+1)/2
329	p = s[size:]
330
331	// Format
332	format := p[1]
333	msg.Compressed = format&0x20 != 0
334	msg.Unicode = format&8 != 0
335
336	// Validity Period
337	if hasVP != 0 {
338		panic("validity period not implemented")
339	}
340	size += 2 + 1 // unknown 0xff byte
341	p = s[size:]
342
343	// Payload
344	var udsize int
345	msg.userData, udsize = parseUserData(p, msg.Unicode, hasUDH)
346	size += udsize
347	return
348}
349
350func parseUserData(p []byte, uni, udh bool) (msg userData, size int) {
351	if uni {
352		// Unicode (70 UCS-2 characters in 140 bytes)
353		length := int(p[0]) // length in bytes
354		msg.RawData = p[1 : length+1]
355		size += length + 1
356	} else {
357		// 7-bit encoded format (160 septets in 140 bytes)
358		length := int(p[0]) // length in septets
359		packedLen := length - length/8
360		msg.RawData = unpack7bit(p[1 : 1+packedLen])
361		msg.RawData = msg.RawData[:length]
362		size += packedLen + 1
363	}
364	ud := p[1:]
365	switch {
366	case len(ud) >= 6 && ud[0] == 5 && ud[1] == 0 && ud[2] == 3:
367		// Concatenated SMS data starts with 0x05 0x00 0x03 Ref NPart Part
368		msg.Concat = true
369		msg.Part = int(ud[5])
370		msg.NParts = int(ud[4])
371		msg.Ref = int(ud[3])
372	case len(ud) >= 7 && ud[0] == 6 && ud[1] == 8 && ud[2] == 4:
373		// Concatenated SMS data with 16-bit ref number.
374		msg.Concat = true
375		msg.Part = int(ud[6])
376		msg.NParts = int(ud[5])
377		msg.Ref = int(ud[3])<<8 | int(ud[4])
378	}
379	// TODO: parse other UDH fields
380	// http://en.wikipedia.org/wiki/User_Data_Header
381	if udh {
382		udhLength := ud[0] + 1
383		if ud[1] == 0x24 {
384			// single shift table
385			msg.SingleShift = ud[3]
386		}
387		if uni {
388			msg.RawData = msg.RawData[udhLength:]
389		} else {
390			n := (8*udhLength + 6) / 7 // n such that 7*n >= udhLength*8
391			msg.RawData = msg.RawData[n:]
392		}
393	}
394	return
395}
396
397func parseAddress(b []byte) (string, error) {
398	length := int(b[0])
399	typ := b[1]
400	switch (typ >> 4) & 7 {
401	case 1: // international
402		num := decodeBCD(b[2:])
403		if len(num) < length {
404			return "", fmt.Errorf("BUG: num=%q when parsing %x", num, b)
405		}
406		return "+" + num[:length], nil
407	case 0, 2: // unknown, national
408		num := decodeBCD(b[2:])
409		return num[:length], nil
410	case 5: // alphanumeric
411		addr7 := unpack7bit(b[2:])
412		return translateSMS(addr7, &basicSMSset), nil
413	default:
414		return "", fmt.Errorf("unsupported address format: 0x%02x", typ)
415	}
416}
417
418// Ref: GSM 03.40 section 9.2.3.11
419func parseDateTime(b []byte) time.Time {
420	var dt [7]int
421	for i := range dt {
422		dt[i] = int(b[i]&0xf)*10 + int(b[i]>>4)
423	}
424	return time.Date(
425		2000+dt[0],
426		time.Month(dt[1]),
427		dt[2],
428		dt[3], dt[4], dt[5], 0, time.FixedZone("", dt[6]*3600/4))
429}
430
431func decodeBCD(b []byte) string {
432	s := make([]byte, 0, len(b)*2)
433	for _, c := range b {
434		s = append(s, '0'+(c&0xf))
435		if c>>4 == 0xf {
436			break
437		} else {
438			s = append(s, '0'+(c>>4))
439		}
440	}
441	return string(s)
442}
443
444func unpack7bit(s []byte) []byte {
445	// each byte may contain a part of septet i in lower bits
446	// and septet i+1 in higher bits.
447	buf := uint16(0)
448	buflen := uint(0)
449	out := make([]byte, 0, len(s)+len(s)/7+1)
450	for len(s) > 0 {
451		buf |= uint16(s[0]) << buflen
452		buflen += 8
453		s = s[1:]
454		for buflen >= 7 {
455			out = append(out, byte(buf&0x7f))
456			buflen -= 7
457			buf >>= 7
458		}
459	}
460	return out
461}
462
463// translateSMS decodes a 7-bit encoded SMS text into a standard
464// UTF-8 encoded string.
465func translateSMS(s []byte, charset *[256]rune) string {
466	r := make([]rune, 0, len(s))
467	esc := byte(0)
468	for _, b := range s {
469		if charset[b] == -1 { // escape
470			esc = 128
471		} else {
472			r = append(r, charset[esc|b])
473			esc = 0
474		}
475	}
476	return string(r)
477}
478
479// See http://en.wikipedia.org/wiki/GSM_03.38
480
481var basicSMSset = [256]rune{
482	// 0x00
483	'@', '£', '$', '¥', 'è', 'é', 'ù', 'ì',
484	'ò', 'Ç', '\n', 'Ø', 'ø', '\r', 'Å', 'å',
485	// 0x10
486	'Δ', '_', 'Φ', 'Γ', 'Λ', 'Ω', 'Π', 'Ψ',
487	'Σ', 'Θ', 'Ξ', -1 /* ESC */, 'Æ', 'æ', 'ß', 'É',
488	// 0x20
489	' ', '!', '"', '#', '¤', '%', '&', '\'',
490	'(', ')', '*', '+', ',', '-', '.', '/',
491	// 0x30
492	'0', '1', '2', '3', '4', '5', '6', '7',
493	'8', '9', ':', ';', '<', '=', '>', '?',
494	// 0x40
495	'¡', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
496	'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
497	// 0x50
498	'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
499	'X', 'Y', 'Z', 'Ä', 'Ö', 'Ñ', 'Ü', '§',
500	// 0x60
501	'¿', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
502	'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
503	// 0x70
504	'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
505	'x', 'y', 'z', 'ä', 'ö', 'ñ', 'ü', 'à',
506	// Extensions
507	0x8A: '\f',
508	0x94: '^',
509	0xA8: '{', 0xA9: '}', 0xAF: '\\',
510	0xBC: '[', 0xBD: '~', 0xBE: ']',
511	0xC0: '|',
512	0xE5: '€',
513}
514