1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
'use strict'
const Encoder = require('./encoder')
const ObjectRecorder = require('./objectRecorder')
const {Buffer} = require('buffer')
/**
* Implement value sharing.
*
* @see {@link cbor.schmorp.de/value-sharing}
*/
class SharedValueEncoder extends Encoder {
constructor(opts) {
super(opts)
this.valueSharing = new ObjectRecorder()
}
/**
* @param {object} obj Object to encode.
* @param {import('./encoder').ObjectOptions} [opts] Options for encoding
* this object.
* @returns {boolean} True on success.
* @throws {Error} Loop detected.
* @ignore
*/
_pushObject(obj, opts) {
if (obj !== null) {
const shared = this.valueSharing.check(obj)
switch (shared) {
case ObjectRecorder.FIRST:
// Prefix with tag 28
this._pushTag(28)
break
case ObjectRecorder.NEVER:
// Do nothing
break
default:
return this._pushTag(29) && this._pushIntNum(shared)
}
}
return super._pushObject(obj, opts)
}
/**
* Between encoding runs, stop recording, and start outputing correct tags.
*/
stopRecording() {
this.valueSharing.stop()
}
/**
* Remove the existing recording and start over. Do this between encoding
* pairs.
*/
clearRecording() {
this.valueSharing.clear()
}
/**
* Encode one or more JavaScript objects, and return a Buffer containing the
* CBOR bytes.
*
* @param {...any} objs The objects to encode.
* @returns {Buffer} The encoded objects.
*/
static encode(...objs) {
const enc = new SharedValueEncoder()
// eslint-disable-next-line no-empty-function
enc.on('data', () => {}) // Sink all writes
for (const o of objs) {
enc.pushAny(o)
}
enc.stopRecording()
enc.removeAllListeners('data')
return enc._encodeAll(objs)
}
// eslint-disable-next-line jsdoc/require-returns-check
/**
* Encode one or more JavaScript objects canonically (slower!), and return
* a Buffer containing the CBOR bytes.
*
* @param {...any} objs The objects to encode.
* @returns {Buffer} Never.
* @throws {Error} Always. This combination doesn't work at the moment.
*/
static encodeCanonical(...objs) {
throw new Error('Cannot encode canonically in a SharedValueEncoder, which serializes objects multiple times.')
}
/**
* Encode one JavaScript object using the given options.
*
* @param {any} obj The object to encode.
* @param {import('./encoder').EncodingOptions} [options={}]
* Passed to the Encoder constructor.
* @returns {Buffer} The encoded objects.
* @static
*/
static encodeOne(obj, options) {
const enc = new SharedValueEncoder(options)
// eslint-disable-next-line no-empty-function
enc.on('data', () => {}) // Sink all writes
enc.pushAny(obj)
enc.stopRecording()
enc.removeAllListeners('data')
return enc._encodeAll([obj])
}
/**
* Encode one JavaScript object using the given options in a way that
* is more resilient to objects being larger than the highWaterMark
* number of bytes. As with the other static encode functions, this
* will still use a large amount of memory. Use a stream-based approach
* directly if you need to process large and complicated inputs.
*
* @param {any} obj The object to encode.
* @param {import('./encoder').EncodingOptions} [options={}]
* Passed to the Encoder constructor.
* @returns {Promise<Buffer>} A promise for the encoded buffer.
*/
static encodeAsync(obj, options) {
return new Promise((resolve, reject) => {
/** @type {Buffer[]} */
const bufs = []
const enc = new SharedValueEncoder(options)
// eslint-disable-next-line no-empty-function
enc.on('data', () => {})
enc.on('error', reject)
enc.on('finish', () => resolve(Buffer.concat(bufs)))
enc.pushAny(obj)
enc.stopRecording()
enc.removeAllListeners('data')
enc.on('data', buf => bufs.push(buf))
enc.pushAny(obj)
enc.end()
})
}
}
module.exports = SharedValueEncoder