├── .gitignore ├── package.json ├── __tests__ └── bert-test.js ├── README.md └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bert-elixir", 3 | "version": "1.0.4", 4 | "description": "BERT (Binary ERlang Term) serialization library for Javascript", 5 | "main": "index.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "jest" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/alexdovzhanyn/bert-elixir.git" 15 | }, 16 | "keywords": [ 17 | "erlang", 18 | "elixir", 19 | "data", 20 | "serialization", 21 | "bert", 22 | "term", 23 | "binary" 24 | ], 25 | "author": "Alex Dovzhanyn", 26 | "license": "MIT", 27 | "devDependencies": { 28 | "jest": "^23.6.0" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /__tests__/bert-test.js: -------------------------------------------------------------------------------- 1 | const Bert = require('../index.js') 2 | 3 | describe('bert', () => { 4 | it ('can encode an atom', () => { 5 | const encodedAtom = Bert.encode(Bert.atom('myAtom')) 6 | const binaryAtom = Bert.binaryToList(encodedAtom) 7 | 8 | expect(binaryAtom).toEqual([131, 100, 0, 6, 109, 121, 65, 116, 111, 109]) 9 | }) 10 | 11 | it ('can decode a tuple', () => { 12 | // decode this tuple... 13 | // const tuple = Bert.tuple( 14 | // Bert.atom('myAtom'), 15 | // 1 16 | // ); 17 | 18 | const binaryTuple = [ 131, 104, 2, 100, 0, 6, 109, 121, 65, 116, 111, 109, 97, 1 ]; 19 | const decodedTuple = Bert.decode(binaryTuple.map(x => String.fromCharCode(x)).join('')); 20 | expect(decodedTuple.type).toEqual('Tuple') 21 | expect(decodedTuple.length).toEqual(2) 22 | expect(decodedTuple.value[0].type).toEqual('Atom') 23 | expect(decodedTuple.value[0].value).toEqual('myAtom') 24 | expect(decodedTuple.value[1]).toEqual(1) 25 | }) 26 | 27 | it('can encode a charlist', () => { 28 | const encodedCharlist = Bert.encode(Bert.charlist('this is going to be a charlist')) 29 | const binaryCharlist = Bert.binaryToList(encodedCharlist) 30 | 31 | expect(binaryCharlist).toEqual([131, 107, 0, 30, 116, 104, 105, 115, 32, 105, 115, 32, 103, 111, 105, 110, 103, 32, 116, 111, 32, 98, 101, 32, 97, 32, 99, 104, 97, 114, 108, 105, 115, 116]) 32 | }) 33 | 34 | it('can encode a string', () => { 35 | const encodedString = Bert.encode('this is going to be a string') 36 | const binaryString = Bert.binaryToList(encodedString) 37 | 38 | expect(binaryString).toEqual([131, 109, 0, 0, 0, 28, 116, 104, 105, 115, 32, 105, 115, 32, 103, 111, 105, 110, 103, 32, 116, 111, 32, 98, 101, 32, 97, 32, 115, 116, 114, 105, 110, 103]) 39 | }) 40 | 41 | it('can encode a boolean', () => { 42 | const encodedBooleanTrue = Bert.encode(true) 43 | const encodedBooleanFalse = Bert.encode(false) 44 | const binaryBooleanTrue = Bert.binaryToList(encodedBooleanTrue) 45 | const binaryBooleanFalse = Bert.binaryToList(encodedBooleanFalse) 46 | 47 | expect(binaryBooleanTrue).toEqual([131, 100, 0, 4, 116, 114, 117, 101]) 48 | expect(binaryBooleanFalse).toEqual([131, 100, 0, 5, 102, 97, 108, 115, 101]) 49 | }) 50 | 51 | it('can encode a small integer', () => { 52 | const encodedSmallInteger = Bert.encode(4) 53 | const binarySmallInteger = Bert.binaryToList(encodedSmallInteger) 54 | 55 | expect(binarySmallInteger).toEqual([131, 97, 4]) 56 | }) 57 | 58 | it('can encode an integer', () => { 59 | const encodedInteger = Bert.encode(256) 60 | const binaryInteger = Bert.binaryToList(encodedInteger) 61 | 62 | expect(binaryInteger).toEqual([131, 98, 0, 0, 1, 0]) 63 | }) 64 | 65 | it('can encode a small big', () => { 66 | const encodedSmallBig = Bert.encode(13421772799) 67 | const binarySmallBig = Bert.binaryToList(encodedSmallBig) 68 | 69 | expect(binarySmallBig).toEqual([131, 110, 5, 0, 255, 255, 255, 31, 3]) 70 | }) 71 | 72 | it('can encode a large big', () => { 73 | const encodedLargeBig = Bert.encode(1.0715086071862673e+301) 74 | const binaryLargeBig = Bert.binaryToList(encodedLargeBig) 75 | 76 | //expect(binaryLargeBig).toEqual([]) 77 | }) 78 | 79 | it('can encode a float', () => { 80 | const encodedFloat = Bert.encode(45.2) 81 | const binaryFloat = Bert.binaryToList(encodedFloat) 82 | 83 | expect(binaryFloat).toEqual([ 84 | 131, 99, 52, 46, 53, 50, 48, 48, 48, 48, 48, 48, 48, 48, 85 | 48, 48, 48, 48, 48, 50, 56, 52, 50, 50, 101, 43, 48, 49, 86 | 0, 0, 0, 0, 0, 0 87 | ]) 88 | }) 89 | }) 90 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BERT 2 | 3 | Binary ERlang Term serialization library for Javascript. (An updated version of [this repo](https://github.com/rustyio/BERT-JS) ) 4 | 5 | ## Usage 6 | -------------------------------- 7 | 8 | ### Example Usage 9 | 10 | When needing to consume data in Javascript from an Erlang system, the Erlang 11 | system can simply send encoded binary data: 12 | 13 | *Elixir/Erlang:* 14 | ```elixir 15 | # This is Elixir code, but function calls will be very similar in Erlang 16 | 17 | personData = %{ 18 | name: "Bob", 19 | age: 32, 20 | eye_color: "Brown", 21 | personality_traits: [ 22 | "Funny", 23 | "Inquisitive" 24 | ] 25 | } 26 | 27 | # Convert to binary 28 | :erlang.term_to_binary(personData) 29 | 30 | # .... Code that sends binary data to javascript 31 | ``` 32 | 33 | *Javascript:* 34 | ```javascript 35 | // ... Code that receives binary data from erlang/elixir and stores it 36 | // to a variable, personData 37 | 38 | const Bert = require('bert-elixir') 39 | 40 | const decodedPerson = Bert.decode(personData) 41 | /* 42 | => { age: 32, 43 | eye_color: 'Brown', 44 | name: 'Bob', 45 | personality_traits: [ 'Funny', 'Inquisitive' ] 46 | } 47 | */ 48 | ``` 49 | 50 | Modifying this data and sending it back to Erlang/Elixir would be as simple as: 51 | 52 | *Javascript:* 53 | ```javascript 54 | // ... Assuming we have a decodedPerson object 55 | 56 | decodedPerson.age = 38 57 | decodedPerson.name = 'Robert' 58 | 59 | const reEncodedPerson = Bert.encode(decodedPerson) 60 | 61 | // ... Send the binary 62 | ``` 63 | 64 | *Elixir/Erlang:* 65 | ```elixir 66 | # ... After having received binary data and setting it to variable modifiedPersonData: 67 | 68 | decodedPerson = :erlang.binary_to_term(modifiedPersonData, [:safe]) 69 | 70 | # => %{ age: 38, eye_color: "Brown", name: "Robert", personality_traits: ["Funny", "Inquisitive"] } 71 | ``` 72 | 73 | safe option should be always used when decoding an untrusted input, make also sure to have already all required atoms in the atoms table. 74 | 75 | ### Encoding 76 | 77 | #### Maps (Elixir) 78 | 79 | Javascript objects map directly to Maps in Erlang 80 | 81 | ```javascript 82 | const Bert = require('bert-elixir') 83 | 84 | // To encode a javascript object to an elixir map: 85 | const mapToEncode = { a: 1, b: "hello!", c: [1, 2, 3] } 86 | const encodedMap = Bert.encode(mapToEncode) 87 | 88 | // BinaryToList shows individual bytes as a javascript array 89 | console.log(Bert.binaryToList(encodedMap)) 90 | // => [ 131, 116, 0, 0, 0, 3, 100, 0, 1, 97, 97, 1, 100, 0, 1, 98, 109, 0, 0, 0, 6, 104, 101, 108, 108, 111, 33, 100, 0, 1, 99, 108, 0, 0, 0, 3, 97, 1, 97, 2, 97, 3, 106 ] 91 | ``` 92 | 93 | #### Lists 94 | 95 | Javascript arrays map to Erlang Lists 96 | 97 | ```javascript 98 | const Bert = require('bert-elixir') 99 | 100 | const arrayToEncode = ['hello', 'world', 32, [{ key: "value" }]] 101 | const encodedArray = Bert.encode(arrayToEncode) 102 | 103 | console.log(Bert.binaryToList(encodedArray)) 104 | ``` 105 | 106 | Todo: 107 | - [ ] Write docs for rest of data types 108 | - [ ] Return `nil` as `null` instead of `'nil'` 109 | - [ ] Add support for NEW_FLOAT_EXT 110 | 111 | -------------------------------- 112 | ### Decoding 113 | 114 | Decoding is typically much simpler than encoding. Just pass the given Binary Erlang Term: 115 | 116 | ```javascript 117 | const Bert = require('bert-elixir') 118 | 119 | // We're showing the term as an array of bytes here for clarity. 120 | // You'll more likely have a string 121 | const erlangTerm = [131, 116, 0, 0, 0, 3, 100, 0, 1, 97, 97, 1, 100, 0, 1, 98, 97, 2, 100, 0, 1, 99, 116, 0, 0, 0, 1, 100, 0, 4, 119, 111, 97, 104, 109, 0, 0, 0, 8, 97, 32, 115, 116, 114, 105, 110, 103] 122 | .map(x => String.fromCharCode(x)).join('') // Convert the array to a string 123 | 124 | const decoded = Bert.decode(erlangTerm) 125 | 126 | console.log(decoded) 127 | // => { a: 1, b: 2, c: { woah: 'a string' } } 128 | ``` 129 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const BERT_START = String.fromCharCode(131) 2 | const SMALL_ATOM = String.fromCharCode(115) 3 | const ATOM = String.fromCharCode(100) 4 | const SMALL_INTEGER = String.fromCharCode(97) 5 | const MAP = String.fromCharCode(116) 6 | const INTEGER = String.fromCharCode(98) 7 | const SMALL_BIG = String.fromCharCode(110) 8 | const LARGE_BIG = String.fromCharCode(111) 9 | const FLOAT = String.fromCharCode(99) 10 | const CHARLIST = String.fromCharCode(107) 11 | const STRING = String.fromCharCode(109) 12 | const LIST = String.fromCharCode(108) 13 | const SMALL_TUPLE = String.fromCharCode(104) 14 | const LARGE_TUPLE = String.fromCharCode(105) 15 | const NIL = String.fromCharCode(106) 16 | const ZERO = String.fromCharCode(0) 17 | const ZERO_CHAR = String.fromCharCode(48) 18 | 19 | const atom = value => ({ type: 'Atom', value, toString: () => value }) 20 | 21 | const charlist = value => ({ type: 'Charlist', value, toString: () => value }) 22 | 23 | const encode = obj => BERT_START + encodeInner(obj) 24 | 25 | const encodeCharlist = ({ value }) => CHARLIST + intToBytes(value.length, 2) + value 26 | 27 | const encodeString = obj => STRING + intToBytes(obj.length, 4) + obj 28 | 29 | const encodeBoolean = obj => encodeInner(atom(obj ? 'true' : 'false')) 30 | 31 | const encodeAtom = ({ value }) => ATOM + intToBytes(value.length, 2) + value 32 | 33 | const decodeSmallInteger = s => ({ value: s.charCodeAt(0), rest: s.substring(1) }) 34 | 35 | const decodeInteger = (s, count) => ({ value: bytesToInt(s, count), rest: s.substring(count) }) 36 | 37 | const encodeAssociativeArray = obj => encodeArray(Object.keys(obj).map(k => tuple(atom(k), obj[k]))) 38 | 39 | const tuple = (...arr) => { 40 | let res = { type: 'Tuple', length: arr.length, value: arr, toString: () => `{${arr.join(", ")}}`, items: [] } 41 | 42 | for (var i = 0; i < arr.length; i++) { 43 | res.items.push(arr[i]) 44 | } 45 | 46 | return res 47 | } 48 | 49 | const encodeInner = obj => { 50 | if (obj === undefined) throw new Error('Cannot encode undefined values.') 51 | 52 | switch(typeof(obj)) { 53 | case 'string': return encodeString(obj) 54 | case 'boolean': return encodeBoolean(obj) 55 | case 'atom': return encodeAtom(obj) 56 | case 'number': return encodeNumber(obj) 57 | case 'float': return encodeFloat(obj) 58 | case 'object': return encodeObject(obj) 59 | case 'tuple': return encodeTuple(obj) 60 | case 'array': return encodeArray(obj) 61 | default: return 62 | } 63 | } 64 | 65 | const decode = s => { 66 | if (s[0] !== BERT_START) throw ('Not a valid BERT.') 67 | 68 | const obj = decodeInner(s.substring(1)) 69 | 70 | if (obj.rest !== '') throw ('Invalid BERT.') 71 | 72 | return obj.value 73 | } 74 | 75 | const encodeNumber = obj => { 76 | const isInteger = obj % 1 === 0 77 | 78 | // Float 79 | if (!isInteger) return encodeFloat(obj) 80 | 81 | // Small int 82 | if (isInteger && obj >= 0 && obj < 256) return SMALL_INTEGER + intToBytes(obj, 1) 83 | 84 | // 4 byte int 85 | if (isInteger && obj >= -134217728 && obj <= 134217727) return INTEGER + intToBytes(obj, 4) 86 | 87 | obj = bignumToBytes(obj) 88 | 89 | if (obj.length < 256) { 90 | return SMALL_BIG + intToBytes(obj.length - 1, 1) + obj 91 | } else { 92 | return LARGE_BIG + intToBytes(obj.length - 1, 4) + obj 93 | } 94 | } 95 | 96 | const encodeFloat = obj => { 97 | obj = obj.toExponential(20) 98 | 99 | const match = /([^e]+)(e[+-])(\d+)/.exec(obj) 100 | let exponentialPart = match[3].length == 1 ? "0" + match[3] : match[3] 101 | 102 | let num = match[1] + match[2] + exponentialPart 103 | 104 | return FLOAT + num + ZERO.repeat(32 - num.length) 105 | } 106 | 107 | const encodeObject = obj => { 108 | if (obj === null) return encodeInner(atom('null')) 109 | 110 | if (obj.type === 'Atom') return encodeAtom(obj) 111 | 112 | if (obj.type === 'Tuple') return encodeTuple(obj) 113 | 114 | if (obj.type === 'Charlist') return encodeCharlist(obj) 115 | 116 | // Check if it's an array... 117 | if (obj.constructor.toString().includes('Array')) return encodeArray(obj) 118 | 119 | // Treat the object as a map 120 | return encodeMap(obj) 121 | } 122 | 123 | const encodeTuple = obj => { 124 | let s 125 | 126 | if (obj.length < 256) { 127 | s = SMALL_TUPLE + intToBytes(obj.length, 1) 128 | } else { 129 | s = LARGE_TUPLE + intToBytes(obj.length, 4) 130 | } 131 | 132 | return s + obj.items.reduce((acc, curr) => acc + encodeInner(curr), '') 133 | } 134 | 135 | const encodeMap = obj => { 136 | const { map } = obj 137 | const arity = Object.keys(obj).length 138 | 139 | return MAP + intToBytes(arity, 4) + Object.keys(obj).map(key => encodeInner(atom(key)) + encodeInner(obj[key])).join('') 140 | } 141 | 142 | const encodeArray = obj => { 143 | if (obj.length === 0) return encodeInner(tuple(atom('bert'), atom('nil'))) 144 | 145 | return LIST + intToBytes(obj.length, 4) + obj.reduce((acc, curr) => acc + encodeInner(curr), '') + NIL 146 | } 147 | 148 | const decodeInner = s => { 149 | const type = s[0] 150 | 151 | s = s.substring(1) 152 | 153 | switch(type) { 154 | case SMALL_ATOM: return decodeAtom(s, 1) 155 | case ATOM: return decodeAtom(s, 2) 156 | case SMALL_INTEGER: return decodeSmallInteger(s) 157 | case INTEGER: return decodeInteger(s, 4) 158 | case SMALL_BIG: return decodeBig(s, 1) 159 | case LARGE_BIG: return decodeBig(s, 4) 160 | case MAP: return decodeMap(s) 161 | case FLOAT: return decodeFloat(s) 162 | case STRING: return decodeString(s) 163 | case CHARLIST: return decodeCharlist(s) 164 | case LIST: return decodeList(s) 165 | case SMALL_TUPLE: return decodeTuple(s, 1) 166 | case LARGE_TUPLE: return decodeLargeTuple(s, 4) 167 | case NIL: return decodeNil(s) 168 | default: throw(`Unexpected BERT type: ${s.charCodeAt(0)}`) 169 | } 170 | } 171 | 172 | const decodeAtom = (s, count) => { 173 | let size = bytesToInt(s, count) 174 | 175 | s = s.substring(count) 176 | let value = s.substring(0, size) 177 | 178 | return { value: atom(value), rest: s.substring(size) } 179 | } 180 | 181 | const decodeBig = (s, count) => { 182 | let size = bytesToInt(s, count) 183 | s = s.substring(count) 184 | 185 | return { value: bytesToBignum(s, size), rest: s.substring(size + 1) } 186 | } 187 | 188 | const decodeFloat = s => ({ value: parseFloat(s.substring(0, 31)), rest: s.substring(31) }) 189 | 190 | const decodeString = s => { 191 | let size = bytesToInt(s, 4) 192 | s = s.substring(4) 193 | 194 | return { value: s.substring(0, size), rest: s.substring(size) } 195 | } 196 | 197 | const decodeCharlist = s => { 198 | let size = bytesToInt(s, 2) 199 | s = s.substring(2) 200 | 201 | return { value: charlist(s.substring(0, size)), rest: s.substring(size) } 202 | } 203 | 204 | const decodeList = s => { 205 | let size = bytesToInt(s, 4) 206 | let arr = [] 207 | s = s.substring(4) 208 | 209 | for (let i = 0; i < size; i++) { 210 | let { value, rest } = decodeInner(s) 211 | arr.push(value) 212 | s = rest 213 | } 214 | 215 | if (s[0] !== NIL) throw('List does not end with NIL!') 216 | 217 | s = s.substring(1) 218 | 219 | return { value: arr, rest: s } 220 | } 221 | 222 | const decodeMap = s => { 223 | let size = bytesToInt(s, 4) 224 | let obj = {} 225 | s = s.substring(4) 226 | 227 | for (let i = 0; i < size; i++) { 228 | let { value: atom, rest: r } = decodeInner(s) 229 | s = r 230 | 231 | let { value, rest } = decodeInner(s) 232 | 233 | obj[atom] = value 234 | 235 | s = rest 236 | } 237 | 238 | return { value: obj, rest: s } 239 | } 240 | 241 | const decodeTuple = (s, count) => { 242 | let size = bytesToInt(s, count) 243 | let arr = [] 244 | s = s.substring(count) 245 | 246 | for (let i = 0; i < size; i++) { 247 | let { value, rest } = decodeInner(s) 248 | arr.push(value) 249 | s = rest 250 | } 251 | 252 | if (size >= 2) { 253 | let head = arr[0] 254 | 255 | if (typeof head === 'object' && head.type === 'Atom' && head.value === 'bert') { 256 | let kind = arr[1] 257 | 258 | if (typeof kind !== 'object' || kind.type !== 'Atom') throw('Invalid {bert, _} tuple!') 259 | 260 | switch(kind.value) { 261 | case 'true': return { value: true, rest: s } 262 | case 'false': return { value: false, rest: s } 263 | case 'nil': return { value: null, rest: s } 264 | case 'time': 265 | case 'dict': 266 | case 'regex': throw(`TODO: decode ${kind.value}`) 267 | default: throw(`Invalid {bert, ${kind.value.toString()}} tuple!`) 268 | } 269 | } 270 | } 271 | 272 | return { value: tuple(...arr), rest: s } 273 | } 274 | 275 | const decodeNil = s => ({ value: [], rest: s }) 276 | 277 | // Encode an integer to a big-endian byte-string of length Length. 278 | // Throw an exception if the integer is too large 279 | // to fit into the specified number of bytes. 280 | const intToBytes = (int, length) => { 281 | let isNegative = int < 0 282 | let s = '' 283 | 284 | if (isNegative) { 285 | int = -int - 1 286 | } 287 | 288 | let originalInt = int 289 | 290 | for (let i = 0; i < length; i++) { 291 | rem = isNegative ? 255 - (int % 256) : int % 256 292 | 293 | s = String.fromCharCode(rem) + s 294 | int = Math.floor(int / 256) 295 | } 296 | 297 | if (int > 0) throw(`Argument out of range: ${originalInt}`) 298 | 299 | return s 300 | } 301 | 302 | // Read a big-endian encoded integer from the first Length bytes 303 | // of the supplied string. 304 | const bytesToInt = (s, length) => { 305 | let isNegative = s.charCodeAt(0) > 128 306 | let num = 0 307 | 308 | for (let i = 0; i < length; i++) { 309 | let n = isNegative ? 255 - s.charCodeAt(i) : s.charCodeAt(i) 310 | 311 | num = num === 0 ? n : num * 256 + n 312 | } 313 | 314 | if (isNegative) num = -num - 1 315 | 316 | return num 317 | } 318 | 319 | // Encode an integer into an Erlang bignum, 320 | // which is a byte of 1 or 0 representing 321 | // whether the number is negative or positive, 322 | // followed by little-endian bytes. 323 | const bignumToBytes = int => { 324 | let s = '' 325 | let isNegative = int < 0 326 | 327 | if (isNegative) { 328 | int *= -1 329 | s += String.fromCharCode(1) 330 | } else { 331 | s += String.fromCharCode(0) 332 | } 333 | 334 | while (int !== 0) { 335 | let rem = int % 256 336 | s += String.fromCharCode(rem) 337 | int = Math.floor(int / 256) 338 | } 339 | 340 | return s 341 | } 342 | 343 | // Encode a list of bytes into an Erlang bignum. 344 | const bytesToBignum = (s, count) => { 345 | let num = 0 346 | s = s.substring(1) 347 | 348 | for (i = count - 1; i >= 0; i--) { 349 | let n = s.charCodeAt(i) 350 | 351 | num = num === 0 ? n : num * 256 + n 352 | } 353 | 354 | if (s.charCodeAt(0) === 1) return num * -1 355 | 356 | return num 357 | } 358 | 359 | // Convert an array of bytes into a string. 360 | const bytesToString = arr => arr.reduce((acc, curr) => acc + String.fromCharCode(curr), '') 361 | 362 | // Pretty Print a byte-string in Erlang binary form. 363 | const ppBytes = bin => bin.split('').map(c => c.charCodeAt(0)).join(', ') 364 | 365 | const ppTerm = obj => obj.toString() 366 | 367 | // Convert string of byes to an array 368 | const binaryToList = str => { 369 | let ret = [] 370 | 371 | for (let i = 0; i < str.length; i++) ret.push(str.charCodeAt(i)) 372 | 373 | return ret 374 | } 375 | 376 | module.exports = { 377 | atom, 378 | tuple, 379 | charlist, 380 | encode, 381 | encodeString, 382 | encodeBoolean, 383 | encodeAtom, 384 | decodeSmallInteger, 385 | decodeInteger, 386 | encodeAssociativeArray, 387 | decode, 388 | encodeNumber, 389 | encodeFloat, 390 | encodeObject, 391 | encodeTuple, 392 | encodeArray, 393 | decodeAtom, 394 | decodeBig, 395 | decodeFloat, 396 | decodeString, 397 | decodeList, 398 | decodeTuple, 399 | decodeNil, 400 | binaryToList, 401 | ppBytes, 402 | ppTerm 403 | } 404 | --------------------------------------------------------------------------------