From 57ccd02816d1c36d0e49c759d5b5c24d39c4c6de Mon Sep 17 00:00:00 2001 From: Geoff Gustafson Date: Mon, 19 Mar 2018 12:25:29 -0700 Subject: [PATCH] add encodeMessage util w/ tests and a little refactoring (#11) JerryScript-DCO-1.0-Signed-off-by: Geoff Gustafson geoff@linux.intel.com --- .../src/lib/__tests__/utils.test.ts | 136 +++++++++++++++++- jerry-debugger/src/lib/utils.ts | 128 +++++++++++++++-- 2 files changed, 253 insertions(+), 11 deletions(-) diff --git a/jerry-debugger/src/lib/__tests__/utils.test.ts b/jerry-debugger/src/lib/__tests__/utils.test.ts index 0d68abf49a..180dca2fb0 100644 --- a/jerry-debugger/src/lib/__tests__/utils.test.ts +++ b/jerry-debugger/src/lib/__tests__/utils.test.ts @@ -12,7 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { getFormatSize, decodeMessage, cesu8ToString, assembleUint8Arrays } from '../utils'; +import { getFormatSize, getUint32, setUint32, decodeMessage, encodeMessage, + cesu8ToString, assembleUint8Arrays } from '../utils'; const defConfig = { cpointerSize: 2, @@ -59,6 +60,43 @@ describe('getFormatSize', () => { }); }); +describe('getUint32', () => { + it('reads little endian values', () => { + const array = Uint8Array.from([0xef, 0xbe, 0xad, 0xde]); + expect(getUint32(true, array, 0)).toEqual(0xdeadbeef); + }); + + it('reads big endian values', () => { + const array = Uint8Array.from([0xde, 0xad, 0xbe, 0xef]); + expect(getUint32(false, array, 0)).toEqual(0xdeadbeef); + }); + + it('reads at an offset', () => { + const array = Uint8Array.from([0x00, 0x00, 0xde, 0xad, 0xbe, 0xef]); + expect(getUint32(false, array, 2)).toEqual(0xdeadbeef); + }); +}); + +describe('setUint32', () => { + it('writes little endian values', () => { + const array = new Uint8Array(4); + setUint32(true, array, 0, 0xdeadbeef); + expect(array).toEqual(Uint8Array.from([0xef, 0xbe, 0xad, 0xde])); + }); + + it('writes big endian values', () => { + const array = new Uint8Array(4); + setUint32(false, array, 0, 0xdeadbeef); + expect(array).toEqual(Uint8Array.from([0xde, 0xad, 0xbe, 0xef])); + }); + + it('writes at an offset', () => { + const array = new Uint8Array(6); + setUint32(false, array, 2, 0xdeadbeef); + expect(array).toEqual(Uint8Array.from([0x00, 0x00, 0xde, 0xad, 0xbe, 0xef])); + }); +}); + describe('decodeMessage', () => { it('throws if message too short', () => { const array = Uint8Array.from([0, 1, 2]); @@ -129,6 +167,102 @@ describe('decodeMessage', () => { }); }); +describe('encodeMessage', () => { + it('throws if value list too short', () => { + expect(() => { + encodeMessage(defConfig, 'BI', [42]); + }).toThrow(); + }); + + it('throws on unexpected format character', () => { + expect(() => { + encodeMessage(defConfig, 'Q', [42]); + }).toThrow(); + }); + + it('encodes a byte with B character', () => { + const array = encodeMessage(defConfig, 'B', [42]); + expect(array).toEqual(Uint8Array.from([42])); + }); + + it('throws on byte outside range', () => { + expect(() => { + encodeMessage(defConfig, 'B', [-1]); + }).toThrow(); + expect(() => { + encodeMessage(defConfig, 'B', [0x100]); + }).toThrow(); + }); + + it('encodes two bytes for C with default config', () => { + const array = encodeMessage(defConfig, 'C', [1 + (2 << 8)]); + expect(array).toEqual(Uint8Array.from([1, 2])); + }); + + it('encodes two bytes for C with big endian', () => { + const array = encodeMessage({ + cpointerSize: 2, + littleEndian: false, + }, 'C', [(1 << 8) + 2]); + expect(array).toEqual(Uint8Array.from([1, 2])); + }); + + it('throws on two bytes outside range', () => { + expect(() => { + encodeMessage(defConfig, 'C', [-1]); + }).toThrow(); + expect(() => { + encodeMessage(defConfig, 'C', [0x10000]); + }).toThrow(); + }); + + it('encodes four bytes for C with default config', () => { + const array = encodeMessage(altConfig, 'C', [1 + (2 << 8) + (3 << 16) + (4 << 24)]); + expect(array).toEqual(Uint8Array.from([1, 2, 3, 4])); + }); + + it('encodes four bytes for C with big endian', () => { + const array = encodeMessage({ + cpointerSize: 4, + littleEndian: false, + }, 'C', [(1 << 24) + (2 << 16) + (3 << 8) + 4]); + expect(array).toEqual(Uint8Array.from([1, 2, 3, 4])); + }); + + it('throws on float', () => { + expect(() => { + encodeMessage(defConfig, 'I', [4.2]); + }).toThrow(); + }); + + it('handles multiple format characters', () => { + const array = encodeMessage(defConfig, 'IBC', [ + 1 + (2 << 8) + (3 << 16) + (4 << 24), + 5, + 6 + (7 << 8), + ]); + expect(array).toEqual(Uint8Array.from([1, 2, 3, 4, 5, 6, 7])); + }); + + it('throws on byte outside range', () => { + expect(() => { + encodeMessage({ + cpointerSize: 6, + littleEndian: true, + }, 'C', [42]); + }).toThrow(); + }); + + it('throws on unexpected pointer size', () => { + expect(() => { + encodeMessage({ + cpointerSize: 6, + littleEndian: true, + }, 'C', [42]); + }).toThrow(); + }); +}); + describe('cesu8ToString', () => { it('returns empty string for undefined input', () => { expect(cesu8ToString(undefined)).toEqual(''); diff --git a/jerry-debugger/src/lib/utils.ts b/jerry-debugger/src/lib/utils.ts index 9b42140f0e..0744649e00 100644 --- a/jerry-debugger/src/lib/utils.ts +++ b/jerry-debugger/src/lib/utils.ts @@ -43,6 +43,61 @@ export function getFormatSize(config: ByteConfig, format: string) { return length; } +/** + * Returns a 32-bit integer read from array at offset, in either direction + * + * @param littleEndian Byte order to use + * @param array Array with length >= offset + 3 + * @param offset Offset at which to start reading + */ +export function getUint32(littleEndian: boolean, array: Uint8Array, offset: number) { + let value = 0; + if (littleEndian) { + value = array[offset]; + value |= array[offset + 1] << 8; + value |= array[offset + 2] << 16; + value |= array[offset + 3] << 24; + } else { + value = array[offset] << 24; + value |= array[offset + 1] << 16; + value |= array[offset + 2] << 8; + value |= array[offset + 3]; + } + return value >>> 0; +} + +/** + * Writes a 32-bit integer to array at offset, in either direction + * + * @param littleEndian Byte order to use + * @param array Array with length >= offset + 3 + * @param offset Offset at which to start writing + * @param value Value to write in 32-bit integer range + */ +export function setUint32(littleEndian: boolean, array: Uint8Array, offset: number, value: number) { + if (littleEndian) { + array[offset] = value & 0xff; + array[offset + 1] = (value >> 8) & 0xff; + array[offset + 2] = (value >> 16) & 0xff; + array[offset + 3] = (value >> 24) & 0xff; + } else { + array[offset] = (value >> 24) & 0xff; + array[offset + 1] = (value >> 16) & 0xff; + array[offset + 2] = (value >> 8) & 0xff; + array[offset + 3] = value & 0xff; + } +} + +/** + * Parses values out of message array matching format + * + * Throws if message not big enough for specified format or bogus format character + * + * @param config Byte order / size info + * @param format String of 'B', 'C', and 'I' characters + * @param message Array containing message + * @param offset Optional offset at which to start reading + */ export function decodeMessage(config: ByteConfig, format: string, message: Uint8Array, offset = 0) { // Format: B=byte I=int32 C=cpointer // Returns an array of decoded numbers @@ -56,8 +111,7 @@ export function decodeMessage(config: ByteConfig, format: string, message: Uint8 for (let i = 0; i < format.length; i++) { if (format[i] === 'B') { - result.push(message[offset]); - offset++; + result.push(message[offset++]); continue; } @@ -77,14 +131,7 @@ export function decodeMessage(config: ByteConfig, format: string, message: Uint8 throw new Error('unexpected decode request'); } - if (config.littleEndian) { - value = (message[offset] | (message[offset + 1] << 8) - | (message[offset + 2] << 16) | (message[offset + 3] << 24)); - } else { - value = ((message[offset] << 24) | (message[offset + 1] << 16) - | (message[offset + 2] << 8) | message[offset + 3]); - } - + value = getUint32(config.littleEndian, message, offset); result.push(value); offset += 4; } @@ -92,6 +139,67 @@ export function decodeMessage(config: ByteConfig, format: string, message: Uint8 return result; } +/** + * Packs values into new array according to format + * + * Throws if not enough values supplied or values exceed expected integer ranges + * + * @param config Byte order / size info + * @param format String of 'B', 'C', and 'I' characters + * @param values Array of values to format into message + */ +export function encodeMessage(config: ByteConfig, format: string, values: Array) { + const length = getFormatSize(config, format); + const message = new Uint8Array(length); + let offset = 0; + + if (values.length < format.length) { + throw new Error('not enough values supplied'); + } + + for (let i = 0; i < format.length; i++) { + const value = values[i]; + + if (format[i] === 'B') { + if ((value & 0xff) !== value) { + throw new Error('expected byte value'); + } + message[offset++] = value; + continue; + } + + if (format[i] === 'C' && config.cpointerSize === 2) { + if ((value & 0xffff) !== value) { + throw new Error('expected two-byte value'); + } + const lowByte = value & 0xff; + const highByte = (value >> 8) & 0xff; + + if (config.littleEndian) { + message[offset++] = lowByte; + message[offset++] = highByte; + } else { + message[offset++] = highByte; + message[offset++] = lowByte; + } + continue; + } + + if (format[i] !== 'I' && (format[i] !== 'C' || config.cpointerSize !== 4)) { + throw new Error('unexpected encode request'); + } + + if ((value & 0xffffffff) !== value) { + throw new Error('expected four-byte value'); + } + + setUint32(config.littleEndian, message, offset, value); + offset += 4; + } + + return message; +} + export function cesu8ToString(array: Uint8Array | undefined) { if (!array) { return '';