├── .gitignore ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── rollup.lib.js ├── rollup.test.js └── src ├── buffer.ts ├── error.ts ├── index.ts ├── tags.ts ├── tests.ts └── types.ts /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | dist 4 | build 5 | .node-version 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Thomas Nitsche 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # msgpack-js 2 | `msgpack-js` is a [MessagePack](http://msgpack.org/) implementation for JavaScript and TypeScript. 3 | 4 | ## Encoding 5 | To encode objects into the binary MessagePack format, an `encode` function is provided: 6 | ```typescript 7 | function encode(v: T, typ?: Type): Uint8Array; 8 | ``` 9 | This function takes an object of an arbitrary type and converts it to its binary representation. If the type of the object is known in advance, an optional `typ` parameter could be passed to indicate the encoding algorithm. For an automatic type detection this parameter could be omitted. 10 | 11 | ## Decoding 12 | To decode binary MessagePack data into objects, a `decode` function is provided: 13 | ```typescript 14 | function decode(buf: BufferSource, typ?: Type): T; 15 | ``` 16 | This function takes a buffer containing the binary data and converts it to an object. The buffer could either be an `ArrayBuffer` or an `ArrayBufferView` and should contain valid MessagePack data. If a certain type is expected, an optional `typ` parameter could be passed. For automatic detection from the buffer's content this parameter could be omitted. 17 | 18 | ## Example 19 | ```typescript 20 | import {encode, decode} from "messagepack"; 21 | 22 | const bin1 = encode({foo: 7, bar: "seven"}); 23 | const obj = decode(bin1); 24 | console.log(obj); 25 | 26 | const bin2 = encode("foobar"); 27 | const str = decode(bin2); 28 | console.log(str); 29 | ``` 30 | 31 | ## Types 32 | Sometimes even a JavaScript developer wants to have a little bit more type safety. In this situation specific types could be passed to the `encode` and `decode` functions. If the object or the binary data has an incompatible type, an error will be thrown. 33 | 34 | The following types are supported: 35 | * `Nil` for null values, 36 | * `Bool` for boolean values, 37 | * `Int` for signed integer values, 38 | * `Uint` for unsigned integer values, 39 | * `Float` for floating-point values, 40 | * `Bytes` for binary data, 41 | * `Str` for string values, 42 | * `Arr` for arrays, 43 | * `Map` for objects, 44 | * `Raw` for already encoded values, 45 | * `Time` for date and time values, and 46 | * `Any` for automatically detecting the type and forward it to one of the types above. 47 | 48 | The `Arr` and `Map` types provide generic encoding and decoding for their elements, i.e. `Arr` and `Map` essentially equal `Any[]` and `Map` respectively. If more stringent element types are required, the `TypedArr` and `TypedMap` functions could be used instead: 49 | ```typescript 50 | import {TypedArr, TypedMap, Int, Str} from "messagepack"; 51 | 52 | const IntArray = TypedArr(Int); 53 | const IntStrMap = TypedMap(Int, Str); 54 | ``` 55 | 56 | ### Structs 57 | A struct is an object type with a predefined shape. To define such a type, the function 58 | ```typescript 59 | function Struct(fields: Fields): Type>; 60 | ``` 61 | can be used, which creates a type out of the predefined fields. All fields, that do not belong to the struct definition, will be omitted during the encoding and decoding process. To save some bytes and allow name changes, a struct is not simply encoded as a map with string keys. Instead, each field consists of a name, a type, and an ordinal, where the ordinal is used to uniquely identify a field. 62 | 63 | Here is an example, how define a struct: 64 | ```typescript 65 | import {Struct, Int, Str} from "messagepack"; 66 | 67 | const S = Struct({ 68 | // ordinal: [name, type], 69 | 1: ["foo", Int], 70 | 2: ["bar", Str], 71 | }); 72 | ``` 73 | 74 | If only the encoding or decoding capability is necessary, the functions 75 | ```typescript 76 | function structEncoder(fields: Fields): EncodeFunc; 77 | function structDecoder(fields: Fields): DecodeFunc; 78 | ``` 79 | can be used to create encoder and decoder functions respectively. 80 | 81 | ### Unions 82 | A union is a value, that can be one of several types. To define a union type, the function 83 | ```typescript 84 | function Union(branches: Branches): Type; 85 | ``` 86 | can be used, which creates a type out of the predefined branches. Each branch consists of an ordinal and a type. If a type should be encoded or decoded, that is not part of the union definition, an exception will be thrown. Also, `branches` needs to define a function to determine the ordinal number from a value. 87 | 88 | Here is an example, how to define a union: 89 | ```typescript 90 | import {Union, Int, Str} from "messagepack"; 91 | 92 | const U = Union({ 93 | // ordinal: type, 94 | 1: Int, 95 | 2: Str, 96 | ordinalOf(v) { 97 | switch(typeof v) { 98 | case "string": return 1; 99 | case "number": return 2; 100 | default: new TypeError("invalid union type"); 101 | } 102 | }, 103 | }); 104 | ``` 105 | 106 | If only the encoding or decoding capability is necessary, the functions 107 | ```typescript 108 | function unionEncoder(branches: Branches): EncodeFunc; 109 | function unionDecoder(branches: Branches): DecodeFunc; 110 | ``` 111 | can be used to create encoder and decoder functions respectively. 112 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "messagepack", 3 | "version": "1.1.12", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "messagepack", 9 | "version": "1.1.12", 10 | "license": "MIT", 11 | "devDependencies": { 12 | "@types/node": "^17.0.1", 13 | "rollup": "^2.61.1", 14 | "rollup-plugin-tsc": "^1.1.16", 15 | "testsome": "^1.0.3" 16 | } 17 | }, 18 | "node_modules/@types/node": { 19 | "version": "17.0.1", 20 | "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.1.tgz", 21 | "integrity": "sha512-NXKvBVUzIbs6ylBwmOwHFkZS2EXCcjnqr8ZCRNaXBkHAf+3mn/rPcJxwrzuc6movh8fxQAsUUfYklJ/EG+hZqQ==", 22 | "dev": true 23 | }, 24 | "node_modules/fsevents": { 25 | "version": "2.3.2", 26 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", 27 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", 28 | "dev": true, 29 | "hasInstallScript": true, 30 | "optional": true, 31 | "os": [ 32 | "darwin" 33 | ], 34 | "engines": { 35 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 36 | } 37 | }, 38 | "node_modules/rollup": { 39 | "version": "2.61.1", 40 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.61.1.tgz", 41 | "integrity": "sha512-BbTXlEvB8d+XFbK/7E5doIcRtxWPRiqr0eb5vQ0+2paMM04Ye4PZY5nHOQef2ix24l/L0SpLd5hwcH15QHPdvA==", 42 | "dev": true, 43 | "bin": { 44 | "rollup": "dist/bin/rollup" 45 | }, 46 | "engines": { 47 | "node": ">=10.0.0" 48 | }, 49 | "optionalDependencies": { 50 | "fsevents": "~2.3.2" 51 | } 52 | }, 53 | "node_modules/rollup-plugin-tsc": { 54 | "version": "1.1.16", 55 | "resolved": "https://registry.npmjs.org/rollup-plugin-tsc/-/rollup-plugin-tsc-1.1.16.tgz", 56 | "integrity": "sha512-6WhDzcm5lJOb0WZkgwoBkUJ7qRrd8vt2uHnXNPbup88Ha8pQ0E75dTiwTgNPBemnEOoPCsUvzgEjvetnVrCq+Q==", 57 | "dev": true, 58 | "dependencies": { 59 | "tslib": "^2.0.1", 60 | "typescript": "^3.9.7" 61 | } 62 | }, 63 | "node_modules/testsome": { 64 | "version": "1.0.3", 65 | "resolved": "https://registry.npmjs.org/testsome/-/testsome-1.0.3.tgz", 66 | "integrity": "sha512-K2GOR8pKglJrEpdhPI7b2hKWp+x0Sb3wCS/U2isItl3DGu1vPXMEY93DjmNTHDf3IEDMmXEntcmff4gSn4tsdw==", 67 | "dev": true, 68 | "bin": { 69 | "testsome": "dist/testsome.js" 70 | } 71 | }, 72 | "node_modules/tslib": { 73 | "version": "2.0.1", 74 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz", 75 | "integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ==", 76 | "dev": true 77 | }, 78 | "node_modules/typescript": { 79 | "version": "3.9.7", 80 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz", 81 | "integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==", 82 | "dev": true, 83 | "bin": { 84 | "tsc": "bin/tsc", 85 | "tsserver": "bin/tsserver" 86 | }, 87 | "engines": { 88 | "node": ">=4.2.0" 89 | } 90 | } 91 | }, 92 | "dependencies": { 93 | "@types/node": { 94 | "version": "17.0.1", 95 | "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.1.tgz", 96 | "integrity": "sha512-NXKvBVUzIbs6ylBwmOwHFkZS2EXCcjnqr8ZCRNaXBkHAf+3mn/rPcJxwrzuc6movh8fxQAsUUfYklJ/EG+hZqQ==", 97 | "dev": true 98 | }, 99 | "fsevents": { 100 | "version": "2.3.2", 101 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", 102 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", 103 | "dev": true, 104 | "optional": true 105 | }, 106 | "rollup": { 107 | "version": "2.61.1", 108 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.61.1.tgz", 109 | "integrity": "sha512-BbTXlEvB8d+XFbK/7E5doIcRtxWPRiqr0eb5vQ0+2paMM04Ye4PZY5nHOQef2ix24l/L0SpLd5hwcH15QHPdvA==", 110 | "dev": true, 111 | "requires": { 112 | "fsevents": "~2.3.2" 113 | } 114 | }, 115 | "rollup-plugin-tsc": { 116 | "version": "1.1.16", 117 | "resolved": "https://registry.npmjs.org/rollup-plugin-tsc/-/rollup-plugin-tsc-1.1.16.tgz", 118 | "integrity": "sha512-6WhDzcm5lJOb0WZkgwoBkUJ7qRrd8vt2uHnXNPbup88Ha8pQ0E75dTiwTgNPBemnEOoPCsUvzgEjvetnVrCq+Q==", 119 | "dev": true, 120 | "requires": { 121 | "tslib": "^2.0.1", 122 | "typescript": "^3.9.7" 123 | } 124 | }, 125 | "testsome": { 126 | "version": "1.0.3", 127 | "resolved": "https://registry.npmjs.org/testsome/-/testsome-1.0.3.tgz", 128 | "integrity": "sha512-K2GOR8pKglJrEpdhPI7b2hKWp+x0Sb3wCS/U2isItl3DGu1vPXMEY93DjmNTHDf3IEDMmXEntcmff4gSn4tsdw==", 129 | "dev": true 130 | }, 131 | "tslib": { 132 | "version": "2.0.1", 133 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz", 134 | "integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ==", 135 | "dev": true 136 | }, 137 | "typescript": { 138 | "version": "3.9.7", 139 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz", 140 | "integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==", 141 | "dev": true 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "messagepack", 3 | "version": "1.1.12", 4 | "description": "A MessagePack implementation for JavaScript.", 5 | "main": "dist/messagepack.cjs.js", 6 | "module": "dist/messagepack.es.js", 7 | "jsnext:main": "dist/messagepack.es.js", 8 | "types": "dist/types/index.d.ts", 9 | "files": [ 10 | "dist", 11 | "README.md" 12 | ], 13 | "keywords": [ 14 | "msgpack", 15 | "messagepack", 16 | "serialization" 17 | ], 18 | "scripts": { 19 | "prebuild": "rm -rf dist/*", 20 | "build": "rollup -c rollup.lib.js", 21 | "pretest": "rm -rf build/*", 22 | "test": "rollup -c rollup.test.js && testsome build/tests.js", 23 | "prepublishOnly": "npm run build && npm run test" 24 | }, 25 | "author": "mprot", 26 | "license": "MIT", 27 | "homepage": "https://github.com/mprot/msgpack-js", 28 | "repository": { 29 | "type": "git", 30 | "url": "git+https://github.com/mprot/msgpack-js.git" 31 | }, 32 | "devDependencies": { 33 | "@types/node": "^17.0.1", 34 | "rollup": "^2.61.1", 35 | "rollup-plugin-tsc": "^1.1.16", 36 | "testsome": "^1.0.3" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /rollup.lib.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const pkg = require("./package.json"); 3 | 4 | 5 | export default { 6 | input: "src/index.ts", 7 | output: [ 8 | {file: pkg["module"], format: "es", sourcemap: true}, 9 | {file: pkg["main"], format: "cjs", sourcemap: true}, 10 | ], 11 | plugins: [ 12 | require("rollup-plugin-tsc")({ 13 | compilerOptions: { 14 | noUnusedLocals: true, 15 | declaration: true, 16 | declarationDir: path.dirname(pkg["types"]), 17 | }, 18 | }), 19 | ], 20 | }; 21 | -------------------------------------------------------------------------------- /rollup.test.js: -------------------------------------------------------------------------------- 1 | const pkg = require("./package.json"); 2 | 3 | 4 | export default { 5 | input: "src/tests.ts", 6 | output: { 7 | file: "build/tests.js", 8 | format: "cjs", 9 | }, 10 | external: [ 11 | "testsome", 12 | ], 13 | plugins: [ 14 | require("rollup-plugin-tsc")({ 15 | compilerOptions: { 16 | noUnusedLocals: true, 17 | }, 18 | }), 19 | ], 20 | }; 21 | -------------------------------------------------------------------------------- /src/buffer.ts: -------------------------------------------------------------------------------- 1 | import {typeError} from "./error"; 2 | import { 3 | Tag, 4 | isPosFixintTag, isNegFixintTag, 5 | fixarrayTag, isFixarrayTag, readFixarray, 6 | fixmapTag, isFixmapTag, readFixmap, 7 | isFixstrTag, readFixstr, 8 | } from "./tags"; 9 | 10 | 11 | export interface WriteBuffer { 12 | put(v: BufferSource): void; 13 | 14 | putI8(v: number): void; 15 | putI16(v: number): void; 16 | putI32(v: number): void; 17 | putI64(v: number): void; 18 | 19 | putUi8(v: number): void; 20 | putUi16(v: number): void; 21 | putUi32(v: number): void; 22 | putUi64(v: number): void; 23 | 24 | putF(v: number): void; 25 | 26 | ui8array(): Uint8Array; 27 | } 28 | 29 | export interface ReadBuffer { 30 | peek(): number; // returns next byte 31 | get(len: number): ArrayBuffer; 32 | 33 | getI8(): number; 34 | getI16(): number; 35 | getI32(): number; 36 | getI64(): number; 37 | 38 | getUi8(): number; 39 | getUi16(): number; 40 | getUi32(): number; 41 | getUi64(): number; 42 | 43 | getF32(): number; 44 | getF64(): number; 45 | } 46 | 47 | 48 | 49 | export function createWriteBuffer(): WriteBuffer { 50 | let view = new DataView(new ArrayBuffer(64)); 51 | let n = 0; 52 | 53 | function need(x: number): void { 54 | if(n+x > view.byteLength) { 55 | const arr = new Uint8Array(Math.max(n+x, view.byteLength+64)); 56 | arr.set(new Uint8Array(view.buffer.slice(0, n))); 57 | view = new DataView(arr.buffer); 58 | } 59 | } 60 | 61 | return { 62 | put(v: ArrayBuffer): void { 63 | need(v.byteLength); 64 | (new Uint8Array(view.buffer)).set(new Uint8Array(v), n); 65 | n += v.byteLength; 66 | }, 67 | 68 | putI8(v: number): void { 69 | need(1); 70 | view.setInt8(n, v); 71 | ++n; 72 | }, 73 | 74 | putI16(v: number): void { 75 | need(2); 76 | view.setInt16(n, v); 77 | n += 2; 78 | }, 79 | 80 | putI32(v: number): void { 81 | need(4); 82 | view.setInt32(n, v); 83 | n += 4; 84 | }, 85 | 86 | putI64(v: number): void { 87 | need(8); 88 | const neg = v < 0; 89 | if(neg) { 90 | v = -v; 91 | } 92 | let hi = (v/0x100000000)|0; 93 | let lo = (v%0x100000000)|0; 94 | if(neg) { 95 | // 2s complement 96 | lo = (~lo+1)|0; 97 | hi = lo === 0 ? (~hi+1)|0 : ~hi; 98 | } 99 | 100 | view.setUint32(n, hi); 101 | view.setUint32(n+4, lo); 102 | n += 8; 103 | }, 104 | 105 | putUi8(v: number): void { 106 | need(1); 107 | view.setUint8(n, v); 108 | ++n; 109 | }, 110 | 111 | putUi16(v: number): void { 112 | need(2); 113 | view.setUint16(n, v); 114 | n += 2; 115 | }, 116 | 117 | putUi32(v: number): void { 118 | need(4); 119 | view.setUint32(n, v); 120 | n += 4; 121 | }, 122 | 123 | putUi64(v: number): void { 124 | need(8); 125 | view.setUint32(n, (v/0x100000000)|0); 126 | view.setUint32(n+4, v%0x100000000); 127 | n += 8; 128 | }, 129 | 130 | putF(v: number): void { 131 | need(8); 132 | view.setFloat64(n, v); 133 | n += 8; 134 | }, 135 | 136 | ui8array(): Uint8Array { 137 | return new Uint8Array(view.buffer.slice(0, n)); 138 | }, 139 | }; 140 | } 141 | 142 | 143 | export function createReadBuffer(buf: BufferSource): ReadBuffer { 144 | let view = ArrayBuffer.isView(buf) ? new DataView(buf.buffer, buf.byteOffset, buf.byteLength) : new DataView(buf); 145 | let n = 0; 146 | 147 | return { 148 | peek(): number { 149 | return view.getUint8(n); 150 | }, 151 | 152 | get(len: number): ArrayBuffer { 153 | n += len; 154 | const off = view.byteOffset; 155 | return view.buffer.slice(off+n-len, off+n); 156 | }, 157 | 158 | getI8(): number { 159 | return view.getInt8(n++); 160 | }, 161 | 162 | getI16(): number { 163 | n += 2; 164 | return view.getInt16(n-2); 165 | }, 166 | 167 | getI32(): number { 168 | n += 4; 169 | return view.getInt32(n-4); 170 | }, 171 | 172 | getI64(): number { 173 | n += 8; 174 | const hi = view.getInt32(n-8); 175 | const lo = view.getUint32(n-4); 176 | return hi*0x100000000 + lo; 177 | }, 178 | 179 | getUi8(): number { 180 | return view.getUint8(n++); 181 | }, 182 | 183 | getUi16(): number { 184 | n += 2; 185 | return view.getUint16(n-2); 186 | }, 187 | 188 | getUi32(): number { 189 | n += 4; 190 | return view.getUint32(n-4); 191 | }, 192 | 193 | getUi64(): number { 194 | n += 8; 195 | const hi = view.getUint32(n-8); 196 | const lo = view.getUint32(n-4); 197 | return hi*0x100000000 + lo; 198 | }, 199 | 200 | getF32(): number { 201 | n += 4; 202 | return view.getFloat32(n-4); 203 | }, 204 | 205 | getF64(): number { 206 | n += 8; 207 | return view.getFloat64(n-8); 208 | }, 209 | }; 210 | } 211 | 212 | 213 | export function putBlob(buf: WriteBuffer, blob: ArrayBuffer, baseTag: Tag): void { 214 | const n = blob.byteLength; 215 | if(n <= 255) { 216 | buf.putUi8(baseTag); 217 | buf.putUi8(n); 218 | } else if(n <= 65535) { 219 | buf.putUi8(baseTag + 1); 220 | buf.putUi16(n); 221 | } else if(n <= 4294967295) { 222 | buf.putUi8(baseTag + 2); 223 | buf.putUi32(n); 224 | } else { 225 | throw new RangeError("length limit exceeded"); 226 | } 227 | buf.put(blob); 228 | } 229 | 230 | 231 | export function getBlob(buf: ReadBuffer): ArrayBuffer { 232 | const tag = buf.getUi8(); 233 | let n: number; 234 | switch(tag) { 235 | case Tag.Nil: 236 | n = 0; 237 | break; 238 | case Tag.Bin8: 239 | case Tag.Str8: 240 | n = buf.getUi8(); 241 | break; 242 | case Tag.Bin16: 243 | case Tag.Str16: 244 | n = buf.getUi16(); 245 | break; 246 | case Tag.Bin32: 247 | case Tag.Str32: 248 | n = buf.getUi32(); 249 | break; 250 | default: 251 | if(!isFixstrTag(tag)) { 252 | typeError(tag, "bytes or string"); 253 | } 254 | n = readFixstr(tag); 255 | } 256 | return buf.get(n); 257 | } 258 | 259 | 260 | export function putArrHeader(buf: WriteBuffer, n: number): void { 261 | if(n < 16) { 262 | buf.putUi8(fixarrayTag(n)); 263 | } else { 264 | putCollectionHeader(buf, Tag.Array16, n); 265 | } 266 | } 267 | 268 | 269 | export function getArrHeader(buf: ReadBuffer, expect?: number): number { 270 | const tag = buf.getUi8(); 271 | const n = isFixarrayTag(tag) 272 | ? readFixarray(tag) 273 | : getCollectionHeader(buf, tag, Tag.Array16, "array"); 274 | if(expect != null && n !== expect) { 275 | throw new Error(`invalid array header size ${n}`); 276 | } 277 | return n; 278 | } 279 | 280 | 281 | export function putMapHeader(buf: WriteBuffer, n: number): void { 282 | if(n < 16) { 283 | buf.putUi8(fixmapTag(n)); 284 | } else { 285 | putCollectionHeader(buf, Tag.Map16, n); 286 | } 287 | } 288 | 289 | 290 | export function getMapHeader(buf: ReadBuffer, expect?: number): number { 291 | const tag = buf.getUi8(); 292 | const n = isFixmapTag(tag) 293 | ? readFixmap(tag) 294 | : getCollectionHeader(buf, tag, Tag.Map16, "map"); 295 | if(expect != null && n !== expect) { 296 | throw new Error(`invalid map header size ${n}`); 297 | } 298 | return n; 299 | } 300 | 301 | 302 | function putCollectionHeader(buf: WriteBuffer, baseTag: Tag, n: number): void { 303 | if(n <= 65535) { 304 | buf.putUi8(baseTag); 305 | buf.putUi16(n); 306 | } else if(n <= 4294967295) { 307 | buf.putUi8(baseTag + 1); 308 | buf.putUi32(n); 309 | } else { 310 | throw new RangeError("length limit exceeded"); 311 | } 312 | } 313 | 314 | function getCollectionHeader(buf: ReadBuffer, tag: Tag, baseTag: Tag, typename: string): number { 315 | switch(tag) { 316 | case Tag.Nil: 317 | return 0; 318 | case baseTag: // 16 bit 319 | return buf.getUi16(); 320 | case baseTag + 1: // 32 bit 321 | return buf.getUi32(); 322 | default: 323 | typeError(tag, typename); 324 | } 325 | } 326 | 327 | 328 | export function getRaw(buf: ReadBuffer, res: WriteBuffer): void { 329 | let n; 330 | const tag = buf.getUi8(); 331 | res.putUi8(tag); 332 | 333 | switch(tag) { 334 | case Tag.Nil: 335 | case Tag.False: 336 | case Tag.True: 337 | break; 338 | 339 | case Tag.Int8: 340 | case Tag.Uint8: 341 | res.putUi8(buf.getUi8()); 342 | break; 343 | 344 | case Tag.Int16: 345 | case Tag.Uint16: 346 | res.putUi16(buf.getUi16()); 347 | break; 348 | 349 | case Tag.Int32: 350 | case Tag.Uint32: 351 | case Tag.Float32: 352 | res.putUi32(buf.getUi32()); 353 | break; 354 | 355 | case Tag.Int64: 356 | case Tag.Uint64: 357 | case Tag.Float64: 358 | res.putUi64(buf.getUi64()); 359 | break; 360 | 361 | case Tag.Str8: 362 | case Tag.Bin8: 363 | res.putUi8(n = buf.getUi8()); 364 | res.put(buf.get(n)); 365 | break; 366 | 367 | case Tag.Str16: 368 | case Tag.Bin16: 369 | res.putUi16(n = buf.getUi16()); 370 | res.put(buf.get(n)); 371 | break; 372 | 373 | case Tag.Str32: 374 | case Tag.Bin32: 375 | res.putUi32(n = buf.getUi32()); 376 | res.put(buf.get(n)); 377 | break; 378 | 379 | case Tag.Array16: 380 | res.putUi16(n = buf.getUi16()); 381 | for(let i = 0; i < n; ++i) { 382 | getRaw(buf, res); 383 | } 384 | break; 385 | 386 | case Tag.Array32: 387 | res.putUi32(n = buf.getUi32()); 388 | for(let i = 0; i < n; ++i) { 389 | getRaw(buf, res); 390 | } 391 | break; 392 | 393 | case Tag.Map16: 394 | res.putUi16(n = buf.getUi16()); 395 | for(let i = 0; i < 2*n; ++i) { 396 | getRaw(buf, res); 397 | } 398 | break; 399 | 400 | case Tag.Map32: 401 | res.putUi32(n = buf.getUi32()); 402 | for(let i = 0; i < 2*n; ++i) { 403 | getRaw(buf, res); 404 | } 405 | break; 406 | 407 | case Tag.FixExt1: 408 | res.put(buf.get(2)) 409 | break; 410 | 411 | case Tag.FixExt2: 412 | res.put(buf.get(3)) 413 | break; 414 | 415 | case Tag.FixExt4: 416 | res.put(buf.get(5)) 417 | break; 418 | 419 | case Tag.FixExt8: 420 | res.put(buf.get(9)) 421 | break; 422 | 423 | case Tag.FixExt16: 424 | res.put(buf.get(17)) 425 | break; 426 | 427 | case Tag.Ext8: 428 | res.putUi8(n = buf.getUi8()); 429 | res.put(buf.get(1 + n)); 430 | break; 431 | 432 | case Tag.Ext16: 433 | res.putUi16(n = buf.getUi16()); 434 | res.put(buf.get(1 + n)); 435 | break; 436 | 437 | case Tag.Ext32: 438 | res.putUi32(n = buf.getUi32()); 439 | res.put(buf.get(1 + n)); 440 | break; 441 | 442 | default: 443 | if(isPosFixintTag(tag) || isNegFixintTag(tag)) { 444 | // do nothing 445 | } else if(isFixstrTag(tag)) { 446 | res.put(buf.get(readFixstr(tag))); 447 | } else if(isFixarrayTag(tag)) { 448 | n = readFixarray(tag); 449 | for(let i = 0; i < n; ++i) { 450 | getRaw(buf, res); 451 | } 452 | } else if(isFixmapTag(tag)) { 453 | n = 2 * readFixmap(tag); 454 | for(let i = 0; i < n; ++i) { 455 | getRaw(buf, res); 456 | } 457 | } else { 458 | throw new TypeError(`unknown tag 0x${tag.toString(16)}`); 459 | } 460 | } 461 | } 462 | -------------------------------------------------------------------------------- /src/error.ts: -------------------------------------------------------------------------------- 1 | import {Tag} from "./tags"; 2 | 3 | 4 | 5 | export function typeError(tag: Tag, expected: string): never { 6 | throw new TypeError(`unexpected tag 0x${tag.toString(16)} (${expected} expected)`); 7 | } 8 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Type, Collection, Obj, 3 | Nil, Bool, Int, Uint, Float, Bytes, Str, TypedArr, TypedMap, Time, Any, Arr, Map, Struct, Union, 4 | structEncoder, structDecoder, unionEncoder, unionDecoder, 5 | } from "./types"; 6 | import { 7 | WriteBuffer, ReadBuffer, 8 | createWriteBuffer, createReadBuffer, 9 | } from "./buffer"; 10 | 11 | 12 | export { 13 | WriteBuffer, ReadBuffer, 14 | Type, Collection, Obj, 15 | Nil, Bool, Int, Uint, Float, Bytes, Str, TypedArr, TypedMap, Time, Any, Arr, Map, Struct, Union, 16 | structEncoder, structDecoder, unionEncoder, unionDecoder, 17 | encode, decode, 18 | }; 19 | 20 | 21 | 22 | 23 | 24 | 25 | function encode(v: T, typ?: Type): Uint8Array { 26 | const buf = createWriteBuffer(); 27 | (typ || Any).enc(buf, v); 28 | return buf.ui8array(); 29 | } 30 | 31 | 32 | 33 | function decode(buf: BufferSource, typ?: Type): T { 34 | return (typ || Any).dec(createReadBuffer(buf)); 35 | } 36 | -------------------------------------------------------------------------------- /src/tags.ts: -------------------------------------------------------------------------------- 1 | 2 | export const enum Tag { 3 | Nil = 0xc0, // 11000000 4 | 5 | // bool 6 | False = 0xc2, // 11000010 7 | True = 0xc3, // 11000011 8 | 9 | // int 10 | Int8 = 0xd0, // 11010000 11 | Int16 = 0xd1, // 11010001 12 | Int32 = 0xd2, // 11010010 13 | Int64 = 0xd3, // 11010011 14 | 15 | // uint 16 | Uint8 = 0xcc, // 11001100 17 | Uint16 = 0xcd, // 11001101 18 | Uint32 = 0xce, // 11001110 19 | Uint64 = 0xcf, // 11001111 20 | 21 | // float 22 | Float32 = 0xca, // 11001010 23 | Float64 = 0xcb, // 11001011 24 | 25 | // string 26 | Str8 = 0xd9, // 11011001 27 | Str16 = 0xda, // 11011010 28 | Str32 = 0xdb, // 11011011 29 | 30 | // binary 31 | Bin8 = 0xc4, // 11000100 32 | Bin16 = 0xc5, // 11000101 33 | Bin32 = 0xc6, // 11000110 34 | 35 | // array 36 | Array16 = 0xdc, // 11011100 37 | Array32 = 0xdd, // 11011101 38 | 39 | // map 40 | Map16 = 0xde, // 11011110 41 | Map32 = 0xdf, // 11011111 42 | 43 | // ext 44 | Ext8 = 0xc7, // 11000111 45 | Ext16 = 0xc8, // 11001000 46 | Ext32 = 0xc9, // 11001001 47 | FixExt1 = 0xd4, // 11010100 48 | FixExt2 = 0xd5, // 11010101 49 | FixExt4 = 0xd6, // 11010110 50 | FixExt8 = 0xd7, // 11010111 51 | FixExt16 = 0xd8, // 11011000 52 | } 53 | 54 | 55 | 56 | // positive fixint: 0xxx xxxx 57 | export function posFixintTag(i: number): Tag { 58 | return i & 0x7f; 59 | } 60 | 61 | export function isPosFixintTag(tag: Tag): boolean { 62 | return (tag & 0x80) === 0; 63 | } 64 | 65 | export function readPosFixint(tag: Tag): number { 66 | return tag & 0x7f 67 | } 68 | 69 | 70 | // negative fixint: 111x xxxx 71 | export function negFixintTag(i: number): Tag { 72 | return 0xe0 | (i & 0x1f); 73 | } 74 | 75 | export function isNegFixintTag(tag: Tag): boolean { 76 | return (tag & 0xe0) == 0xe0; 77 | } 78 | 79 | export function readNegFixint(tag: Tag): number { 80 | return tag - 0x100; 81 | } 82 | 83 | 84 | // fixstr: 101x xxxx 85 | export function fixstrTag(length: number): Tag { 86 | return 0xa0 | (length & 0x1f); 87 | } 88 | 89 | export function isFixstrTag(tag: Tag): boolean { 90 | return (tag & 0xe0) == 0xa0; 91 | } 92 | 93 | export function readFixstr(tag: Tag): number { 94 | return tag & 0x1f; 95 | } 96 | 97 | 98 | // fixarray: 1001 xxxx 99 | export function fixarrayTag(length: number): Tag { 100 | return 0x90 | (length & 0x0f); 101 | } 102 | 103 | export function isFixarrayTag(tag: Tag): boolean { 104 | return (tag & 0xf0) == 0x90; 105 | } 106 | 107 | export function readFixarray(tag: Tag): number { 108 | return tag & 0x0f; 109 | } 110 | 111 | 112 | // fixmap: 1000 xxxx 113 | export function fixmapTag(length: number): Tag { 114 | return 0x80 | (length & 0x0f); 115 | } 116 | 117 | export function isFixmapTag(tag: Tag): boolean { 118 | return (tag & 0xf0) == 0x80; 119 | } 120 | 121 | export function readFixmap(tag: Tag): number { 122 | return tag & 0x0f; 123 | } 124 | -------------------------------------------------------------------------------- /src/tests.ts: -------------------------------------------------------------------------------- 1 | import {test} from "testsome"; 2 | import {Tag, posFixintTag, negFixintTag, fixstrTag, fixarrayTag, fixmapTag} from "./tags"; 3 | import {Nil, Bool, Int, Uint, Float, Bytes, Str, Arr, Map, Raw, Time, Any, Struct, Union} from "./types"; 4 | import {encode, decode} from "./index"; 5 | 6 | 7 | 8 | test("encode", t => { 9 | const tests = [ 10 | // nil 11 | { 12 | val: undefined, 13 | typ: Nil, 14 | bin: [Tag.Nil], 15 | }, 16 | { 17 | val: null, 18 | typ: Nil, 19 | bin: [Tag.Nil], 20 | }, 21 | { 22 | val: 7, 23 | typ: Nil, 24 | bin: [Tag.Nil], 25 | }, 26 | { 27 | val: "foo", 28 | typ: Nil, 29 | bin: [Tag.Nil], 30 | }, 31 | { 32 | val: [7, "foo"], 33 | typ: Nil, 34 | bin: [Tag.Nil], 35 | }, 36 | // bool 37 | { 38 | val: true, 39 | typ: Bool, 40 | bin: [Tag.True], 41 | }, 42 | { 43 | val: false, 44 | typ: Bool, 45 | bin: [Tag.False], 46 | }, 47 | // int 48 | { 49 | val: 7, 50 | typ: Int, 51 | bin: [posFixintTag(7)], 52 | }, 53 | { 54 | val: -7, 55 | typ: Int, 56 | bin: [negFixintTag(-7)], 57 | }, 58 | { 59 | val: -128, 60 | typ: Int, 61 | bin: [Tag.Int8, 0x80], 62 | }, 63 | { 64 | val: -32768, 65 | typ: Int, 66 | bin: [Tag.Int16, 0x80, 0x0], 67 | }, 68 | { 69 | val: 32767, 70 | typ: Int, 71 | bin: [Tag.Int16, 0x7f, 0xff], 72 | }, 73 | { 74 | val: 2147483647, 75 | typ: Int, 76 | bin: [Tag.Int32, 0x7f, 0xff, 0xff, 0xff], 77 | }, 78 | { 79 | val: -2147483648, 80 | typ: Int, 81 | bin: [Tag.Int32, 0x80, 0x0, 0x0, 0x0], 82 | }, 83 | { 84 | val: 2147483648, 85 | typ: Int, 86 | bin: [Tag.Int64, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00], 87 | }, 88 | { 89 | val: 4294967296, 90 | typ: Int, 91 | bin: [Tag.Int64, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00], 92 | }, 93 | { 94 | val: -2147483649, 95 | typ: Int, 96 | bin: [Tag.Int64, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xff, 0xff, 0xff], 97 | }, 98 | { 99 | val: 549764202560, 100 | typ: Int, 101 | bin: [Tag.Int64, 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x40], 102 | }, 103 | { 104 | val: -549764202560, 105 | typ: Int, 106 | bin: [Tag.Int64, 0xff, 0xff, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0xc0], 107 | }, 108 | // uint 109 | { 110 | val: 7, 111 | typ: Uint, 112 | bin: [posFixintTag(7)], 113 | }, 114 | { 115 | val: 255, 116 | typ: Uint, 117 | bin: [Tag.Uint8, 0xff], 118 | }, 119 | { 120 | val: 65535, 121 | typ: Uint, 122 | bin: [Tag.Uint16, 0xff, 0xff], 123 | }, 124 | { 125 | val: 4294967295, 126 | typ: Uint, 127 | bin: [Tag.Uint32, 0xff, 0xff, 0xff, 0xff], 128 | }, 129 | { 130 | val: 4294967296, 131 | typ: Uint, 132 | bin: [Tag.Uint64, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00], 133 | }, 134 | { 135 | val: 549764202560, 136 | typ: Uint, 137 | bin: [Tag.Uint64, 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x40], 138 | }, 139 | // float 140 | { 141 | val: 3.141592, 142 | typ: Float, 143 | bin: [Tag.Float64, 0x40, 0x09, 0x21, 0xfa, 0xfc, 0x8b, 0x00, 0x7a], 144 | }, 145 | // bytes 146 | { 147 | val: (new Uint8Array(repeat(0x30, 1))).buffer, 148 | typ: Bytes, 149 | bin: [Tag.Bin8, 0x1].concat(repeat(0x30, 1)), 150 | }, 151 | { 152 | val: (new Uint8Array(repeat(0x30, 256))).buffer, 153 | typ: Bytes, 154 | bin: [Tag.Bin16, 0x01, 0x00].concat(repeat(0x30, 256)), 155 | }, 156 | { 157 | val: (new Uint8Array(repeat(0x30, 65536))).buffer, 158 | typ: Bytes, 159 | bin: [Tag.Bin32, 0x00, 0x01, 0x00, 0x00].concat(repeat(0x30, 65536)), 160 | }, 161 | // string 162 | { 163 | val: "0", 164 | typ: Str, 165 | bin: [fixstrTag(1), 0x30], 166 | }, 167 | { 168 | val: "ä", 169 | typ: Str, 170 | bin: [fixstrTag(2), 0xc3, 0xa4], 171 | }, 172 | { 173 | val: "∞", 174 | typ: Str, 175 | bin: [fixstrTag(3), 0xe2, 0x88, 0x9e], 176 | }, 177 | { 178 | val: "𐍈", 179 | typ: Str, 180 | bin: [fixstrTag(4), 0xf0, 0x90, 0x8d, 0x88], 181 | }, 182 | { 183 | val: repeats("0", 7), 184 | typ: Str, 185 | bin: [fixstrTag(7)].concat(repeat(0x30, 7)), 186 | }, 187 | { 188 | val: repeats("0", 32), 189 | typ: Str, 190 | bin: [Tag.Str8, 0x20].concat(repeat(0x30, 32)), 191 | }, 192 | { 193 | val: repeats("0", 256), 194 | typ: Str, 195 | bin: [Tag.Str16, 0x01, 0x00].concat(repeat(0x30, 256)), 196 | }, 197 | { 198 | val: repeats("0", 65536), 199 | typ: Str, 200 | bin: [Tag.Str32, 0x00, 0x01, 0x00, 0x00].concat(repeat(0x30, 65536)), 201 | }, 202 | // array (32 bit not testable due to oom) 203 | { 204 | val: repeat(13, 7), 205 | typ: Arr, 206 | bin: [fixarrayTag(7)].concat(repeat(posFixintTag(13), 7)), 207 | }, 208 | { 209 | val: repeat(13, 65535), 210 | typ: Arr, 211 | bin: [Tag.Array16, 0xff, 0xff].concat(repeat(posFixintTag(13), 65535)), 212 | }, 213 | // map 214 | { 215 | val: {a: 7, b: 13}, 216 | typ: Map, 217 | bin: [fixmapTag(2), fixstrTag(1), 0x61, posFixintTag(7), fixstrTag(1), 0x62, posFixintTag(13)], 218 | }, 219 | // raw 220 | { 221 | val: (new Uint8Array(repeat(0x30, 7))).buffer, 222 | typ: Raw, 223 | bin: repeat(0x30, 7), 224 | }, 225 | // time 226 | { 227 | val: new Date(Date.UTC(2017, 8, 26, 13, 14, 15)), 228 | typ: Time, 229 | bin: [Tag.Ext8, 12, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0xca, 0x52, 0xa7], 230 | }, 231 | { 232 | val: new Date(Date.UTC(2017, 8, 26, 13, 14, 15, 16)), 233 | typ: Time, 234 | bin: [Tag.Ext8, 12, 0xff, 0x00, 0xf4, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0xca, 0x52, 0xa7], 235 | }, 236 | // any 237 | { 238 | val: undefined, 239 | typ: Any, 240 | bin: [Tag.Nil], 241 | }, 242 | { 243 | val: null, 244 | typ: Any, 245 | bin: [Tag.Nil], 246 | }, 247 | { 248 | val: true, 249 | typ: Any, 250 | bin: [Tag.True], 251 | }, 252 | { 253 | val: false, 254 | typ: Any, 255 | bin: [Tag.False], 256 | }, 257 | { 258 | val: -128, 259 | typ: Any, 260 | bin: [Tag.Int8, 0x80], 261 | }, 262 | { 263 | val: 255, 264 | typ: Any, 265 | bin: [Tag.Uint8, 0xff], 266 | }, 267 | { 268 | val: 3.141592, 269 | typ: Any, 270 | bin: [Tag.Float64, 0x40, 0x09, 0x21, 0xfa, 0xfc, 0x8b, 0x00, 0x7a], 271 | }, 272 | { 273 | val: new Uint8Array([0x0a, 0x0b, 0x0c]), 274 | typ: Any, 275 | bin: [Tag.Bin8, 0x03, 0x0a, 0x0b, 0x0c], 276 | }, 277 | { 278 | val: (new Uint8Array([0x0a, 0x0b, 0x0c])).buffer, 279 | typ: Any, 280 | bin: [Tag.Bin8, 0x03, 0x0a, 0x0b, 0x0c], 281 | }, 282 | { 283 | val: "12345678901234567890123456789012", 284 | typ: Any, 285 | bin: [Tag.Str8, 0x20, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30, 0x31, 0x32], 286 | }, 287 | { 288 | val: [], 289 | typ: Any, 290 | bin: [fixarrayTag(0)], 291 | }, 292 | { 293 | val: {}, 294 | typ: Any, 295 | bin: [fixmapTag(0)], 296 | }, 297 | { 298 | val: new Date(Date.UTC(2017, 8, 26, 13, 14, 15, 16)), 299 | typ: Any, 300 | bin: [Tag.Ext8, 12, 0xff, 0x00, 0xf4, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0xca, 0x52, 0xa7], 301 | }, 302 | // struct 303 | { 304 | val: { 305 | foo: 7, 306 | bar: "7", 307 | }, 308 | typ: Struct({ 309 | 1: ["foo", Int], 310 | 3: ["bar", Str], 311 | }), 312 | bin: [fixmapTag(2), posFixintTag(1), posFixintTag(7), posFixintTag(3), fixstrTag(1), 0x37], 313 | }, 314 | // union 315 | { 316 | val: 7, 317 | typ: Union({ 318 | 4: Int, 319 | 6: Str, 320 | ordinalOf(v: any): number { return 4; }, 321 | }), 322 | bin: [fixarrayTag(2), posFixintTag(4), posFixintTag(7)], 323 | }, 324 | { 325 | val: "7", 326 | typ: Union({ 327 | 13: Str, 328 | 14: Int, 329 | ordinalOf(v: any): number { return 13; }, 330 | }), 331 | bin: [fixarrayTag(2), posFixintTag(13), fixstrTag(1), 0x37], 332 | }, 333 | ]; 334 | 335 | for(let i = 0; i < tests.length; ++i) { 336 | const test = tests[i]; 337 | try { 338 | const bin = encode(test.val, test.typ); 339 | const expected = new Uint8Array(test.bin); 340 | if(!bufEqual(bin, expected)) { 341 | t.error(`unexpected encoding at ${i} for '${test.val}': ${fmtBuf(bin)}, expected ${fmtBuf(expected)}`); 342 | } 343 | } catch(e) { 344 | t.error(`unexpected encoding error at ${i} for '${test.val}': ${e}`); 345 | } 346 | } 347 | }); 348 | 349 | test("decode", t => { 350 | const tests = [ 351 | // nil 352 | { 353 | bin: [Tag.Nil], 354 | typ: Nil, 355 | val: null, 356 | }, 357 | // bool 358 | { 359 | bin: [Tag.Nil], 360 | typ: Bool, 361 | val: false, 362 | }, 363 | { 364 | bin: [Tag.False], 365 | typ: Bool, 366 | val: false, 367 | }, 368 | { 369 | bin: [Tag.True], 370 | typ: Bool, 371 | val: true, 372 | }, 373 | // int 374 | { 375 | bin: [posFixintTag(7)], 376 | typ: Int, 377 | val: 7, 378 | }, 379 | { 380 | bin: [negFixintTag(-7)], 381 | typ: Int, 382 | val: -7, 383 | }, 384 | { 385 | bin: [Tag.Nil], 386 | typ: Int, 387 | val: 0, 388 | }, 389 | { 390 | bin: [Tag.Int8, 0x9e], 391 | typ: Int, 392 | val: -98, 393 | }, 394 | { 395 | bin: [Tag.Int8, 0x62], 396 | typ: Int, 397 | val: 98, 398 | }, 399 | { 400 | bin: [Tag.Int8, 0x9e], 401 | typ: Int, 402 | val: -98, 403 | }, 404 | { 405 | bin: [Tag.Int16, 0x48, 0x72], 406 | typ: Int, 407 | val: 18546, 408 | }, 409 | { 410 | bin: [Tag.Int16, 0xb7, 0x8e], 411 | typ: Int, 412 | val: -18546, 413 | }, 414 | { 415 | bin: [Tag.Int32, 0x04, 0x8a, 0x51, 0x9d], 416 | typ: Int, 417 | val: 76173725, 418 | }, 419 | { 420 | bin: [Tag.Int32, 0xfb, 0x75, 0xae, 0x63], 421 | typ: Int, 422 | val: -76173725, 423 | }, 424 | { 425 | bin: [Tag.Int64, 0x00, 0x00, 0x04, 0x8a, 0x51, 0x9d, 0x7f, 0xa3], 426 | typ: Int, 427 | val: 4992121274275, 428 | }, 429 | { 430 | bin: [Tag.Int64, 0xff, 0xff, 0xfb, 0x75, 0xae, 0x62, 0x80, 0x5d], 431 | typ: Int, 432 | val: -4992121274275, 433 | }, 434 | { 435 | bin: [Tag.Uint8, 0x62], 436 | typ: Int, 437 | val: 98, 438 | }, 439 | { 440 | bin: [Tag.Uint16, 0x48, 0x72], 441 | typ: Int, 442 | val: 18546, 443 | }, 444 | { 445 | bin: [Tag.Uint32, 0x04, 0x8a, 0x51, 0x9d], 446 | typ: Int, 447 | val: 76173725, 448 | }, 449 | { 450 | bin: [Tag.Uint64, 0x00, 0x00, 0x04, 0x8a, 0x51, 0x9d, 0x7f, 0xa3], 451 | typ: Int, 452 | val: 4992121274275, 453 | }, 454 | // uint 455 | { 456 | bin: [posFixintTag(7)], 457 | typ: Int, 458 | val: 7, 459 | }, 460 | { 461 | bin: [Tag.Nil], 462 | typ: Int, 463 | val: 0, 464 | }, 465 | { 466 | bin: [Tag.Int8, 0x62], 467 | typ: Int, 468 | val: 98, 469 | }, 470 | { 471 | bin: [Tag.Int16, 0x48, 0x72], 472 | typ: Int, 473 | val: 18546, 474 | }, 475 | { 476 | bin: [Tag.Int32, 0x04, 0x8a, 0x51, 0x9d], 477 | typ: Int, 478 | val: 76173725, 479 | }, 480 | { 481 | bin: [Tag.Int64, 0x00, 0x00, 0x04, 0x8a, 0x51, 0x9d, 0x7f, 0xa3], 482 | typ: Int, 483 | val: 4992121274275, 484 | }, 485 | { 486 | bin: [Tag.Uint8, 0x62], 487 | typ: Int, 488 | val: 98, 489 | }, 490 | { 491 | bin: [Tag.Uint16, 0x48, 0x72], 492 | typ: Int, 493 | val: 18546, 494 | }, 495 | { 496 | bin: [Tag.Uint32, 0x04, 0x8a, 0x51, 0x9d], 497 | typ: Int, 498 | val: 76173725, 499 | }, 500 | { 501 | bin: [Tag.Uint64, 0x00, 0x00, 0x04, 0x8a, 0x51, 0x9d, 0x7f, 0xa3], 502 | typ: Int, 503 | val: 4992121274275, 504 | }, 505 | // float 506 | { 507 | bin: [Tag.Nil], 508 | typ: Float, 509 | val: 0, 510 | }, 511 | { 512 | bin: [Tag.Float32, 0x3f, 0xc0, 0x00, 0x00], 513 | typ: Float, 514 | val: 1.5, 515 | }, 516 | { 517 | bin: [Tag.Float64, 0x40, 0x09, 0x21, 0xfa, 0xfc, 0x8b, 0x00, 0x7a], 518 | typ: Float, 519 | val: 3.141592, 520 | }, 521 | // bytes 522 | { 523 | bin: [fixstrTag(5), 0x30, 0x30, 0x30, 0x30, 0x30], 524 | typ: Bytes, 525 | val: new Uint8Array([0x30, 0x30, 0x30, 0x30, 0x30]), 526 | eq: (x, y) => bufEqual(new Uint8Array(x), y), 527 | }, 528 | { 529 | bin: [Tag.Bin8, 0x05, 0x30, 0x30, 0x30, 0x30, 0x30], 530 | typ: Bytes, 531 | val: new Uint8Array([0x30, 0x30, 0x30, 0x30, 0x30]), 532 | eq: (x, y) => bufEqual(new Uint8Array(x), y), 533 | }, 534 | { 535 | bin: [Tag.Bin16, 0x01, 0x00].concat(repeat(0x30, 256)), 536 | typ: Bytes, 537 | val: new Uint8Array(repeat(0x30, 256)), 538 | eq: (x, y) => bufEqual(new Uint8Array(x), y), 539 | }, 540 | { 541 | bin: [Tag.Bin32, 0x00, 0x01, 0x00, 0x00].concat(repeat(0x30, 65536)), 542 | typ: Bytes, 543 | val: new Uint8Array(repeat(0x30, 65536)), 544 | eq: (x, y) => bufEqual(new Uint8Array(x), y), 545 | }, 546 | // string 547 | { 548 | bin: [fixstrTag(2), 0xc3, 0xa4], 549 | typ: Str, 550 | val: "ä", 551 | }, 552 | { 553 | bin: [fixstrTag(3), 0xe2, 0x88, 0x9e], 554 | typ: Str, 555 | val: "∞", 556 | }, 557 | { 558 | bin: [fixstrTag(4), 0xf0, 0x90, 0x8d, 0x88], 559 | typ: Str, 560 | val: "𐍈", 561 | }, 562 | { 563 | bin: [fixstrTag(5), 0x30, 0x30, 0x30, 0x30, 0x30], 564 | typ: Str, 565 | val: "00000", 566 | }, 567 | { 568 | bin: [Tag.Bin8, 0x05, 0x30, 0x30, 0x30, 0x30, 0x30], 569 | typ: Str, 570 | val: "00000", 571 | }, 572 | { 573 | bin: [Tag.Bin16, 0x01, 0x00].concat(repeat(0x30, 256)), 574 | typ: Str, 575 | val: repeats("0", 256), 576 | }, 577 | { 578 | bin: [Tag.Bin32, 0x00, 0x01, 0x00, 0x00].concat(repeat(0x30, 65536)), 579 | typ: Str, 580 | val: repeats("0", 65536), 581 | }, 582 | // array 583 | { 584 | bin: [fixarrayTag(2), posFixintTag(7), fixstrTag(1), 0x30], 585 | typ: Arr, 586 | val: [7, "0"], 587 | eq: arrayEqual, 588 | }, 589 | { 590 | bin: [Tag.Array16, 0x00, 0x01, negFixintTag(-7)], 591 | typ: Arr, 592 | val: [-7], 593 | eq: arrayEqual, 594 | }, 595 | { 596 | bin: [Tag.Array32, 0x00, 0x00, 0x00, 0x01, fixstrTag(3), 0xe2, 0x88, 0x9e], 597 | typ: Arr, 598 | val: ["∞"], 599 | eq: arrayEqual, 600 | }, 601 | // map 602 | { 603 | bin: [fixmapTag(1), fixstrTag(1), 0x61, posFixintTag(7)], 604 | typ: Map, 605 | val: {"a": 7}, 606 | eq: objectEqual, 607 | }, 608 | { 609 | bin: [Tag.Map16, 0x00, 0x01, fixstrTag(1), 0x61, posFixintTag(7)], 610 | typ: Map, 611 | val: {"a": 7}, 612 | eq: objectEqual, 613 | }, 614 | { 615 | bin: [Tag.Map32, 0x00, 0x00, 0x00, 0x01, fixstrTag(3), 0x69, 0x6e, 0x66, fixstrTag(3), 0xe2, 0x88, 0x9e], 616 | typ: Map, 617 | val: {"inf": "∞"}, 618 | eq: objectEqual, 619 | }, 620 | // raw 621 | { 622 | bin: [Tag.True], 623 | typ: Raw, 624 | val: new Uint8Array([Tag.True]), 625 | eq: (x, y) => bufEqual(new Uint8Array(x), y), 626 | }, 627 | { 628 | bin: [Tag.Uint8, 0x00], 629 | typ: Raw, 630 | val: new Uint8Array([Tag.Uint8, 0x00]), 631 | eq: (x, y) => bufEqual(new Uint8Array(x), y), 632 | }, 633 | { 634 | bin: [Tag.Int16, 0x00, 0x00], 635 | typ: Raw, 636 | val: new Uint8Array([Tag.Int16, 0x00, 0x00]), 637 | eq: (x, y) => bufEqual(new Uint8Array(x), y), 638 | }, 639 | { 640 | bin: [Tag.Float32, 0x00, 0x00, 0x00, 0x00], 641 | typ: Raw, 642 | val: new Uint8Array([Tag.Float32, 0x00, 0x00, 0x00, 0x00]), 643 | eq: (x, y) => bufEqual(new Uint8Array(x), y), 644 | }, 645 | { 646 | bin: [Tag.Float64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], 647 | typ: Raw, 648 | val: new Uint8Array([Tag.Float64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), 649 | eq: (x, y) => bufEqual(new Uint8Array(x), y), 650 | }, 651 | { 652 | bin: [Tag.Bin8, 0x03, 0x30, 0x30, 0x30], 653 | typ: Raw, 654 | val: new Uint8Array([Tag.Bin8, 0x03, 0x30, 0x30, 0x30]), 655 | eq: (x, y) => bufEqual(new Uint8Array(x), y), 656 | }, 657 | { 658 | bin: [Tag.Bin16, 0x0, 0x03, 0x30, 0x30, 0x30], 659 | typ: Raw, 660 | val: new Uint8Array([Tag.Bin16, 0x0, 0x03, 0x30, 0x30, 0x30]), 661 | eq: (x, y) => bufEqual(new Uint8Array(x), y), 662 | }, 663 | { 664 | bin: [Tag.Bin32, 0x0, 0x0, 0x0, 0x03, 0x30, 0x30, 0x30], 665 | typ: Raw, 666 | val: new Uint8Array([Tag.Bin32, 0x0, 0x0, 0x0, 0x03, 0x30, 0x30, 0x30]), 667 | eq: (x, y) => bufEqual(new Uint8Array(x), y), 668 | }, 669 | { 670 | bin: [Tag.Array16, 0x0, 0x03, Tag.Nil, Tag.Nil, Tag.Nil], 671 | typ: Raw, 672 | val: new Uint8Array([Tag.Array16, 0x0, 0x03, Tag.Nil, Tag.Nil, Tag.Nil]), 673 | eq: (x, y) => bufEqual(new Uint8Array(x), y), 674 | }, 675 | { 676 | bin: [Tag.Array32, 0x0, 0x0, 0x0, 0x03, Tag.Nil, Tag.Nil, Tag.Nil], 677 | typ: Raw, 678 | val: new Uint8Array([Tag.Array32, 0x0, 0x0, 0x0, 0x03, Tag.Nil, Tag.Nil, Tag.Nil]), 679 | eq: (x, y) => bufEqual(new Uint8Array(x), y), 680 | }, 681 | { 682 | bin: [Tag.Map16, 0x0, 0x03, Tag.Nil, Tag.Nil, Tag.Nil, Tag.Nil, Tag.Nil, Tag.Nil], 683 | typ: Raw, 684 | val: new Uint8Array([Tag.Map16, 0x0, 0x03, Tag.Nil, Tag.Nil, Tag.Nil, Tag.Nil, Tag.Nil, Tag.Nil]), 685 | eq: (x, y) => bufEqual(new Uint8Array(x), y), 686 | }, 687 | { 688 | bin: [Tag.Map32, 0x0, 0x0, 0x0, 0x03, Tag.Nil, Tag.Nil, Tag.Nil, Tag.Nil, Tag.Nil, Tag.Nil], 689 | typ: Raw, 690 | val: new Uint8Array([Tag.Map32, 0x0, 0x0, 0x0, 0x03, Tag.Nil, Tag.Nil, Tag.Nil, Tag.Nil, Tag.Nil, Tag.Nil]), 691 | eq: (x, y) => bufEqual(new Uint8Array(x), y), 692 | }, 693 | { 694 | bin: [Tag.FixExt1, 0x0d, 0x30], 695 | typ: Raw, 696 | val: new Uint8Array([Tag.FixExt1, 0x0d, 0x30]), 697 | eq: (x, y) => bufEqual(new Uint8Array(x), y), 698 | }, 699 | { 700 | bin: [Tag.FixExt2, 0x0d, 0x30, 0x30], 701 | typ: Raw, 702 | val: new Uint8Array([Tag.FixExt2, 0x0d, 0x30, 0x30]), 703 | eq: (x, y) => bufEqual(new Uint8Array(x), y), 704 | }, 705 | { 706 | bin: [Tag.FixExt4, 0x0d, 0x30, 0x30, 0x30, 0x30], 707 | typ: Raw, 708 | val: new Uint8Array([Tag.FixExt4, 0x0d, 0x30, 0x30, 0x30, 0x30]), 709 | eq: (x, y) => bufEqual(new Uint8Array(x), y), 710 | }, 711 | { 712 | bin: [Tag.FixExt8, 0x0d, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30], 713 | typ: Raw, 714 | val: new Uint8Array([Tag.FixExt8, 0x0d, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30]), 715 | eq: (x, y) => bufEqual(new Uint8Array(x), y), 716 | }, 717 | { 718 | bin: [Tag.FixExt16, 0x0d, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30], 719 | typ: Raw, 720 | val: new Uint8Array([Tag.FixExt16, 0x0d, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30]), 721 | eq: (x, y) => bufEqual(new Uint8Array(x), y), 722 | }, 723 | { 724 | bin: [Tag.Ext8, 0x05, 0x0d, 0x30, 0x30, 0x30, 0x30, 0x30], 725 | typ: Raw, 726 | val: new Uint8Array([Tag.Ext8, 0x05, 0x0d, 0x30, 0x30, 0x30, 0x30, 0x30]), 727 | eq: (x, y) => bufEqual(new Uint8Array(x), y), 728 | }, 729 | { 730 | bin: [Tag.Ext16, 0x0, 0x05, 0x0d, 0x30, 0x30, 0x30, 0x30, 0x30], 731 | typ: Raw, 732 | val: new Uint8Array([Tag.Ext16, 0x0, 0x05, 0x0d, 0x30, 0x30, 0x30, 0x30, 0x30]), 733 | eq: (x, y) => bufEqual(new Uint8Array(x), y), 734 | }, 735 | { 736 | bin: [Tag.Ext32, 0x0, 0x0, 0x0, 0x05, 0x0d, 0x30, 0x30, 0x30, 0x30, 0x30], 737 | typ: Raw, 738 | val: new Uint8Array([Tag.Ext32, 0x0, 0x0, 0x0, 0x05, 0x0d, 0x30, 0x30, 0x30, 0x30, 0x30]), 739 | eq: (x, y) => bufEqual(new Uint8Array(x), y), 740 | }, 741 | { 742 | bin: [posFixintTag(7)], 743 | typ: Raw, 744 | val: new Uint8Array([posFixintTag(7)]), 745 | eq: (x, y) => bufEqual(new Uint8Array(x), y), 746 | }, 747 | { 748 | bin: [negFixintTag(-7)], 749 | typ: Raw, 750 | val: new Uint8Array([negFixintTag(-7)]), 751 | eq: (x, y) => bufEqual(new Uint8Array(x), y), 752 | }, 753 | { 754 | bin: [fixstrTag(3), 0x30, 0x30, 0x30], 755 | typ: Raw, 756 | val: new Uint8Array([fixstrTag(3), 0x30, 0x30, 0x30]), 757 | eq: (x, y) => bufEqual(new Uint8Array(x), y), 758 | }, 759 | { 760 | bin: [fixarrayTag(3), Tag.Nil, Tag.Nil, Tag.Nil], 761 | typ: Raw, 762 | val: new Uint8Array([fixarrayTag(3), Tag.Nil, Tag.Nil, Tag.Nil]), 763 | eq: (x, y) => bufEqual(new Uint8Array(x), y), 764 | }, 765 | { 766 | bin: [fixmapTag(3), Tag.Nil, Tag.Nil, Tag.Nil, Tag.Nil, Tag.Nil, Tag.Nil], 767 | typ: Raw, 768 | val: new Uint8Array([fixmapTag(3), Tag.Nil, Tag.Nil, Tag.Nil, Tag.Nil, Tag.Nil, Tag.Nil]), 769 | eq: (x, y) => bufEqual(new Uint8Array(x), y), 770 | }, 771 | // time 772 | { 773 | bin: [Tag.FixExt4, 0xff, 0x59, 0xca, 0x52, 0xa7], 774 | typ: Time, 775 | val: new Date(Date.UTC(2017, 8, 26, 13, 14, 15)), 776 | eq: dateEqual, 777 | }, 778 | { 779 | bin: [Tag.FixExt8, 0xff, 0x03, 0xd0, 0x90, 0x00, 0x59, 0xca, 0x52, 0xa7], 780 | typ: Time, 781 | val: new Date(Date.UTC(2017, 8, 26, 13, 14, 15, 16)), 782 | eq: dateEqual, 783 | }, 784 | { 785 | bin: [Tag.Ext8, 12, 0xff, 0x00, 0xf4, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0xca, 0x52, 0xa7], 786 | typ: Time, 787 | val: new Date(Date.UTC(2017, 8, 26, 13, 14, 15, 16)), 788 | eq: dateEqual, 789 | }, 790 | // any 791 | { 792 | bin: [Tag.Nil], 793 | typ: Any, 794 | val: null, 795 | }, 796 | { 797 | bin: [Tag.False], 798 | typ: Any, 799 | val: false, 800 | }, 801 | { 802 | bin: [Tag.True], 803 | typ: Any, 804 | val: true, 805 | }, 806 | { 807 | bin: [posFixintTag(7)], 808 | typ: Any, 809 | val: 7, 810 | }, 811 | { 812 | bin: [negFixintTag(-7)], 813 | typ: Any, 814 | val: -7, 815 | }, 816 | { 817 | bin: [Tag.Int8, 0x80], 818 | typ: Any, 819 | val: -128, 820 | }, 821 | { 822 | bin: [Tag.Int16, 0xff, 0x80], 823 | typ: Any, 824 | val: -128, 825 | }, 826 | { 827 | bin: [Tag.Int32, 0xff, 0xff, 0xff, 0x80], 828 | typ: Any, 829 | val: -128, 830 | }, 831 | { 832 | bin: [Tag.Int64, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80], 833 | typ: Any, 834 | val: -128, 835 | }, 836 | { 837 | bin: [Tag.Uint8, 0x07], 838 | typ: Any, 839 | val: 7, 840 | }, 841 | { 842 | bin: [Tag.Uint16, 0x01, 0x10], 843 | typ: Any, 844 | val: 272, 845 | }, 846 | { 847 | bin: [Tag.Uint32, 0x0a, 0x7e, 0x00, 0x43], 848 | typ: Any, 849 | val: 176029763, 850 | }, 851 | { 852 | bin: [Tag.Uint64, 0x00, 0x00, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc], 853 | typ: Any, 854 | val: 20015998343868, 855 | }, 856 | { 857 | bin: [Tag.Float32, 0x3e, 0x20, 0x00, 0x00], 858 | typ: Any, 859 | val: 0.15625, 860 | }, 861 | { 862 | bin: [Tag.Float64, 0x40, 0x09, 0x21, 0xfa, 0xfc, 0x8b, 0x00, 0x7a], 863 | typ: Any, 864 | val: 3.141592, 865 | }, 866 | { 867 | bin: [Tag.Bin8, 0x03, 0x0d, 0x0e, 0x0f], 868 | typ: Any, 869 | val: new Uint8Array([0x0d, 0x0e, 0x0f]), 870 | eq: (x, y) => bufEqual(new Uint8Array(x), y), 871 | }, 872 | { 873 | bin: [Tag.Bin16, 0x00, 0x03, 0x0d, 0x0e, 0x0f], 874 | typ: Any, 875 | val: new Uint8Array([0x0d, 0x0e, 0x0f]), 876 | eq: (x, y) => bufEqual(new Uint8Array(x), y), 877 | }, 878 | { 879 | bin: [Tag.Bin32, 0x00, 0x00, 0x00, 0x03, 0x0d, 0x0e, 0x0f], 880 | typ: Any, 881 | val: new Uint8Array([0x0d, 0x0e, 0x0f]), 882 | eq: (x, y) => bufEqual(new Uint8Array(x), y), 883 | }, 884 | { 885 | bin: [fixstrTag(1), 0x30], 886 | typ: Any, 887 | val: "0", 888 | }, 889 | { 890 | bin: [Tag.Str8, 0x03, 0xe2, 0x88, 0x9e], 891 | typ: Any, 892 | val: "∞", 893 | }, 894 | { 895 | bin: [Tag.Str16, 0x00, 0x04, 0xf0, 0x90, 0x8d, 0x88], 896 | typ: Any, 897 | val: "𐍈", 898 | }, 899 | { 900 | bin: [Tag.Str32, 0x00, 0x00, 0x00, 0x02, 0xc3, 0xa4], 901 | typ: Any, 902 | val: "ä", 903 | }, 904 | { 905 | bin: [fixarrayTag(1), fixstrTag(1), 0x30], 906 | typ: Any, 907 | val: ["0"], 908 | eq: arrayEqual, 909 | }, 910 | { 911 | bin: [Tag.Array16, 0x00, 0x01, posFixintTag(5)], 912 | typ: Any, 913 | val: [5], 914 | eq: arrayEqual, 915 | }, 916 | { 917 | bin: [Tag.Array32, 0x00, 0x00, 0x00, 0x01, negFixintTag(-13)], 918 | typ: Any, 919 | val: [-13], 920 | eq: arrayEqual, 921 | }, 922 | { 923 | bin: [fixmapTag(1), fixstrTag(1), 0x61, negFixintTag(-12)], 924 | typ: Any, 925 | val: {"a": -12}, 926 | eq: objectEqual, 927 | }, 928 | { 929 | bin: [Tag.Map16, 0x00, 0x01, fixstrTag(2), 0xc3, 0xa4, posFixintTag(11)], 930 | typ: Any, 931 | val: {"ä": 11}, 932 | eq: objectEqual, 933 | }, 934 | { 935 | bin: [Tag.Map32, 0x00, 0x00, 0x00, 0x01, fixstrTag(2), 0x31, 0x30, fixstrTag(1), 0x32], 936 | typ: Any, 937 | val: {"10": "2"}, 938 | eq: objectEqual, 939 | }, 940 | { 941 | bin: [Tag.FixExt4, 0xff, 0x59, 0xca, 0x52, 0xa7], 942 | typ: Any, 943 | val: new Date(Date.UTC(2017, 8, 26, 13, 14, 15)), 944 | eq: dateEqual, 945 | }, 946 | { 947 | bin: [Tag.FixExt8, 0xff, 0x03, 0xd0, 0x90, 0x00, 0x59, 0xca, 0x52, 0xa7], 948 | typ: Any, 949 | val: new Date(Date.UTC(2017, 8, 26, 13, 14, 15, 16)), 950 | eq: dateEqual, 951 | }, 952 | { 953 | bin: [Tag.Ext8, 12, 0xff, 0x00, 0xf4, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0xca, 0x52, 0xa7], 954 | typ: Any, 955 | val: new Date(Date.UTC(2017, 8, 26, 13, 14, 15, 16)), 956 | eq: dateEqual, 957 | }, 958 | // struct 959 | { 960 | bin: [fixmapTag(2), posFixintTag(1), posFixintTag(7), posFixintTag(3), fixstrTag(1), 0x37], 961 | typ: Struct({ 962 | 1: ["foo", Int], 963 | 3: ["bar", Str], 964 | }), 965 | val: { 966 | foo: 7, 967 | bar: "7", 968 | }, 969 | eq: objectEqual, 970 | }, 971 | // union 972 | { 973 | bin: [fixarrayTag(2), posFixintTag(4), posFixintTag(7)], 974 | typ: Union({ 975 | 4: Int, 976 | 6: Str, 977 | ordinalOf(v: any): number { throw new Error("not implemented"); }, 978 | }), 979 | val: 7, 980 | }, 981 | { 982 | bin: [fixarrayTag(2), posFixintTag(13), fixstrTag(1), 0x37], 983 | typ: Union({ 984 | 13: Str, 985 | 14: Int, 986 | ordinalOf(v: any): number { throw new Error("not implemented"); }, 987 | }), 988 | val: "7", 989 | }, 990 | ]; 991 | 992 | for(let i = 0; i < tests.length; ++i) { 993 | const test = tests[i]; 994 | const bin = new Uint8Array(test.bin); 995 | try { 996 | const val = decode(bin, test.typ); 997 | const eq = opEqual(test); 998 | if(!eq(val, test.val)) { 999 | t.error(`unexpected decoding at ${i} for '${fmtBuf(bin)}': ${val}, expected ${test.val}`); 1000 | } 1001 | } catch(e) { 1002 | t.error(`unexpected decoding error at ${i} for '${fmtBuf(bin)}': ${e}`); 1003 | } 1004 | } 1005 | }); 1006 | 1007 | 1008 | 1009 | function repeat(x: T, n: number): T[] { 1010 | let res = []; 1011 | for(let i = 0; i < n; ++i) { 1012 | res.push(x); 1013 | } 1014 | return res; 1015 | } 1016 | 1017 | function repeats(s: string, n: number): string { 1018 | let res = ""; 1019 | for(let i = 0; i < n; ++i) { 1020 | res += s; 1021 | } 1022 | return res; 1023 | } 1024 | 1025 | function fmtBuf(buf: Uint8Array): string { 1026 | const list = Array.prototype.map.call(buf, x => `0${ x.toString(16)}`.slice(-2)).join(","); 1027 | return `[${list}]`; 1028 | } 1029 | 1030 | function opEqual(test: any): (x: any, y: any) => boolean { 1031 | if(test.eq) { 1032 | return test.eq; 1033 | } 1034 | return (x, y) => x === y; 1035 | } 1036 | 1037 | function arrayEqual(x: any[], y: any[]): boolean { 1038 | return x.length === y.length 1039 | && x.every((v, i) => v === y[i]); 1040 | } 1041 | 1042 | function objectEqual(x: any, y: any): boolean { 1043 | for(const p in x) { 1044 | if(!(p in y) || x[p] !== y[p]) { 1045 | return false; 1046 | } 1047 | } 1048 | for(const p in y) { 1049 | if(!(p in x) || x[p] !== y[p]) { 1050 | return false; 1051 | } 1052 | } 1053 | return true; 1054 | } 1055 | 1056 | function bufEqual(left: Uint8Array, right: Uint8Array): boolean { 1057 | if(left.length !== right.length) { 1058 | return false; 1059 | } 1060 | for(let i = 0; i < left.length; ++i) { 1061 | if(left[i] !== right[i]) { 1062 | return false; 1063 | } 1064 | } 1065 | return true; 1066 | } 1067 | 1068 | function dateEqual(left: Date, right: Date): boolean { 1069 | return left.getTime() === right.getTime(); 1070 | } 1071 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import {typeError} from "./error"; 2 | import { 3 | WriteBuffer, ReadBuffer, createWriteBuffer, 4 | putBlob, getBlob, 5 | putArrHeader, getArrHeader, 6 | putMapHeader, getMapHeader, 7 | getRaw, 8 | } from "./buffer"; 9 | import { 10 | Tag, 11 | posFixintTag, isPosFixintTag, readPosFixint, 12 | negFixintTag, isNegFixintTag, readNegFixint, 13 | fixstrTag, isFixstrTag, isFixarrayTag, isFixmapTag, 14 | } from "./tags"; 15 | 16 | 17 | 18 | export type EncodeFunc = (buf: WriteBuffer, v: T) => void; 19 | export type DecodeFunc = (buf: ReadBuffer) => T; 20 | 21 | export interface Type { 22 | readonly enc: EncodeFunc; 23 | readonly dec: DecodeFunc; 24 | } 25 | 26 | export interface Collection extends Type { 27 | encHeader(buf: WriteBuffer, len: number): void; 28 | decHeader(buf: ReadBuffer, expect?: number): number; 29 | } 30 | 31 | export type Obj = {[key: string]: T}; 32 | 33 | export type Field = [string, Type]; // (name, type) 34 | export type Fields = {readonly [ordinal: number]: Field}; 35 | 36 | export interface Branches { 37 | readonly [ordinal: number]: Type; 38 | ordinalOf(v: any): number; 39 | }; 40 | 41 | 42 | 43 | export const Any: Type = { 44 | enc(buf: WriteBuffer, v: any): void { 45 | typeOf(v).enc(buf, v); 46 | }, 47 | 48 | dec(buf: ReadBuffer): any { 49 | return tagType(buf.peek()).dec(buf); 50 | }, 51 | }; 52 | 53 | 54 | export const Nil: Type = { 55 | enc(buf: WriteBuffer, v: null): void { 56 | buf.putUi8(Tag.Nil); 57 | }, 58 | 59 | dec(buf: ReadBuffer): null { 60 | const tag = buf.getUi8(); 61 | if(tag !== Tag.Nil) { 62 | typeError(tag, "nil"); 63 | } 64 | return null; 65 | }, 66 | }; 67 | 68 | 69 | export const Bool: Type = { 70 | enc(buf: WriteBuffer, v: boolean): void { 71 | buf.putUi8(v ? Tag.True : Tag.False); 72 | }, 73 | 74 | dec(buf: ReadBuffer): boolean { 75 | const tag = buf.getUi8(); 76 | switch(tag) { 77 | case Tag.Nil: 78 | case Tag.False: 79 | return false; 80 | case Tag.True: 81 | return true; 82 | default: 83 | typeError(tag, "bool"); 84 | } 85 | }, 86 | }; 87 | 88 | 89 | export const Int: Type = { 90 | enc(buf: WriteBuffer, v: number): void { 91 | if(-128 <= v && v <= 127) { 92 | if(v >= 0) { 93 | buf.putUi8(posFixintTag(v)); 94 | } else if(v > -32) { 95 | buf.putUi8(negFixintTag(v)); 96 | } else { 97 | buf.putUi8(Tag.Int8); 98 | buf.putUi8(v); 99 | } 100 | } else if(-32768 <= v && v <= 32767) { 101 | buf.putI8(Tag.Int16); 102 | buf.putI16(v); 103 | } else if(-2147483648 <= v && v <= 2147483647) { 104 | buf.putI8(Tag.Int32); 105 | buf.putI32(v); 106 | } else { 107 | buf.putI8(Tag.Int64); 108 | buf.putI64(v); 109 | } 110 | }, 111 | 112 | dec(buf: ReadBuffer): number { 113 | const tag = buf.getUi8(); 114 | if(isPosFixintTag(tag)) { 115 | return readPosFixint(tag); 116 | } else if(isNegFixintTag(tag)) { 117 | return readNegFixint(tag); 118 | } 119 | 120 | switch(tag) { 121 | case Tag.Nil: 122 | return 0; 123 | 124 | // signed int types 125 | case Tag.Int8: 126 | return buf.getI8(); 127 | case Tag.Int16: 128 | return buf.getI16(); 129 | case Tag.Int32: 130 | return buf.getI32(); 131 | case Tag.Int64: 132 | return buf.getI64(); 133 | 134 | // unsigned int types 135 | case Tag.Uint8: 136 | return buf.getUi8(); 137 | case Tag.Uint16: 138 | return buf.getUi16(); 139 | case Tag.Uint32: 140 | return buf.getUi32(); 141 | case Tag.Uint64: 142 | return buf.getUi64(); 143 | 144 | default: 145 | typeError(tag, "int"); 146 | } 147 | }, 148 | }; 149 | 150 | 151 | export const Uint: Type = { 152 | enc(buf: WriteBuffer, v: number): void { 153 | if(v < 0) { 154 | throw new Error(`not an uint: ${v}`); 155 | } else if(v <= 127) { 156 | buf.putUi8(posFixintTag(v)); 157 | } else if(v <= 255) { 158 | buf.putUi8(Tag.Uint8); 159 | buf.putUi8(v); 160 | } else if(v <= 65535) { 161 | buf.putUi8(Tag.Uint16); 162 | buf.putUi16(v); 163 | } else if(v <= 4294967295) { 164 | buf.putUi8(Tag.Uint32); 165 | buf.putUi32(v); 166 | } else { 167 | buf.putUi8(Tag.Uint64); 168 | buf.putUi64(v); 169 | } 170 | }, 171 | 172 | dec(buf: ReadBuffer): number { 173 | const v = Int.dec(buf); 174 | if(v < 0) { 175 | throw new RangeError("uint underflow"); 176 | } 177 | return v; 178 | }, 179 | }; 180 | 181 | 182 | export const Float: Type = { 183 | enc(buf: WriteBuffer, v: number): void { 184 | buf.putUi8(Tag.Float64); 185 | buf.putF(v); 186 | }, 187 | 188 | dec(buf: ReadBuffer): number { 189 | const tag = buf.getUi8(); 190 | switch(tag) { 191 | case Tag.Nil: 192 | return 0; 193 | case Tag.Float32: 194 | return buf.getF32(); 195 | case Tag.Float64: 196 | return buf.getF64(); 197 | default: 198 | typeError(tag, "float"); 199 | } 200 | }, 201 | }; 202 | 203 | 204 | export const Bytes: Type = { 205 | enc(buf: WriteBuffer, v: ArrayBuffer): void { 206 | putBlob(buf, v, Tag.Bin8); 207 | }, 208 | 209 | dec: getBlob, 210 | }; 211 | 212 | 213 | export const Str: Type = { 214 | enc(buf: WriteBuffer, v: string): void { 215 | const utf8 = (new TextEncoder()).encode(v).buffer; 216 | if(utf8.byteLength < 32) { 217 | buf.putUi8(fixstrTag(utf8.byteLength)); 218 | buf.put(utf8); 219 | } else { 220 | putBlob(buf, utf8, Tag.Str8); 221 | } 222 | }, 223 | 224 | dec(buf: ReadBuffer): string { 225 | return (new TextDecoder()).decode(getBlob(buf)); 226 | }, 227 | }; 228 | 229 | 230 | export const Raw: Type = { 231 | enc(buf: WriteBuffer, v: ArrayBuffer): void { 232 | buf.put(v); 233 | }, 234 | 235 | dec(buf: ReadBuffer): ArrayBuffer { 236 | const res = createWriteBuffer(); 237 | getRaw(buf, res); 238 | const arr = res.ui8array(); 239 | return arr.buffer.slice(0, arr.length); 240 | }, 241 | }; 242 | 243 | 244 | export const Time: Type = { 245 | enc(buf: WriteBuffer, v: Date): void { 246 | const ms = v.getTime(); 247 | buf.putUi8(Tag.Ext8); 248 | buf.putUi8(12); 249 | buf.putI8(-1); 250 | buf.putUi32((ms%1000)*1000000); 251 | buf.putI64(ms/1000); 252 | }, 253 | 254 | dec(buf: ReadBuffer): Date { 255 | const tag = buf.getUi8(); 256 | switch(tag) { 257 | case Tag.FixExt4: // 32-bit seconds 258 | if(buf.getI8() === -1) { 259 | return new Date(buf.getUi32() * 1000); 260 | } 261 | break; 262 | case Tag.FixExt8: // 34-bit seconds + 30-bit nanoseconds 263 | if(buf.getI8() === -1) { 264 | const lo = buf.getUi32(); 265 | const hi = buf.getUi32(); 266 | // seconds: hi + (lo&0x3)*0x100000000 267 | // nanoseconds: lo>>2 == lo/4 268 | return new Date((hi + (lo&0x3)*0x100000000)*1000 + lo/4000000); 269 | } 270 | break; 271 | case Tag.Ext8: // 64-bit seconds + 32-bit nanoseconds 272 | if(buf.getUi8() === 12 && buf.getI8() === -1) { 273 | const ns = buf.getUi32(); 274 | const s = buf.getI64(); 275 | return new Date(s*1000 + ns/1000000); 276 | } 277 | break; 278 | } 279 | typeError(tag, "time"); 280 | }, 281 | }; 282 | 283 | 284 | export const Arr = TypedArr(Any); 285 | export const Map = TypedMap(Any, Any); 286 | 287 | 288 | export function TypedArr(valueT: Type): Collection { 289 | return { 290 | encHeader: putArrHeader, 291 | decHeader: getArrHeader, 292 | 293 | enc(buf: WriteBuffer, v: T[]): void { 294 | putArrHeader(buf, v.length); 295 | v.forEach(x => valueT.enc(buf, x)); 296 | }, 297 | 298 | dec(buf: ReadBuffer): T[] { 299 | const res = []; 300 | for(let n = getArrHeader(buf); n > 0; --n) { 301 | res.push(valueT.dec(buf)); 302 | } 303 | return res; 304 | }, 305 | }; 306 | } 307 | 308 | 309 | export function TypedMap(keyT: Type, valueT: Type): Collection> { 310 | return { 311 | encHeader: putMapHeader, 312 | decHeader: getMapHeader, 313 | 314 | enc(buf: WriteBuffer, v: Obj): void { 315 | const props = Object.keys(v); 316 | putMapHeader(buf, props.length); 317 | props.forEach(p => { 318 | keyT.enc(buf, p); 319 | valueT.enc(buf, v[p]); 320 | }); 321 | }, 322 | 323 | dec(buf: ReadBuffer): Obj { 324 | const res = {}; 325 | for(let n = getMapHeader(buf); n > 0; --n) { 326 | const k = keyT.dec(buf); 327 | res[k] = valueT.dec(buf); 328 | } 329 | return res; 330 | }, 331 | }; 332 | } 333 | 334 | 335 | export function structEncoder(fields: Fields): EncodeFunc { 336 | const ordinals = Object.keys(fields); 337 | 338 | return (buf: WriteBuffer, v: any): void => { 339 | putMapHeader(buf, ordinals.length); 340 | ordinals.forEach(ord => { 341 | const f = fields[ord]; 342 | Int.enc(buf, Number(ord)); 343 | f[1].enc(buf, v[f[0]]); 344 | }); 345 | }; 346 | } 347 | 348 | export function structDecoder(fields: Fields): DecodeFunc { 349 | return (buf: ReadBuffer): any => { 350 | const res = {}; 351 | for(let n = getMapHeader(buf); n > 0; --n) { 352 | const f = fields[Int.dec(buf)]; 353 | if(f) { 354 | res[f[0]] = f[1].dec(buf); 355 | } else { 356 | Any.dec(buf); 357 | } 358 | } 359 | return res; 360 | }; 361 | } 362 | 363 | export function Struct(fields: Fields): Type> { 364 | return { 365 | enc: structEncoder(fields), 366 | dec: structDecoder(fields), 367 | }; 368 | } 369 | 370 | 371 | export function unionEncoder(branches: Branches): EncodeFunc { 372 | return (buf: WriteBuffer, v: any): void => { 373 | putArrHeader(buf, 2); 374 | 375 | const ord = branches.ordinalOf(v); 376 | Int.enc(buf, ord); 377 | branches[ord].enc(buf, v); 378 | }; 379 | } 380 | 381 | export function unionDecoder(branches: Branches): DecodeFunc { 382 | return (buf: ReadBuffer): any => { 383 | getArrHeader(buf, 2); 384 | 385 | const t = branches[Int.dec(buf)]; 386 | if(!t) { 387 | throw new TypeError("invalid union type"); 388 | } 389 | return t.dec(buf); 390 | }; 391 | } 392 | 393 | export function Union(branches: Branches): Type { 394 | return { 395 | enc: unionEncoder(branches), 396 | dec: unionDecoder(branches), 397 | }; 398 | } 399 | 400 | function typeOf(v: any): Type { 401 | switch(typeof v) { 402 | case "undefined": 403 | return Nil; 404 | case "boolean": 405 | return Bool; 406 | case "number": 407 | return !isFinite(v) || Math.floor(v) !== v ? Float 408 | : v < 0 ? Int 409 | : Uint; 410 | case "string": 411 | return Str; 412 | case "object": 413 | return v === null ? Nil 414 | : Array.isArray(v) ? Arr 415 | : v instanceof Uint8Array || v instanceof ArrayBuffer ? Bytes 416 | : v instanceof Date ? Time 417 | : Map; 418 | default: 419 | throw new TypeError(`unsupported type ${typeof v}`); 420 | } 421 | } 422 | 423 | function tagType(tag: Tag): Type { 424 | switch(tag) { 425 | case Tag.Nil: 426 | return Nil; 427 | case Tag.False: 428 | case Tag.True: 429 | return Bool; 430 | case Tag.Int8: 431 | case Tag.Int16: 432 | case Tag.Int32: 433 | case Tag.Int64: 434 | return Int; 435 | case Tag.Uint8: 436 | case Tag.Uint16: 437 | case Tag.Uint32: 438 | case Tag.Uint64: 439 | return Uint; 440 | case Tag.Float32: 441 | case Tag.Float64: 442 | return Float; 443 | case Tag.Bin8: 444 | case Tag.Bin16: 445 | case Tag.Bin32: 446 | return Bytes; 447 | case Tag.Str8: 448 | case Tag.Str16: 449 | case Tag.Str32: 450 | return Str; 451 | case Tag.Array16: 452 | case Tag.Array32: 453 | return Arr; 454 | case Tag.Map16: 455 | case Tag.Map32: 456 | return Map; 457 | case Tag.FixExt4: 458 | case Tag.FixExt8: 459 | case Tag.Ext8: 460 | return Time; 461 | default: 462 | if(isPosFixintTag(tag) || isNegFixintTag(tag)) { 463 | return Int; 464 | } 465 | if(isFixstrTag(tag)) { 466 | return Str; 467 | } 468 | if(isFixarrayTag(tag)) { 469 | return Arr; 470 | } 471 | if(isFixmapTag(tag)) { 472 | return Map; 473 | } 474 | throw new TypeError(`unsupported tag ${tag}`); 475 | } 476 | } 477 | --------------------------------------------------------------------------------