Skip to content

Commit ada1204

Browse files
committed
BinaryWriter was added with some rudimentary test coverage
1 parent d57c036 commit ada1204

File tree

7 files changed

+634
-146
lines changed

7 files changed

+634
-146
lines changed

src/BinaryWriter.ts

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
import * as bigInt from 'big-integer';
2+
import {Encoding} from "./Encoding";
3+
import {writeUtf8StringFromCodePoints} from "./Utf8";
4+
5+
export class BinaryWriter
6+
{
7+
private _stream: number[];
8+
private _position: number;
9+
10+
public constructor()
11+
{
12+
this._stream = [];
13+
this._position = 0;
14+
}
15+
16+
public writeBoolean(value: boolean): void
17+
{
18+
this.writeBytes(value ? 1 : 0);
19+
}
20+
21+
public writeByte(value: number): void
22+
{
23+
this._stream[this._position++] = value;
24+
}
25+
26+
public writeSignedByte(value: number): void
27+
{
28+
this._stream[this._position++] = value < 0 ? value + 256 : value;
29+
}
30+
31+
public writeShort(value: number): void
32+
{
33+
this._stream[this._position++] = value & 0xFF;
34+
this._stream[this._position++] = (value >> 8 & 0xFF);
35+
}
36+
37+
public writeUnsignedShort(value: number): void
38+
{
39+
this._stream[this._position++] = value & 0xFF;
40+
this._stream[this._position++] = (value >> 8 & 0xFF);
41+
}
42+
43+
public writeInt(value: number): void
44+
{
45+
this._stream[this._position++] = value & 0xFF;
46+
this._stream[this._position++] = (value >> 8 & 0xFF);
47+
this._stream[this._position++] = (value >> 16 & 0xFF);
48+
this._stream[this._position++] = (value >> 24 & 0xFF);
49+
}
50+
51+
public writeUnsignedInt(value: number): void
52+
{
53+
this._stream[this._position++] = value & 0xFF;
54+
this._stream[this._position++] = (value >> 8 & 0xFF);
55+
this._stream[this._position++] = (value >> 16 & 0xFF);
56+
this._stream[this._position++] = (value >> 24 & 0xFF);
57+
}
58+
59+
public writeLong(value: number | string): void
60+
{
61+
const bigint = typeof value === "number"
62+
? bigInt(value)
63+
: bigInt(value);
64+
65+
const leftHalf = bigint.and(0xFFFFFFFF).toJSNumber();
66+
const rightHalf = bigint.shiftRight(32).and(0xFFFFFFFF).toJSNumber();
67+
68+
this._stream[this._position++] = leftHalf & 0xFF;
69+
this._stream[this._position++] = (leftHalf >> 8 & 0xFF);
70+
this._stream[this._position++] = (leftHalf >> 16 & 0xFF);
71+
this._stream[this._position++] = (leftHalf >> 24 & 0xFF);
72+
73+
this._stream[this._position++] = rightHalf & 0xFF;
74+
this._stream[this._position++] = (rightHalf >> 8 & 0xFF);
75+
this._stream[this._position++] = (rightHalf >> 16 & 0xFF);
76+
this._stream[this._position++] = (rightHalf >> 24 & 0xFF);
77+
}
78+
79+
public writeUnsignedLong(value: number | string): void
80+
{
81+
const bigint = typeof value === "number"
82+
? bigInt(value)
83+
: bigInt(value);
84+
85+
const leftHalf = bigint.and(0xFFFFFFFF).toJSNumber();
86+
const rightHalf = bigint.shiftRight(32).and(0xFFFFFFFF).toJSNumber();
87+
88+
this._stream[this._position++] = leftHalf & 0xFF;
89+
this._stream[this._position++] = (leftHalf >> 8 & 0xFF);
90+
this._stream[this._position++] = (leftHalf >> 16 & 0xFF);
91+
this._stream[this._position++] = (leftHalf >> 24 & 0xFF);
92+
93+
this._stream[this._position++] = rightHalf & 0xFF;
94+
this._stream[this._position++] = (rightHalf >> 8 & 0xFF);
95+
this._stream[this._position++] = (rightHalf >> 16 & 0xFF);
96+
this._stream[this._position++] = (rightHalf >> 24 & 0xFF);
97+
}
98+
99+
public writeFloat(value: number): void
100+
{
101+
if (Number.isNaN(value)) {
102+
this._stream[this._position++] = 0x00;
103+
this._stream[this._position++] = 0x00;
104+
this._stream[this._position++] = 0xc0;
105+
this._stream[this._position++] = 0xff;
106+
return;
107+
}
108+
109+
const floatBuffer = new Float32Array([value]);
110+
const uintBuffer = new Uint8Array(floatBuffer.buffer, floatBuffer.byteOffset, 4);
111+
112+
this._stream[this._position++] = uintBuffer[0];
113+
this._stream[this._position++] = uintBuffer[1];
114+
this._stream[this._position++] = uintBuffer[2];
115+
this._stream[this._position++] = uintBuffer[3];
116+
}
117+
118+
public writeDouble(value: number): void
119+
{
120+
if (Number.isNaN(value)) {
121+
this._stream[this._position++] = 0x00;
122+
this._stream[this._position++] = 0x00;
123+
this._stream[this._position++] = 0x00;
124+
this._stream[this._position++] = 0x00;
125+
this._stream[this._position++] = 0x00;
126+
this._stream[this._position++] = 0x00;
127+
this._stream[this._position++] = 0xf8;
128+
this._stream[this._position++] = 0xff;
129+
return;
130+
}
131+
132+
const floatBuffer = new Float64Array([value]);
133+
const uintBuffer = new Uint8Array(floatBuffer.buffer, floatBuffer.byteOffset, 8);
134+
135+
this._stream[this._position++] = uintBuffer[0];
136+
this._stream[this._position++] = uintBuffer[1];
137+
this._stream[this._position++] = uintBuffer[2];
138+
this._stream[this._position++] = uintBuffer[3];
139+
this._stream[this._position++] = uintBuffer[4];
140+
this._stream[this._position++] = uintBuffer[5];
141+
this._stream[this._position++] = uintBuffer[6];
142+
this._stream[this._position++] = uintBuffer[7];
143+
}
144+
145+
public writeChar(value: number | string, encoding: Encoding): void
146+
{
147+
this._position = writeUtf8StringFromCodePoints(this._stream, this._position, typeof value === "number" ? [value] : value);
148+
}
149+
150+
public writeChars(value: number[] | string, encoding: Encoding): void
151+
{
152+
this._position = writeUtf8StringFromCodePoints(this._stream, this._position, value);
153+
}
154+
155+
public writeString(value: number[] | string, encoding: Encoding): void
156+
{
157+
const tempBuffer: number[] = [];
158+
writeUtf8StringFromCodePoints(tempBuffer, 0, value);
159+
160+
let length = tempBuffer.length;
161+
while (length >= 0x80) {
162+
this._stream[this._position++] = (length & 0xFF) | 0x80;
163+
length >>= 7;
164+
}
165+
this._stream[this._position++] = length;
166+
167+
for(let i = 0; i < tempBuffer.length; i++) {
168+
this._stream[this._position++] = tempBuffer[i];
169+
}
170+
}
171+
172+
public clear(): void
173+
{
174+
this._position = 0;
175+
this._stream.length = 0;
176+
}
177+
178+
public toArray(): number[]
179+
{
180+
return this._stream.concat();
181+
}
182+
183+
private writeBytes(...bytes: number[]): void
184+
{
185+
for(let i = 0; i < bytes.length; i++) {
186+
this._stream[this._position++] = bytes[i];
187+
}
188+
}
189+
}

src/Utf8.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,37 @@ interface Utf8ReadResult
2929
finalPosition: number;
3030
}
3131

32+
export function writeUtf8StringFromCodePoints(buffer: number[], position: number, dataToWrite: number[] | string): number
33+
{
34+
if (typeof dataToWrite === "string") {
35+
dataToWrite = Array.from(dataToWrite).map(x => x.codePointAt(0));
36+
}
37+
38+
for(let i = 0; i < dataToWrite.length; i++) {
39+
const codepoint = dataToWrite[i];
40+
if (codepoint < 0x80) {
41+
buffer[position++] = codepoint;
42+
43+
} else if (codepoint < 0x800) {
44+
buffer[position++] = 0xc0 | (codepoint >> 6);
45+
buffer[position++] = 0x80 | (codepoint & 0x3f);
46+
}
47+
else if (codepoint < 0x10000) {
48+
buffer[position++] = 0xe0 | (codepoint >> 12);
49+
buffer[position++] = 0x80 | ((codepoint >> 6) & 0x3f);
50+
buffer[position++] = 0x80 | (codepoint & 0x3f);
51+
}
52+
else {
53+
buffer[position++] = 0xf0 | (codepoint >> 18);
54+
buffer[position++] = 0x80 | ((codepoint >> 12) & 0x3f);
55+
buffer[position++] = 0x80 | ((codepoint >> 6) & 0x3f);
56+
buffer[position++] = 0x80 | (codepoint & 0x3f);
57+
}
58+
}
59+
60+
return position;
61+
}
62+
3263
/** @ignore */
3364
export function readUtf8StringFromBytes(bytes: Uint8Array, position: number, maxCharactersToRead: number = Number.MAX_SAFE_INTEGER, maxBytesToRead: number = Number.MAX_SAFE_INTEGER): Utf8ReadResult
3465
{

test/BinaryReader.fixtures.numbers.test.ts

Lines changed: 5 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -2,63 +2,7 @@ import * as fs from 'fs';
22
import {expect} from 'chai';
33
import {BinaryReader} from "../src";
44
import {combineBuffers} from "./common";
5-
6-
type TestCallback = (data: BinaryReader) => void;
7-
8-
interface TestCallbackDictionary
9-
{
10-
[index: string]: TestCallback;
11-
}
12-
13-
interface TestCallbackDictionaryDictionary
14-
{
15-
[index: string]: TestCallbackDictionary;
16-
}
17-
18-
interface TestFileDictionary
19-
{
20-
[index: string]: string;
21-
}
22-
23-
interface TestFileDictionaryDictionary
24-
{
25-
[index: string]: TestFileDictionary;
26-
}
27-
28-
interface StreamTypesDictionary
29-
{
30-
[index: string]: ((buffer: Buffer) => BinaryReader);
31-
}
32-
33-
const testsDirectory = __dirname + "/fixtures";
34-
35-
function buildTestsFilesIndex(): TestFileDictionaryDictionary
36-
{
37-
const tests: TestFileDictionaryDictionary = {};
38-
39-
fs.readdirSync(testsDirectory).forEach(file =>
40-
{
41-
if (file.indexOf("test.common") !== 0) {
42-
return;
43-
}
44-
45-
const bits = file.split('.');
46-
if (bits.length !== 5) {
47-
throw new Error("There should be no file in test/fixtures/ directory that does not have four dots in its name.");
48-
}
49-
50-
const type = bits[2];
51-
const testName = bits[3];
52-
53-
if (!tests[type]) {
54-
tests[type] = {};
55-
}
56-
57-
tests[type][testName] = file;
58-
});
59-
60-
return tests;
61-
}
5+
import {buildTestsFilesIndex, FixturesDirectory, ReaderStreamTypesDictionary, ReaderTestCallbackDictionaryDictionary} from "./fixtureCommon";
626

637
function assertSimilarFloats(smaller: number, bigger: number): void
648
{
@@ -78,7 +22,7 @@ function assertSimilarFloats(smaller: number, bigger: number): void
7822
expect(factor).to.be.closeTo(1, 0.001);
7923
}
8024

81-
function buildTestExecutors(): TestCallbackDictionaryDictionary
25+
function buildTestExecutors(): ReaderTestCallbackDictionaryDictionary
8226
{
8327
return {
8428
bool: {
@@ -380,10 +324,10 @@ function buildTestExecutors(): TestCallbackDictionaryDictionary
380324

381325
describe("BinaryReader, number fixture tests", () =>
382326
{
383-
const testFileIndex = buildTestsFilesIndex();
327+
const testFileIndex = buildTestsFilesIndex("test.common");
384328
const testExecutors = buildTestExecutors();
385329

386-
const streamTypes: StreamTypesDictionary = {
330+
const streamTypes: ReaderStreamTypesDictionary = {
387331
'Exactly matching ArrayBuffer': (buffer: Buffer) => new BinaryReader(buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength)),
388332
'Uint8Array with exactly matching buffer inside': (buffer: Buffer) =>
389333
{
@@ -438,7 +382,7 @@ describe("BinaryReader, number fixture tests", () =>
438382
expect(testExecutors).to.have.any.keys(type);
439383
expect(testExecutors[type]).to.have.any.keys(testName);
440384

441-
const buffer = fs.readFileSync(`${testsDirectory}/${testPath}`);
385+
const buffer = fs.readFileSync(`${FixturesDirectory}/${testPath}`);
442386
const reader = binaryReaderBuilder(buffer);
443387
testExecutors[type][testName].call(null, reader);
444388
});

0 commit comments

Comments
 (0)