From 4cb8bf2731be1eb973e4befa01b291de13d33647 Mon Sep 17 00:00:00 2001 From: chenyan-dfinity Date: Sun, 9 Feb 2020 12:58:30 -0800 Subject: [PATCH 1/3] fix: bug in slebEncode --- src/userlib/js/src/idl.test.ts | 48 ++++++++++----------- src/userlib/js/src/utils/leb128.test.ts | 18 ++++++++ src/userlib/js/src/utils/leb128.ts | 55 +++++++++++-------------- 3 files changed, 68 insertions(+), 53 deletions(-) diff --git a/src/userlib/js/src/idl.test.ts b/src/userlib/js/src/idl.test.ts index 7ecb3e4587..3c39d7a8d9 100644 --- a/src/userlib/js/src/idl.test.ts +++ b/src/userlib/js/src/idl.test.ts @@ -29,11 +29,11 @@ test('IDL encoding (magic number)', () => { expect(() => IDL.decode([IDL.Nat], Buffer.from('4449444d2a'))).toThrow(/Wrong magic number:/); }); -test('IDL encoding (none)', () => { - // None - expect(() => IDL.encode([IDL.None], [undefined])).toThrow(/Invalid None argument:/); - expect(() => IDL.decode([IDL.None], Buffer.from('DIDL'))).toThrow( - /None cannot appear as an output/, +test('IDL encoding (empty)', () => { + // Empty + expect(() => IDL.encode([IDL.Empty], [undefined])).toThrow(/Invalid empty argument:/); + expect(() => IDL.decode([IDL.Empty], Buffer.from('DIDL'))).toThrow( + /Empty cannot appear as an output/, ); }); @@ -51,8 +51,8 @@ test('IDL encoding (text)', () => { '4449444c016e7101000107486920e298830a', 'Nested text with unicode', ); - expect(() => IDL.encode([IDL.Text], [0])).toThrow(/Invalid Text argument/); - expect(() => IDL.encode([IDL.Text], [null])).toThrow(/Invalid Text argument/); + expect(() => IDL.encode([IDL.Text], [0])).toThrow(/Invalid text argument/); + expect(() => IDL.encode([IDL.Text], [null])).toThrow(/Invalid text argument/); }); test('IDL encoding (int)', () => { @@ -60,6 +60,7 @@ test('IDL encoding (int)', () => { test_(IDL.Int, new BigNumber(0), '4449444c00017c00', 'Int'); test_(IDL.Int, new BigNumber(42), '4449444c00017c2a', 'Int'); test_(IDL.Int, new BigNumber(1234567890), '4449444c00017cd285d8cc04', 'Positive Int'); + test_(IDL.Int, new BigNumber('60000000000000000'), '4449444c00017c808098f4e9b5caea00', 'Positive BigInt'); test_(IDL.Int, new BigNumber(-1234567890), '4449444c00017caefaa7b37b', 'Negative Int'); test_(IDL.Opt(IDL.Int), new BigNumber(42), '4449444c016e7c0100012a', 'Nested Int'); testEncode(IDL.Opt(IDL.Int), 42, '4449444c016e7c0100012a', 'Nested Int (number)'); @@ -69,7 +70,8 @@ test('IDL encoding (nat)', () => { // Nat test_(IDL.Nat, new BigNumber(42), '4449444c00017d2a', 'Nat'); test_(IDL.Nat, new BigNumber(1234567890), '4449444c00017dd285d8cc04', 'Positive Nat'); - expect(() => IDL.encode([IDL.Nat], [-1])).toThrow(/Invalid Nat argument/); + test_(IDL.Nat, new BigNumber('60000000000000000'), '4449444c00017d808098f4e9b5ca6a', 'Positive BigInt'); + expect(() => IDL.encode([IDL.Nat], [-1])).toThrow(/Invalid nat argument/); testEncode(IDL.Opt(IDL.Int), 42, '4449444c016e7c0100012a', 'Nested Int (number)'); }); @@ -96,9 +98,9 @@ test('IDL encoding (fixed-width number)', () => { test_(IDL.Nat32, 42, '4449444c0001792a000000', 'Nat32'); test_(IDL.Nat32, 0xffffffff, '4449444c000179ffffffff', 'Nat32'); test_(IDL.Nat64, new BigNumber(1234567890), '4449444c000178d202964900000000', 'Positive Nat64'); - expect(() => IDL.encode([IDL.Nat32], [-42])).toThrow(/Invalid Nat32 argument/); - expect(() => IDL.encode([IDL.Int8], [256])).toThrow(/Invalid Int8 argument/); - expect(() => IDL.encode([IDL.Int32], [0xffffffff])).toThrow(/Invalid Int32 argument/); + expect(() => IDL.encode([IDL.Nat32], [-42])).toThrow(/Invalid nat32 argument/); + expect(() => IDL.encode([IDL.Int8], [256])).toThrow(/Invalid int8 argument/); + expect(() => IDL.encode([IDL.Int32], [0xffffffff])).toThrow(/Invalid int32 argument/); }); test('IDL encoding (tuple)', () => { @@ -110,7 +112,7 @@ test('IDL encoding (tuple)', () => { 'Pairs', ); expect(() => IDL.encode([IDL.Tuple(IDL.Int, IDL.Text)], [[0]])).toThrow( - /Invalid Record\(_0_:Int,_1_:Text\) argument/, + /Invalid record {_0_:int; _1_:text} argument/, ); }); @@ -123,9 +125,9 @@ test('IDL encoding (array)', () => { 'Array of Ints', ); expect(() => IDL.encode([IDL.Vec(IDL.Int)], [new BigNumber(0)])).toThrow( - /Invalid Vec\(Int\) argument/, + /Invalid vec int argument/, ); - expect(() => IDL.encode([IDL.Vec(IDL.Int)], [['fail']])).toThrow(/Invalid Vec\(Int\) argument/); + expect(() => IDL.encode([IDL.Vec(IDL.Int)], [['fail']])).toThrow(/Invalid vec int argument/); }); test('IDL encoding (array + tuples)', () => { @@ -196,8 +198,8 @@ test('IDL encoding (bool)', () => { // Bool test_(IDL.Bool, true, '4449444c00017e01', 'true'); test_(IDL.Bool, false, '4449444c00017e00', 'false'); - expect(() => IDL.encode([IDL.Bool], [0])).toThrow(/Invalid Bool argument/); - expect(() => IDL.encode([IDL.Bool], ['false'])).toThrow(/Invalid Bool argument/); + expect(() => IDL.encode([IDL.Bool], [0])).toThrow(/Invalid bool argument/); + expect(() => IDL.encode([IDL.Bool], ['false'])).toThrow(/Invalid bool argument/); }); test('IDL encoding (variants)', () => { @@ -205,9 +207,9 @@ test('IDL encoding (variants)', () => { const Result = IDL.Variant({ ok: IDL.Text, err: IDL.Text }); test_(Result, { ok: 'good' }, '4449444c016b029cc20171e58eb4027101000004676f6f64', 'Result ok'); test_(Result, { err: 'uhoh' }, '4449444c016b029cc20171e58eb402710100010475686f68', 'Result err'); - expect(() => IDL.encode([Result], [{}])).toThrow(/Invalid Variant\(ok:Text,err:Text\) argument/); + expect(() => IDL.encode([Result], [{}])).toThrow(/Invalid variant {ok:text; err:text} argument/); expect(() => IDL.encode([Result], [{ ok: 'ok', err: 'err' }])).toThrow( - /Invalid Variant\(ok:Text,err:Text\) argument/, + /Invalid variant {ok:text; err:text} argument/, ); // Test that nullary constructors work as expected @@ -218,16 +220,16 @@ test('IDL encoding (variants)', () => { 'Nullary constructor in variant', ); - // Test that None within variants works as expected + // Test that Empty within variants works as expected test_( - IDL.Variant({ ok: IDL.Text, err: IDL.None }), + IDL.Variant({ ok: IDL.Text, err: IDL.Empty }), { ok: 'good' }, '4449444c016b029cc20171e58eb4026f01000004676f6f64', - 'None within variants', + 'Empty within variants', ); expect(() => - IDL.encode([IDL.Variant({ ok: IDL.Text, err: IDL.None })], [{ err: 'uhoh' }]), - ).toThrow(/Invalid Variant\(ok:Text,err:None\) argument:/); + IDL.encode([IDL.Variant({ ok: IDL.Text, err: IDL.Empty })], [{ err: 'uhoh' }]), + ).toThrow(/Invalid variant {ok:text; err:empty} argument:/); // Test for option test_(IDL.Opt(IDL.Nat), null, '4449444c016e7d010000', 'Null option'); diff --git a/src/userlib/js/src/utils/leb128.test.ts b/src/userlib/js/src/utils/leb128.test.ts index e3a245e0b8..5144d20b90 100644 --- a/src/userlib/js/src/utils/leb128.test.ts +++ b/src/userlib/js/src/utils/leb128.test.ts @@ -22,6 +22,12 @@ test('leb', () => { expect(lebEncode(new BigNumber('1234567890abcdef1234567890abcdef', 16)).toString('hex')).toBe( 'ef9baf8589cf959a92deb7de8a929eabb424', ); + expect( + lebEncode(new BigNumber('2000000')).toString('hex'), + ).toBe('80897a'); + expect( + lebEncode(new BigNumber('60000000000000000')).toString('hex'), + ).toBe('808098f4e9b5ca6a'); expect(lebDecode(new Pipe(Buffer.from([0]))).toNumber()).toBe(0); expect(lebDecode(new Pipe(Buffer.from([1]))).toNumber()).toBe(1); @@ -35,9 +41,18 @@ test('sleb', () => { expect(slebEncode(-1).toString('hex')).toBe('7f'); expect(slebEncode(-123456).toString('hex')).toBe('c0bb78'); expect(slebEncode(42).toString('hex')).toBe('2a'); + expect(slebEncode(new BigNumber('1234567890abcdef1234567890abcdef', 16)).toString('hex')).toBe( + 'ef9baf8589cf959a92deb7de8a929eabb424', + ); expect( slebEncode(new BigNumber('1234567890abcdef1234567890abcdef', 16).negated()).toString('hex'), ).toBe('91e4d0faf6b0eae5eda1c8a1f5ede1d4cb5b'); + expect( + slebEncode(new BigNumber('2000000')).toString('hex'), + ).toBe('8089fa00'); + expect( + slebEncode(new BigNumber('60000000000000000')).toString('hex'), + ).toBe('808098f4e9b5caea00'); expect(slebDecode(new Pipe(Buffer.from([0x7f]))).toNumber()).toBe(-1); expect(slebDecode(new Pipe(Buffer.from([0xc0, 0xbb, 0x78]))).toNumber()).toBe(-123456); @@ -45,6 +60,9 @@ test('sleb', () => { expect( slebDecode(new Pipe(Buffer.from('91e4d0faf6b0eae5eda1c8a1f5ede1d4cb5b', 'hex'))).toString(16), ).toBe('-1234567890abcdef1234567890abcdef'); + expect( + slebDecode(new Pipe(Buffer.from('808098f4e9b5caea00', 'hex'))).toString(), + ).toBe('60000000000000000'); }); test('IntLE', () => { diff --git a/src/userlib/js/src/utils/leb128.ts b/src/userlib/js/src/utils/leb128.ts index 2e5ffd04b0..e0767f87ab 100644 --- a/src/userlib/js/src/utils/leb128.ts +++ b/src/userlib/js/src/utils/leb128.ts @@ -11,20 +11,16 @@ export function lebEncode(value: number | BigNumber): Buffer { if (value.lt(0)) { throw new Error('Cannot leb encode negative values.'); } - if (value.eq(0)) { - // Clamp to 0. - return Buffer.from([0]); - } const pipe = new Pipe(); - while (value.gt(0)) { + while (true) { const i = value.mod(0x80).toNumber(); value = value.idiv(0x80); - - if (value.gt(0)) { - pipe.write([i | 0x80]); - } else { + if (value.eq(0)) { pipe.write([i]); + break; + } else { + pipe.write([i | 0x80]); } } @@ -49,35 +45,34 @@ export function slebEncode(value: BigNumber | number): Buffer { if (typeof value === 'number') { value = new BigNumber(value); } + value = value.integerValue(); - if (value.gte(0)) { - return lebEncode(value); - } - - value = value - .abs() - .integerValue() - .minus(1); - - // We need to special case 0, as it would return an empty buffer. Since - // we removed 1 above, this is really -1. - if (value.eq(0)) { - return Buffer.from([0x7f]); + const isNeg = value.lt(0); + if (isNeg) { + value = value.abs().minus(1); } - const pipe = new Pipe(); - while (value.gt(0)) { - // We swap the bits here again, and remove 1 to do two's complement. - const i = 0x80 - value.mod(0x80).toNumber() - 1; + while (true) { + const i = getLowerBytes(value); value = value.idiv(0x80); - - if (value.gt(0)) { - pipe.write([i | 0x80]); - } else { + if ((isNeg && value.eq(0) && (i & 0x40) !== 0) || + (!isNeg && value.eq(0) && (i & 0x40) === 0)) { pipe.write([i]); + break; + } else { + pipe.write([i | 0x80]); } } + function getLowerBytes(num: BigNumber): number { + const bytes = num.mod(0x80).toNumber(); + if (isNeg) { + // We swap the bits here again, and remove 1 to do two's complement. + return 0x80 - bytes - 1; + } else { + return bytes; + } + } return pipe.buffer; } From f8d4907532c0f526535fbbc5a5523c2849db09db Mon Sep 17 00:00:00 2001 From: chenyan-dfinity Date: Sun, 9 Feb 2020 13:06:40 -0800 Subject: [PATCH 2/3] fix --- src/userlib/js/src/idl.test.ts | 46 +++++++++++++++++----------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/userlib/js/src/idl.test.ts b/src/userlib/js/src/idl.test.ts index 3c39d7a8d9..6a070a9030 100644 --- a/src/userlib/js/src/idl.test.ts +++ b/src/userlib/js/src/idl.test.ts @@ -29,11 +29,11 @@ test('IDL encoding (magic number)', () => { expect(() => IDL.decode([IDL.Nat], Buffer.from('4449444d2a'))).toThrow(/Wrong magic number:/); }); -test('IDL encoding (empty)', () => { - // Empty - expect(() => IDL.encode([IDL.Empty], [undefined])).toThrow(/Invalid empty argument:/); - expect(() => IDL.decode([IDL.Empty], Buffer.from('DIDL'))).toThrow( - /Empty cannot appear as an output/, +test('IDL encoding (none)', () => { + // None + expect(() => IDL.encode([IDL.None], [undefined])).toThrow(/Invalid None argument:/); + expect(() => IDL.decode([IDL.None], Buffer.from('DIDL'))).toThrow( + /None cannot appear as an output/, ); }); @@ -51,8 +51,8 @@ test('IDL encoding (text)', () => { '4449444c016e7101000107486920e298830a', 'Nested text with unicode', ); - expect(() => IDL.encode([IDL.Text], [0])).toThrow(/Invalid text argument/); - expect(() => IDL.encode([IDL.Text], [null])).toThrow(/Invalid text argument/); + expect(() => IDL.encode([IDL.Text], [0])).toThrow(/Invalid Text argument/); + expect(() => IDL.encode([IDL.Text], [null])).toThrow(/Invalid Text argument/); }); test('IDL encoding (int)', () => { @@ -71,7 +71,7 @@ test('IDL encoding (nat)', () => { test_(IDL.Nat, new BigNumber(42), '4449444c00017d2a', 'Nat'); test_(IDL.Nat, new BigNumber(1234567890), '4449444c00017dd285d8cc04', 'Positive Nat'); test_(IDL.Nat, new BigNumber('60000000000000000'), '4449444c00017d808098f4e9b5ca6a', 'Positive BigInt'); - expect(() => IDL.encode([IDL.Nat], [-1])).toThrow(/Invalid nat argument/); + expect(() => IDL.encode([IDL.Nat], [-1])).toThrow(/Invalid Nat argument/); testEncode(IDL.Opt(IDL.Int), 42, '4449444c016e7c0100012a', 'Nested Int (number)'); }); @@ -98,9 +98,9 @@ test('IDL encoding (fixed-width number)', () => { test_(IDL.Nat32, 42, '4449444c0001792a000000', 'Nat32'); test_(IDL.Nat32, 0xffffffff, '4449444c000179ffffffff', 'Nat32'); test_(IDL.Nat64, new BigNumber(1234567890), '4449444c000178d202964900000000', 'Positive Nat64'); - expect(() => IDL.encode([IDL.Nat32], [-42])).toThrow(/Invalid nat32 argument/); - expect(() => IDL.encode([IDL.Int8], [256])).toThrow(/Invalid int8 argument/); - expect(() => IDL.encode([IDL.Int32], [0xffffffff])).toThrow(/Invalid int32 argument/); + expect(() => IDL.encode([IDL.Nat32], [-42])).toThrow(/Invalid Nat32 argument/); + expect(() => IDL.encode([IDL.Int8], [256])).toThrow(/Invalid Int8 argument/); + expect(() => IDL.encode([IDL.Int32], [0xffffffff])).toThrow(/Invalid Int32 argument/); }); test('IDL encoding (tuple)', () => { @@ -112,7 +112,7 @@ test('IDL encoding (tuple)', () => { 'Pairs', ); expect(() => IDL.encode([IDL.Tuple(IDL.Int, IDL.Text)], [[0]])).toThrow( - /Invalid record {_0_:int; _1_:text} argument/, + /Invalid Record\(_0_:Int,_1_:Text\) argument/, ); }); @@ -125,9 +125,9 @@ test('IDL encoding (array)', () => { 'Array of Ints', ); expect(() => IDL.encode([IDL.Vec(IDL.Int)], [new BigNumber(0)])).toThrow( - /Invalid vec int argument/, + /Invalid Vec\(Int\) argument/, ); - expect(() => IDL.encode([IDL.Vec(IDL.Int)], [['fail']])).toThrow(/Invalid vec int argument/); + expect(() => IDL.encode([IDL.Vec(IDL.Int)], [['fail']])).toThrow(/Invalid Vec\(Int\) argument/); }); test('IDL encoding (array + tuples)', () => { @@ -198,8 +198,8 @@ test('IDL encoding (bool)', () => { // Bool test_(IDL.Bool, true, '4449444c00017e01', 'true'); test_(IDL.Bool, false, '4449444c00017e00', 'false'); - expect(() => IDL.encode([IDL.Bool], [0])).toThrow(/Invalid bool argument/); - expect(() => IDL.encode([IDL.Bool], ['false'])).toThrow(/Invalid bool argument/); + expect(() => IDL.encode([IDL.Bool], [0])).toThrow(/Invalid Bool argument/); + expect(() => IDL.encode([IDL.Bool], ['false'])).toThrow(/Invalid Bool argument/); }); test('IDL encoding (variants)', () => { @@ -207,9 +207,9 @@ test('IDL encoding (variants)', () => { const Result = IDL.Variant({ ok: IDL.Text, err: IDL.Text }); test_(Result, { ok: 'good' }, '4449444c016b029cc20171e58eb4027101000004676f6f64', 'Result ok'); test_(Result, { err: 'uhoh' }, '4449444c016b029cc20171e58eb402710100010475686f68', 'Result err'); - expect(() => IDL.encode([Result], [{}])).toThrow(/Invalid variant {ok:text; err:text} argument/); + expect(() => IDL.encode([Result], [{}])).toThrow(/Invalid Variant\(ok:Text,err:Text\) argument/); expect(() => IDL.encode([Result], [{ ok: 'ok', err: 'err' }])).toThrow( - /Invalid variant {ok:text; err:text} argument/, + /Invalid Variant\(ok:Text,err:Text\) argument/, ); // Test that nullary constructors work as expected @@ -220,16 +220,16 @@ test('IDL encoding (variants)', () => { 'Nullary constructor in variant', ); - // Test that Empty within variants works as expected + // Test that None within variants works as expected test_( - IDL.Variant({ ok: IDL.Text, err: IDL.Empty }), + IDL.Variant({ ok: IDL.Text, err: IDL.None }), { ok: 'good' }, '4449444c016b029cc20171e58eb4026f01000004676f6f64', - 'Empty within variants', + 'None within variants', ); expect(() => - IDL.encode([IDL.Variant({ ok: IDL.Text, err: IDL.Empty })], [{ err: 'uhoh' }]), - ).toThrow(/Invalid variant {ok:text; err:empty} argument:/); + IDL.encode([IDL.Variant({ ok: IDL.Text, err: IDL.None })], [{ err: 'uhoh' }]), + ).toThrow(/Invalid Variant\(ok:Text,err:None\) argument:/); // Test for option test_(IDL.Opt(IDL.Nat), null, '4449444c016e7d010000', 'Null option'); From 3922cc077dfc7941f3d7632a951344c456315fe7 Mon Sep 17 00:00:00 2001 From: chenyan-dfinity Date: Sun, 9 Feb 2020 13:08:59 -0800 Subject: [PATCH 3/3] fix --- src/userlib/js/src/idl.test.ts | 14 ++++++++++++-- src/userlib/js/src/utils/leb128.test.ts | 8 ++++---- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/userlib/js/src/idl.test.ts b/src/userlib/js/src/idl.test.ts index 6a070a9030..8bd658873a 100644 --- a/src/userlib/js/src/idl.test.ts +++ b/src/userlib/js/src/idl.test.ts @@ -60,7 +60,12 @@ test('IDL encoding (int)', () => { test_(IDL.Int, new BigNumber(0), '4449444c00017c00', 'Int'); test_(IDL.Int, new BigNumber(42), '4449444c00017c2a', 'Int'); test_(IDL.Int, new BigNumber(1234567890), '4449444c00017cd285d8cc04', 'Positive Int'); - test_(IDL.Int, new BigNumber('60000000000000000'), '4449444c00017c808098f4e9b5caea00', 'Positive BigInt'); + test_( + IDL.Int, + new BigNumber('60000000000000000'), + '4449444c00017c808098f4e9b5caea00', + 'Positive BigInt', + ); test_(IDL.Int, new BigNumber(-1234567890), '4449444c00017caefaa7b37b', 'Negative Int'); test_(IDL.Opt(IDL.Int), new BigNumber(42), '4449444c016e7c0100012a', 'Nested Int'); testEncode(IDL.Opt(IDL.Int), 42, '4449444c016e7c0100012a', 'Nested Int (number)'); @@ -70,7 +75,12 @@ test('IDL encoding (nat)', () => { // Nat test_(IDL.Nat, new BigNumber(42), '4449444c00017d2a', 'Nat'); test_(IDL.Nat, new BigNumber(1234567890), '4449444c00017dd285d8cc04', 'Positive Nat'); - test_(IDL.Nat, new BigNumber('60000000000000000'), '4449444c00017d808098f4e9b5ca6a', 'Positive BigInt'); + test_( + IDL.Nat, + new BigNumber('60000000000000000'), + '4449444c00017d808098f4e9b5ca6a', + 'Positive BigInt', + ); expect(() => IDL.encode([IDL.Nat], [-1])).toThrow(/Invalid Nat argument/); testEncode(IDL.Opt(IDL.Int), 42, '4449444c016e7c0100012a', 'Nested Int (number)'); }); diff --git a/src/userlib/js/src/utils/leb128.test.ts b/src/userlib/js/src/utils/leb128.test.ts index 5144d20b90..88bf6ef46a 100644 --- a/src/userlib/js/src/utils/leb128.test.ts +++ b/src/userlib/js/src/utils/leb128.test.ts @@ -24,10 +24,10 @@ test('leb', () => { ); expect( lebEncode(new BigNumber('2000000')).toString('hex'), - ).toBe('80897a'); + ).toBe('80897a'); expect( lebEncode(new BigNumber('60000000000000000')).toString('hex'), - ).toBe('808098f4e9b5ca6a'); + ).toBe('808098f4e9b5ca6a'); expect(lebDecode(new Pipe(Buffer.from([0]))).toNumber()).toBe(0); expect(lebDecode(new Pipe(Buffer.from([1]))).toNumber()).toBe(1); @@ -43,13 +43,13 @@ test('sleb', () => { expect(slebEncode(42).toString('hex')).toBe('2a'); expect(slebEncode(new BigNumber('1234567890abcdef1234567890abcdef', 16)).toString('hex')).toBe( 'ef9baf8589cf959a92deb7de8a929eabb424', - ); + ); expect( slebEncode(new BigNumber('1234567890abcdef1234567890abcdef', 16).negated()).toString('hex'), ).toBe('91e4d0faf6b0eae5eda1c8a1f5ede1d4cb5b'); expect( slebEncode(new BigNumber('2000000')).toString('hex'), - ).toBe('8089fa00'); + ).toBe('8089fa00'); expect( slebEncode(new BigNumber('60000000000000000')).toString('hex'), ).toBe('808098f4e9b5caea00');