open-sml
Version:
Smart Message Language Implementation
520 lines (456 loc) • 15.8 kB
JavaScript
/*!
* OpenSML
* Copyright(c) 2014-2015 D. Spautz (d.spautz@web.de)
* MIT Licensed
*/
var Constants = require('./Constants');
function SmlBuffer(value){
if(value===undefined) {
this.buffer = Buffer.alloc(Constants.MAX_BUFFER_SIZE);
} else {
this.buffer=value;
}
this.offset=0;
this.lastReadValueType=null;
}
SmlBuffer.prototype.getBuffer = function getBuffer(){
return this.buffer;
};
SmlBuffer.prototype.getOffset = function getOffset(){
return this.offset;
};
SmlBuffer.prototype.setOffset = function setOffset(value){
this.offset = value;
};
SmlBuffer.prototype.getLength = function getLength(){
return this.buffer.length;
};
SmlBuffer.prototype.toString = function toString(){
if (!Buffer.isBuffer(this.buffer)) return this.buffer;
return this.buffer.toString('hex');
};
SmlBuffer.prototype.finalize = function finalize(startOffset){
if (!startOffset) startOffset=0;
this.buffer=this.buffer.slice(startOffset, this.offset);
};
// ########################## OctetString #########################################################
SmlBuffer.prototype.writeOctetString = function writeOctetString(value){
/*var bits = Math.ceil(Math.log(value.length)/Math.log(2));
var bytes = Math.ceil(bits/4);
var hexLength = (value.length+bytes).toString(16).length % 2 === 0 ? (value.length+bytes).toString(16) : "0"+(value.length+bytes).toString(16);
var length = new Buffer(hexLength, 'hex');
for(var i=0; i<bytes; i++){
if(bytes-(i+1) > 0){
byte = 0x80 | ((length.readUInt8(Math.floor(i/2)) & 0xF0)>>4);
} else {
byte = 0x00 | (length.readUInt8(Math.floor(i/2)) & 0x0F);
}
this.buffer.writeUInt8(byte, this.offset);
this.offset+=1;
}*/
this.writeTLField(0x0, value.length); // SEQUENZ
if (Buffer.isBuffer(value)) {
value.copy(this.buffer, this.offset);
this.offset+=value.length;
}
else {
for(var j=0; j<value.length; j++){
this.buffer.writeUInt8(value.charCodeAt(j), this.offset);
this.offset++;
}
}
};
SmlBuffer.prototype.readOctetString = function readOctetString(){
var tlField = this.readTLField();
var type = tlField.type;
var length = tlField.length;
// OPTIONAL
if(type === 0x00 && length===0x01){
return undefined;
} else {
if(type !== 0x08 && type !== 0x00){
throw new Error("Unknown TL-Field 0x"+tlField.type.toString(16)+tlField.length.toString(16)+" for OctetString [Offset: "+this.offset+"]!");
} else {
this.lastReadValueType=type;
var result = this.buffer.slice(this.offset, this.offset+tlField.length-1);
this.offset+=tlField.length-1;
return result;
}
}
};
// ############################ read #############################################
SmlBuffer.prototype.readUInt8 = function readUInt8(){
var result = this.buffer.readUInt8(this.offset);
this.offset++;
this.lastReadValueType = Constants.UINT8;
return result;
};
SmlBuffer.prototype.readInt8 = function readInt8(){
var result = this.buffer.readInt8(this.offset);
this.offset++;
this.lastReadValueType = Constants.INT8;
return result;
};
SmlBuffer.prototype.readUInt16 = function readUInt16(){
var result = this.buffer.readUInt16BE(this.offset);
this.offset+=2;
this.lastReadValueType = Constants.UINT16;
return result;
};
SmlBuffer.prototype.readInt16 = function readInt16(){
var result = this.buffer.readInt16BE(this.offset);
this.offset+=2;
this.lastReadValueType = Constants.INT16;
return result;
};
SmlBuffer.prototype.readUInt32 = function readUInt32(){
var result = this.buffer.readUInt32BE(this.offset);
this.offset+=4;
this.lastReadValueType = Constants.UINT32;
return result;
};
SmlBuffer.prototype.readInt32 = function readInt32(){
var result = this.buffer.readInt32BE(this.offset);
this.offset+=4;
this.lastReadValueType = Constants.INT32;
return result;
};
SmlBuffer.prototype.readUInt64 = function readUInt64(){
//var result = (this.buffer.readUInt32BE(this.offset+4)<<8) & this.buffer.readUIntBE(this.offset);
/*var result = this.buffer.readDoubleBE(this.offset);
this.offset+=8;
this.lastReadValueType = Constants.UINT64;
return result;*/
var Int64 = require('int64-buffer');
var int64 = new Int64.Uint64BE(this.buffer, this.offset);
this.offset+=8;
this.lastReadValueType = Constants.UINT64;
if (int64.toNumber().toString(10)==int64.toString(10)) {
return int64.toNumber();
}
else {
return int64.toString(10);
}
};
SmlBuffer.prototype.readInt64 = function readInt64(){
var Int64 = require('int64-buffer');
var int64 = new Int64.Int64BE(this.buffer, this.offset);
this.offset+=8;
this.lastReadValueType = Constants.INT64;
if (int64.toNumber().toString(10)==int64.toString(10)) {
return int64.toNumber();
}
else {
return int64.toString(10);
}
};
// ################################ write ########################################
SmlBuffer.prototype.writeInt8 = function writeInt8(value){
this.buffer.writeInt8(value, this.offset);
this.offset+=1;
};
SmlBuffer.prototype.writeUInt8 = function writeUInt8(value){
this.buffer.writeUInt8(value, this.offset);
this.offset+=1;
};
SmlBuffer.prototype.writeInt16 = function writeInt16(value){
this.buffer.writeInt16BE(value, this.offset);
this.offset+=2;
};
SmlBuffer.prototype.writeUInt16 = function writeUInt16(value){
this.buffer.writeUInt16BE(value, this.offset);
this.offset+=2;
};
SmlBuffer.prototype.writeInt32 = function writeInt32(value){
this.buffer.writeInt32BE(value, this.offset);
this.offset+=4;
};
SmlBuffer.prototype.writeUInt32 = function writeUInt32(value){
this.buffer.writeUInt32BE(value, this.offset);
this.offset+=4;
};
SmlBuffer.prototype.writeInt64 = function writeInt64(value){
//this.buffer.writeDoubleBE(value, this.offset);
var Int64 = require('int64-buffer');
if (typeof value == "string") int64 = new Int64.Int64BE(value,10);
else int64 = new Int64.Int64BE(value);
int64.toBuffer().copy(this.buffer,this.offset);
this.offset+=8;
};
SmlBuffer.prototype.writeUInt64 = function writeUInt64(value){
//this.buffer.writeDoubleBE(value, this.offset);
var Int64 = require('int64-buffer');
var int64;
if (typeof value == "string") int64 = new Int64.Uint64BE(value,10);
else int64 = new Int64.Uint64BE(value);
int64.toBuffer().copy(this.buffer,this.offset);
this.offset+=8;
};
// #########################################################################
SmlBuffer.prototype.readTLField = function readTLField(){
var length = 0;
var counter = 0;
var type;
var readMore = false;
do {
var tlField = this.buffer.readUInt8(this.offset);
this.offset++;
readMore = (tlField >> 7 === 1) ? true : false;
if (type === undefined) {
type = ((tlField & 0x7F) >> 4);
}
length = ((length<<4) + (tlField & 0x0F));
if ((tlField & 0x7F) >> 4 === 0 && readMore) { // We have an OctetString and want to return "left length" instead of full length
counter++;
}
} while (readMore);
length-=counter;
return {type: type, length: length};
};
// ToDo: Multibyte tl fields
SmlBuffer.prototype.writeTLField = function writeTLField(value, length){
if (! length) {
// we directly write out the value because not allowed to be bigger then 1 bytes
if (value > 0xFF) {
throw new Error("writeTLField without length only allowed for 1 byte values");
}
this.buffer.writeUInt8(value, this.offset);
this.offset++;
}
else {
if (value > 0x7) {
throw new Error("writeTLField with type >0x7 is not allowed");
}
// value if the "type" and length is the length
var bytes = Math.ceil(Math.log(length)/Math.log(2) / 4);
if (value === 0x0) {
length += bytes;
}
var lengthVals = [];
do {
lengthVals.push(length - ((length >> 4) << 4));
length = length >> 4;
} while (length > 0);
var baseValue = value << 4;
while (lengthVals.length) {
var currentLength = lengthVals.pop();
var writeValue = 0;
if (lengthVals.length) writeValue = 0x80; // set highest bit because length still > 15
writeValue = writeValue | baseValue | currentLength;
this.buffer.writeUInt8(writeValue, this.offset);
this.offset++;
baseValue = 0;
}
}
};
SmlBuffer.prototype.readUnsigned = function readUnsigned(){
var tlField = this.readTLField();
var type = tlField.type;
var length = tlField.length;
// Optional
if(type===0x00 && length === 0x01){
this.lastReadValueType = Constants.OPTIONAL;
return undefined;
} else {
if(type!=Constants.UNSIGNED && type!==0x00){
throw new Error("Wrong TL-Field (" + type.toString(16) + ") for Unsigned!");
} else {
if(length == 0x02){
this.lastReadValueType = Constants.UINT8;
return this.readUInt8();
} else if(length == 0x03){
this.lastReadValueType = Constants.UINT16;
return this.readUInt16();
} else if(length == 0x04){
// Just for EMH impelementation and short Timestamps because of misconfigured/unreachable NTP Server
this.lastReadValueType = Constants.UINT32;
var result = this.buffer.readUIntBE(this.offset,3);
this.offset+=3;
return result;
} else if(length == 0x05){
this.lastReadValueType = Constants.UINT32;
return this.readUInt32();
} else if(length == 0x09){
this.lastReadValueType = Constants.UINT64;
return this.readUInt64();
}
}
}
};
SmlBuffer.prototype.writeUnsigned = function writeUnsigned(value, tlField){
if(tlField==Constants.UINT8){
this.writeTLField(Constants.UINT8);
this.writeUInt8(value);
} else if(tlField==Constants.UINT16){
this.writeTLField(Constants.UINT16);
this.writeUInt16(value);
} else if(tlField==Constants.UINT32){
this.writeTLField(Constants.UINT32);
this.writeUInt32(value);
} else if(tlField==Constants.UINT64){
this.writeTLField(Constants.UINT64);
this.writeUInt64(value);
} else if (valueType==Constants.OPTIONAL){
this.writeUInt8(0x01);
}
};
SmlBuffer.prototype.readInteger = function readInteger(){
var tlField = this.readTLField();
var type = tlField.type;
var length = tlField.length;
if(type===0x00 && length === 0x01){
return undefined;
} else {
if(type != Constants.INTEGER && type !== 0x00){
throw new Error("Wrong TL-Field for Integer!");
} else {
if(length == 0x02){
this.lastReadValueType = Constants.INT8;
return this.readInt8();
} else if(length == 0x03){
this.lastReadValueType = Constants.INT16;
return this.readInt16();
} else if(length == 0x04){
this.lastReadValueType = Constants.INT32;
var result = this.buffer.readIntBE(this.offset,3);
this.offset+=3;
return result;
} else if(length == 0x05){
this.lastReadValueType = Constants.INT32;
return this.readInt32();
} else if(length == 0x06){
this.lastReadValueType = Constants.INT64;
var result = this.buffer.readIntBE(this.offset,5);
this.offset+=5;
return result;
} else if(length == 0x09){
this.lastReadValueType = Constants.INT64;
return this.readInt64();
}
}
}
};
SmlBuffer.prototype.writeInteger = function writeInteger(value, tlField){
if(tlField==Constants.INT8){
this.writeTLField(Constants.INT8);
this.writeInt8(value);
} else if(tlField==Constants.INT16){
this.writeTLField(Constants.INT16);
this.writeInt16(value);
} else if(tlField==Constants.INT32){
this.writeTLField(Constants.INT32);
this.writeInt32(value);
} else if(tlField==Constants.INT64){
this.writeTLField(Constants.INT64);
this.writeInt64(value);
} else if (valueType==Constants.OPTIONAL){
this.writeUInt8(0x01);
}
};
SmlBuffer.prototype.readSmlValue = function readSmlValue(){
if((this.buffer.readUInt8(this.offset)>>4)===0x00 || (this.buffer.readUInt8(this.offset)>>4)===0x08){
return this.readOctetString();
} else if((this.buffer.readUInt8(this.offset)>>4)==0x04){
return this.readSmlBoolean();
} else if((this.buffer.readUInt8(this.offset)>>4)==0x05){
return this.readInteger();
} else if((this.buffer.readUInt8(this.offset)>>4)==0x06){
return this.readUnsigned();
} else {
throw new Error("Wrong TL-Field 0x"+this.buffer.readUInt8(this.offset).toString(16)+" for SmlValue!");
}
};
SmlBuffer.prototype.writeSmlValue = function writeSmlValue(value, valueType){
if(valueType==Constants.BOOLEAN){
this.writeSmlBoolean(value);
} else if(valueType==Constants.OCTET_STRING){
this.writeOctetString(value);
} else if(valueType==Constants.UINT8){
this.writeUnsigned(value, valueType);
} else if(valueType==Constants.UINT16){
this.writeUnsigned(value, valueType);
} else if(valueType==Constants.UINT32){
this.writeUnsigned(value, valueType);
} else if(valueType==Constants.UINT64){
this.writeUnsigned(value, valueType);
} else if(valueType==Constants.INT8){
this.writeInteger(value, valueType);
} else if(valueType==Constants.INT16){
this.writeInteger(value, valueType);
} else if(valueType==Constants.INT32){
this.writeInteger(value, valueType);
} else if(valueType==Constants.INT64){
this.writeInteger(value, valueType);
} else if (valueType==Constants.OPTIONAL){
this.writeUInt8(0x01);
}
};
SmlBuffer.prototype.readSmlBoolean = function readSmlBoolean(){
var tlField = this.readTLField();
var type = tlField.type;
var length = tlField.length;
if(type === 0x00 && length === 0x01){
// OPTIONAL
return undefined;
} else {
if(type != Constants.BOOLEAN || length != 0x02){
throw new Error("Wrong TL-Field for Boolean!");
} else {
if(this.readUInt8()===0x00){
this.lastReadValueType = Constants.BOOLEAN;
return false;
} else {
this.lastReadValueType = Constants.BOOLEAN;
return true;
}
}
}
};
SmlBuffer.prototype.writeSmlBoolean = function writeSmlBoolean(value){
if(value === undefined){
// Optional
this.writeUInt8(Constants.OPTIONAL);
} else {
this.writeTLField(0x42);
if(value === true){
this.writeUInt8(0x01);
} else {
this.writeUInt8(0x00);
}
}
};
SmlBuffer.prototype.readChoice = function readChoice(){
var tlField = this.readTLField();
var type = tlField.type;
var length = tlField.length;
if (type === 0x00 && length === 0x01) {
// OPTIONAL
return undefined;
} else if (tlField.type == 0x06 && tlField.length == 0x05) {
// workaround Holley DTZ541
// if SML_ListEntry valTime (SML_Time) is given there are missing bytes:
// 0x72: indicate a list for SML_Time with 2 entries
// 0x62 0x01: indicate secIndex
// 0x65 + 4 bytes seconds
// instead, the DTZ541 starts with the last line: 0x65 + 4 bytes secIndex
//
// reset the previously read tl-field to be able
// to re-read the TLField 0x65 to get the secIndex value
this.setOffset(this.offset - 1);
// this workaround will return choice 0x01 and sets the offset back to
return 0x01;
} else if (tlField.type != 0x07 && tlField.length != 0x02) {
throw new Error("Wrong TL-Field 0x" + type.toString(16) + length.toString(16) + " for Choice!");
} else {
return this.readUnsigned();
}
};
SmlBuffer.prototype.writeChoice = function writeChoice(value, tlField){
this.writeTLField(0x72);
if([Constants.UINT8, Constants.UINT16, Constants.UINT32, Constants.UINT64].indexOf(tlField) > -1){
this.writeUnsigned(value, tlField);
} else {
throw new Error("Unknown Choice-Element!");
}
};
module.exports = SmlBuffer;