├── .gitignore ├── MIT-LICENSE ├── README.markdown ├── bert.js ├── package.json ├── test.html └── tests.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | node_modules/ -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2009 Rusty Klophaus (@rklophaus) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 |

BERT-JS

2 | 3 |

What is BERT?

4 | BERT (Binary ERlang Term) is a format created by the Erlang development team for serializing Erlang terms, and promoted by Tom Preston-Werner as a way for different languages to communicate in a simple and efficient manner. 5 | 6 | Read Tom's Slides 7 | 8 | 9 |

What is BERT JS?

10 | 11 | BERT-JS is a first cut Javascript implementation of the BERT protocol. In other words, using BERT-JS, you can serialize data into a binary format that can then be de-serialized by Erlang directly into an Erlang term. (Or, by Ruby, as Tom has written a BERT library for Ruby.) 12 | 13 |

Limitations

14 | 15 | * Decoding floats is not yet supported. 16 | 17 |

Interface

18 | 19 | * Bert.encode(Object) - Encode a Javascript object into BERT, return a String. The object can be a Boolean, Integer, Float, String, Array, Associative Array, or an Atom, Binary, or Tuple. (with the help of Bert.atom(), Bert.binary(), or Bert.tuple(), respectively). 20 | * Bert.decode(String) - Decode a BERT string into a Javascript object. Atoms, Binaries, and Tuples are special objects. See code for structure. 21 | * Bert.atom(String) - Create a Javascript object that will be encoded to an Atom. 22 | * Bert.binary(String) - Create a Javascript object that will be encoded to an Binary. 23 | * Bert.tuple(Element1, Element2, Element3, ...) - Create a Javascript object that will be encoded to a Tuple. 24 |

Examples

25 | 26 | Note, below the return value is given in the form of an Erlang binary which can be fed into Erlang's binary_to_term/1. In reality, this returns a Javascript String with the ASCII values of the binary. 27 | 28 | Bert.encode(Bert.atom("hello")); 29 | Returns: <<131,100,0,5,104,101,108,108,111>> 30 | Erlang: hello 31 | 32 | Bert.encode(Bert.binary("hello")); 33 | Returns: <<131,109,0,0,0,5,104,101,108,108,111>> 34 | Erlang: <<"hello">> 35 | 36 | Bert.encode(true); 37 | Returns: <<131,100,0,4,116,114,117,101>> 38 | Erlang: true 39 | 40 | Bert.encode(42); 41 | Returns: <<131,97,42>> 42 | Erlang: 42 43 | 44 | Bert.encode(5000); 45 | Returns: <<131,98,0,0,19,136>> 46 | Erlang: 5000 47 | 48 | Bert.encode(-5000); 49 | Returns: <<131,98,255,255,236,120>> 50 | Erlang: -5000 51 | 52 | Bert.encode(987654321); 53 | Returns: <<131,110,4,0,177,104,222,58>> 54 | Erlang: 987654321 55 | 56 | Bert.encode(-987654321); 57 | Returns: <<131,110,4,1,177,104,222,58>> 58 | Erlang: -987654321 59 | 60 | Bert.encode(3.14159); 61 | Returns: <<131,99,51,46,49,52,49,53,57,101,43,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0>> 62 | Erlang: 3.14159 63 | 64 | Bert.encode(-3.14159); 65 | Returns: <<131,99,45,51,46,49,52,49,53,57,101,43,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0>> 66 | Erlang: -3.14159 67 | 68 | Bert.encode([1, 2, 3]); 69 | Returns: <<131,108,0,0,0,3,97,1,97,2,97,3,106>> 70 | Erlang: [1,2,3] 71 | 72 | Bert.encode({a:1, b:2, c:3}); 73 | Returns: <<131,108,0,0,0,3,104,2,100,0,1,97,97,1,104,2,100,0,1,98,97,2,104,2,100,0,1,99,97,3,106>> 74 | Erlang: [{a,1},{b,2},{c,3}] 75 | 76 | Bert.encode(Bert.tuple("Hello", 1)); 77 | Returns: <<131,104,2,107,0,5,72,101,108,108,111,97,1>> 78 | Erlang: {"Hello",1} 79 | 80 | Bert.encode({ 81 | a : Bert.tuple(1, 2, 3), 82 | b : [4,5,6] 83 | }); 84 | Returns: <<131,108,0,0,0,2,104,2,100,0,1,97,104,3,97,1,97,2,97,3,104,2,100,0,1,98,108,0,0,0,3,97,4,97,5,97,6,106,106>> 85 | Erlang: [{a,{1,2,3}},{b,[4,5,6]}] 86 | 87 | var S = Bert.bytes_to_string([131,108,0,0,0,3,104,2,100,0,4,97,116,111,109,100,0,6,109,121,65,116,111,109, 88 | 104,2,100,0,6,98,105,110,97,114,121,109,0,0,0,9,77,121,32,66,105,110,97,114, 89 | 121,104,2,100,0,4,98,111,111,108,100,0,4,116,114,117,101,106]); 90 | var Obj = Bert.decode(S); 91 | Object is equiv to: [{atom,myAtom},{binary,<<"My Binary">>},{bool,true},{string,"Hello there"}] 92 | 93 | var S = Bert.bytes_to_string([131,108,0,0,0,5,104,2,100,0,13,115,109,97,108,108,95,105,110,116,101,103,101, 94 | 114,97,42,104,2,100,0,8,105,110,116,101,103,101,114,49,98,0,0,19,136,104,2, 100,0,8,105,110,116,101,103, 95 | 101,114,50,98,255,255,236,120,104,2,100,0,8,98,105,103,95,105,110,116,49,110,4,0,177,104,222,58,104,2, 96 | 100,0,8,98,105,103,95,105,110,116,50,110,4,1,177,104,222,58,106]); 97 | var Obj = Bert.decode(S); 98 | Object is equiv to: [{small_integer,42},{integer1,5000},{integer2,-5000},{big_int1,987654321},{big_int2,-987654321}] -------------------------------------------------------------------------------- /bert.js: -------------------------------------------------------------------------------- 1 | // BERT-JS 2 | // Copyright (c) 2009 Rusty Klophaus (@rklophaus) 3 | // Contributions by Ben Browning (@bbrowning) 4 | // See MIT-LICENSE for licensing information. 5 | 6 | 7 | // BERT-JS is a Javascript implementation of Binary Erlang Term Serialization. 8 | // - http://github.com/rklophaus/BERT-JS 9 | // 10 | // References: 11 | // - http://www.erlang-factory.com/upload/presentations/36/tom_preston_werner_erlectricity.pdf 12 | // - http://www.erlang.org/doc/apps/erts/erl_ext_dist.html#8 13 | 14 | 15 | // - CLASSES - 16 | 17 | function BertClass() { 18 | this.BERT_START = String.fromCharCode(131); 19 | this.SMALL_ATOM = String.fromCharCode(115); 20 | this.ATOM = String.fromCharCode(100); 21 | this.BINARY = String.fromCharCode(109); 22 | this.SMALL_INTEGER = String.fromCharCode(97); 23 | this.INTEGER = String.fromCharCode(98); 24 | this.SMALL_BIG = String.fromCharCode(110); 25 | this.LARGE_BIG = String.fromCharCode(111); 26 | this.FLOAT = String.fromCharCode(99); 27 | this.STRING = String.fromCharCode(107); 28 | this.LIST = String.fromCharCode(108); 29 | this.SMALL_TUPLE = String.fromCharCode(104); 30 | this.LARGE_TUPLE = String.fromCharCode(105); 31 | this.NIL = String.fromCharCode(106); 32 | this.ZERO = String.fromCharCode(0); 33 | this.ZERO_CHAR = String.fromCharCode(48); 34 | } 35 | 36 | function BertAtom(Obj) { 37 | this.type = "Atom"; 38 | this.value = Obj; 39 | this.toString = function () { 40 | return Obj; 41 | }; 42 | } 43 | 44 | function BertBinary(Obj) { 45 | this.type = "Binary"; 46 | this.value = Obj; 47 | this.toString = function () { 48 | return "<<\"" + Obj + "\">>"; 49 | }; 50 | } 51 | 52 | function BertTuple(Arr) { 53 | this.type = "Tuple"; 54 | this.length = Arr.length; 55 | this.value = Arr; 56 | for (var i = 0; i < Arr.length; i++) { 57 | this[i] = Arr[i]; 58 | } 59 | this.toString = function () { 60 | var i, s = ""; 61 | for (i = 0; i < this.length; i++) { 62 | if (s !== "") { 63 | s += ", "; 64 | } 65 | s += this[i].toString(); 66 | } 67 | 68 | return "{" + s + "}"; 69 | }; 70 | } 71 | 72 | 73 | 74 | // - INTERFACE - 75 | 76 | BertClass.prototype.encode = function (Obj) { 77 | return this.BERT_START + this.encode_inner(Obj); 78 | }; 79 | 80 | BertClass.prototype.decode = function (S) { 81 | if (S[0] !== this.BERT_START) { 82 | throw ("Not a valid BERT."); 83 | } 84 | var Obj = this.decode_inner(S.substring(1)); 85 | if (Obj.rest !== "") { 86 | throw ("Invalid BERT."); 87 | } 88 | return Obj.value; 89 | }; 90 | 91 | BertClass.prototype.atom = function (Obj) { 92 | return new BertAtom(Obj); 93 | }; 94 | 95 | BertClass.prototype.binary = function (Obj) { 96 | return new BertBinary(Obj); 97 | }; 98 | 99 | BertClass.prototype.tuple = function () { 100 | return new BertTuple(arguments); 101 | }; 102 | 103 | 104 | 105 | // - ENCODING - 106 | 107 | BertClass.prototype.encode_inner = function (Obj) { 108 | if (Obj === undefined) throw new Error("Cannot encode undefined values.") 109 | var func = 'encode_' + typeof(Obj); 110 | return this[func](Obj); 111 | }; 112 | 113 | BertClass.prototype.encode_string = function (Obj) { 114 | return this.STRING + this.int_to_bytes(Obj.length, 2) + Obj; 115 | }; 116 | 117 | BertClass.prototype.encode_boolean = function (Obj) { 118 | if (Obj) { 119 | return this.encode_inner( 120 | this.tuple(this.atom("bert"), this.atom("true"))); 121 | } 122 | else { 123 | return this.encode_inner( 124 | this.tuple(this.atom("bert"), this.atom("false"))); 125 | } 126 | }; 127 | 128 | BertClass.prototype.encode_number = function (Obj) { 129 | var s, isInteger = (Obj % 1 === 0); 130 | 131 | // Handle floats... 132 | if (!isInteger) { 133 | return this.encode_float(Obj); 134 | } 135 | 136 | // Small int... 137 | if (isInteger && Obj >= 0 && Obj < 256) { 138 | return this.SMALL_INTEGER + this.int_to_bytes(Obj, 1); 139 | } 140 | 141 | // 4 byte int... 142 | if (isInteger && Obj >= -134217728 && Obj <= 134217727) { 143 | return this.INTEGER + this.int_to_bytes(Obj, 4); 144 | } 145 | 146 | // Bignum... 147 | s = this.bignum_to_bytes(Obj); 148 | if (s.length < 256) { 149 | return this.SMALL_BIG + this.int_to_bytes(s.length - 1, 1) + s; 150 | } else { 151 | return this.LARGE_BIG + this.int_to_bytes(s.length - 1, 4) + s; 152 | } 153 | }; 154 | 155 | BertClass.prototype.encode_float = function (Obj) { 156 | // float... 157 | var s = Obj.toExponential(20); 158 | var match = /([^e]+)(e[+-])(\d+)/.exec(s); 159 | var a = match[1]; 160 | var b = match[2]; 161 | var c = match[3]; 162 | var exponentialPart = c; 163 | if ( exponentialPart.length == 1 ) { 164 | exponentialPart = '0' + exponentialPart; 165 | } 166 | s = a+b+exponentialPart; 167 | while (s.length < 31) { 168 | s += this.ZERO; 169 | } 170 | return this.FLOAT + s; 171 | }; 172 | 173 | BertClass.prototype.encode_object = function (Obj) { 174 | // Check if it's an atom, binary, or tuple... 175 | if (Obj === null){ 176 | return this.encode_inner(this.atom("null")); 177 | } 178 | if (Obj.type === "Atom") { 179 | return this.encode_atom(Obj); 180 | } 181 | if (Obj.type === "Binary") { 182 | return this.encode_binary(Obj); 183 | } 184 | if (Obj.type === "Tuple") { 185 | return this.encode_tuple(Obj); 186 | } 187 | 188 | // Check if it's an array... 189 | if (Obj.constructor.toString().indexOf("Array") !== -1) { 190 | return this.encode_array(Obj); 191 | } 192 | 193 | // Treat the object as an associative array... 194 | return this.encode_associative_array(Obj); 195 | }; 196 | 197 | BertClass.prototype.encode_atom = function (Obj) { 198 | return this.ATOM + this.int_to_bytes(Obj.value.length, 2) + Obj.value; 199 | }; 200 | 201 | BertClass.prototype.encode_binary = function (Obj) { 202 | return this.BINARY + this.int_to_bytes(Obj.value.length, 4) + Obj.value; 203 | }; 204 | 205 | BertClass.prototype.encode_tuple = function (Obj) { 206 | var i, s = ""; 207 | if (Obj.length < 256) { 208 | s += this.SMALL_TUPLE + this.int_to_bytes(Obj.length, 1); 209 | } else { 210 | s += this.LARGE_TUPLE + this.int_to_bytes(Obj.length, 4); 211 | } 212 | for (i = 0; i < Obj.length; i++) { 213 | s += this.encode_inner(Obj[i]); 214 | } 215 | return s; 216 | }; 217 | 218 | BertClass.prototype.encode_array = function (Obj) { 219 | if (Obj.length == 0) 220 | return this.encode_inner( 221 | this.tuple(this.atom("bert"), this.atom("nil"))); 222 | var i, s = this.LIST + this.int_to_bytes(Obj.length, 4); 223 | for (i = 0; i < Obj.length; i++) { 224 | s += this.encode_inner(Obj[i]); 225 | } 226 | s += this.NIL; 227 | return s; 228 | }; 229 | 230 | BertClass.prototype.encode_associative_array = function (Obj) { 231 | var key, Arr = []; 232 | for (key in Obj) { 233 | if (Obj.hasOwnProperty(key)) { 234 | Arr.push(this.tuple(this.atom(key), Obj[key])); 235 | } 236 | } 237 | return this.encode_array(Arr); 238 | }; 239 | 240 | 241 | 242 | // - DECODING - 243 | 244 | BertClass.prototype.decode_inner = function (S) { 245 | var Type = S[0]; 246 | S = S.substring(1); 247 | switch (Type) { 248 | case this.SMALL_ATOM: 249 | return this.decode_atom(S, 1); 250 | case this.ATOM: 251 | return this.decode_atom(S, 2); 252 | case this.BINARY: 253 | return this.decode_binary(S); 254 | case this.SMALL_INTEGER: 255 | return this.decode_small_integer(S); 256 | case this.INTEGER: 257 | return this.decode_integer(S, 4); 258 | case this.SMALL_BIG: 259 | return this.decode_big(S, 1); 260 | case this.LARGE_BIG: 261 | return this.decode_big(S, 4); 262 | case this.FLOAT: 263 | return this.decode_float(S); 264 | case this.STRING: 265 | return this.decode_string(S); 266 | case this.LIST: 267 | return this.decode_list(S); 268 | case this.SMALL_TUPLE: 269 | return this.decode_tuple(S, 1); 270 | case this.LARGE_TUPLE: 271 | return this.decode_large_tuple(S, 4); 272 | case this.NIL: 273 | return this.decode_nil(S); 274 | default: 275 | throw ("Unexpected BERT type: " + S.charCodeAt(0)); 276 | } 277 | }; 278 | 279 | BertClass.prototype.decode_atom = function (S, Count) { 280 | var Size, Value; 281 | Size = this.bytes_to_int(S, Count); 282 | S = S.substring(Count); 283 | Value = S.substring(0, Size); 284 | return { 285 | value: this.atom(Value), 286 | rest: S.substring(Size) 287 | }; 288 | }; 289 | 290 | BertClass.prototype.decode_binary = function (S) { 291 | var Size = this.bytes_to_int(S, 4); 292 | S = S.substring(4); 293 | return { 294 | value: this.binary(S.substring(0, Size)), 295 | rest: S.substring(Size) 296 | }; 297 | }; 298 | 299 | BertClass.prototype.decode_small_integer = function (S) { 300 | var Value = S.charCodeAt(0); 301 | S = S.substring(1); 302 | return { 303 | value: Value, 304 | rest: S 305 | }; 306 | }; 307 | 308 | BertClass.prototype.decode_integer = function (S, Count) { 309 | var Value = this.bytes_to_int(S, Count); 310 | S = S.substring(Count); 311 | return { 312 | value: Value, 313 | rest: S 314 | }; 315 | }; 316 | 317 | BertClass.prototype.decode_big = function (S, Count) { 318 | var Size, Value; 319 | Size = this.bytes_to_int(S, Count); 320 | S = S.substring(Count); 321 | Value = this.bytes_to_bignum(S, Size); 322 | return { 323 | value : Value, 324 | rest: S.substring(Size + 1) 325 | }; 326 | }; 327 | 328 | BertClass.prototype.decode_float = function (S) { 329 | var Size = 31; 330 | return { 331 | value: parseFloat(S.substring(0, Size)), 332 | rest: S.substring(Size) 333 | }; 334 | }; 335 | 336 | BertClass.prototype.decode_string = function (S) { 337 | var Size = this.bytes_to_int(S, 2); 338 | S = S.substring(2); 339 | return { 340 | value: S.substring(0, Size), 341 | rest: S.substring(Size) 342 | }; 343 | }; 344 | 345 | BertClass.prototype.decode_list = function (S) { 346 | var Size, i, El, LastChar, Arr = []; 347 | Size = this.bytes_to_int(S, 4); 348 | S = S.substring(4); 349 | for (i = 0; i < Size; i++) { 350 | El = this.decode_inner(S); 351 | Arr.push(El.value); 352 | S = El.rest; 353 | } 354 | LastChar = S[0]; 355 | if (LastChar !== this.NIL) { 356 | throw ("List does not end with NIL!"); 357 | } 358 | S = S.substring(1); 359 | return { 360 | value: Arr, 361 | rest: S 362 | }; 363 | }; 364 | 365 | BertClass.prototype.decode_tuple = function (S, Count) { 366 | var Size, i, El, Arr = []; 367 | Size = this.bytes_to_int(S, Count); 368 | S = S.substring(Count); 369 | for (i = 0; i < Size; i++) { 370 | El = this.decode_inner(S); 371 | Arr.push(El.value); 372 | S = El.rest; 373 | } 374 | if (Size >= 2) { 375 | var Head = Arr[0]; 376 | if (typeof Head === 'object' && Head.type === 'Atom' 377 | && Head.value === "bert") { 378 | var Kind = Arr[1]; 379 | if (typeof Kind !== 'object' || Kind.type !== 'Atom') { 380 | throw ("Invalid {bert, _} tuple!"); 381 | } 382 | switch (Kind.value) { 383 | case "true": 384 | return {value: true, rest: S}; 385 | case "false": 386 | return {value: false, rest: S}; 387 | case "nil": 388 | return {value: [], rest: S}; 389 | case "time": 390 | case "dict": 391 | case "regex": 392 | throw ("TODO: decode " + Kind.Value); 393 | default: 394 | throw ("Invalid {bert, " + 395 | Kind.Value.toString() + "} tuple!"); 396 | } 397 | } 398 | } 399 | return { 400 | value: this.tuple.apply(this,Arr), 401 | rest: S 402 | }; 403 | }; 404 | 405 | BertClass.prototype.decode_nil = function (S) { 406 | // nil is an empty list 407 | return { 408 | value: [], 409 | rest: S 410 | }; 411 | }; 412 | 413 | 414 | 415 | // - UTILITY FUNCTIONS - 416 | 417 | // Encode an integer to a big-endian byte-string of length Length. 418 | // Throw an exception if the integer is too large 419 | // to fit into the specified number of bytes. 420 | BertClass.prototype.int_to_bytes = function (Int, Length) { 421 | var isNegative, OriginalInt, i, Rem, s = ""; 422 | isNegative = (Int < 0); 423 | if (isNegative) { 424 | Int = - Int - 1; 425 | } 426 | OriginalInt = Int; 427 | for (i = 0; i < Length; i++) { 428 | Rem = Int % 256; 429 | if (isNegative) { 430 | Rem = 255 - Rem; 431 | } 432 | s = String.fromCharCode(Rem) + s; 433 | Int = Math.floor(Int / 256); 434 | } 435 | if (Int > 0) { 436 | throw ("Argument out of range: " + OriginalInt); 437 | } 438 | return s; 439 | }; 440 | 441 | // Read a big-endian encoded integer from the first Length bytes 442 | // of the supplied string. 443 | BertClass.prototype.bytes_to_int = function (S, Length) { 444 | var isNegative, i, n, Num = 0; 445 | isNegative = (S.charCodeAt(0) > 128); 446 | for (i = 0; i < Length; i++) { 447 | n = S.charCodeAt(i); 448 | if (isNegative) { 449 | n = 255 - n; 450 | } 451 | if (Num === 0) { 452 | Num = n; 453 | } 454 | else { 455 | Num = Num * 256 + n; 456 | } 457 | } 458 | if (isNegative) { 459 | Num = -Num - 1; 460 | } 461 | return Num; 462 | }; 463 | 464 | // Encode an integer into an Erlang bignum, 465 | // which is a byte of 1 or 0 representing 466 | // whether the number is negative or positive, 467 | // followed by little-endian bytes. 468 | BertClass.prototype.bignum_to_bytes = function (Int) { 469 | var isNegative, Rem, s = ""; 470 | isNegative = Int < 0; 471 | if (isNegative) { 472 | Int *= -1; 473 | s += String.fromCharCode(1); 474 | } else { 475 | s += String.fromCharCode(0); 476 | } 477 | 478 | while (Int !== 0) { 479 | Rem = Int % 256; 480 | s += String.fromCharCode(Rem); 481 | Int = Math.floor(Int / 256); 482 | } 483 | 484 | return s; 485 | }; 486 | 487 | // Encode a list of bytes into an Erlang bignum. 488 | BertClass.prototype.bytes_to_bignum = function (S, Count) { 489 | var isNegative, i, n, Num = 0; 490 | isNegative = (S.charCodeAt(0) === 1); 491 | S = S.substring(1); 492 | for (i = Count - 1; i >= 0; i--) { 493 | n = S.charCodeAt(i); 494 | if (Num === 0) { 495 | Num = n; 496 | } 497 | else { 498 | Num = Num * 256 + n; 499 | } 500 | } 501 | if (isNegative) { 502 | return Num * -1; 503 | } 504 | return Num; 505 | }; 506 | 507 | // Convert an array of bytes into a string. 508 | BertClass.prototype.bytes_to_string = function (Arr) { 509 | var i, s = ""; 510 | for (i = 0; i < Arr.length; i++) { 511 | s += String.fromCharCode(Arr[i]); 512 | } 513 | return s; 514 | }; 515 | 516 | // - TESTING - 517 | 518 | // Pretty Print a byte-string in Erlang binary form. 519 | BertClass.prototype.pp_bytes = function (Bin) { 520 | var i, s = ""; 521 | for (i = 0; i < Bin.length; i++) { 522 | if (s !== "") { 523 | s += ","; 524 | } 525 | s += "" + Bin.charCodeAt(i); 526 | } 527 | return "<<" + s + ">>"; 528 | }; 529 | 530 | // Pretty Print a JS object in Erlang term form. 531 | BertClass.prototype.pp_term = function (Obj) { 532 | return Obj.toString(); 533 | }; 534 | 535 | BertClass.prototype.binary_to_list = function (Str){ 536 | var ret = []; 537 | for (var i = 0; i < Str.length; i++) 538 | ret.push(Str.charCodeAt(i)); 539 | return ret; 540 | }; 541 | 542 | module.exports = new BertClass(); 543 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bert-js", 3 | "version": "1.0.0", 4 | "description": "Erlang BERT format encoder/decoder for javascript", 5 | "main": "bert.js", 6 | "scripts": { 7 | "test": "mocha tests.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/stjepano/BERT-JS.git" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "bugs": { 16 | "url": "https://github.com/stjepano/BERT-JS/issues" 17 | }, 18 | "homepage": "https://github.com/stjepano/BERT-JS#readme", 19 | "dependencies": { 20 | "chai": "^3.5.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |

10 | 
11 | 
12 | 


--------------------------------------------------------------------------------
/tests.js:
--------------------------------------------------------------------------------
  1 | var Bert = require('./bert.js');
  2 | var chai = require('chai');
  3 | 
  4 | var should = chai.should();
  5 | var expect = chai.expect;
  6 | 
  7 | describe('Bert', function() {
  8 | 
  9 |     it ('should encode atom', function() {
 10 |         Bert.binary_to_list(Bert.encode(Bert.atom('hello'))).should.deep.equal([
 11 |             131,100,0,5,104,101,108,108,111
 12 |         ]);
 13 |     });
 14 | 
 15 |     it ('should encode binary', function() {
 16 |         Bert.binary_to_list(Bert.encode(Bert.binary("hello"))).should.deep.equal([
 17 |             131,109,0,0,0,5,104,101,108,108,111
 18 |         ]);
 19 |     });
 20 | 
 21 |     it ('should encode boolean', function() {
 22 |         Bert.binary_to_list(Bert.encode(true)).should.deep.equal([
 23 |             131,104,2,100,0,4,98,101,114,116,100,0,4,116,114,117,101
 24 |         ]);
 25 | 
 26 |         Bert.binary_to_list(Bert.encode(false)).should.deep.equal([
 27 |             131,104,2,100,0,4,98,101,114,116,100,0,5,102,97,108,115,101
 28 |         ]);
 29 |     });
 30 | 
 31 |     it ('should encode ints', function() {
 32 |         expect(Bert.binary_to_list(Bert.encode(0))).to.deep.equal([
 33 |             131,97,0
 34 |         ]);
 35 | 
 36 |         expect(Bert.binary_to_list(Bert.encode(-1))).to.deep.equal([
 37 |             131,98,255,255,255,255
 38 |         ]);
 39 | 
 40 |         expect(Bert.binary_to_list(Bert.encode(42))).to.deep.equal([
 41 |             131,97,42
 42 |         ]);
 43 | 
 44 |         expect(Bert.binary_to_list(Bert.encode(5000))).to.deep.equal([
 45 |             131,98,0,0,19,136
 46 |         ]);
 47 | 
 48 |         expect(Bert.binary_to_list(Bert.encode(-5000))).to.deep.equal([
 49 |             131,98,255,255,236,120
 50 |         ]);
 51 | 
 52 |         expect(Bert.binary_to_list(Bert.encode(987654321))).to.deep.equal([
 53 |             131,110,4,0,177,104,222,58
 54 |         ]);
 55 | 
 56 |         expect(Bert.binary_to_list(Bert.encode(-987654321))).to.deep.equal([
 57 |             131,110,4,1,177,104,222,58
 58 |         ]);
 59 |     });
 60 | 
 61 |     it ('should encode null', function() {
 62 |         expect(Bert.binary_to_list(Bert.encode(null))).to.deep.equal([
 63 |             131,100,0,4,110,117,108,108
 64 |         ]);
 65 |     });
 66 | 
 67 |     it ('should not encode undefined', function() {
 68 |         expect(function(){
 69 |             Bert.binary_to_list(Bert.encode(undefined))
 70 |         }).to.throw("Cannot encode undefined values.")
 71 |     });
 72 | 
 73 |     it ('should encode floats', function() {
 74 |         expect(Bert.binary_to_list(Bert.encode(2.5))).to.deep.equal([
 75 |             131,99,50,46,53,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,101,
 76 |             43,48,48,0,0,0,0,0
 77 |         ], 'failed 2.5');
 78 | 
 79 |         expect(Bert.binary_to_list(Bert.encode(3.14159))).to.deep.equal([
 80 |             131,99,51,46,49,52,49,53,56,57,57,57,57,57,57,57,57,57,57,56,56,50,54,50,101,
 81 |             43,48,48,0,0,0,0,0
 82 |         ], 'failed 3.14158');
 83 | 
 84 |         expect(Bert.binary_to_list(Bert.encode(-3.14159))).to.deep.equal([
 85 |             131,99,45,51,46,49,52,49,53,56,57,57,57,57,57,57,57,57,57,57,56,56,50,54,50,
 86 |             101,43,48,48,0,0,0,0
 87 |         ], 'failed -3.14159');
 88 | 
 89 |         expect(Bert.binary_to_list(Bert.encode(0.0012))).to.deep.equal([
 90 |             131,99,49,46,49,57,57,57,57,57,57,57,57,57,57,57,57,57,57,56,57,52,56,56,101,
 91 |             45,48,51,0,0,0,0,0
 92 |         ]);
 93 | 
 94 |         expect(Bert.binary_to_list(Bert.encode(-0.0012))).to.deep.equal([
 95 |             131,99,45,49,46,49,57,57,57,57,57,57,57,57,57,57,57,57,57,57,56,57,52,56,56,
 96 |             101,45,48,51,0,0,0,0
 97 |         ]);
 98 |     });
 99 | 
100 |     it ('should encode arrays', function() {
101 |         expect(Bert.binary_to_list(Bert.encode(["1","2","3"]))).to.deep.equal([
102 |             131,108,0,0,0,3,107,0,1,49,107,0,1,50,107,0,1,51,106
103 |         ]);
104 |     });
105 | 
106 |     it ('should encode assoc arrays', function(){
107 |         expect(Bert.binary_to_list(Bert.encode({a : 1, b : 2, c : 3}))).to.deep.equal([
108 |             131,108,0,0,0,3,104,2,100,0,1,97,97,1,104,2,100,0,1,98,97,2,104,2,100,0,1,99,97,3,106
109 |         ])
110 |     });
111 |     it ('should encode tuple', function(){
112 |         expect(Bert.binary_to_list(Bert.encode(Bert.tuple("Hello", 1)))).to.deep.equal([
113 |             131,104,2,107,0,5,72,101,108,108,111,97,1
114 |         ])
115 |     });
116 |     it ('should encode empty list', function(){
117 |         expect(Bert.binary_to_list(Bert.encode([]))).to.deep.equal([
118 |             131,104,2,100,0,4,98,101,114,116,100,0,3,110,105,108
119 |         ])
120 |     });
121 |     it ('should encode complex', function(){
122 |         expect(Bert.binary_to_list(Bert.encode({
123 |             a : Bert.tuple(1, 2, 3),
124 |             b : [400, 5, 6]
125 |         }))).to.deep.equal([
126 |             131,108,0,0,0,2,104,2,100,0,1,97,104,3,97,1,97,2,97,3,104,2,100,0,1,98,108,0,0,0,3,98,0,0,1,144,97,5,97,6,106,106
127 |         ])
128 |     });
129 |     it ('should decode complex', function(){
130 | 
131 |         var term = Bert.decode(Bert.bytes_to_string([131, 108, 0, 0, 0, 4, 104, 2, 100, 0, 4, 97, 116, 111, 109, 100, 0, 6, 109, 121, 65, 116, 111, 109, 104, 2, 100, 0, 6, 98, 105, 110, 97, 114, 121, 109, 0, 0, 0, 9, 77, 121, 32, 66, 105, 110, 97, 114, 121, 104, 2, 100, 0, 4, 98, 111, 111, 108, 100, 0, 4, 116, 114, 117, 101, 104, 2, 100, 0, 6, 115, 116, 114, 105, 110, 103, 107, 0, 11, 72, 101, 108, 108, 111, 32, 116, 104, 101, 114, 101, 106]))
132 |         expect(Bert.pp_term(term)).to.equal('{atom, myAtom},{binary, <<"My Binary">>},{bool, true},{string, Hello there}')
133 | 
134 |     });
135 |     it ('should decode small ints', function(){
136 |         expect(Bert.decode(Bert.bytes_to_string([131,97,130]))).to.equal(130)
137 |     })
138 |     it ('should decode negative ints', function(){
139 |         expect(Bert.decode(Bert.bytes_to_string([131,98,255,255,255,255]))).to.equal(-1)
140 |     });
141 |     it ('should decode ints', function(){
142 |         var term = Bert.decode(Bert.bytes_to_string([131, 108, 0, 0, 0, 5, 104, 2, 100, 0, 13, 115, 109, 97, 108, 108, 95, 105, 110, 116, 101, 103, 101, 114, 97, 42, 104, 2, 100, 0, 8, 105, 110, 116, 101, 103, 101, 114, 49, 98, 0, 0, 19, 136, 104, 2, 100, 0, 8, 105, 110, 116, 101, 103, 101, 114, 50, 98, 255, 255, 236, 120, 104, 2, 100, 0, 8, 98, 105, 103, 95, 105, 110, 116, 49, 110, 4, 0, 177, 104, 222, 58, 104, 2, 100, 0, 8, 98, 105, 103, 95, 105, 110, 116, 50, 110, 4, 1, 177, 104, 222, 58, 106]));
143 |         expect(Bert.pp_term(term)).to.equal('{small_integer, 42},{integer1, 5000},{integer2, -5000},{big_int1, 987654321},{big_int2, -987654321}')
144 |     });
145 |     it ('should decode floats', function(){
146 |         // Try decoding this: -3.14159
147 |         var term = Bert.decode(Bert.bytes_to_string([131,99,45,51,46,49,52,49,53,56,57,57,57,57,57,57,57,57,57,57,56,56,50,54,50,101,43,48,48,0,0,0,0]));
148 |         expect(term).to.equal(-3.14159);
149 |     });
150 |     it ('should decode empty list', function(){
151 |         var term = Bert.decode(Bert.bytes_to_string([131, 106]));
152 |         expect(term).to.deep.equal([]);
153 |     });
154 |     it ('should decode true', function(){
155 |         var term = Bert.decode(Bert.bytes_to_string([131,104,2,100,0,4,98,101,114,116,100,0,4,116,114,117,101]));
156 |         expect(term).to.equal(true);
157 |     })
158 |     it ('should decode false', function(){
159 |         var term = Bert.decode(Bert.bytes_to_string([131,104,2,100,0,4,98,101,114,116,100,0,5,102,97,108,115,101]));
160 |         expect(term).to.equal(false);
161 |     })
162 | 
163 | });
164 | 


--------------------------------------------------------------------------------