Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support re-encoding of indefinite length types. #113

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 55 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
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