forked from ton-core/ton-core
-
Notifications
You must be signed in to change notification settings - Fork 31
/
Copy pathBitString.ts
187 lines (162 loc) · 5.4 KB
/
BitString.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
/**
* Copyright (c) Whales Corp.
* All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import { bitsToPaddedBuffer } from "./utils/paddedBits";
import inspectSymbol from 'symbol.inspect';
/**
* BitString is a class that represents a bitstring in a buffer with a specified offset and length
*/
export class BitString {
static readonly EMPTY = new BitString(Buffer.alloc(0), 0, 0);
// NOTE: We want to hide this fields from the user, but
// using private fields would break the compatibility
// between different versions in typescript
private readonly _offset: number;
private readonly _length: number;
private readonly _data: Buffer;
/**
* Checks if supplied object is BitString
* @param src is unknow object
* @returns true if object is BitString and false otherwise
**/
static isBitString(src: unknown): src is BitString {
return src instanceof BitString;
}
/**
* Constructing BitString from a buffer
* @param data data that contains the bitstring data. NOTE: We are expecting this buffer to be NOT modified
* @param offset offset in bits from the start of the buffer
* @param length length of the bitstring in bits
*/
constructor(data: Buffer, offset: number, length: number) {
// Check bounds
if (length < 0) {
throw new Error(`Length ${length} is out of bounds`);
}
this._length = length;
this._data = data;
this._offset = offset;
}
/**
* Returns the length of the bitstring
*/
get length() {
return this._length;
}
/**
* Returns the bit at the specified index
* @param index index of the bit
* @throws Error if index is out of bounds
* @returns true if the bit is set, false otherwise
*/
at(index: number) {
// Check bounds
if (index >= this._length) {
throw new Error(`Index ${index} > ${this._length} is out of bounds`);
}
if (index < 0) {
throw new Error(`Index ${index} < 0 is out of bounds`);
}
// Calculcate offsets
let byteIndex = (this._offset + index) >> 3;
let bitIndex = 7 - ((this._offset + index) % 8); // NOTE: We are using big endian
// Return the bit
return (this._data[byteIndex] & (1 << bitIndex)) !== 0;
}
/**
* Get a subscring of the bitstring
* @param offset
* @param length
* @returns
*/
substring(offset: number, length: number) {
// Check offset
if (offset > this._length) {
throw new Error(`Offset(${offset}) > ${this._length} is out of bounds`);
}
if (offset < 0) {
throw new Error(`Offset(${offset}) < 0 is out of bounds`);
}
// Corner case of empty string
if (length === 0) {
return BitString.EMPTY;
}
if (offset + length > this._length) {
throw new Error(`Offset ${offset} + Length ${length} > ${this._length} is out of bounds`);
}
// Create substring
return new BitString(this._data, this._offset + offset, length);
}
/**
* Try to get a buffer from the bitstring without allocations
* @param offset offset in bits
* @param length length in bits
* @returns buffer if the bitstring is aligned to bytes, null otherwise
*/
subbuffer(offset: number, length: number) {
// Check offset
if (offset > this._length) {
throw new Error(`Offset ${offset} is out of bounds`);
}
if (offset < 0) {
throw new Error(`Offset ${offset} is out of bounds`);
}
if (offset + length > this._length) {
throw new Error(`Offset + Lenght = ${offset + length} is out of bounds`);
}
// Check alignment
if (length % 8 !== 0) {
return null;
}
if ((this._offset + offset) % 8 !== 0) {
return null;
}
// Create substring
let start = ((this._offset + offset) >> 3);
let end = start + (length >> 3);
return this._data.subarray(start, end);
}
/**
* Checks for equality
* @param b other bitstring
* @returns true if the bitstrings are equal, false otherwise
*/
equals(b: BitString) {
if (this._length !== b._length) {
return false;
}
for (let i = 0; i < this._length; i++) {
if (this.at(i) !== b.at(i)) {
return false;
}
}
return true;
}
/**
* Format to canonical string
* @returns formatted bits as a string
*/
toString(): string {
const padded = bitsToPaddedBuffer(this);
if (this._length % 4 === 0) {
const s = padded.subarray(0, Math.ceil(this._length / 8)).toString('hex').toUpperCase();
if (this._length % 8 === 0) {
return s;
} else {
return s.substring(0, s.length - 1);
}
} else {
const hex = padded.toString('hex').toUpperCase();
if (this._length % 8 <= 4) {
return hex.substring(0, hex.length - 1) + '_';
} else {
return hex + '_';
}
}
}
[inspectSymbol] = () => this.toString()
}