22 | * @constructor
23 | */
24 | (function(){
25 | 'use strict';
26 |
27 | var $ = require('jquery');
28 |
29 |
30 | $( 'document' ).ready( function() {
31 |
32 | $( '#ud_only' ).change( function( evt ) {
33 | if (evt.target.checked) {
34 | $( '.prefix' ).attr( 'disabled', false );
35 | }
36 | else {
37 | $( '.prefix' ).attr( 'disabled', true );
38 | }
39 | } );
40 |
41 | var $fPdu = $( '#fPdu' );
42 | $fPdu.find( ':input' ).change( function() {
43 | $fPdu.submit();
44 | } );
45 |
46 | $fPdu.submit( function( evt ) {
47 | evt.preventDefault();
48 | evt.stopPropagation();
49 | evt.stopImmediatePropagation();
50 |
51 | var t = evt.target;
52 |
53 | cleanInput( t.pdu );
54 |
55 | var pdu = t.pdu.value;
56 |
57 | var $output = $( '#output' );
58 |
59 | if (!pdu) {
60 | $output.empty();
61 | return false;
62 | }
63 |
64 | var prefix = '00';
65 | var alphabet = undefined;
66 | var len;
67 |
68 | if (t.ud_only.checked) {
69 |
70 | if (t.udhi.checked) {
71 | prefix += '41';
72 | }
73 | else {
74 | prefix += '01';
75 | }
76 |
77 | prefix += '000000';
78 |
79 | $( t.alphabet ).each( function() {
80 | if (this.checked) {
81 | alphabet = this.value;
82 | }
83 | } );
84 |
85 | if (alphabet === 'standard') {
86 | prefix += '00';
87 | }
88 | else if (alphabet === 'ucs2') {
89 | prefix += '08';
90 | }
91 | else if (alphabet === '8bit') {
92 | prefix += '04';
93 | }
94 |
95 | len = (pdu.length / 2).toString( 16 );
96 |
97 | prefix += len.length < 2 ? '0' + len : len;
98 |
99 | pdu = prefix + pdu;
100 | }
101 |
102 | $output.html( constructOutput( pdu ) );
103 |
104 | $output.find( 'td:last:contains(<)' ).each( function() {
105 | var $this = $( this );
106 |
107 | $this.text( $this.text().replace( /</g, '<' ).replace( />/g, '>' ).replace( /&/g, '&' ) );
108 | } );
109 |
110 | if (t.ud_only.checked) {
111 | $( 'table.data > tbody' ).prepend( 'Some information hidden - click here to reveal. |
' );
112 | $( 'table.data tr.hideable' ).hide();
113 | $( 'table.data td.one' ).click( function() {
114 | $( 'table.data td.one' ).parent().remove();
115 | $( 'table.data tr.hideable' ).show();
116 | } );
117 | }
118 |
119 | return false;
120 | } );
121 |
122 | if (setForm()) {
123 | $( 'form#fPdu' ).submit();
124 | }
125 | } );
126 |
127 | /**
128 | * Constructs the HTML markup with the information derived from two decoders.
129 | *
130 | * @param {string} pdu Contains the PDU decoded SMS
131 | * @return {string} HTML markup
132 | */
133 | function constructOutput( pdu ) {
134 | var i,
135 | info = '';
136 |
137 | var data = pduDecoder( pdu );
138 |
139 | var datastr = '';
140 |
141 | if (typeof data === 'object') {
142 | for (i = 0; i < data.length; ++i) {
143 | datastr += '' + data[ i ].replace( /\t/, ' | ' ) + ' |
';
144 | }
145 | }
146 | else {
147 | // Probably an error text instead of decoded PDU information
148 | datastr = '' + data + ' |
';
149 | }
150 |
151 | datastr = datastr.replace( />\(hideable\)/g, ' class="hideable"> | ' );
152 |
153 | return ' ' + info.replace( /\n/g, ' ' ) + ' ';
154 | }
155 |
156 | /**
157 | * Actual implementation of a PDU decoder. Decodes all information defined in
158 | * {@linkplain http://www.dreamfabric.com/sms/} and {@linkplain http://mobiletidings.com/}
159 | *
160 | * @param {string} pdu Contains the PDU decoded SMS
161 | * @return {Array|string} Decoded information from PDU as one dimensional array, description and information split through '\t'
162 | * or error string if not a valid PDU
163 | */
164 | function pduDecoder( pdu ) {
165 | var i,
166 | result = [];
167 |
168 | var octets = splitter( pdu );
169 |
170 | if (!octets) {
171 | return "Invalid PDU String!";
172 | }
173 |
174 | var tokens = tokenizer( octets );
175 |
176 | for (i = 0; i < tokens.length; ++i) {
177 | result.push( tokens[ i ]() );
178 | }
179 |
180 | return result;
181 | }
182 |
183 | /**
184 | * Splits a PDU string into an array of 2 byte octets
185 | *
186 | * @param {string} pdu
187 | * @return {?Array} Octets or null if PDU contains invalid characters or has invalid length
188 | */
189 | function splitter( pdu ) {
190 | var i,
191 | octets = [];
192 |
193 | for (i = 0; i < pdu.length; i += 2) {
194 | var octet = pdu.substr( i, 2 );
195 |
196 | if (!octet.match( /^[0-9A-F]{2}$/i )) {
197 | return null;
198 | }
199 |
200 | octets.push( octet );
201 | }
202 |
203 | return octets;
204 | }
205 |
206 | /**
207 | * Analyses the PDU octets and returns a list of functions representing one line of
208 | * information, each.
209 | *
210 | * @param {Array} octets
211 | * @return {Array} List of tokens represented by resolving functions
212 | */
213 | function tokenizer( octets ) {
214 | var tokenList = [];
215 | var pos;
216 | var numberLength;
217 | var sliceNumber;
218 | var sliceNumberToA;
219 | var TP_PID;
220 | var TP_DCS;
221 |
222 | // smsc part
223 | var smscLength = parseInt( octets[0], 16 );
224 |
225 | if (smscLength) {
226 | var sliceSmsc = octets.slice( 2, smscLength + 1 );
227 | var sliceSmscToA = octets[1];
228 | tokenList.push( function(){ return '(hideable)SMSC number\t' + tokens.Number( sliceSmsc, undefined, tokens.ToA( sliceSmscToA ) ); } );
229 | tokenList.push( function(){ return '(hideable)SMSC number info\t' + tokens.ToA( sliceSmscToA ).info; } );
230 | }
231 |
232 | // Sender/Receiver part
233 | pos = smscLength + 1;
234 | var pduType = tokens.ToM( octets[ pos ] );
235 | tokenList.push( function(){ return '(hideable)PDU Type\t' + pduType.info; } );
236 |
237 | if (pduType.type === 'deliver') {
238 | pos++;
239 | numberLength = parseInt( octets[ pos ], 16 );
240 |
241 | pos++;
242 | if (numberLength) {
243 | sliceNumber = octets.slice( pos + 1, pos + 1 + Math.ceil( numberLength / 2 ) );
244 | sliceNumberToA = octets[ pos ];
245 | tokenList.push( function(){ return '(hideable)Number\t' + tokens.Number( sliceNumber, numberLength, tokens.ToA( sliceNumberToA ) ); } );
246 | tokenList.push( function(){ return '(hideable)Number info\t' + tokens.ToA( sliceNumberToA ).info; } );
247 |
248 | pos += 1 + Math.ceil( numberLength / 2 );
249 | }
250 |
251 | TP_PID = octets[ pos ];
252 | tokenList.push( function(){ return '(hideable)Protocol Identifier\t' + tokens.PID( TP_PID ); } );
253 |
254 | pos++;
255 | TP_DCS = tokens.DCS( octets[ pos ] );
256 | tokenList.push( function(){ return '(hideable)Data Coding Scheme\t' + TP_DCS.info; } );
257 |
258 | pos++;
259 | var sliceTimeStamp = octets.slice( pos, pos + 7 );
260 | tokenList.push( function(){ return '(hideable)Service Centre Time Stamp\t' + tokens.SCTS( sliceTimeStamp ); } );
261 |
262 | pos += 6;
263 | }
264 | else if (pduType.type === 'submit') {
265 | pos++;
266 | var MR = octets[ pos ];
267 | tokenList.push( function() { return '(hideable)TP Message Reference\t' + tokens.MR( MR ); } );
268 |
269 | pos++;
270 | numberLength = parseInt( octets[ pos ], 16 );
271 |
272 | pos++;
273 | if (numberLength) {
274 | sliceNumber = octets.slice( pos + 1, pos + 1 + Math.ceil( numberLength / 2 ) );
275 | sliceNumberToA = octets[ pos ];
276 | tokenList.push( function(){ return '(hideable)Number\t' + tokens.Number( sliceNumber, numberLength, tokens.ToA( sliceNumberToA ) ); } );
277 | tokenList.push( function(){ return '(hideable)Number info\t' + tokens.ToA( sliceNumberToA ).info; } );
278 |
279 | pos += 1 + Math.ceil( numberLength / 2 );
280 | }
281 |
282 | TP_PID = octets[ pos ];
283 | tokenList.push( function(){ return '(hideable)Protocol Identifier\t' + tokens.PID( TP_PID ); } );
284 |
285 | pos++;
286 | TP_DCS = tokens.DCS( octets[ pos ] );
287 | tokenList.push( function(){ return '(hideable)Data Coding Scheme\t' + TP_DCS.info; } );
288 |
289 | if (pduType.TP_VPF) {
290 | pos++;
291 | var sliceVP;
292 | if (pduType.TP_VPF === 'relative') {
293 | sliceVP = octets[ pos ];
294 | tokenList.push( function(){ return '(hideable)Validity Period\t' + tokens.VPrelative( sliceVP ); } );
295 | }
296 | else if (pduType.TP_VPF.match( /^(absolute|relative)$/ )) {
297 | sliceVP = octets.slice( pos, pos + 7 );
298 | tokenList.push( function(){ return '(hideable)Validity Period\tuntil ' + tokens.SCTS( sliceVP ); } );
299 | pos += 6;
300 | }
301 | }
302 | }
303 |
304 | pos ++;
305 | var TP_UDL = tokens.UDL( octets[ pos ], TP_DCS.alphabet );
306 | tokenList.push( function(){ return 'User Data Length\t' + TP_UDL.info; } );
307 |
308 | var TP_UDHL = {};
309 | var TP_UDH = {};
310 | if (pduType.TP_UDHI) {
311 | pos++;
312 | TP_UDHL = tokens.UDHL( octets[ pos ], TP_DCS.alphabet );
313 | tokenList.push( function() { return 'User Data Header Length\t' + TP_UDHL.info; } );
314 |
315 | pos++;
316 | TP_UDH = tokens.UDH( octets.slice( pos, pos + TP_UDHL.length ) );
317 | tokenList.push( function() { return 'User Data Header\t' + TP_UDH.info; } );
318 | pos += TP_UDHL.length - 1;
319 | }
320 |
321 | pos++;
322 | var expectedMsgEnd = pos + TP_UDL.octets - (TP_UDHL.length ? TP_UDHL.length + 1 : 0);
323 | var sliceMessage = octets.slice( pos, expectedMsgEnd );
324 |
325 | if (TP_UDH.wap) {
326 | var wapMessage = wapDecoder( sliceMessage );
327 | tokenList.push( function(){ return 'User Data\tWireless Session Protocol (WSP) / WBXML ' + wapMessage; } );
328 | }
329 | else {
330 | tokenList.push( function(){ return 'User Data\t' + tokens.UD( sliceMessage, TP_DCS.alphabet, TP_UDHL.padding, TP_UDH.formatting ); } );
331 |
332 | if (expectedMsgEnd < octets.length) {
333 | tokenList.push( function(){ return 'VIOLATION\tPDU longer than expected!'; } );
334 |
335 | var sliceMessageAll = octets.slice( pos, octets.length );
336 | tokenList.push( function(){ return 'User Data /w additional stuff\t' + tokens.UD( sliceMessageAll, TP_DCS.alphabet, TP_UDHL.padding, TP_UDH.formatting ); } );
337 |
338 | }
339 | else if (expectedMsgEnd > octets.length) {
340 | tokenList.push( function(){ return 'VIOLATION\tPDU shorter than expected!'; } );
341 | }
342 | }
343 |
344 | return tokenList;
345 | }
346 |
347 | var tokens = {
348 |
349 | /**
350 | * Number token
351 | *
352 | * {@linkplain http://www.dreamfabric.com/sms/}
353 | *
354 | * @param {Array} octets containing a call number in BCD inverted nibble format or GSM 7-bit encoding
355 | * @param {?number=} length expected length of number
356 | * @param {Object=} addressType the result of the ToA token
357 | * @return {string} Call number of sender, receiver, SMSC etc.
358 | */
359 | Number: function( octets, length, addressType ) {
360 | var i,
361 | number = '';
362 |
363 | if (addressType && addressType.ToN === 0x50) {
364 | number = decode7Bit( octets );
365 | } else {
366 | for (i = 0; i < octets.length; ++i) {
367 | number += reverse( octets[ i ] );
368 | }
369 |
370 | if (number.match( /\D$/ ) || (length && number.length > length)) {
371 | var paddingEx = /(.)$/;
372 | var result = paddingEx.exec( number );
373 |
374 | number = number.substring( 0, number.length - 1 );
375 |
376 | if (result && result[1] && result[1] !== 'F') {
377 | number += ' (VIOLATION: number not padded with "F" but with "' + result[1] + '"!)';
378 | }
379 | }
380 | }
381 |
382 | return number;
383 | },
384 |
385 | /**
386 | * Type-of-Address token
387 | *
388 | * {@linkplain http://www.dreamfabric.com/sms/type_of_address.html}
389 | *
390 | * @param {string} octet ToA octet
391 | * @return {Object} containing ToN (Type of Number) and NPI (Numbering Plan Identification) indicators
392 | * and description text
393 | */
394 | ToA: function( octet ) {
395 | var type = parseInt( octet, 16 );
396 |
397 | var ToN = type & 0x70; // Type of number Bits
398 | var NPI = type & 0xF; // Numbering Plan Identification
399 |
400 | var text = '';
401 |
402 | if (ToN === 0) {
403 | text += 'Unknown type of address';
404 | }
405 | else if (ToN === 0x10) {
406 | text += 'International number';
407 | }
408 | else if (ToN === 0x20) {
409 | text += 'National number';
410 | }
411 | else if (ToN === 0x30) {
412 | text += 'Network specific number';
413 | }
414 | else if (ToN === 0x40) {
415 | text += 'Subscriber number';
416 | }
417 | else if (ToN === 0x50) {
418 | text += 'Alphanumeric, (coded according to GSM TS 03.38 7-bit default alphabet)';
419 | }
420 | else if (ToN === 0x60) {
421 | text += 'Abbreviated number';
422 | }
423 | else if (ToN === 0x70) {
424 | text += 'Reserved for extension';
425 | }
426 | else {
427 | text += 'Reserved type of address';
428 | }
429 |
430 | text += ', ';
431 |
432 | if (NPI === 0) {
433 | text += 'Unknown';
434 | }
435 | else if (NPI === 1) {
436 | text += 'ISDN/telephone numbering plan (E.164/E.163)';
437 | }
438 | else if (NPI === 3) {
439 | text += 'IData numbering plan (X.121)';
440 | }
441 | else if (NPI === 4) {
442 | text += 'Telex numbering plan';
443 | }
444 | else if (NPI === 8) {
445 | text += 'National numbering plan';
446 | }
447 | else if (NPI === 9) {
448 | text += 'Private numbering plan';
449 | }
450 | else if (NPI === 0xA) {
451 | text += 'ERMES numbering plan (ETSI DE/PS 3 01-3)';
452 | }
453 | else if (NPI === 0xF) {
454 | text += 'Reserved for extension';
455 | }
456 | else {
457 | text += 'Reserved numbering plan';
458 | }
459 |
460 | if ((type & 0x80) === 0) {
461 | text += ' (VIOLATION: Highest bit should always be set!)';
462 | }
463 |
464 | return {
465 | ToN: ToN,
466 | NPI: NPI,
467 | info: text
468 | };
469 | },
470 |
471 | /**
472 | * Type-of-Message Token
473 | *
474 | * (This function only recognizes SMS-DELIVER and SMS-SUBMIT, there are others!)
475 | *
476 | * {@linkplain http://www.dreamfabric.com/sms/deliver_fo.html}
477 | * {@linkplain http://www.dreamfabric.com/sms/submit_fo.html}
478 | *
479 | * @param {string} octet ToM octet
480 | * @return {Object} containing type string 'submit' or 'deliver', UDHI flag, VPF flag, PDU type
481 | * description text
482 | * @see UDHI token, VPF token
483 | */
484 | ToM: function( octet ) {
485 | var o = parseInt( octet, 16 );
486 | var TP_MTI_mask = 0x1; //0x3;
487 | var text = '';
488 | var flags = [];
489 | var deliver = false;
490 | var submit =false;
491 | var TP_VPF = null;
492 | var TP_UDHI = false;
493 |
494 | if ((o & TP_MTI_mask) === 0) {
495 | text += 'SMS-DELIVER';
496 | deliver = true;
497 | }
498 | else if ((o & TP_MTI_mask) === 1) {
499 | text += 'SMS-SUBMIT';
500 | submit = true;
501 | }
502 | else {
503 | console.debug( o, padwZeros( o.toString( 2 ) ) );
504 | }
505 |
506 | // noinspection JSBitwiseOperatorUsage
507 | if (o & 0x80) {
508 | flags.push( 'TP-RP (Reply path exists)' );
509 | }
510 | // noinspection JSBitwiseOperatorUsage
511 | if (o & 0x40) {
512 | TP_UDHI = true;
513 | flags.push( 'TP-UDHI (User data header indicator)' );
514 | }
515 |
516 | if (submit) {
517 | // noinspection JSBitwiseOperatorUsage
518 | if (o & 0x20) {
519 | flags.push( 'TP-SRR (Status report request)' );
520 | }
521 |
522 |
523 | var TP_VPF_mask = o & 0x18;
524 | var vpfText = 'TP-VPF (Validity Period Format): ';
525 |
526 | if (TP_VPF_mask === 0) {
527 | // do nothing
528 | }
529 | else if (TP_VPF_mask === 8) {
530 | TP_VPF = 'enhanced';
531 | flags.push( vpfText + 'enhanced format' );
532 | }
533 | else if (TP_VPF_mask === 0x10) {
534 | TP_VPF = 'relative';
535 | flags.push( vpfText + 'relative format' );
536 | }
537 | else if (TP_VPF_mask === 0x18) {
538 | TP_VPF = 'absolute';
539 | flags.push( vpfText + 'absolute format' );
540 | }
541 |
542 |
543 | if ((o & 0x4) === 0) {
544 | flags.push( 'TP-RD (Reject duplicates)' );
545 | }
546 | }
547 | else if (deliver) {
548 | // noinspection JSBitwiseOperatorUsage
549 | if (o & 0x20) {
550 | flags.push( 'TP-SRI (Status report indication)' );
551 | }
552 |
553 | if ((o & 0x4) === 0) {
554 | flags.push( 'TP-MMS (More messages to send)' );
555 | }
556 | }
557 |
558 | if (flags.length) {
559 | text += ', Flags: ' + flags.join( ', ' );
560 | }
561 |
562 |
563 | return {
564 | type: deliver ? 'deliver' : (submit ? 'submit' : ''),
565 | TP_UDHI: TP_UDHI,
566 | TP_VPF: TP_VPF,
567 | info: text
568 | };
569 | },
570 |
571 | /**
572 | * Protocol IDentifier token
573 | *
574 | * {@linkplain http://www.dreamfabric.com/sms/pid.html}
575 | *
576 | * @param {string} octet PID octet
577 | * @return {string} PID description text
578 | */
579 | PID: function( octet ) {
580 | var o = parseInt( octet, 16 );
581 | var text = '';
582 | var type = o & 0xC0;
583 |
584 | if (type === 0) {
585 | var firstFive = o & 0x1F;
586 |
587 | // noinspection JSBitwiseOperatorUsage
588 | if (o & 0x20) {
589 | text += 'Telematic interworking (Type: ';
590 |
591 | if (firstFive === 0) {
592 | text += 'implicit';
593 | }
594 | else if (firstFive === 1) {
595 | text += 'telex';
596 | }
597 | else if (firstFive === 2) {
598 | text += 'group 3 telefax';
599 | }
600 | else if (firstFive === 3) {
601 | text += 'group 4 telefax';
602 | }
603 | else if (firstFive === 4) {
604 | text += 'voice telephone - speech conversion';
605 | }
606 | else if (firstFive === 5) {
607 | text += 'ERMES - European Radio Messaging System';
608 | }
609 | else if (firstFive === 6) {
610 | text += 'National Paging System';
611 | }
612 | else if (firstFive === 7) {
613 | text += 'Videotex - T.100/T.101';
614 | }
615 | else if (firstFive === 8) {
616 | text += 'teletex, carrier unspecified';
617 | }
618 | else if (firstFive === 9) {
619 | text += 'teletex, in PSPDN';
620 | }
621 | else if (firstFive === 0xA) {
622 | text += 'teletex, in CSPDN';
623 | }
624 | else if (firstFive === 0xB) {
625 | text += 'teletex, in analog PSTN';
626 | }
627 | else if (firstFive === 0xC) {
628 | text += 'teletex, in digital ISDN';
629 | }
630 | else if (firstFive === 0xD) {
631 | text += 'UCI - Universal Computer Interface, ETSI DE/PS 3 01-3';
632 | }
633 | else if (firstFive === 0x10) {
634 | text += 'message handling facility known to the SC';
635 | }
636 | else if (firstFive === 0x11) {
637 | text += 'public X.400-based message handling system';
638 | }
639 | else if (firstFive === 0x12) {
640 | text += 'Internet E-Mail';
641 | }
642 | else if (firstFive >= 0x18 && firstFive <= 0x1E) {
643 | text += 'SC specific value';
644 | }
645 | else if (firstFive === 0x1F) {
646 | text += 'GSM mobile station';
647 | }
648 | else {
649 | text += 'reserved';
650 | }
651 |
652 | text += ')';
653 | }
654 | else {
655 | text += 'SME-to-SME protocol';
656 |
657 | if (firstFive > 0) {
658 | text += ' (Unknown bitmask: ' + firstFive.toString( 2 ) + '- in case of SMS-DELIVER these indicate the SM-AL protocol being used between the SME and the MS!)';
659 | }
660 | }
661 | }
662 | else if (type === 0x40) {
663 | var firstSix = o & 0x3F;
664 |
665 | if (firstSix >= 0 && firstSix <= 7) {
666 | text += 'Short Message Type ' + firstSix;
667 | }
668 | else if (firstSix === 0x1F) {
669 | text += 'Return Call Message';
670 | }
671 | else if (firstSix === 0x3D) {
672 | text += 'ME Data download';
673 | }
674 | else if (firstSix === 0x3E) {
675 | text += 'ME De-personalization Short Message';
676 | }
677 | else if (firstSix === 0x3F) {
678 | text += 'SIM Data download';
679 | }
680 | else {
681 | text += 'reserved';
682 | }
683 | }
684 | else if (type === 0x80) {
685 | text += 'reserved';
686 | }
687 | else if (type === 0xC0) {
688 | text += 'SC specific use';
689 | }
690 |
691 | return text;
692 | },
693 |
694 | /**
695 | * Data Coding Scheme token
696 | *
697 | * {@linkplain http://www.dreamfabric.com/sms/dcs.html}
698 | *
699 | * @param {string} octet DCS octet
700 | * @return {Object} Object containing recognized alphabet, DCS description text
701 | */
702 | DCS: function( octet ) {
703 | var o = parseInt( octet, 16 );
704 | var text = '';
705 | var alphabet = 'default';
706 | var codingGroup = o & 0xF0;
707 |
708 | if (codingGroup >= 0 && codingGroup <= 0x30) {
709 | text += 'General Data Coding groups, ';
710 |
711 | // noinspection JSBitwiseOperatorUsage
712 | if (o & 0x20) {
713 | text += 'compressed';
714 | }
715 | else {
716 | text += 'uncompressed';
717 | }
718 |
719 | text += ', ';
720 | var alphabetFlag = o & 0xC;
721 |
722 | if (alphabetFlag === 0) {
723 | text += 'default alphabet';
724 | }
725 | else if (alphabetFlag === 4) {
726 | text += '8 bit data';
727 | alphabet = '8bit';
728 | }
729 | else if (alphabetFlag === 8) {
730 | text += 'UCS2 (16 bit)';
731 | alphabet = 'ucs2';
732 | }
733 | else if (alphabetFlag === 0xC) {
734 | text += 'reserved alphabet';
735 | }
736 | }
737 | else if (codingGroup >= 0x40 && codingGroup <= 0xB0) {
738 | text += 'Reserved coding groups';
739 | }
740 | else if (codingGroup === 0xC0) {
741 | text += 'Message Waiting Indication Group: Discard Message, ';
742 | }
743 | else if (codingGroup === 0xD0) {
744 | text += 'Message Waiting Indication Group: Store Message, standard encoding, ';
745 | }
746 | else if (codingGroup === 0xE0) {
747 | text += 'Message Waiting Indication Group: Store Message, UCS2 encoding, ';
748 | }
749 | else if (codingGroup === 0xF0) {
750 | text += 'Data coding/message class, ';
751 |
752 | // noinspection JSBitwiseOperatorUsage
753 | if (o & 8) {
754 | text += '(VIOLATION: reserved bit set, but should not!), ';
755 | }
756 |
757 | // noinspection JSBitwiseOperatorUsage
758 | if (o & 4) {
759 | text += '8 bit data';
760 | alphabet = '8bit';
761 | }
762 | else {
763 | text += 'Default alphabet';
764 | }
765 | }
766 |
767 | if ((codingGroup >= 0 && codingGroup <= 0x30) || codingGroup === 0xF0) {
768 | text += ', ';
769 |
770 | if ((codingGroup >= 0 && codingGroup <= 0x30) && (o & 0x10) === 0) {
771 | text += ' no message class set (but given bits would be: ';
772 | }
773 |
774 | var msgClass = o & 3;
775 |
776 | text += 'Class ' + msgClass + ' - ';
777 |
778 | if (msgClass === 0) {
779 | text += 'immediate display';
780 | }
781 | else if (msgClass === 1) {
782 | text += 'ME specific';
783 | }
784 | else if (msgClass === 2) {
785 | text += 'SIM specific';
786 | }
787 | else if (msgClass === 3) {
788 | text += 'TE specific';
789 | }
790 |
791 | text += ')';
792 |
793 | }
794 |
795 | if (codingGroup >= 0xC0 && codingGroup <= 0xE0) {
796 | // noinspection JSBitwiseOperatorUsage
797 | if (o & 8) {
798 | text += 'Set Indication Active';
799 | }
800 | else {
801 | text += 'Set Indication Inactive';
802 | }
803 |
804 | text += ', ';
805 |
806 | // noinspection JSBitwiseOperatorUsage
807 | if (o & 4) {
808 | text += '(reserved bit set, but should not!), ';
809 | }
810 |
811 | var indicationType = o & 3;
812 |
813 | if (indicationType === 0) {
814 | text += 'Voicemail Message Waiting';
815 | }
816 | else if (indicationType === 1) {
817 | text += 'Fax Message Waiting';
818 | }
819 | else if (indicationType === 2) {
820 | text += 'E-Mail Message Waiting';
821 | }
822 | else if (indicationType === 3) {
823 | text += 'Other Message Waiting (not yet standardized)';
824 | }
825 | }
826 |
827 | return {
828 | alphabet: alphabet,
829 | info: text
830 | };
831 | },
832 |
833 | /**
834 | * Service Center Time Stamp token
835 | *
836 | * {@linkplain http://www.dreamfabric.com/sms/scts.html}
837 | *
838 | * @param {Array} octets containing SCTS in BCD inverted nibble format
839 | * @return {string} TimeStamp in format 'YYYY-MM-DD HH:MM:SS GMT +/-X'
840 | */
841 | SCTS: function( octets ) {
842 | var i;
843 |
844 | for (i = 0; i < 7; ++i) {
845 | octets[ i ] = reverse( octets[ i ] );
846 | }
847 |
848 | var ts = '';
849 |
850 | if (parseInt( octets[0], 10 ) < 70) {
851 | ts += '20';
852 | }
853 | else {
854 | ts += '19';
855 | }
856 |
857 | ts += octets[0] + '-' + octets[1] + '-' + octets[2] + ' ' + octets[3] + ':' + octets[4] + ':' + octets[5] + ' GMT ';
858 |
859 | var tz = parseInt( octets[6], 10 );
860 |
861 | // noinspection JSBitwiseOperatorUsage
862 | if (tz & 0x80) {
863 | tz = tz & 0x7F;
864 | ts += '-';
865 | }
866 | else {
867 | ts += '+';
868 | }
869 |
870 | return ts + tz / 4;
871 | },
872 |
873 | /**
874 | * User Data Length token
875 | *
876 | * @param {string} octet UDL octet
877 | * @param {string} alphabet type
878 | * @return {Object} length by septets and octets, info text
879 | */
880 | UDL: function( octet, alphabet ) {
881 | var o = parseInt( octet, 16 );
882 | var length = 0;
883 | var chars = o;
884 |
885 | if (alphabet === 'default') {
886 | length = Math.ceil( o * 70 / 80 );
887 | }
888 | else {
889 | length = o;
890 | }
891 |
892 | if (alphabet === 'ucs2') {
893 | chars = length / 2;
894 | }
895 |
896 | return {
897 | septets: o,
898 | octets: length,
899 | info: chars + ' characters, ' + length + ' bytes'
900 | };
901 | },
902 |
903 | /**
904 | * User Data Header Length token
905 | *
906 | * Evaluates the length of the User Data Header and the padding to the next septet start
907 | *
908 | * {@linkplain http://mobiletidings.com/2009/02/18/combining-sms-messages/}
909 | *
910 | * @param {string} octet UDHL octet
911 | * @param {string} alphabet type ('default', '8bit', 'ucs2')
912 | * @return {Object} UDH length in octets / bytes, padding in no. of bits, info text
913 | */
914 | UDHL: function( octet, alphabet ) {
915 | var length = parseInt( octet, 16 );
916 | var padding = 0;
917 |
918 | if (alphabet === 'default') {
919 | var udhBitLength = (length + 1) * 8;
920 | var nextSeptetStart = Math.ceil( udhBitLength / 7 ) * 7;
921 |
922 | padding = nextSeptetStart - udhBitLength;
923 | }
924 |
925 | return {
926 | length: length,
927 | padding: padding,
928 | info: length + ' bytes'
929 | };
930 | },
931 |
932 | /**
933 | * User Data Header token
934 | *
935 | * Recognizes some Information Elements (IE): concatenated SMS, usage of WAP protocol stack,
936 | * some well-known destination ports, some EMS text formatting
937 | *
938 | * {@linkplain http://mobiletidings.com/2009/02/18/combining-sms-messages/}
939 | * {@linkplain http://mobiletidings.com/2009/02/21/wap-push-over-sms-encodings/}
940 | * {@linkplain http://mobiletidings.com/2009/02/26/wap-push-over-sms-si-encoding/}
941 | * {@linkplain http://mobiletidings.com/2009/03/12/text-formatting-sms-ems/}
942 | * {@linkplain http://www.csoft.co.uk/sckl/index.htm}
943 | *
944 | * @param {Array} octets containing UDH
945 | * @return {Object} Wap indication, array of EMS text formatter callbacks, info text
946 | */
947 | UDH: function( octets ) {
948 | var i,
949 | IEs = [], // all Information Elements
950 | IE = {}, // actual Information Element
951 | info = [],
952 | text = '',
953 | isWap = false,
954 | destPort,
955 | isEMS = false,
956 | formatting = [],
957 | ems = [],
958 | style,
959 | format,
960 | color;
961 |
962 | // break up Information Elements
963 | while (octets.length) {
964 | var o = parseInt( octets.shift(), 16 );
965 |
966 | if (IE.IEI === undefined) {
967 | IE.IEI = o; // Information Element Identifier
968 | }
969 | else if (IE.IEDL === undefined) {
970 | IE.IEDL = o; // Information Element Data Length
971 | }
972 | else {
973 | if (IE.IED === undefined) {
974 | IE.IED = [];
975 | }
976 | IE.IED.push( o );
977 |
978 | if (IE.IED.length >= IE.IEDL) {
979 | IEs.push( IE );
980 | IE = {};
981 | }
982 | }
983 | }
984 |
985 | // Wireless Datagram Protocol IE
986 | for (i = 0; i < IEs.length; ++i) {
987 | if (IEs[ i ].IEI === 5) {
988 | destPort = IEs[ i ].IED[0] * 256 + IEs[ i ].IED[1];
989 |
990 | if (destPort === 5505) {
991 | destPort += ' (Ring Tone)';
992 | }
993 | else if (destPort === 5506) {
994 | destPort += ' (Operator Logo)';
995 | }
996 | else if (destPort === 5507) {
997 | destPort += ' (Group Graphic - CLI Logo)';
998 | }
999 | else if (destPort === 9200) {
1000 | destPort += ' (Connectionless WAP browser proxy server)';
1001 | }
1002 | else if (destPort === 9202) {
1003 | destPort += ' (Secure connectionless WAP browser proxy server)';
1004 | }
1005 | else if (destPort === 9203) {
1006 | destPort += ' (Secure WAP Browser proxy server)';
1007 | }
1008 | else if (destPort === 9204) {
1009 | destPort += ' (vCard)';
1010 | }
1011 | else if (destPort === 9205) {
1012 | destPort += ' (vCalendar)';
1013 | }
1014 | else if (destPort === 9206) {
1015 | destPort += ' (Secure vCard)';
1016 | }
1017 | else if (destPort === 9207) {
1018 | destPort += ' (Secure vCalendar)';
1019 | }
1020 | else {
1021 | isWap = true;
1022 | }
1023 |
1024 | text = 'WDP (Wireless Datagram Protocol): Destination port is ' + destPort + ', source port is ' + (IEs[ i ].IED[2] * 256 + IEs[ i ].IED[3]);
1025 |
1026 | if (IEs[ i ].IEDL !== 4) {
1027 | text += ' (VIOLATON: This Information Element should have exactly 4 bytes but says it has ' + IEs[ i ].IEDL + ' instead!)';
1028 | }
1029 | if (IEs[i].IED.length !== 4) {
1030 | text += ' (VIOLATION: This Information Element should have exactly 4 bytes but actually has ' + IEs[i].IED.length + ' instead!)';
1031 | }
1032 |
1033 | info.push( text );
1034 | }
1035 |
1036 | // Concatenation IE
1037 | else if (IEs[ i ].IEI === 0) {
1038 | text = 'Concatenated message: reference number ' + IEs[ i ].IED[0] + ', part ' + IEs[ i ].IED[2] + ' of ' + IEs[ i ].IED[1] + ' parts';
1039 |
1040 | if (IEs[ i ].IEDL !== 3) {
1041 | text += ' (VIOLATON: This Information Element should have exactly 3 bytes but says it has ' + IEs[ i ].IEDL + ' instead!)';
1042 | }
1043 | if (IEs[i].IED.length !== 3) {
1044 | text += ' (VIOLATION: This Information Element should have exactly 3 bytes but actually has ' + IEs[i].IED.length + ' instead!)';
1045 | }
1046 |
1047 | info.push( text );
1048 | }
1049 |
1050 | // EMS formatting IE
1051 | else if (IEs[ i ].IEI === 10) {
1052 | isEMS = true;
1053 |
1054 | style = [];
1055 | format = IEs[ i ].IED[2];
1056 |
1057 |
1058 | if ((format & 3) === 1) {
1059 | style.push( 'text-align: center' );
1060 | }
1061 | else if ((format & 3) === 2) {
1062 | style.push( 'text-align: right' );
1063 | }
1064 |
1065 | if ((format & 0xC) === 4) {
1066 | style.push( 'font-size: large' );
1067 | }
1068 | else if ((format & 0xC) === 8) {
1069 | style.push( 'font-size: small' );
1070 | }
1071 |
1072 | // noinspection JSBitwiseOperatorUsage
1073 | if (format & 0x20) {
1074 | style.push( 'font-style: italic' );
1075 | }
1076 |
1077 | // noinspection JSBitwiseOperatorUsage
1078 | if (format & 0x10) {
1079 | style.push( 'font-weight: bold' );
1080 | }
1081 |
1082 | // noinspection JSBitwiseOperatorUsage
1083 | if (format & 0x40) {
1084 | style.push( 'text-decoration: underline' );
1085 | }
1086 |
1087 | // noinspection JSBitwiseOperatorUsage
1088 | if (format & 0x80) {
1089 | style.push( 'text-decoration: line-through' );
1090 | }
1091 |
1092 | color = IEs[ i ].IED[3];
1093 |
1094 | if (color) {
1095 | if ((color & 0xF) === 1) {
1096 | style.push( 'color: darkGray' );
1097 | }
1098 | else if ((color & 0xF) === 2) {
1099 | style.push( 'color: darkRed' );
1100 | }
1101 | else if ((color & 0xF) === 3) {
1102 | style.push( 'color: GoldenRod' );
1103 | }
1104 | else if ((color & 0xF) === 4) {
1105 | style.push( 'color: darkGreen' );
1106 | }
1107 | else if ((color & 0xF) === 5) {
1108 | style.push( 'color: darkCyan' );
1109 | }
1110 | else if ((color & 0xF) === 6) {
1111 | style.push( 'color: darkBlue' );
1112 | }
1113 | else if ((color & 0xF) === 7) {
1114 | style.push( 'color: darkMagenta' );
1115 | }
1116 | else if ((color & 0xF) === 8) {
1117 | style.push( 'color: gray' );
1118 | }
1119 | else if ((color & 0xF) === 9) {
1120 | style.push( 'color: white' );
1121 | }
1122 | else if ((color & 0xF) === 0xA) {
1123 | style.push( 'color: red' );
1124 | }
1125 | else if ((color & 0xF) === 0xB) {
1126 | style.push( 'color: yellow' );
1127 | }
1128 | else if ((color & 0xF) === 0xC) {
1129 | style.push( 'color: green' );
1130 | }
1131 | else if ((color & 0xF) === 0xD) {
1132 | style.push( 'color: cyan' );
1133 | }
1134 | else if ((color & 0xF) === 0xE) {
1135 | style.push( 'color: blue' );
1136 | }
1137 | else if ((color & 0xF) === 0xF) {
1138 | style.push( 'color: magenta' );
1139 | }
1140 |
1141 | if ((color & 0xF0) === 0) {
1142 | style.push( 'background-color: black' );
1143 | }
1144 | else if ((color & 0xF0) === 0x10) {
1145 | style.push( 'background-color: darkGray' );
1146 | }
1147 | else if ((color & 0xF0) === 0x20) {
1148 | style.push( 'background-color: darkRed' );
1149 | }
1150 | else if ((color & 0xF0) === 0x30) {
1151 | style.push( 'background-color: GoldenRod' );
1152 | }
1153 | else if ((color & 0xF0) === 0x40) {
1154 | style.push( 'background-color: darkGreen' );
1155 | }
1156 | else if ((color & 0xF0) === 0x50) {
1157 | style.push( 'background-color: darkCyan' );
1158 | }
1159 | else if ((color & 0xF0) === 0x60) {
1160 | style.push( 'background-color: darkBlue' );
1161 | }
1162 | else if ((color & 0xF0) === 0x70) {
1163 | style.push( 'background-color: darkMagenta' );
1164 | }
1165 | else if ((color & 0xF0) === 0x80) {
1166 | style.push( 'background-color: gray' );
1167 | }
1168 | else if ((color & 0xF0) === 0x90) {
1169 | style.push( 'background-color: white' );
1170 | }
1171 | else if ((color & 0xF0) === 0xA0) {
1172 | style.push( 'background-color: red' );
1173 | }
1174 | else if ((color & 0xF0) === 0xB0) {
1175 | style.push( 'background-color: yellow' );
1176 | }
1177 | else if ((color & 0xF0) === 0xC0) {
1178 | style.push( 'background-color: green' );
1179 | }
1180 | else if ((color & 0xF0) === 0xD0) {
1181 | style.push( 'background-color: cyan' );
1182 | }
1183 | else if ((color & 0xF0) === 0xE0) {
1184 | style.push( 'background-color: blue' );
1185 | }
1186 | else if ((color & 0xF0) === 0xF0) {
1187 | style.push( 'background-color: magenta' );
1188 | }
1189 | }
1190 |
1191 | if (style.length) {
1192 | IEs[ i ].markupOpen = '';
1193 | IEs[ i ].markupClose = '';
1194 | }
1195 | else {
1196 | IEs[ i ].markupOpen = '';
1197 | IEs[ i ].markupClose = '';
1198 | }
1199 |
1200 | ems.push( IEs[ i ] );
1201 |
1202 | formatting.push( function( text, original, i ) {
1203 | original = original.substr( ems[ i ].IED[0], ems[ i ].IED[1] );
1204 |
1205 | var getPart = new RegExp( original );
1206 |
1207 | return text.replace( getPart, ems[ i ].markupOpen + original + ems[ i ].markupClose );
1208 | } );
1209 |
1210 | }
1211 | }
1212 |
1213 | if (isEMS) {
1214 | info.push( 'has EMS formatting' );
1215 | }
1216 |
1217 | return {wap: isWap, formatting: formatting, info: info.join( '; ' )};
1218 | },
1219 |
1220 | /**
1221 | * User Data token
1222 | *
1223 | * Tries to decode the user data:
1224 | * - default 7 Bit charset
1225 | * - UCS2 2 byte decoding
1226 | * - Fallback to ASCII decoding, often one can see some useful information there (e.g. name of wallpaper)
1227 | *
1228 | * {@linkplain http://www.dreamfabric.com/sms/hello.html}
1229 | *
1230 | * @param {Array} octets
1231 | * @param {string} alphabet type ('default', '8bit', 'ucs2')
1232 | * @param {number?} padding in no. of bits from UDHL (optional)
1233 | * @param {Array} formatting EMS formatter callbacks
1234 | * @return {string} Decoded user data
1235 | */
1236 | UD: function( octets, alphabet, padding, formatting ) {
1237 | var thisChar, original,
1238 | text = '',
1239 | i = 0;
1240 |
1241 | if (alphabet === 'default') {
1242 | text = decode7Bit( octets, padding );
1243 | }
1244 | else if (alphabet === 'ucs2') {
1245 | while (octets.length) {
1246 | thisChar = octets.shift() + octets.shift();
1247 | text += String.fromCharCode( parseInt( thisChar, 16 ) );
1248 | }
1249 | }
1250 | else {
1251 | text += '(';
1252 |
1253 | if (alphabet === '8bit') {
1254 | text += 'unknown binary data';
1255 | }
1256 | else {
1257 | text += 'unrecognized alphpabet';
1258 | }
1259 |
1260 | text += ', try ASCII decoding) ';
1261 |
1262 | while (octets.length) {
1263 | text += String.fromCharCode( parseInt( octets.shift(), 16 ) );
1264 | }
1265 | }
1266 |
1267 | // Execute EMS formatting
1268 | if (formatting && formatting.length) {
1269 | original = text;
1270 | for (i = 0; i < formatting.length; i++) {
1271 | text = formatting[ i ]( text, original, i );
1272 | }
1273 | }
1274 |
1275 | return text;
1276 | },
1277 |
1278 | /**
1279 | * Message Reference token (only on PDU type 'submit')
1280 | *
1281 | * @param {string} octet
1282 | * @return {string} Info text
1283 | */
1284 | MR: function( octet ) {
1285 | if (octet === '00') {
1286 | return 'Mobile equipment sets reference number';
1287 | }
1288 | return '0x' + octet;
1289 | },
1290 |
1291 | /**
1292 | * Validity Period token (only on PDU type 'submit')
1293 | * This only handles the relative type, absolute and enhanced are timestamps like SCTS
1294 | *
1295 | * {@linkplain http://www.dreamfabric.com/sms/vp.html}
1296 | *
1297 | * @param {string} octet
1298 | * @return {string} info text
1299 | */
1300 | VPrelative: function( octet ) {
1301 | var vp = parseInt( octet, 16 );
1302 | var text = '';
1303 |
1304 | if (vp < 144) {
1305 | text = ((vp + 1) * 5) + ' minutes';
1306 | }
1307 | else if (vp > 143 && vp < 168) {
1308 | text = ((vp - 143) * 30 / 60 + 12) + ' hours';
1309 | }
1310 | else if (vp > 167 && vp < 197) {
1311 | text = (vp - 166 ) + ' days';
1312 | }
1313 | else if (vp > 186) {
1314 | text = (vp - 192) + ' weeks';
1315 | }
1316 |
1317 | return text;
1318 | }
1319 |
1320 | };
1321 |
1322 | /**
1323 | * Decodes all given octets to a string using the GSM 7-bit encoding.
1324 | *
1325 | * @param {Array} octets
1326 | * @param {number?} padding in no. of bits from UDHL (optional)
1327 | * @returns {string} the readable content of the given octets.
1328 | */
1329 | function decode7Bit( octets, padding ) {
1330 | var thisAndNext, thisChar, character,
1331 | nextChar = '',
1332 | text = '';
1333 |
1334 | if (padding && octets.length) {
1335 | nextChar = padwZeros( parseInt( octets.shift(), 16 ).toString( 2 ) );
1336 | nextChar = nextChar.substring( 0, nextChar.length - padding );
1337 | }
1338 |
1339 | while (octets.length || parseInt( nextChar, 2 )) {
1340 | thisAndNext = getChar( octets, nextChar );
1341 | thisChar = thisAndNext[0];
1342 | nextChar = thisAndNext[1];
1343 | character = gsm7bit[ parseInt( thisChar, 2 ) ];
1344 |
1345 | // Extension table on 0x1B
1346 | if (typeof character === 'object') {
1347 | thisAndNext = getChar( octets, nextChar );
1348 | thisChar = thisAndNext[0];
1349 | nextChar = thisAndNext[1];
1350 | character = character[ parseInt( thisChar, 2 ) ];
1351 | }
1352 |
1353 | text += character ? character : '';
1354 | }
1355 |
1356 | return text;
1357 | }
1358 |
1359 | /**
1360 | * Decodes septets-in-octets encoding of the GSM 7 Bit character set
1361 | *
1362 | * @param {Array} octets
1363 | * @param {string} nextChar
1364 | * @return {Array} 7 digit bitstream string representing the current decoded character and parts of the next one.
1365 | */
1366 | function getChar( octets, nextChar ) {
1367 | if (nextChar.length === 7) {
1368 | return [nextChar, ''];
1369 | }
1370 |
1371 | var octet = padwZeros( parseInt( octets.shift(), 16 ).toString( 2 ) );
1372 | var bitsFromNextChar = nextChar.length + 1;
1373 | var thisChar = octet.substr( bitsFromNextChar ) + nextChar;
1374 | nextChar = octet.substr( 0, bitsFromNextChar );
1375 |
1376 | return [thisChar, nextChar];
1377 | }
1378 |
1379 | /**
1380 | * Reverse an octet
1381 | *
1382 | * Used to decode BCD inversed nibbles format
1383 | *
1384 | * @param {string} octet
1385 | * @return {string} Reversed octet
1386 | */
1387 | function reverse( octet ) {
1388 | if (typeof octet === 'string') {
1389 | return octet.substr( 1, 1 ) + octet.substr( 0, 1 );
1390 | }
1391 | else {
1392 | return '00';
1393 | }
1394 | }
1395 |
1396 | /**
1397 | * Pads a bitsream in a string with zeros as long as its shorter than 8 digits
1398 | *
1399 | * @param {string} bitstream
1400 | * @return {string} a Zero-padded binary bitstream
1401 | */
1402 | function padwZeros( bitstream ) {
1403 | while (bitstream.length < 8) {
1404 | bitstream = '0' + bitstream;
1405 | }
1406 |
1407 | return bitstream;
1408 | }
1409 |
1410 | /**
1411 | * GSM 7 bit default alphabet lookup table
1412 | *
1413 | * {@linkplain http://www.dreamfabric.com/sms/default_alphabet.html}
1414 | */
1415 | var gsm7bit = {
1416 | 0: '@', 1: '£', 2: '$', 3: '¥', 4: 'è', 5: 'é', 6: 'ù', 7: 'ì', 8: 'ò', 9: 'Ç',
1417 | 10:'\n', 11: 'Ø', 12: 'ø', 13: '\r', 14: 'Å', 15: 'å', 16: '\u0394', 17: '_', 18: '\u03a6', 19: '\u0393',
1418 | 20: '\u039b', 21: '\u03a9', 22: '\u03a0', 23: '\u03a8', 24: '\u03a3', 25: '\u0398', 26: '\u039e', 28: 'Æ', 29: 'æ',
1419 | 30: 'ß', 31: 'É', 32: ' ', 33: '!', 34: '"', 35: '#', 36: '¤', 37: '%', 38: '&', 39: '\'',
1420 | 40: '(', 41: ')', 42: '*', 43: '+', 44: ',', 45: '-', 46: '.', 47: '/', 48: '0', 49: '1',
1421 | 50: '2', 51: '3', 52: '4', 53: '5', 54: '6', 55: '7', 56: '8', 57: '9', 58: ':', 59: ';',
1422 | 60: '<', 61: '=', 62: '>', 63: '?', 64: '¡', 65: 'A', 66: 'B', 67: 'C', 68: 'D', 69: 'E',
1423 | 70: 'F', 71: 'G', 72: 'H', 73: 'I', 74: 'J', 75: 'K', 76: 'L', 77: 'M', 78: 'N', 79: 'O',
1424 | 80: 'P', 81: 'Q', 82: 'R', 83: 'S', 84: 'T', 85: 'U', 86: 'V', 87: 'W', 88: 'X', 89: 'Y',
1425 | 90: 'Z', 91: 'Ä', 92: 'Ö', 93: 'Ñ', 94: 'Ü', 95: '§', 96: '¿', 97: 'a', 98: 'b', 99: 'c',
1426 | 100: 'd', 101: 'e', 102: 'f', 103: 'g', 104: 'h', 105: 'i', 106: 'j', 107: 'k', 108: 'l', 109: 'm',
1427 | 110: 'n', 111: 'o', 112: 'p', 113: 'q', 114: 'r', 115: 's', 116: 't', 117: 'u', 118: 'v', 119: 'w',
1428 | 120: 'x', 121: 'y', 122: 'z', 123: 'ä', 124: 'ö', 125: 'ñ', 126: 'ü', 127: 'à',
1429 | 27: {
1430 | 10: '\n', // Should be FORM-FEED but no good here
1431 | 20: '^', 40: '{', 41: '}', 47: '\\',
1432 | 60: '[', 61: '~', 62: ']', 64: '|', 101: '€'
1433 | }
1434 | };
1435 |
1436 | /**
1437 | * wapDecoder decodes some parts of the WAP stack (WBXML / WSP / WDP) contained in SMS
1438 | *
1439 | * {@linkplain http://mobiletidings.com/2009/02/21/wap-push-over-sms-encodings/}
1440 | *
1441 | * @param {Array} octets
1442 | * @return {string} HTML table containing all decoded information
1443 | */
1444 | function wapDecoder( octets ) {
1445 | var i,
1446 | pos = 0,
1447 | data = [],
1448 | dataStr = '';
1449 |
1450 | data.push( 'WSP Transaction ID\t0x' + octets[ pos ] );
1451 |
1452 | pos++;
1453 | data.push( 'Type\t' + wapTokens.type( octets[ pos ] ) );
1454 |
1455 | pos++;
1456 | var headerLength = parseInt( octets[ pos ], 16 );
1457 | pos++;
1458 | data.push( 'Wireless Session Protocol\t' + wapTokens.WSP( octets.slice( pos, pos + headerLength ) ) );
1459 |
1460 | pos += headerLength;
1461 |
1462 | data.push( 'WAP Binary XML\t' + wapTokens.WBXML( octets.slice( pos ) ) );
1463 |
1464 |
1465 | for (i = 0; i < data.length; ++i) {
1466 | dataStr += '' + data[ i ].replace( /\t/, ' | ' ) + ' | ';
1467 | }
1468 |
1469 |
1470 | return '';
1471 | }
1472 |
1473 | var wapTokens = {
1474 |
1475 | /**
1476 | * Type token
1477 | *
1478 | * {@linkplain http://mobiletidings.com/2009/02/21/wap-push-over-sms-encodings/}
1479 | *
1480 | * @param {string} octet
1481 | * @return {string} type of WAP encoded message
1482 | */
1483 | type: function( octet ) {
1484 | // noinspection EqualityComparisonWithCoercionJS
1485 | if (octet == 6) {
1486 | return 'Push';
1487 | }
1488 | return 'unknown';
1489 | },
1490 |
1491 | /**
1492 | * Wireless Session Protocol token
1493 | *
1494 | * Decodes a WSP header - at least all the information i could get a grip on.
1495 | *
1496 | * {@linkplain http://mobiletidings.com/2009/02/21/wap-push-over-sms-encodings/}
1497 | * {@linkplain http://mobiletidings.com/2009/02/26/wap-push-over-sms-si-encoding/#comment-1216}
1498 | *
1499 | * @param {Array} octets
1500 | * @return {string} Information text
1501 | */
1502 | WSP: function( octets ) {
1503 | var i,
1504 | o,
1505 | text = '',
1506 | headers = [],
1507 | header = {},
1508 | wellKnown;
1509 |
1510 | while (octets.length) {
1511 | o = parseInt( octets.shift(), 16 );
1512 |
1513 | // 0 is either a string terminator, somewhere in between
1514 | // or indicate a 0-length header (which shouldn't really happen)
1515 | // -> do nothing on 0, just increase counter
1516 | if (o === 0 && header.octets) {
1517 | header.pos++;
1518 | }
1519 |
1520 | if (o > 0 && o < 32) { // start of next header
1521 | if (header.octets) { // there is an unfinished header left -> this indicates a illegal WSP header
1522 | headers.push( header );
1523 | }
1524 |
1525 | header = {
1526 | key: '',
1527 | value: '',
1528 | pos: 0,
1529 | octets: o // the next 0 - 30 octets are the data
1530 | };
1531 |
1532 | if (o === 31) { // special case: length is in next octet
1533 | header.octets = parseInt( octets.shift(), 16 );
1534 | }
1535 |
1536 | if (headers.length === 0) {
1537 | header.key = 'Content-Type'; // first WSP header has to be content type
1538 | }
1539 | }
1540 | else if (o > 31 && o < 128) { // this is a character
1541 | header.value += String.fromCharCode( o );
1542 | header.pos++;
1543 | }
1544 | else if (o > 127) {
1545 | wellKnown = o & 0x7f;
1546 |
1547 | if (wellKnown === 0x01) {
1548 | header.value += '; charset=';
1549 | }
1550 | else if (wellKnown === 0x30) {
1551 | header.value += 'application/vnd.wap.slc';
1552 | }
1553 | else if (wellKnown === 0x2e) {
1554 | header.value += 'application/vnd.wap.sic';
1555 | }
1556 | else if (wellKnown === 0x6A) {
1557 | header.value += 'UTF-8';
1558 | }
1559 |
1560 | header.pos++;
1561 | }
1562 |
1563 | if (header.pos >= header.octets) {
1564 | headers.push( header );
1565 | header = {
1566 | key: '',
1567 | value: '',
1568 | pos: 0,
1569 | octets: 0
1570 | };
1571 | }
1572 | }
1573 |
1574 | for (i = 0; i < headers.length; i++) {
1575 | text += headers[ i ].key + ': ' + headers[ i ].value;
1576 | }
1577 |
1578 | return text;
1579 | },
1580 |
1581 | /**
1582 | * WAP Binary XML token
1583 | *
1584 | * Invokes a server-side decoder.
1585 | * Since XML markup should be returned, it's probably safe to asume that if the
1586 | * return value doesn't contain a '&', the decoding failed.
1587 | * If this happens, a fallback ASCII decoding will take place.
1588 | *
1589 | * {@linkplain http://libwbxml.aymerick.com/}
1590 | * {@linkplain http://search.cpan.org/~glasser/XML-WBXML-0.03/lib/XML/WBXML.pm}
1591 | * {@linkplain http://mobiletidings.com/2009/02/21/wap-push-over-sms-encodings/}
1592 | *
1593 | * @param {Array} octets
1594 | * @return {string} Decoded WPBXML message
1595 | */
1596 | WBXML: function( octets ) {
1597 | var i,
1598 | text = '';
1599 |
1600 | for (i = 0; i < octets.length; ++i) {
1601 | text += octets[ i ];
1602 | }
1603 |
1604 | $.ajax( {
1605 | async: false,
1606 | cache: false,
1607 | data: {octets: text},
1608 | timeout: 1000,
1609 | url: 'wbxml.pl',
1610 | success: function( xml ) {
1611 | text = xml.replace( //g, '>' ).replace( /&/g, '&' );
1612 | }
1613 | } );
1614 |
1615 | if (!text.match( /&/ )) {
1616 | text += ' (Could not be decoded, try ASCII decoding)';
1617 |
1618 | while (octets.length) {
1619 | text += String.fromCharCode( parseInt( octets.shift(), 16 ) );
1620 | }
1621 | }
1622 |
1623 | return text;
1624 | }
1625 |
1626 | };
1627 |
1628 | /**
1629 | * Sets form values from URI query paramater values
1630 | *
1631 | * @return {boolean} true if anything was changed
1632 | */
1633 | function setForm() {
1634 | var query = document.location.search.substr( 1 ).split( '&' );
1635 | var params = {};
1636 | var i;
1637 | var p;
1638 | var $fields;
1639 | var re = {
1640 | textarea: /^TEXTAREA$/i,
1641 | input: /^INPUT$/i,
1642 | text: /^text$/i,
1643 | checkbox_radio: /^(checkbox|radio)$/i
1644 | };
1645 | var changed = false;
1646 |
1647 | for (i = 0; i < query.length; ++i) {
1648 | p = query[ i ].split( '=' );
1649 |
1650 | if (!params[ p[0] ]) {
1651 | params[ p[0] ] = p[1];
1652 | }
1653 | else {
1654 | params[ p[0] ] = [params[ p[0] ], p[1]];
1655 | }
1656 | }
1657 |
1658 | for (i in params) {
1659 | if (params.hasOwnProperty( i )) {
1660 | $fields = $( '[name="' + i + '"]' );
1661 |
1662 | $fields.each( function() {
1663 | if (this.tagName.match( re.textarea ) || (this.tagName.match( re.input ) && this.type.match( re.text ))) {
1664 | this.value = params[ i ];
1665 | changed = true;
1666 | }
1667 |
1668 | else if (this.tagName.match( re.input ) && this.type.match( re.checkbox_radio ) && this.value === params[ i ]) {
1669 | this.checked = true;
1670 | $( this ).change();
1671 | changed = true;
1672 | }
1673 | } );
1674 | }
1675 | }
1676 |
1677 | return changed;
1678 | }
1679 |
1680 | /**
1681 | * Removes all whitespaces, linebreaks etc. from a form field content
1682 | *
1683 | * @param field a form field DOM element
1684 | */
1685 | function cleanInput( field ) {
1686 | var $field = $( field );
1687 |
1688 | $field.val( $field.val().replace( /\s/g, '' ) );
1689 | }
1690 |
1691 | }());
--------------------------------------------------------------------------------
/source/reset.css:
--------------------------------------------------------------------------------
1 | /* v1.0 | 20080212
2 | * http://meyerweb.com/eric/tools/css/reset/
3 | */
4 |
5 | html, body, div, span, applet, object, iframe,
6 | h1, h2, h3, h4, h5, h6, p, blockquote, pre,
7 | a, abbr, acronym, address, big, cite, code,
8 | del, dfn, em, font, img, ins, kbd, q, s, samp,
9 | small, strike, strong, sub, sup, tt, var,
10 | b, u, i, center,
11 | dl, dt, dd, ol, ul, li,
12 | fieldset, form, label, legend,
13 | table, caption, tbody, tfoot, thead, tr, th, td {
14 | margin: 0;
15 | padding: 0;
16 | border: 0;
17 | outline: 0;
18 | font-size: 100%;
19 | vertical-align: baseline;
20 | background: transparent;
21 | }
22 | body {
23 | line-height: 1;
24 | }
25 | ol, ul {
26 | list-style: none;
27 | }
28 | blockquote, q {
29 | quotes: none;
30 | }
31 | blockquote:before, blockquote:after,
32 | q:before, q:after {
33 | content: '';
34 | content: none;
35 | }
36 |
37 | /* remember to define focus styles! */
38 | :focus {
39 | outline: 0;
40 | }
41 |
42 | /* remember to highlight inserts somehow! */
43 | ins {
44 | text-decoration: none;
45 | }
46 | del {
47 | text-decoration: line-through;
48 | }
49 |
50 | /* tables still need 'cellspacing="0"' in the markup */
51 | table {
52 | border-collapse: collapse;
53 | border-spacing: 0;
54 | }
55 |
--------------------------------------------------------------------------------
/source/wbxml.pl:
--------------------------------------------------------------------------------
1 | #!/usr/bin/perl
2 |
3 | use strict;
4 | use warnings;
5 |
6 | use CGI::Minimal;
7 |
8 | my $cgi = CGI::Minimal->new();
9 |
10 | my $octets = $cgi->param( 'octets' ) || $ARGV[0] || '';
11 | my $text = '';
12 | my $xml = '';
13 |
14 | for (my $i = 0; $i < length $octets; $i += 2) {
15 | $text .= chr hex substr( $octets, $i, 2 );
16 | }
17 |
18 | eval 'use XML::WBXML';
19 |
20 | if (!$@) {
21 | $xml = XML::WBXML::wbxml_to_xml( $text );
22 | }
23 |
24 | binmode STDOUT, ':utf8';
25 |
26 | print "Content-Type: text/plain; charset=UTF-8\r\n\r\n$xml";
27 |
--------------------------------------------------------------------------------
|