UNPKG

pdu

Version:

A PDU parser and generator for browser and node

370 lines (288 loc) 11 kB
var pduParser = {}; pduParser.parse = function(pdu) { //Cursor points to the last octet we've read. var cursor = 0; var buffer = new Buffer(pdu.slice(0,4), 'hex'); var smscSize = buffer[0]; var smscType = buffer[1].toString(16); cursor = (smscSize*2+2); var smscNum = pduParser.deSwapNibbles(pdu.slice(4, cursor)); var buffer = new Buffer(pdu.slice(cursor,cursor+6), 'hex'); cursor += 6; var smsDeliver = buffer[0]; var smsDeliverBits = ("00000000"+parseInt(smsDeliver).toString(2)).slice(-8); var udhi = smsDeliverBits.slice(1,2) === "1"; var senderSize = buffer[1]; if(senderSize % 2 === 1) senderSize++; var senderType = parseInt(buffer[2]).toString(16) var senderNum = pduParser.deSwapNibbles(pdu.slice(cursor, cursor+senderSize)); cursor += senderSize; var protocolIdentifier = pdu.slice(cursor, cursor+2); cursor += 2; var dataCodingScheme = pdu.slice(cursor, cursor+2); cursor = cursor+2; var encoding = pduParser.detectEncoding(dataCodingScheme); var timestamp = pduParser.deSwapNibbles(pdu.slice(cursor, cursor+14)); var time = new Date; time.setUTCFullYear('20'+timestamp.slice(0,2)); time.setUTCMonth(timestamp.slice(2,4)-1); time.setUTCDate(timestamp.slice(4,6)); time.setUTCHours(timestamp.slice(6,8)); time.setUTCMinutes(timestamp.slice(8,10)); time.setUTCSeconds(timestamp.slice(10,12)); var firstTimezoneOctet = parseInt(timestamp.slice(12,13)); var binary = ("0000"+firstTimezoneOctet.toString(2)).slice(-4); var factor = binary.slice(0,1) === '1' ? 1 : -1; var binary = '0'+binary.slice(1, 4); var firstTimezoneOctet = parseInt(binary, 2).toString(10); var timezoneDiff = parseInt(firstTimezoneOctet + timestamp.slice(13, 14)); var time = new Date(time.getTime() + (timezoneDiff * 15 * 1000 * 60 * factor)); cursor += 14; var dataLength = parseInt(pdu.slice(cursor, cursor+2), 16).toString(10); cursor += 2; if(udhi) { //User-Data-Header-Indicator: means there's some User-Data-Header. var udhLength = pdu.slice(cursor, cursor+2); var iei = pdu.slice(cursor+2, cursor+4); if(iei == "00") { //Concatenated sms. var headerLength = pdu.slice(cursor+4, cursor+6); var referenceNumber = pdu.slice(cursor+6, cursor+8); var parts = pdu.slice(cursor+8, cursor+10); var currentPart = pdu.slice(cursor+10, cursor+12); } if(iei == "08") { //Concatenaded sms with a two-bytes reference number var headerLength = pdu.slice(cursor+4, cursor+6); var referenceNumber = pdu.slice(cursor+6, cursor+10); var parts = pdu.slice(cursor+10, cursor+12); var currentPart = pdu.slice(cursor+12, cursor+14); } if(encoding === '16bit') if(iei == '00') cursor += (udhLength-2)*4; else if(iei == '08') cursor += ((udhLength-2)*4)+2; else cursor += (udhLength-2)*2; } if(encoding === '16bit') var text = pduParser.decode16Bit(pdu.slice(cursor), dataLength); else if(encoding === '7bit') var text = pduParser.decode7Bit(pdu.slice(cursor), dataLength); else if(encoding === '8bit') var text = ''; //TODO var data = { 'smsc' : smscNum, 'smsc_type' : smscType, 'sender' : senderNum, 'sender_type' : senderType, 'encoding' : encoding, 'time' : time, 'text' : text }; if(udhi) { data['udh'] = { 'length' : udhLength, 'iei' : iei, }; if(iei == '00' || iei == '08') { data['udh']['reference_number'] = referenceNumber; data['udh']['parts'] = parseInt(parts); data['udh']['current_part'] = parseInt(currentPart); } } return data; } pduParser.detectEncoding = function(dataCodingScheme) { var binary = ('00000000'+(parseInt(dataCodingScheme, 16).toString(2))).slice(-8); if(binary == '00000000') return '7bit'; if(binary.slice(0, 2) === '00') { var compressed = binary.slice(2, 1) === '1'; var bitsHaveMeaning = binary.slice(3, 1) === '1'; if(binary.slice(4,6) === '00') return '7bit'; if(binary.slice(4,6) === '01') return '8bit'; if(binary.slice(4,6) === '10') return '16bit'; } } pduParser.decode16Bit = function(data, length) { //We are getting ucs2 characters. var ucs2 = ''; for(var i = 0;i<=data.length;i=i+4) { ucs2 += String.fromCharCode("0x"+data[i]+data[i+1]+data[i+2]+data[i+3]); } return ucs2; } pduParser.deSwapNibbles = function(nibbles) { var out = ''; for(var i = 0; i< nibbles.length; i=i+2) { if(nibbles[i] === 'F') //Dont consider trailing F. out += parseInt(nibbles[i+1], 16).toString(10); else out += parseInt(nibbles[i+1], 16).toString(10)+parseInt(nibbles[i], 16).toString(10); } return out; } pduParser.decode7Bit = function(code, count) { //We are getting 'septeps'. We should decode them. var binary = ''; for(var i = 0; i<code.length;i++) binary += ('0000'+parseInt(code.slice(i,i+1), 16).toString(2)).slice(-4); var bin = Array(); var cursor = 0; var fromPrevious = ''; var i = 0; while(binary[i]) { var remaining = 7 - fromPrevious.length; var toNext = 8 - remaining; bin[i] = binary.slice(cursor+toNext, cursor+toNext+remaining) + fromPrevious; var fromPrevious = binary.slice(cursor, cursor+toNext); if(toNext === 8) fromPrevious = ''; else cursor += 8; i++; } var ascii = ''; for(i in bin) ascii += String.fromCharCode(parseInt(bin[i], 2)); return ascii; } pduParser.encode7Bit = function(ascii) { //We should create septeps now. var octets = new Array(); for(var i = 0; i<ascii.length; i++) octets.push(('0000000'+(ascii.charCodeAt(i).toString(2))).slice(-7)); for(var i in octets) { var i = parseInt(i); var freeSpace = 8 - octets[i].length; if(octets[i+1] && freeSpace !== 8) { octets[i] = octets[i+1].slice(7-freeSpace) + octets[i]; octets[i+1] = octets[i+1].slice(0, 7-freeSpace); } } var hex = ''; for(i in octets) if(octets[i].length > 0) hex += ('00'+(parseInt(octets[i], 2).toString(16))).slice(-2); return hex; } //TODO: TP-Validity-Period (Delivery) pduParser.generate = function(message) { var pdu = '00'; var parts = 1; if(message.encoding === '16bit' && message.text.length > 70) parts = message.text.length / 66; else if(message.encoding === '7bit' && message.text.length > 160) parts = message.text.length / 153; parts = Math.ceil(parts); TPMTI = 1; TPRD = 4; TPVPF = 8; TPSRR = 32; TPUDHI = 64; TPRP = 128; var submit = TPMTI; if(parts > 1) //UDHI submit = submit | TPUDHI; submit = submit | TPSRR; pdu += submit.toString(16); pdu += '00'; //TODO: Reference Number; var receiverSize = ('00'+(parseInt(message.receiver.length, 10).toString(16))).slice(-2); var receiver = pduParser.swapNibbles(message.receiver); var receiverType = 81; //TODO: NOT-Hardcoded PDU generation. Please note that Hamrah1 doesnt work if we set it to 91 (International). pdu += receiverSize.toString(16) + receiverType + receiver; pdu += '00'; //TODO TP-PID if(message.encoding === '16bit') pdu += '08'; else if(message.encoding === '7bit') pdu += '00'; var pdus = new Array(); for(var i=0; i< parts; i++) { pdus[i] = pdu; if(message.encoding === '16bit') { /* If there are more than one messages to be sent, we are going to have to put some UDH. Then, we would have space only * for 66 UCS2 characters instead of 70 */ if(parts === 1) var length = 70; else var length = 66; } else if(message.encoding === '7bit') { /* If there are more than one messages to be sent, we are going to have to put some UDH. Then, we would have space only * for 153 ASCII characters instead of 160 */ if(parts === 1) var length = 160; else var length = 153; } var text = message.text.slice(i*length, (i*length)+length); if(message.encoding === '16bit') { user_data = pduParser.encode16Bit(text); var size = (user_data.length / 2); if(parts > 1) size += 6; //6 is the number of data headers we append. } else if(message.encoding === '7bit') { user_data = pduParser.encode7Bit(text); var size = user_data.length / 2; } pdus[i] += ('00'+parseInt(size).toString(16)).slice(-2); if(parts > 1) { pdus[i] += '05'; pdus[i] += '00'; pdus[i] += '03'; pdus[i] += '00'; pdus[i] += ('00'+parts.toString(16)).slice(-2); pdus[i] += ('00'+(i+1).toString(16)).slice(-2); } pdus[i] += user_data; } return pdus; } pduParser.encode16Bit = function(text) { var out = ''; for(var i = 0; i<text.length;i++) { out += ('0000'+(parseInt(text.charCodeAt(i), 10).toString(16))).slice(-4); } return out; } pduParser.swapNibbles = function(nibbles) { var out = ''; for(var i = 0; i< nibbles.length; i=i+2) { if(typeof(nibbles[i+1]) === 'undefined') // Add a trailing F. out += 'F'+parseInt(nibbles[i], 16).toString(10); else out += parseInt(nibbles[i+1], 16).toString(10)+parseInt(nibbles[i], 16).toString(10); } return out; } pduParser.parseStatusReport = function(pdu) { //Cursor points to the last octet we've read. var cursor = 0; var smscSize = parseInt(pdu.slice(0, 2), 16); cursor += 2; var smscType = parseInt(pdu.slice(cursor, cursor+2), 16); cursor += 2; var smscNum = pduParser.deSwapNibbles(pdu.slice(cursor, (smscSize*2)+2)); cursor = (smscSize*2+2); var header = parseInt(pdu.slice(cursor,cursor+2)); cursor += 2; var reference = parseInt(pdu.slice(cursor,cursor+2), 16); cursor += 2; var senderSize = parseInt(pdu.slice(cursor,cursor+2), 16); if(senderSize % 2 === 1) senderSize++; cursor += 2; var senderType = parseInt(pdu.slice(cursor,cursor+2)); cursor += 2; var sender = pduParser.deSwapNibbles(pdu.slice(cursor, cursor+senderSize)); var status = pdu.slice(-2); return { smsc:smscNum, reference:reference, sender:sender, status:status } } module.exports = pduParser;