Skip to content

Commit

Permalink
Support re-encoding of indefinite length types.
Browse files Browse the repository at this point in the history
  • Loading branch information
mesudip committed Sep 10, 2024
1 parent a8ef583 commit 660244f
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 16 deletions.
59 changes: 57 additions & 2 deletions decode.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,49 @@ try {
inlineObjectReadThreshold = Infinity
}

// required in order to make toJSON work with String proxy
function stringProxy(strings) {
const combinedString =strings.join('');
const stringObject =new String(combinedString)
// const stringObject = {value:combinedString}
const handlers ={
toJSON : () => combinedString,
valueOf : () => combinedString,
toString : () => combinedString,
_cbor_indef: strings
}
return new Proxy(stringObject, {
get(target, prop) {
if (typeof combinedString[prop] === 'function') {
if(prop == 'constructor'){
return target[prop]
}
return (combinedString[prop]).bind(combinedString);
}
if(handlers[prop]){
return handlers[prop]
}
return combinedString[prop];
}
});
}

function indefWrapper(target, extra_prop, value) {
return new Proxy(target, {
get(target, prop) {
if (typeof target[prop] === 'function') {
if(prop == 'constructor'){
return target[prop]
}
return target[prop].bind(target);
}
if (prop === extra_prop) {
return value;
}
return target[prop];
}
});
}


export class Decoder {
Expand All @@ -69,6 +112,7 @@ export class Decoder {
this.mapKey = new Map()
for (let [k,v] of Object.entries(options.keyMap)) this.mapKey.set(v,k)
}
options.preserveIndefiniteLength = options.preserveIndefiniteLength === true ? true:false
}
Object.assign(this, options)
}
Expand Down Expand Up @@ -296,15 +340,24 @@ export function read() {
switch(majorType) {
case 2: // byte string
case 3: // text string
throw new Error('Indefinite length not supported for byte or text strings')
case 4: // array
let array = []
let value, i = 0
while ((value = read()) != STOP_CODE) {
if (i >= maxArraySize) throw new Error(`Array length exceeds ${maxArraySize}`)
array[i++] = value
}
if(currentDecoder.preserveIndefiniteLength){// decoded data for later re-encode
if (majorType == 2)
return indefWrapper(Buffer.concat(array),"_cbor_indef",array)
else if (majorType == 4)
return indefWrapper(array,"_cbor_indef",true)
else if (majorType == 3)
return stringProxy(array)
}

return majorType == 4 ? array : majorType == 3 ? array.join('') : Buffer.concat(array)

case 5: // map
let key
if (currentDecoder.mapsAsObjects) {
Expand Down Expand Up @@ -347,7 +400,7 @@ export function read() {
map.set(key, read())
}
}
return map
return currentDecoder.preserveIndefiniteLength?indefWrapper(map,"_cbor_indef",true):map
}
case 7:
return STOP_CODE
Expand Down Expand Up @@ -800,6 +853,8 @@ function shortStringInJS(length) {
}

function readBin(length) {
console.log('readingbinlen',length)

return currentDecoder.copyBuffers ?
// specifically use the copying slice (not the node one)
Uint8Array.prototype.slice.call(src, position, position += length) :
Expand Down
64 changes: 50 additions & 14 deletions encode.js
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,18 @@ export class Encoder extends Decoder {
position += 8
}
} else if (type === 'object') {
if( value instanceof String){
if (value._cbor_indef && this.preserveIndefiniteLength){
target[position++]= 0x7f
value._cbor_indef.forEach(str=>{
encode(str)
})
target[position++]=0xff
}else{
encode(String(value))
}
return
}
if (!value)
target[position++] = 0xf6
else {
Expand All @@ -471,24 +483,38 @@ export class Encoder extends Decoder {
if (constructor === Object) {
writeObject(value)
} else if (constructor === Array) {
const write=()=>{
for (let i = 0; i < length; i++) {
encode(value[i])
}
}
length = value.length
if (value._cbor_indef && this.preserveIndefiniteLength){
target[position++]=0x9f
write()
target[position++]= 0xff
return
}
if (length < 0x18) {
target[position++] = 0x80 | length
} else {
writeArrayHeader(length)
}
for (let i = 0; i < length; i++) {
encode(value[i])
}
write()
} else if (constructor === Map) {

if (this.mapsAsObjects ? this.useTag259ForMaps !== false : this.useTag259ForMaps) {
// 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
target[position++] = 0xd9
target[position++] = 1
target[position++] = 3
}
length = value.size
if (length < 0x18) {

if(encoder.preserveIndefiniteLength && value._cbor_indef){
target[position++]=0xbf
}
else if (length < 0x18) {
target[position++] = 0xa0 | length
} else if (length < 0x100) {
target[position++] = 0xb8
Expand All @@ -513,6 +539,10 @@ export class Encoder extends Decoder {
encode(entryValue)
}
}
if(encoder.preserveIndefiniteLength && value._cbor_indef){
target[position++]=0xff
}

} else {
for (let i = 0, l = extensions.length; i < l; i++) {
let extensionClass = extensionClasses[i]
Expand Down Expand Up @@ -988,8 +1018,8 @@ function isBlob(object) {
return tag === 'Blob' || tag === 'File';
}
function findRepetitiveStrings(value, packedValues) {
switch(typeof value) {
case 'string':
const valType = typeof value
if (valType == 'string' || value instanceof String){
if (value.length > 3) {
if (packedValues.objectMap[value] > -1 || packedValues.values.length >= packedValues.maxValues)
return
Expand All @@ -1013,9 +1043,8 @@ function findRepetitiveStrings(value, packedValues) {
}
}
}
break
case 'object':
if (value) {
}
else if (valType === 'object'){
if (value instanceof Array) {
for (let i = 0, l = value.length; i < l; i++) {
findRepetitiveStrings(value[i], packedValues)
Expand All @@ -1031,10 +1060,9 @@ function findRepetitiveStrings(value, packedValues) {
}
}
}
}
break
case 'function': console.log(value)
}
}
else if(valType ==='function')
console.log(value)
}
const isLittleEndianMachine = new Uint8Array(new Uint16Array([1]).buffer)[0] == 1
extensionClasses = [ Date, Set, Error, RegExp, Tag, ArrayBuffer,
Expand Down Expand Up @@ -1095,7 +1123,7 @@ extensions = [{ // Date
} // else no tag
},
encode(typedArray, encode, makeRoom) {
writeBuffer(typedArray, makeRoom)
writeBuffer.bind(this)(typedArray, makeRoom)
}
},
typedArrayEncoder(68, 1),
Expand Down Expand Up @@ -1152,6 +1180,14 @@ function typedArrayEncoder(tag, size) {
}
function writeBuffer(buffer, makeRoom) {
let length = buffer.byteLength
if(buffer._cbor_indef && this && this.preserveIndefiniteLength){
target[position++]= 0x5f
buffer._cbor_indef.forEach(buf=>{
writeBuffer(buf,makeRoom)
})
target[position++] = 0xff
return;
}
if (length < 0x18) {
target[position++] = 0x40 + length
} else if (length < 0x100) {
Expand Down
30 changes: 30 additions & 0 deletions tests/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ function tryRequire(module) {
var assert = chai.assert

var Encoder = CBOR.Encoder
var Decoder = CBOR.Encoder

var EncoderStream = CBOR.EncoderStream
var DecoderStream = CBOR.DecoderStream
var decode = CBOR.decode
Expand All @@ -47,6 +49,10 @@ try {

var ITERATIONS = 4000

if(process.env.LOG_LEVEL !=='DEBUG' ){
console.debug=()=>{}
}

suite('CBOR basic tests', function(){
test('encode/decode with keyMaps (basic)', function() {
var data = senmlData
Expand Down Expand Up @@ -746,6 +752,30 @@ suite('CBOR basic tests', function(){
var deserialized = decode(serialized)
assert.deepEqual(deserialized, data)
})
test ('re-encode indefinite length',()=>{
const decoder=new Decoder({preserveIndefiniteLength: true})
const encoder=new Encoder({preserveIndefiniteLength: true})


const data=Buffer.from('9F81007F6563626F72786563626F7278FF5F43ABCDEFFF7F6563626F7278FFBF6563626F7278F5FFFF','hex')
console.debug("re-encode indefinite length:initial ",data.toString('hex'))

const decoded = decoder.decode(data)
console.debug("re-encode indefinite length:decoded ",decoded)

const reEncoded= encoder.encode(decoded)
console.debug("re-encode indefinite length:re-encoded",reEncoded.toString('hex'))

const defaultEncoded = CBOR.encode(decoded)
console.debug("re-encode not preserved: encoded ",defaultEncoded.toString('hex'))
assert.equal(defaultEncoded.toString('hex'),'8581006a63626f727863626f727843abcdef6563626f7278d90103a16563626f7278f5')


const reDecoded= decoder.decode(reEncoded)
console.debug("re-encode indefinite length:re-decoded",reDecoded)

assert.equal(data.toString('hex'), encoder.encode(reDecoded).toString('hex'))
})
test('decodeMultiple', () => {
let values = CBOR.decodeMultiple(new Uint8Array([1, 2, 3, 4]))
assert.deepEqual(values, [1, 2, 3, 4])
Expand Down

0 comments on commit 660244f

Please sign in to comment.