czwartek, 6 stycznia 2011

[PL] SMS PDU - Format wiadomości w praktyce

Popularne SMSy przesyłane mogą być w dwóch formatach: tekstowym lub PDU (ang. Protocol Data Unit). Z oczywistych przyczyn pierwszy wariant jest używany rzadziej. Możliwości, jakie mamy zaś w PDU są następujące.

  • 7 bity na znak - rozwiązanie pozwalające na uzyskanie max. 160 znaków na SMS,
  • 8 bity na znak - opcja pośrednia, 140 znaków,
  • 16 bitów na znak - czyli dla bogatych (70 znaków/SMS).
Powyższe skomplikowane obliczenia uwzględniają nagłówek PDU, więc proszę mnie nie posądzać o nieumiejętność dzielenia :-). Format wspomnianego nagłowka jest całkiem nieźle opisany w tym miejscu.
Koniec części teoretycznej. Odpalamy swój najlepszy edytor tekstowy (vim), zaprzągamy ulubiony język (python), i ... dewelopujemy na wesoło.
 1 #!/usr/bin/env python
 2 # -*- coding: utf-8 -*-
 3 
 4 def int2hex (n):
 5     s = '%X'%n
 6     if int(len(s))%2 == 1:
 7         s = '0'+s
 8     return s
 9 
10 def encode_message (message):
11     pdu = ""
12     l = len (message)
13     message += '\0'
14 
15     # Wyznaczamy rozmiar tekstu po upakowaniu.
16     msglen = int (len(message)*7/8)
17 
18     # Inicjujemy bufor i zmienne.
19     buf = [0] * msglen
20     c = shift = 0
21 
22     # Dla każdego septetu...
23     for n in range(msglen):
24         # Przeskok po każdej paczce 7 bajtów.
25         if shift == 6:
26             c += 1
27 
28         # Wyznaczamy oktet z dwóch septetów.
29         shift = n % 7
30         lb = ord(message[c]) >> shift
31         hb = (ord(message[c+1]) << (7-shift) & 255)
32         buf[n] = lb+hb
33 
34         c += 1
35 
36     # Łączymy wiadomość z jej rozmiarem w postać heksadecymalną.
37     pdu = chr(l) + ''.join(map(chr, buf))
38     return ''.join(["%02x" % ord(n) for n in pdu])
39 
40 def encode_SMS (dest_number, text):
41     # Zaczynamy od szablonu SMSa.
42     smspdu = "001100"
43 
44     # Dodajemy długość numeru i jego format.
45     smspdu += int2hex(len(dest_number)) + '81'
46 
47     # Dodajemy numer nadawcy.
48     tmp_number = dest_number
49     if len(tmp_number) % 2 == 1:
50         tmp_number += 'F'
51     i = 0
52     while i < len(tmp_number):
53         smspdu = smspdu + tmp_number[i+1] + tmp_number[i]
54         i += 2
55 
56     # Dodajemy id protokołu, pole DCS i okres ważności.
57     smspdu += '0000FF'
58 
59     # Dodajemy dł. wiadomości no i samą wiadomość :-).
60     smspdu += int2hex(len(text))
61     smspdu += encode_message(text)
62 
63     return smspdu
Zaletą pythona jest to, że kod w nim napisany jest prawie samo-dokumentujący się. Jeżeli deklarujesz się jako profesjonalny programista, ale jednak nie rozumiesz co się dzieje powyżej, to (nie chce mi się wymyślać żadnych epitetów, po prostu zacytuję pewnego pana):
to prawdopodobnie masz jeszcze szanse na jakąś karierę, nie wiem, w polityce chyba wiele nie trzeba, musisz tylko dobrze wyglądać w krawacie
Powyższy kod całkiem dobrze się sprawuje pod warunkiem, że wszystko uda się upakować w jednej wiadomości. W innych wypadkach mogą dziać się różne rzeczy, od bełkotu wyświetlającego się na twojej komórce do niczego - SMS nie dochodzi w ogóle i nikt nie wie dlaczego (sytuacje rodem z politechniki), bramka twierdzi, że wiadomość wysłała, a komórka milczy. Wracając jeszcze raz do dokumentacji szybko znajdujemy informacje jako by przy długich wiadomościach istotną rolę odgrywał tzw. nagłówekUDH. Dodajmy więc małą modyfikację:
 1 message = '\x00\x00\x00\x00\x00\x00' + message
...
 ? for i, ch in enumerate (udh):
 ?    buf[i] = ord(ch)
      
Pierwszą linię dodajemy dokładnie na początku funkcji encode_SMS, a pętlę zaraz po upakowaniu wiadomości (czyli zaraz za pierwszą pętlą we wcześniej opisanej funkcji encode_SMS.

Na zakończenie powiem coś o sprzęcie. Można zakupić takie urządzenie które nazywa się chyba modemem GPRS. To taki mały pierdolnik podpinany do komputera przez USB i udostępniający mu internet. Nie jestem pewien na 100%, lecz bardzo prawdopodobne, że do tego modemu można podpiąć się za pomocą np. telnetu i wysyłać komendyHayes'a. Ja, szczęśliwie, miałem możliwość obcowania z pełnoprawną bramką GSM, która mieściła w sobie do 30 kart SIM pozwalając nieźle naspamować ]:->. Dziękuję za dotrwanie do końca.