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