Skip to content

Commit 660244f

Browse files
committed
Support re-encoding of indefinite length types.
1 parent a8ef583 commit 660244f

File tree

3 files changed

+137
-16
lines changed

3 files changed

+137
-16
lines changed

decode.js

+57-2
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,49 @@ try {
5050
inlineObjectReadThreshold = Infinity
5151
}
5252

53+
// required in order to make toJSON work with String proxy
54+
function stringProxy(strings) {
55+
const combinedString =strings.join('');
56+
const stringObject =new String(combinedString)
57+
// const stringObject = {value:combinedString}
58+
const handlers ={
59+
toJSON : () => combinedString,
60+
valueOf : () => combinedString,
61+
toString : () => combinedString,
62+
_cbor_indef: strings
63+
}
64+
return new Proxy(stringObject, {
65+
get(target, prop) {
66+
if (typeof combinedString[prop] === 'function') {
67+
if(prop == 'constructor'){
68+
return target[prop]
69+
}
70+
return (combinedString[prop]).bind(combinedString);
71+
}
72+
if(handlers[prop]){
73+
return handlers[prop]
74+
}
75+
return combinedString[prop];
76+
}
77+
});
78+
}
79+
80+
function indefWrapper(target, extra_prop, value) {
81+
return new Proxy(target, {
82+
get(target, prop) {
83+
if (typeof target[prop] === 'function') {
84+
if(prop == 'constructor'){
85+
return target[prop]
86+
}
87+
return target[prop].bind(target);
88+
}
89+
if (prop === extra_prop) {
90+
return value;
91+
}
92+
return target[prop];
93+
}
94+
});
95+
}
5396

5497

5598
export class Decoder {
@@ -69,6 +112,7 @@ export class Decoder {
69112
this.mapKey = new Map()
70113
for (let [k,v] of Object.entries(options.keyMap)) this.mapKey.set(v,k)
71114
}
115+
options.preserveIndefiniteLength = options.preserveIndefiniteLength === true ? true:false
72116
}
73117
Object.assign(this, options)
74118
}
@@ -296,15 +340,24 @@ export function read() {
296340
switch(majorType) {
297341
case 2: // byte string
298342
case 3: // text string
299-
throw new Error('Indefinite length not supported for byte or text strings')
300343
case 4: // array
301344
let array = []
302345
let value, i = 0
303346
while ((value = read()) != STOP_CODE) {
304347
if (i >= maxArraySize) throw new Error(`Array length exceeds ${maxArraySize}`)
305348
array[i++] = value
306349
}
350+
if(currentDecoder.preserveIndefiniteLength){// decoded data for later re-encode
351+
if (majorType == 2)
352+
return indefWrapper(Buffer.concat(array),"_cbor_indef",array)
353+
else if (majorType == 4)
354+
return indefWrapper(array,"_cbor_indef",true)
355+
else if (majorType == 3)
356+
return stringProxy(array)
357+
}
358+
307359
return majorType == 4 ? array : majorType == 3 ? array.join('') : Buffer.concat(array)
360+
308361
case 5: // map
309362
let key
310363
if (currentDecoder.mapsAsObjects) {
@@ -347,7 +400,7 @@ export function read() {
347400
map.set(key, read())
348401
}
349402
}
350-
return map
403+
return currentDecoder.preserveIndefiniteLength?indefWrapper(map,"_cbor_indef",true):map
351404
}
352405
case 7:
353406
return STOP_CODE
@@ -800,6 +853,8 @@ function shortStringInJS(length) {
800853
}
801854

802855
function readBin(length) {
856+
console.log('readingbinlen',length)
857+
803858
return currentDecoder.copyBuffers ?
804859
// specifically use the copying slice (not the node one)
805860
Uint8Array.prototype.slice.call(src, position, position += length) :

encode.js

+50-14
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,18 @@ export class Encoder extends Decoder {
447447
position += 8
448448
}
449449
} else if (type === 'object') {
450+
if( value instanceof String){
451+
if (value._cbor_indef && this.preserveIndefiniteLength){
452+
target[position++]= 0x7f
453+
value._cbor_indef.forEach(str=>{
454+
encode(str)
455+
})
456+
target[position++]=0xff
457+
}else{
458+
encode(String(value))
459+
}
460+
return
461+
}
450462
if (!value)
451463
target[position++] = 0xf6
452464
else {
@@ -471,24 +483,38 @@ export class Encoder extends Decoder {
471483
if (constructor === Object) {
472484
writeObject(value)
473485
} else if (constructor === Array) {
486+
const write=()=>{
487+
for (let i = 0; i < length; i++) {
488+
encode(value[i])
489+
}
490+
}
474491
length = value.length
492+
if (value._cbor_indef && this.preserveIndefiniteLength){
493+
target[position++]=0x9f
494+
write()
495+
target[position++]= 0xff
496+
return
497+
}
475498
if (length < 0x18) {
476499
target[position++] = 0x80 | length
477500
} else {
478501
writeArrayHeader(length)
479502
}
480-
for (let i = 0; i < length; i++) {
481-
encode(value[i])
482-
}
503+
write()
483504
} else if (constructor === Map) {
505+
484506
if (this.mapsAsObjects ? this.useTag259ForMaps !== false : this.useTag259ForMaps) {
485507
// use Tag 259 (https://github.com/shanewholloway/js-cbor-codec/blob/master/docs/CBOR-259-spec--explicit-maps.md) for maps if the user wants it that way
486508
target[position++] = 0xd9
487509
target[position++] = 1
488510
target[position++] = 3
489511
}
490512
length = value.size
491-
if (length < 0x18) {
513+
514+
if(encoder.preserveIndefiniteLength && value._cbor_indef){
515+
target[position++]=0xbf
516+
}
517+
else if (length < 0x18) {
492518
target[position++] = 0xa0 | length
493519
} else if (length < 0x100) {
494520
target[position++] = 0xb8
@@ -513,6 +539,10 @@ export class Encoder extends Decoder {
513539
encode(entryValue)
514540
}
515541
}
542+
if(encoder.preserveIndefiniteLength && value._cbor_indef){
543+
target[position++]=0xff
544+
}
545+
516546
} else {
517547
for (let i = 0, l = extensions.length; i < l; i++) {
518548
let extensionClass = extensionClasses[i]
@@ -988,8 +1018,8 @@ function isBlob(object) {
9881018
return tag === 'Blob' || tag === 'File';
9891019
}
9901020
function findRepetitiveStrings(value, packedValues) {
991-
switch(typeof value) {
992-
case 'string':
1021+
const valType = typeof value
1022+
if (valType == 'string' || value instanceof String){
9931023
if (value.length > 3) {
9941024
if (packedValues.objectMap[value] > -1 || packedValues.values.length >= packedValues.maxValues)
9951025
return
@@ -1013,9 +1043,8 @@ function findRepetitiveStrings(value, packedValues) {
10131043
}
10141044
}
10151045
}
1016-
break
1017-
case 'object':
1018-
if (value) {
1046+
}
1047+
else if (valType === 'object'){
10191048
if (value instanceof Array) {
10201049
for (let i = 0, l = value.length; i < l; i++) {
10211050
findRepetitiveStrings(value[i], packedValues)
@@ -1031,10 +1060,9 @@ function findRepetitiveStrings(value, packedValues) {
10311060
}
10321061
}
10331062
}
1034-
}
1035-
break
1036-
case 'function': console.log(value)
1037-
}
1063+
}
1064+
else if(valType ==='function')
1065+
console.log(value)
10381066
}
10391067
const isLittleEndianMachine = new Uint8Array(new Uint16Array([1]).buffer)[0] == 1
10401068
extensionClasses = [ Date, Set, Error, RegExp, Tag, ArrayBuffer,
@@ -1095,7 +1123,7 @@ extensions = [{ // Date
10951123
} // else no tag
10961124
},
10971125
encode(typedArray, encode, makeRoom) {
1098-
writeBuffer(typedArray, makeRoom)
1126+
writeBuffer.bind(this)(typedArray, makeRoom)
10991127
}
11001128
},
11011129
typedArrayEncoder(68, 1),
@@ -1152,6 +1180,14 @@ function typedArrayEncoder(tag, size) {
11521180
}
11531181
function writeBuffer(buffer, makeRoom) {
11541182
let length = buffer.byteLength
1183+
if(buffer._cbor_indef && this && this.preserveIndefiniteLength){
1184+
target[position++]= 0x5f
1185+
buffer._cbor_indef.forEach(buf=>{
1186+
writeBuffer(buf,makeRoom)
1187+
})
1188+
target[position++] = 0xff
1189+
return;
1190+
}
11551191
if (length < 0x18) {
11561192
target[position++] = 0x40 + length
11571193
} else if (length < 0x100) {

tests/test.js

+30
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ function tryRequire(module) {
2525
var assert = chai.assert
2626

2727
var Encoder = CBOR.Encoder
28+
var Decoder = CBOR.Encoder
29+
2830
var EncoderStream = CBOR.EncoderStream
2931
var DecoderStream = CBOR.DecoderStream
3032
var decode = CBOR.decode
@@ -47,6 +49,10 @@ try {
4749

4850
var ITERATIONS = 4000
4951

52+
if(process.env.LOG_LEVEL !=='DEBUG' ){
53+
console.debug=()=>{}
54+
}
55+
5056
suite('CBOR basic tests', function(){
5157
test('encode/decode with keyMaps (basic)', function() {
5258
var data = senmlData
@@ -746,6 +752,30 @@ suite('CBOR basic tests', function(){
746752
var deserialized = decode(serialized)
747753
assert.deepEqual(deserialized, data)
748754
})
755+
test ('re-encode indefinite length',()=>{
756+
const decoder=new Decoder({preserveIndefiniteLength: true})
757+
const encoder=new Encoder({preserveIndefiniteLength: true})
758+
759+
760+
const data=Buffer.from('9F81007F6563626F72786563626F7278FF5F43ABCDEFFF7F6563626F7278FFBF6563626F7278F5FFFF','hex')
761+
console.debug("re-encode indefinite length:initial ",data.toString('hex'))
762+
763+
const decoded = decoder.decode(data)
764+
console.debug("re-encode indefinite length:decoded ",decoded)
765+
766+
const reEncoded= encoder.encode(decoded)
767+
console.debug("re-encode indefinite length:re-encoded",reEncoded.toString('hex'))
768+
769+
const defaultEncoded = CBOR.encode(decoded)
770+
console.debug("re-encode not preserved: encoded ",defaultEncoded.toString('hex'))
771+
assert.equal(defaultEncoded.toString('hex'),'8581006a63626f727863626f727843abcdef6563626f7278d90103a16563626f7278f5')
772+
773+
774+
const reDecoded= decoder.decode(reEncoded)
775+
console.debug("re-encode indefinite length:re-decoded",reDecoded)
776+
777+
assert.equal(data.toString('hex'), encoder.encode(reDecoded).toString('hex'))
778+
})
749779
test('decodeMultiple', () => {
750780
let values = CBOR.decodeMultiple(new Uint8Array([1, 2, 3, 4]))
751781
assert.deepEqual(values, [1, 2, 3, 4])

0 commit comments

Comments
 (0)