Created
August 13, 2014 00:47
-
-
Save yurydelendik/89a046484dcf02a3013e to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf | |
var UNIVERSAL_TAG_CLASS = 0; | |
var EOC_TAG = 0; | |
var BOOLEAN_TAG = 1; | |
var INTEGER_TAG = 2; | |
var NULL_TAG = 5; | |
var OBJECT_ID_TAG = 6; | |
var RELATIVE_OBJECT_ID_TAG = 13; | |
var PRINTABLE_STRING_TAG = 19; | |
var IA5_STRING_TAG = 22; | |
var UTC_TIME_TAG = 23; | |
function readBerPrimitive(tagClass, tagNumber, data, start, end) { | |
if (tagClass === UNIVERSAL_TAG_CLASS) { | |
switch (tagNumber) { | |
case EOC_TAG: | |
if (start !== end) { | |
throw new Error('Invalid EOC tag'); | |
} | |
return undefined; | |
case BOOLEAN_TAG: | |
if (start + 1 !== end) { | |
throw new Error('Invalid boolean tag'); | |
} | |
return !!data[start]; | |
case NULL_TAG: | |
if (start !== end) { | |
throw new Error('Invalid null tag'); | |
} | |
return null; | |
case INTEGER_TAG: | |
if (start === end) { | |
throw new Error('Invalid integer tag'); | |
} else if (start + 1 === end) { | |
return (data[start] << 24) >> 24; | |
} else if (start + 2 === end) { | |
return ((data[start] << 24) | (data[start + 1] << 16)) >> 16; | |
} else if (start + 3 === end) { | |
return ((data[start] << 24) | (data[start + 1] << 16) | | |
(data[start + 2] << 8)) >> 8; | |
} else if (start + 4 === end) { | |
return (data[start] << 24) | (data[start + 1] << 16) | | |
(data[start + 2] << 8) | data[start + 3]; | |
} | |
// to long for us | |
break; | |
case OBJECT_ID_TAG: | |
case RELATIVE_OBJECT_ID_TAG: | |
if (start === end) { | |
throw new Error('Invalid identifier tag'); | |
} | |
var id = []; | |
for (var i = start; i < end; ) { | |
var tmp = decodeCompressedNumber(data, i, end); | |
id.push(tmp.value); | |
i += tmp.octetLength; | |
} | |
if (tagNumber === OBJECT_ID_TAG) { | |
var firstNumber = +id.shift(); | |
id.unshift(((firstNumber / 40) | 0).toString(), | |
(firstNumber % 40).toString()); | |
} | |
return id.join('.'); | |
case PRINTABLE_STRING_TAG: | |
case IA5_STRING_TAG: | |
case UTC_TIME_TAG: | |
return String.fromCharCode.apply(null, data.subarray(start, end)); | |
} | |
} | |
return data.subarray(start, end); | |
} | |
function decodeCompressedNumber(data, start, end) { | |
var j = start, k; | |
while (j < end && (data[j] & 0x80)) { | |
j++; | |
} | |
if (j >= end) { | |
throw new Error('Invalid identifier bytes encoding'); | |
} | |
var str = '', mult = 128, buffer = data[j]; | |
k = j; | |
var partSize = 10000; | |
while (start < k) { | |
buffer += (data[--k] & 0x7F) * mult; | |
mult <<= 7; | |
if (mult >= partSize) { | |
str = ((buffer % partSize) + partSize).toString().substr(1) + str; | |
mult = (mult / partSize) | 0; | |
buffer = (buffer / partSize) | 0; | |
} | |
} | |
if (buffer !== 0) { | |
str = buffer.toString() + str; | |
} else { | |
while (str.length > 1 && str[0] === '0') { | |
str = str.substr(1); | |
} | |
} | |
return { | |
value: str, | |
octetLength: j - start + 1 | |
}; | |
} | |
function readBer(data, start, end) { | |
var i = start, k; | |
// 8.1.2 Identifier octets | |
var tagOctet = data[i++]; | |
var tagNumber = tagOctet & 0x1F; | |
if (tagNumber === 0x1F) { | |
// 8.1.2.4 | |
var tmpNumber = decodeCompressedNumber(data, i, end); | |
tagNumber = tmpNumber.value; | |
i += tmpNumber.octetLength; | |
} | |
var tagClass = tagOctet >> 6; | |
var constructed = !!(tagOctet & 0x20); // 8.1.2.3 | |
var contents; | |
var b = data[i]; | |
if (b === 0x80) { | |
// 8.1.3.6 indefinite form | |
if (!constructed) { | |
throw new Error('primitive cannot have indefinite form length'); | |
} | |
i++; | |
contents = []; | |
var eocFound = false; | |
while (i < end) { | |
child = readBer(data, i, end); | |
i += child.length; | |
if (child.class === UNIVERSAL_TAG_CLASS && child.number === EOC_TAG) { | |
eocFound = true; // end-of-contents octets | |
break; | |
} | |
contents.push(child); | |
} | |
if (!eocFound) { | |
throw new Error('EOC is not found'); | |
} | |
} else { | |
// 8.1.3.3 | |
var shortForm = !(b & 0x80); | |
var length; | |
if (shortForm) { | |
length = b; | |
i++; | |
} else { | |
// 8.1.3.5 | |
var lengthOfLength = b & 0x7F; | |
if (lengthOfLength >= 4) { | |
throw new Error('length is too long'); | |
} | |
i++; | |
length = 0; | |
for (k = 0; k < lengthOfLength; k++) { | |
length = (length << 8) | data[i++]; | |
} | |
length = length >>> 0; | |
} | |
if (i + length > end) { | |
throw new Error('data tag overflow'); | |
} | |
if (!constructed) { | |
contents = readBerPrimitive(tagClass, tagNumber, data, i, i + length); | |
i += length; | |
} else { | |
contents = []; | |
while (i < end) { | |
child = readBer(data, i, end); | |
i += child.length; | |
contents.push(child); | |
} | |
} | |
} | |
return { | |
class: tagClass, | |
number: tagNumber, | |
constructed: constructed, | |
contents: contents, | |
offset: start, | |
length: i - start | |
}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment