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

Cannot use reusable encoders/decoders in custom codecs #195

Open
grantila opened this issue Oct 23, 2021 · 0 comments
Open

Cannot use reusable encoders/decoders in custom codecs #195

grantila opened this issue Oct 23, 2021 · 0 comments

Comments

@grantila
Copy link
Contributor

grantila commented Oct 23, 2021

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 );
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant