delphi - When I encode/decode SMS PDU (GSM 7 Bit) user data, do I need prepend the UDH first? -
while can encode , decode user data part of sms message when udh not present, i'm having trouble doing when udh is present (in case, concatenated sms).
when decode or encode user data, need prepend udh text before doing so?
this article provides encoding routine sample compensates udh padding bits (which still don't understand) doesn't give example of data being passed routine don't have clear use case (and not find decoding sample on site): http://mobiletidings.com/2009/07/06/how-to-pack-gsm7-into-septets/.
so far, have been able results if prepend udh user data before decoding it, suspect coincidence.
as example (using values https://en.wikipedia.org/wiki/concatenated_sms):
udh := '050003000302'; encoded_user_data_part := 'd06536fb0dbabfe56c32'; // padding, evidently decodeduserdata := decode7bit(udh + encoded_user_data_part); writeln(decodeduserdata); output: "ß@ø¿Æ @hello world"
encodeduserdata := encode7bit(decodeduserdata); decodeduserdata := decode7bit(encodedencodeduserdata); writeln(decodeduserdata); same output: "ß@ø¿Æ @hello world"
without prepending udh garbage:
decodeduserdata := decode7bit(encoded_user_data_part); writeln(decodeduserdata); output: "pkyy§an§eyi"
what correct way of handling this?
am supposed include udh text when encoding user data?
am supposed strip off garbage characters after decoding, or (as suspect) off base assumption?
while decoding algorithm here seems work without udh doesn't seem take udh information account: looking gsm 7bit encode/decode algorithm.
i eternally grateful if set me straight on correct way proceed. clear examples/code samples appreciated. ;-)
i provide small sample application includes algorithms if feels solve riddle.
edit 1:
i'm using delphi xe2 update 4 hotfix 1
edit 2:
thanks @whosrdaddy, able encoding/decoding routines work.
as side note, curious why user data needed on 7-bit boundary when udh wasn't encoded it, last sentence in paragraph etsi specification quoted @whosrdaddy answered that:
if 7 bit data used , tp-ud-header not finish on septet boundary fill bits inserted after last information element data octet there integral number of septets entire tp-ud header. this ensure sm starts on octet boundary earlier phase mobile capable of displaying sm although tp-ud header in tp-ud field may not understood
my code based in part on examples following resources:
looking gsm 7bit encode/decode algorithm
https://en.wikipedia.org/wiki/concatenated_sms
http://mobiletidings.com/2009/02/18/combining-sms-messages/
http://mobiletidings.com/2009/07/06/how-to-pack-gsm7-into-septets/
http://mobileforensics.files.wordpress.com/2007/06/understanding_sms.pdf
http://www.dreamfabric.com/sms/
http://www.mediaburst.co.uk/blog/concatenated-sms/
here's code else who's had trouble sms encoding/decoding. i'm sure can simplified/optimized (and comments welcome), i've tested several different permutations , udh header lengths success. hope helps.
unit smsutils; interface uses windows, classes, math; function encode7bit(const atext: string; audhlen: byte; out atextlen: byte): string; function decode7bit(const apdudata: string; audhlen: integer): string; implementation var g7bittoasciitable: array [0 .. 127] of byte; gasciito7bittable: array [0 .. 255] of byte; procedure initializetables; var asciivalue: integer; i: integer; begin // create 7-bit ascii table g7bittoasciitable[0] := 64; // @ g7bittoasciitable[1] := 163; g7bittoasciitable[2] := 36; g7bittoasciitable[3] := 165; g7bittoasciitable[4] := 232; g7bittoasciitable[5] := 223; g7bittoasciitable[6] := 249; g7bittoasciitable[7] := 236; g7bittoasciitable[8] := 242; g7bittoasciitable[9] := 199; g7bittoasciitable[10] := 10; g7bittoasciitable[11] := 216; g7bittoasciitable[12] := 248; g7bittoasciitable[13] := 13; g7bittoasciitable[14] := 197; g7bittoasciitable[15] := 229; g7bittoasciitable[16] := 0; g7bittoasciitable[17] := 95; g7bittoasciitable[18] := 0; g7bittoasciitable[19] := 0; g7bittoasciitable[20] := 0; g7bittoasciitable[21] := 0; g7bittoasciitable[22] := 0; g7bittoasciitable[23] := 0; g7bittoasciitable[24] := 0; g7bittoasciitable[25] := 0; g7bittoasciitable[26] := 0; g7bittoasciitable[27] := 0; g7bittoasciitable[28] := 198; g7bittoasciitable[29] := 230; g7bittoasciitable[30] := 223; g7bittoasciitable[31] := 201; g7bittoasciitable[32] := 32; g7bittoasciitable[33] := 33; g7bittoasciitable[34] := 34; g7bittoasciitable[35] := 35; g7bittoasciitable[36] := 164; g7bittoasciitable[37] := 37; g7bittoasciitable[38] := 38; g7bittoasciitable[39] := 39; g7bittoasciitable[40] := 40; g7bittoasciitable[41] := 41; g7bittoasciitable[42] := 42; g7bittoasciitable[43] := 43; g7bittoasciitable[44] := 44; g7bittoasciitable[45] := 45; g7bittoasciitable[46] := 46; g7bittoasciitable[47] := 47; g7bittoasciitable[48] := 48; g7bittoasciitable[49] := 49; g7bittoasciitable[50] := 50; g7bittoasciitable[51] := 51; g7bittoasciitable[52] := 52; g7bittoasciitable[53] := 53; g7bittoasciitable[54] := 54; g7bittoasciitable[55] := 55; g7bittoasciitable[56] := 56; g7bittoasciitable[57] := 57; g7bittoasciitable[58] := 58; g7bittoasciitable[59] := 59; g7bittoasciitable[60] := 60; g7bittoasciitable[61] := 61; g7bittoasciitable[62] := 62; g7bittoasciitable[63] := 63; g7bittoasciitable[64] := 161; g7bittoasciitable[65] := 65; g7bittoasciitable[66] := 66; g7bittoasciitable[67] := 67; g7bittoasciitable[68] := 68; g7bittoasciitable[69] := 69; g7bittoasciitable[70] := 70; g7bittoasciitable[71] := 71; g7bittoasciitable[72] := 72; g7bittoasciitable[73] := 73; g7bittoasciitable[74] := 74; g7bittoasciitable[75] := 75; g7bittoasciitable[76] := 76; g7bittoasciitable[77] := 77; g7bittoasciitable[78] := 78; g7bittoasciitable[79] := 79; g7bittoasciitable[80] := 80; g7bittoasciitable[81] := 81; g7bittoasciitable[82] := 82; g7bittoasciitable[83] := 83; g7bittoasciitable[84] := 84; g7bittoasciitable[85] := 85; g7bittoasciitable[86] := 86; g7bittoasciitable[87] := 87; g7bittoasciitable[88] := 88; g7bittoasciitable[89] := 89; g7bittoasciitable[90] := 90; g7bittoasciitable[91] := 196; g7bittoasciitable[92] := 204; g7bittoasciitable[93] := 209; g7bittoasciitable[94] := 220; g7bittoasciitable[95] := 167; g7bittoasciitable[96] := 191; g7bittoasciitable[97] := 97; g7bittoasciitable[98] := 98; g7bittoasciitable[99] := 99; g7bittoasciitable[100] := 100; g7bittoasciitable[101] := 101; g7bittoasciitable[102] := 102; g7bittoasciitable[103] := 103; g7bittoasciitable[104] := 104; g7bittoasciitable[105] := 105; g7bittoasciitable[106] := 106; g7bittoasciitable[107] := 107; g7bittoasciitable[108] := 108; g7bittoasciitable[109] := 109; g7bittoasciitable[110] := 110; g7bittoasciitable[111] := 111; g7bittoasciitable[112] := 112; g7bittoasciitable[113] := 113; g7bittoasciitable[114] := 114; g7bittoasciitable[115] := 115; g7bittoasciitable[116] := 116; g7bittoasciitable[117] := 117; g7bittoasciitable[118] := 118; g7bittoasciitable[119] := 119; g7bittoasciitable[120] := 120; g7bittoasciitable[121] := 121; g7bittoasciitable[122] := 122; g7bittoasciitable[123] := 228; g7bittoasciitable[124] := 246; g7bittoasciitable[125] := 241; g7bittoasciitable[126] := 252; g7bittoasciitable[127] := 224; // create ascii 7-bit table zeromemory(@gasciito7bittable, sizeof(gasciito7bittable)); := 0 high(g7bittoasciitable) begin asciivalue := g7bittoasciitable[i]; gasciito7bittable[asciivalue] := i; end; end; function convertasciito7bit(const atext: string; audhlen: byte): ansistring; const esc = #27; escaped_ascii_codes = [#94, #123, #125, #92, #91, #126, #93, #124, #164]; var septet: byte; ch: ansichar; i: integer; begin := 1 length(atext) begin ch := ansichar(atext[i]); if not(ch in escaped_ascii_codes) septet := gasciito7bittable[byte(ch)] else begin result := result + esc; case (ch) of #12: septet := 10; #94: septet := 20; #123: septet := 40; #125: septet := 41; #92: septet := 47; #91: septet := 60; #126: septet := 61; #93: septet := 62; #124: septet := 64; #164: septet := 101; else septet := 0; end; end; result := result + ansichar(septet); end; end; function convert7bittoascii(const atext: ansistring): string; const esc = #27; var textlen: integer; ch: char; i: integer; begin result := ''; textlen := length(atext); := 1; while (i <= textlen) begin ch := char(atext[i]); if (ch <> esc) result := result + char(g7bittoasciitable[ord(ch)]) else begin inc(i); // skip esc if (i <= textlen) begin ch := char(atext[i]); case (ch) of #10: ch := #12; #20: ch := #94; #40: ch := #123; #41: ch := #125; #47: ch := #92; #60: ch := #91; #61: ch := #126; #62: ch := #93; #64: ch := #124; #101: ch := #164; end; result := result + ch; end; end; inc(i); end; end; function strtohex(const atext: ansistring): ansistring; overload; var textlen: integer; begin // set text buffer size textlen := length(atext); // set length of result double string length setlength(result, textlen * 2); // convert string hex bintohex(pansichar(atext), pansichar(result), textlen); end; function strtohex(const atext: string): string; overload; begin result := string(strtohex(ansistring(atext))); end; function hextostr(const atext: ansistring): ansistring; overload; var resultlen: integer; begin // set length of result half text length resultlen := length(atext) div 2; setlength(result, resultlen); // convert hex string if (hextobin(pansichar(atext), pansichar(result), resultlen) <> resultlen) result := 'error converting hex string: ' + atext; end; function hextostr(const atext: string): string; overload; begin result := string(hextostr(ansistring(atext))); end; function encode7bit(const atext: string; audhlen: byte; out atextlen: byte): string; // atext: ascii text // audhlen: length of udh including udh len byte (e.g. '050003cc0101' = 6 bytes) // atextlen: returns length of text encoded. can different // length(atext) due escape characters // returns text encoded pdu hex string var text7bit: ansistring; pdu: ansistring; pduidx: integer; pdulen: byte; paddingbits: byte; bitstomove: byte; septet: byte; octet: byte; prevoctet: byte; shiftedoctet: byte; i: integer; begin result := ''; text7bit := convertasciito7bit(atext, audhlen); atextlen := length(text7bit); bitstomove := 0; // determine how many padding bits needed based on udh if (audhlen > 0) paddingbits := 7 - ((audhlen * 8) mod 7) else paddingbits := 0; // calculate number of bytes needed store 7-bit text // along padding bits required pdulen := ceil(((atextlen * 7) + paddingbits) / 8); // reserve space pdu bytes pdu := ansistring(stringofchar(#0, pdulen)); pduidx := 1; := 1 atextlen begin if (bitstomove = 7) bitstomove := 0 else begin // convert current character septet (7-bits) , make room // bits next 1 septet := (byte(text7bit[i]) shr bitstomove); if (i = atextlen) octet := septet else begin // convert next character septet , copy bits // octet (pdu byte) octet := septet or byte((byte(text7bit[i + 1]) shl byte(7 - bitstomove))); end; byte(pdu[pduidx]) := octet; inc(pduidx); inc(bitstomove); end; end; // following code pads pdu on *right* shifting *left* // <paddingbits>. using same bit storage convention // 7-bit compression routine above, taking significant // <paddingbits> each pdu byte , moving them least significant // bits of next pdu byte. if there no room in last pdu byte // high bits of previous byte removed, bits // placed additional byte reserved purpose. // note: <pdulen> has been set account reserved byte if // required. if (paddingbits > 0) begin setlength(result, (pdulen * 2)); prevoctet := 0; pduidx := 1 pdulen begin octet := byte(pdu[pduidx]); if (pduidx = 1) shiftedoctet := byte(octet shl paddingbits) else shiftedoctet := byte(octet shl paddingbits) or byte(prevoctet shr (8 - paddingbits)); byte(pdu[pduidx]) := shiftedoctet; prevoctet := octet; end; end; result := string(strtohex(pdu)); end; function decode7bit(const apdudata: string; audhlen: integer): string; // apdudata: hex string representation of pdu data // audhlen: length of udh including udh len (e.g. '050003cc0101' = 6 bytes) // returns decoded ascii text var pdu: ansistring; numseptets: byte; septets: ansistring; pduidx: integer; pdulen: integer; by: byte; currby: byte; left: byte; mask: byte; nextby: byte; octet: byte; nextoctet: byte; paddingbits: byte; shiftedoctet: byte; i: integer; begin result := ''; paddingbits := 0; // convert hex string bytes pdu := ansistring(hextostr(apdudata)); pdulen := length(pdu); // following code removes padding @ end of pdu shifting // *right* <paddingbits>. taking least significant // <paddingbits> following pdu byte , moving them // significant current pdu byte. if (audhlen > 0) begin paddingbits := 7 - ((audhlen * 8) mod 7); pduidx := 1 pdulen begin octet := byte(pdu[pduidx]); if (pduidx = pdulen) shiftedoctet := byte(octet shr paddingbits) else begin nextoctet := byte(pdu[pduidx + 1]); shiftedoctet := byte(octet shr paddingbits) or byte(nextoctet shl (8 - paddingbits)); end; byte(pdu[pduidx]) := shiftedoctet; end; end; // decode // number of septets in pdu after excluding padding bits numseptets := ((pdulen * 8) - paddingbits) div 7; septets := ansistring(stringofchar(#0, numseptets)); left := 7; mask := $7f; nextby := 0; pduidx := 1; := 1 numseptets begin if mask = 0 begin septets[i] := ansichar(nextby); left := 7; mask := $7f; nextby := 0; end else begin if (pduidx > pdulen) break; := byte(pdu[pduidx]); inc(pduidx); currby := ((by , mask) shl (7 - left)) or nextby; nextby := (by , (not mask)) shr left; septets[i] := ansichar(currby); mask := mask shr 1; left := left - 1; end; end; // // remove last character if unused // kind of hack, frankly don't know how else compensate // it. if (septets[numseptets] = #0) setlength(septets, numseptets - 1); // convert 7-bit alphabet ascii result := convert7bittoascii(septets); end; initialization initializetables; end.
no don't include udh part when encoding, if read gsm phase 2 specification on page 57, mention fact : "if 7 bit data used , tp-ud-header not finish on septet boundary fill bits inserted after last information element data octet there integral number of septets entire tp-ud header". when include udh part not case, need calculate offset (= number of fill bits)
calculating offset, code assumes udhpart ansistring:
len := length(udhpart) shr 1; offset := 7 - ((len * 8) mod 7); // fill bits now when encoding 7bit data, proceed normal @ end, shift data offset bits left, code has encoded data in variable result (ansistring):
// fill bits if offset > 0 begin v := result; len := length(v); bytesremain := ceil(((len * 7)+offset) / 8); result := stringofchar(#0, bytesremain); inpos := 1 bytesremain begin if inpos = 1 byte(result[inpos]) := byte(v[inpos]) shl offset else byte(result[inpos]) := (byte(v[inpos]) shl offset) or (byte(v[inpos-1]) shr (8 - offset)); end; end; decoding same thing really, first shift 7 bit data offset bits right before decoding...
i hope set onto right track...
Comments
Post a Comment