msgpack-javascript icon indicating copy to clipboard operation
msgpack-javascript copied to clipboard

Cannot use reusable encoders/decoders in custom codecs

Open grantila opened this issue 4 years ago • 0 comments
trafficstars

When using reusable encoders/decoders, decoding fails when using the reused decode() function in the custom decoding function.

The following example works if USE_REUSABLE_CODING is set to false, meaning the whole logic uses the global encode/decode function exported by msgpack, but when set to true, encode and decode are reusable instances of Encode/Decore correspondibly. It fails with RangeError: Extra 18 of 21 byte(s) found at buffer[3].

Does reusable encoders/decoders not work with custom codecs?

Example:

import {
	Encoder,
	Decoder,
	ExtensionCodec,
	encode as _encode,
	decode as _decode,
} from "@msgpack/msgpack"


const USE_REUSABLE_CODING = true;

export class MsgPackContext
{
	readonly context = this;
	readonly encode: ( value: unknown ) => Uint8Array;
	readonly decode: ( buffer: BufferSource | ArrayLike< number > ) => unknown;
	readonly extensionCodec = new ExtensionCodec< MsgPackContext >( );

	constructor( )
	{
		const encoder = new Encoder( this.extensionCodec, this );
		const decoder = new Decoder( this.extensionCodec, this );

		this.encode = encoder.encode.bind( encoder );
		this.decode = decoder.decode.bind( decoder );

		registerCodecs( this );
	}
}

const MSGPACK_EXT_TYPE_BIGINT = 0;
const MSGPACK_EXT_TYPE_MAP = 1;

export function registerCodecs( context: MsgPackContext )
{
	const { extensionCodec, encode, decode } = context;

	extensionCodec.register( {
		type: MSGPACK_EXT_TYPE_BIGINT,
		encode: value =>
			( typeof value === 'bigint' || value instanceof BigInt )
			?
				USE_REUSABLE_CODING
				? encode( value.toString( ) )
				: _encode( value.toString( ), context )
			: null,
		decode: data =>
			USE_REUSABLE_CODING
			? BigInt( decode( data ) as string )
			: BigInt( _decode( data, context ) as string ),
	} );

	extensionCodec.register( {
		type: MSGPACK_EXT_TYPE_MAP,
		encode: value =>
			value instanceof Map
			?
				USE_REUSABLE_CODING
				? encode( [ ...value.entries( ) ] )
				: _encode( [ ...value.entries( ) ], context )
			: null,
		decode: data =>
			USE_REUSABLE_CODING
			? new Map( decode( data ) as Array<any> )
			: new Map( _decode( data, context ) as Array<any> ),
	} );
}

const context = new MsgPackContext( );
if ( USE_REUSABLE_CODING )
{
	const buf = context.encode( { m: new Map( [ [ 'big', BigInt( 42 ) ] ] ) } );
	const data = context.decode( buf );
	console.log( data );
}
else
{
	const buf = _encode( { m: new Map( [ [ 'big', BigInt( 42 ) ] ] ) }, context );
	const data = _decode( buf, context );
	console.log( data );
}

Just to be clear, the following is the same code without global encode/decode usage (USE_REUSABLE_CODING being false). This code should work, and could be a regression unit test ☺️:

import {
	Encoder,
	Decoder,
	ExtensionCodec,
} from "@msgpack/msgpack"


export class MsgPackContext
{
	readonly encode: ( value: unknown ) => Uint8Array;
	readonly decode: ( buffer: BufferSource | ArrayLike< number > ) => unknown;
	readonly extensionCodec = new ExtensionCodec< MsgPackContext >( );

	constructor( )
	{
		const encoder = new Encoder( this.extensionCodec, this );
		const decoder = new Decoder( this.extensionCodec, this );

		this.encode = encoder.encode.bind( encoder );
		this.decode = decoder.decode.bind( decoder );

		registerCodecs( this );
	}
}

const MSGPACK_EXT_TYPE_BIGINT = 0;
const MSGPACK_EXT_TYPE_MAP = 1;

export function registerCodecs( context: MsgPackContext )
{
	const { extensionCodec, encode, decode } = context;

	extensionCodec.register( {
		type: MSGPACK_EXT_TYPE_BIGINT,
		encode: value =>
			( typeof value === 'bigint' || value instanceof BigInt )
			? encode( value.toString( ) )
			: null,
		decode: data =>
			BigInt( decode( data ) as string ),
	} );

	extensionCodec.register( {
		type: MSGPACK_EXT_TYPE_MAP,
		encode: value =>
			value instanceof Map
			? encode( [ ...value.entries( ) ] )
			: null,
		decode: data =>
			new Map( decode( data ) as Array<any> ),
	} );
}

const context = new MsgPackContext( );
const buf = context.encode( { m: new Map( [ [ 'big', BigInt( 42 ) ] ] ) } );
const data = context.decode( buf );
console.log( data );

grantila avatar Oct 23 '21 19:10 grantila