Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion lib/defs.js
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,14 @@ filters.message = {
if (Buffer.isBuffer(value)) {
return value;
}
// Tolerate non-string scalars (e.g. a numeric short_message) and
// null/undefined, which previously threw "Cannot read properties of
// undefined (reading 'message')" (issues #229, #256).
if (value === null || value === undefined) {
value = '';
} else if (typeof value === 'number' || typeof value === 'boolean') {
value = String(value);
}
var message = typeof value === 'string' ? value : value.message;
if (typeof message === 'string' && message) {
var encoded = false;
Expand Down Expand Up @@ -548,7 +556,24 @@ filters.message = {
if (!Buffer.isBuffer(value) || !('data_coding' in this)) {
return value;
}
var encoding = this.data_coding & 0x0F;
// Resolve the encoding from the full Data Coding Scheme (DCS) byte, not
// just the low nibble. The naive `data_coding & 0x0F` is wrong when the
// DCS carries message-class / MWI / compression bits in the high nibble
// (GSM 03.38), causing mis-decoded / empty messages (issues #66, #252).
var dc = this.data_coding;
var enc = 0;
if (dc <= 0x0E) {
enc = dc & 0x0F;
} else if ((dc & 0xF0) === 0xF0) {
enc = (dc & 0x04) ? consts.ENCODING.BINARY : consts.ENCODING.ASCII;
} else if ((dc & 0xC0) === 0x00) {
var mode = (dc & 0x0C) >> 2;
enc = mode === 1 ? consts.ENCODING.BINARY :
mode === 2 ? consts.ENCODING.UCS2 : consts.ENCODING.ASCII;
} else if ((dc & 0xC0) === 0xC0) {
enc = ((dc & 0xE0) === 0xE0) ? consts.ENCODING.UCS2 : consts.ENCODING.ASCII;
}
var encoding = enc;
if (!encoding) {
encoding = encodings.default;
} else {
Expand Down
32 changes: 32 additions & 0 deletions lib/pdu.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,22 @@ var pduHeadParams = [
'sequence_number'
];

// A user-defined / unknown TLV is addressed by its numeric tag id (e.g. 0x3C02)
// which becomes a numeric-string key on the PDU. Treat such keys as raw TLVs so
// they round-trip on encode, not only on decode (issue #231).
function isRawTlvKey(key, params) {
if (!/^[0-9]+$/.test(key)) return false;
var id = Number(key);
if (id < 0 || id > 0xFFFF) return false;
return !(key in params) && !tlvs[key];
}

function toRawTlvBuffer(value) {
if (Buffer.isBuffer(value)) return value;
if (value === null || value === undefined) return Buffer.alloc(0);
return Buffer.from(String(value), 'ascii');
}

function PDU(command, options) {
if (Buffer.isBuffer(command)) {
return this.fromBuffer(command);
Expand All @@ -38,6 +54,10 @@ function PDU(command, options) {
for (var key in options) if (key in tlvs && !(key in params)) {
this[key] = options[key];
}
// user-defined / unknown TLVs addressed by numeric tag id (issue #231)
for (var rkey in options) if (isRawTlvKey(rkey, params)) {
this[rkey] = options[rkey];
}
}

PDU.commandLength = function(stream) {
Expand Down Expand Up @@ -184,6 +204,9 @@ PDU.prototype.toBuffer = function() {
values.forEach(function(value) {
this.command_length += tlvs[key].type.size(value) + 4;
}.bind(this));
} else if (isRawTlvKey(key, params)) {
// user-defined / unknown TLV by numeric tag id (issue #231)
this.command_length += toRawTlvBuffer(this[key]).length + 4;
}
}
var buffer = this._initBuffer();
Expand All @@ -205,6 +228,15 @@ PDU.prototype.toBuffer = function() {
offset += length;
});
}
for (var rkey in this) if (isRawTlvKey(rkey, params)) {
// user-defined / unknown TLV by numeric tag id (issue #231)
var raw = toRawTlvBuffer(this[rkey]);
buffer.writeUInt16BE(Number(rkey), offset);
buffer.writeUInt16BE(raw.length, offset + 2);
offset += 4;
raw.copy(buffer, offset);
offset += raw.length;
}
return buffer;
};

Expand Down
66 changes: 66 additions & 0 deletions test/fixes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
var assert = require('assert'),
smpp = require('..'),
PDU = require('../lib/pdu').PDU,
Buffer = require('safer-buffer').Buffer;

describe('issue fixes', function () {

describe('#229 / #256 - tolerant short_message encoding', function () {
it('does not crash when short_message is a number', function () {
var pdu = new PDU('submit_sm', { destination_addr: '12345', short_message: 123 });
var buf;
assert.doesNotThrow(function () { buf = pdu.toBuffer(); });
assert.equal(new PDU(buf).short_message.message, '123');
});

it('does not crash when short_message is undefined', function () {
var pdu = new PDU('submit_sm', { destination_addr: '12345' });
pdu.short_message = undefined;
assert.doesNotThrow(function () { pdu.toBuffer(); });
});

it('does not crash when short_message is null', function () {
var pdu = new PDU('submit_sm', { destination_addr: '12345', short_message: null });
assert.doesNotThrow(function () { pdu.toBuffer(); });
});
});

describe('#66 / #252 - DCS-aware encoding detection on decode', function () {
function decodeWith(data_coding, buffer) {
return smpp.filters.message.decode.call({ data_coding: data_coding, esm_class: 0 }, buffer, false);
}

it('decodes UCS2 (data_coding 0x08)', function () {
assert.equal(decodeWith(0x08, smpp.encodings.UCS2.encode('héllo')).message, 'héllo');
});

it('treats DCS 0xF8 as default alphabet, not UCS2 (low-nibble trap)', function () {
assert.equal(decodeWith(0xF8, smpp.encodings.ASCII.encode('test')).message, 'test');
});

it('treats DCS 0xF4 as binary (returns raw buffer)', function () {
var out = decodeWith(0xF4, Buffer.from([1, 2, 3])).message;
assert.ok(Buffer.isBuffer(out));
assert.deepEqual([].slice.call(out), [1, 2, 3]);
});

it('keeps low data_coding values working (0x00 => default ASCII)', function () {
assert.equal(decodeWith(0x00, smpp.encodings.ASCII.encode('hi')).message, 'hi');
});
});

describe('#231 - user-defined TLVs round-trip by numeric tag', function () {
it('round-trips a custom TLV addressed by its numeric tag id', function () {
var pdu = new PDU('submit_sm', { destination_addr: '12345', short_message: 'x' });
pdu[0x3C02] = Buffer.from('TEST', 'ascii');
var round = new PDU(pdu.toBuffer());
assert.ok(Buffer.isBuffer(round[0x3C02]));
assert.equal(round[0x3C02].toString('ascii'), 'TEST');
});

it('accepts a custom TLV passed via constructor options', function () {
var pdu = new PDU('submit_sm', { destination_addr: '12345', short_message: 'x', 15362: Buffer.from('ALEX', 'ascii') });
assert.equal(new PDU(pdu.toBuffer())[15362].toString('ascii'), 'ALEX');
});
});
});