bin-serde 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/index.ts +206 -0
- package/lib/index.d.ts +30 -0
- package/lib/index.js +187 -0
- package/package.json +35 -0
- package/tsconfig.json +10 -0
package/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# bin-serde
|
package/index.ts
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import * as utf8 from "utf8-buffer";
|
|
2
|
+
import utf8Size from "utf8-buffer-size";
|
|
3
|
+
const { pack, unpack } = (utf8 as any).default ?? utf8;
|
|
4
|
+
|
|
5
|
+
export class Writer {
|
|
6
|
+
private pos = 0;
|
|
7
|
+
private view = new DataView(new ArrayBuffer(64));
|
|
8
|
+
private bytes = new Uint8Array(this.view.buffer);
|
|
9
|
+
|
|
10
|
+
public writeUInt8(val: number) {
|
|
11
|
+
this.ensureSize(1);
|
|
12
|
+
this.view.setUint8(this.pos, val);
|
|
13
|
+
this.pos += 1;
|
|
14
|
+
return this;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
public writeUInt32(val: number) {
|
|
18
|
+
this.ensureSize(4);
|
|
19
|
+
this.view.setUint32(this.pos, val);
|
|
20
|
+
this.pos += 4;
|
|
21
|
+
return this;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public writeUVarint(val: number) {
|
|
25
|
+
if (val < 0x80) {
|
|
26
|
+
this.ensureSize(1);
|
|
27
|
+
this.view.setUint8(this.pos, val);
|
|
28
|
+
this.pos += 1;
|
|
29
|
+
} else if (val < 0x4000) {
|
|
30
|
+
this.ensureSize(2);
|
|
31
|
+
this.view.setUint16(this.pos, (val & 0x7f) | ((val & 0x3f80) << 1) | 0x8000);
|
|
32
|
+
this.pos += 2;
|
|
33
|
+
} else if (val < 0x200000) {
|
|
34
|
+
this.ensureSize(3);
|
|
35
|
+
this.view.setUint8(this.pos, (val >> 14) | 0x80);
|
|
36
|
+
this.view.setUint16(this.pos + 1, (val & 0x7f) | ((val & 0x3f80) << 1) | 0x8000);
|
|
37
|
+
this.pos += 3;
|
|
38
|
+
} else if (val < 0x10000000) {
|
|
39
|
+
this.ensureSize(4);
|
|
40
|
+
this.view.setUint32(
|
|
41
|
+
this.pos,
|
|
42
|
+
(val & 0x7f) | ((val & 0x3f80) << 1) | ((val & 0x1fc000) << 2) | ((val & 0xfe00000) << 3) | 0x80808000
|
|
43
|
+
);
|
|
44
|
+
this.pos += 4;
|
|
45
|
+
} else if (val < 0x800000000) {
|
|
46
|
+
this.ensureSize(5);
|
|
47
|
+
this.view.setUint8(this.pos, Math.floor(val / Math.pow(2, 28)) | 0x80);
|
|
48
|
+
this.view.setUint32(
|
|
49
|
+
this.pos + 1,
|
|
50
|
+
(val & 0x7f) | ((val & 0x3f80) << 1) | ((val & 0x1fc000) << 2) | ((val & 0xfe00000) << 3) | 0x80808000
|
|
51
|
+
);
|
|
52
|
+
this.pos += 5;
|
|
53
|
+
} else if (val < 0x40000000000) {
|
|
54
|
+
this.ensureSize(6);
|
|
55
|
+
const shiftedVal = Math.floor(val / Math.pow(2, 28));
|
|
56
|
+
this.view.setUint16(this.pos, (shiftedVal & 0x7f) | ((shiftedVal & 0x3f80) << 1) | 0x8080);
|
|
57
|
+
this.view.setUint32(
|
|
58
|
+
this.pos + 2,
|
|
59
|
+
(val & 0x7f) | ((val & 0x3f80) << 1) | ((val & 0x1fc000) << 2) | ((val & 0xfe00000) << 3) | 0x80808000
|
|
60
|
+
);
|
|
61
|
+
this.pos += 6;
|
|
62
|
+
} else {
|
|
63
|
+
throw new Error("Value out of range");
|
|
64
|
+
}
|
|
65
|
+
return this;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
public writeVarint(val: number) {
|
|
69
|
+
const bigval = BigInt(val);
|
|
70
|
+
this.writeUVarint(Number((bigval >> 63n) ^ (bigval << 1n)));
|
|
71
|
+
return this;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
public writeFloat(val: number) {
|
|
75
|
+
this.ensureSize(4);
|
|
76
|
+
this.view.setFloat32(this.pos, val, true);
|
|
77
|
+
this.pos += 4;
|
|
78
|
+
return this;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
public writeBits(bits: boolean[]) {
|
|
82
|
+
for (let i = 0; i < bits.length; i += 8) {
|
|
83
|
+
let byte = 0;
|
|
84
|
+
for (let j = 0; j < 8; j++) {
|
|
85
|
+
if (i + j == bits.length) {
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
byte |= (bits[i + j] ? 1 : 0) << j;
|
|
89
|
+
}
|
|
90
|
+
this.writeUInt8(byte);
|
|
91
|
+
}
|
|
92
|
+
return this;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
public writeString(val: string) {
|
|
96
|
+
if (val.length > 0) {
|
|
97
|
+
const byteSize = utf8Size(val);
|
|
98
|
+
this.writeUVarint(byteSize);
|
|
99
|
+
this.ensureSize(byteSize);
|
|
100
|
+
pack(val, this.bytes, this.pos);
|
|
101
|
+
this.pos += byteSize;
|
|
102
|
+
} else {
|
|
103
|
+
this.writeUInt8(0);
|
|
104
|
+
}
|
|
105
|
+
return this;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
public writeBuffer(buf: Uint8Array) {
|
|
109
|
+
this.ensureSize(buf.length);
|
|
110
|
+
this.bytes.set(buf, this.pos);
|
|
111
|
+
this.pos += buf.length;
|
|
112
|
+
return this;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
public toBuffer() {
|
|
116
|
+
return this.bytes.subarray(0, this.pos);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private ensureSize(size: number) {
|
|
120
|
+
while (this.view.byteLength < this.pos + size) {
|
|
121
|
+
const newView = new DataView(new ArrayBuffer(this.view.byteLength * 2));
|
|
122
|
+
const newBytes = new Uint8Array(newView.buffer);
|
|
123
|
+
newBytes.set(this.bytes);
|
|
124
|
+
this.view = newView;
|
|
125
|
+
this.bytes = newBytes;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export class Reader {
|
|
131
|
+
private pos = 0;
|
|
132
|
+
private view: DataView;
|
|
133
|
+
private bytes: Uint8Array;
|
|
134
|
+
|
|
135
|
+
public constructor(buf: ArrayBufferView) {
|
|
136
|
+
this.view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
137
|
+
this.bytes = new Uint8Array(this.view.buffer, buf.byteOffset, buf.byteLength);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
public readUInt8() {
|
|
141
|
+
const val = this.view.getUint8(this.pos);
|
|
142
|
+
this.pos += 1;
|
|
143
|
+
return val;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
public readUInt32() {
|
|
147
|
+
const val = this.view.getUint32(this.pos);
|
|
148
|
+
this.pos += 4;
|
|
149
|
+
return val;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
public readUVarint() {
|
|
153
|
+
let val = 0;
|
|
154
|
+
while (true) {
|
|
155
|
+
let byte = this.view.getUint8(this.pos++);
|
|
156
|
+
if (byte < 0x80) {
|
|
157
|
+
return val + byte;
|
|
158
|
+
}
|
|
159
|
+
val = (val + (byte & 0x7f)) * 128;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
public readVarint() {
|
|
164
|
+
const val = BigInt(this.readUVarint());
|
|
165
|
+
return Number((val >> 1n) ^ -(val & 1n));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
public readFloat() {
|
|
169
|
+
const val = this.view.getFloat32(this.pos, true);
|
|
170
|
+
this.pos += 4;
|
|
171
|
+
return val;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
public readBits(numBits: number) {
|
|
175
|
+
const numBytes = Math.ceil(numBits / 8);
|
|
176
|
+
const bytes = this.bytes.slice(this.pos, this.pos + numBytes);
|
|
177
|
+
const bits: boolean[] = [];
|
|
178
|
+
for (const byte of bytes) {
|
|
179
|
+
for (let i = 0; i < 8 && bits.length < numBits; i++) {
|
|
180
|
+
bits.push(((byte >> i) & 1) === 1);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
this.pos += numBytes;
|
|
184
|
+
return bits;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
public readString() {
|
|
188
|
+
const len = this.readUVarint();
|
|
189
|
+
if (len === 0) {
|
|
190
|
+
return "";
|
|
191
|
+
}
|
|
192
|
+
const val = unpack(this.bytes, this.pos, this.pos + len);
|
|
193
|
+
this.pos += len;
|
|
194
|
+
return val;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
public readBuffer(numBytes: number) {
|
|
198
|
+
const bytes = this.bytes.slice(this.pos, this.pos + numBytes);
|
|
199
|
+
this.pos += numBytes;
|
|
200
|
+
return bytes;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
public remaining() {
|
|
204
|
+
return this.view.byteLength - this.pos;
|
|
205
|
+
}
|
|
206
|
+
}
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export declare class Writer {
|
|
2
|
+
private pos;
|
|
3
|
+
private view;
|
|
4
|
+
private bytes;
|
|
5
|
+
writeUInt8(val: number): this;
|
|
6
|
+
writeUInt32(val: number): this;
|
|
7
|
+
writeUVarint(val: number): this;
|
|
8
|
+
writeVarint(val: number): this;
|
|
9
|
+
writeFloat(val: number): this;
|
|
10
|
+
writeBits(bits: boolean[]): this;
|
|
11
|
+
writeString(val: string): this;
|
|
12
|
+
writeBuffer(buf: Uint8Array): this;
|
|
13
|
+
toBuffer(): Uint8Array;
|
|
14
|
+
private ensureSize;
|
|
15
|
+
}
|
|
16
|
+
export declare class Reader {
|
|
17
|
+
private pos;
|
|
18
|
+
private view;
|
|
19
|
+
private bytes;
|
|
20
|
+
constructor(buf: ArrayBufferView);
|
|
21
|
+
readUInt8(): number;
|
|
22
|
+
readUInt32(): number;
|
|
23
|
+
readUVarint(): number;
|
|
24
|
+
readVarint(): number;
|
|
25
|
+
readFloat(): number;
|
|
26
|
+
readBits(numBits: number): boolean[];
|
|
27
|
+
readString(): any;
|
|
28
|
+
readBuffer(numBytes: number): Uint8Array;
|
|
29
|
+
remaining(): number;
|
|
30
|
+
}
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Reader = exports.Writer = void 0;
|
|
4
|
+
const utf8 = require("utf8-buffer");
|
|
5
|
+
const utf8_buffer_size_1 = require("utf8-buffer-size");
|
|
6
|
+
const { pack, unpack } = utf8.default ?? utf8;
|
|
7
|
+
class Writer {
|
|
8
|
+
pos = 0;
|
|
9
|
+
view = new DataView(new ArrayBuffer(64));
|
|
10
|
+
bytes = new Uint8Array(this.view.buffer);
|
|
11
|
+
writeUInt8(val) {
|
|
12
|
+
this.ensureSize(1);
|
|
13
|
+
this.view.setUint8(this.pos, val);
|
|
14
|
+
this.pos += 1;
|
|
15
|
+
return this;
|
|
16
|
+
}
|
|
17
|
+
writeUInt32(val) {
|
|
18
|
+
this.ensureSize(4);
|
|
19
|
+
this.view.setUint32(this.pos, val);
|
|
20
|
+
this.pos += 4;
|
|
21
|
+
return this;
|
|
22
|
+
}
|
|
23
|
+
writeUVarint(val) {
|
|
24
|
+
if (val < 0x80) {
|
|
25
|
+
this.ensureSize(1);
|
|
26
|
+
this.view.setUint8(this.pos, val);
|
|
27
|
+
this.pos += 1;
|
|
28
|
+
}
|
|
29
|
+
else if (val < 0x4000) {
|
|
30
|
+
this.ensureSize(2);
|
|
31
|
+
this.view.setUint16(this.pos, (val & 0x7f) | ((val & 0x3f80) << 1) | 0x8000);
|
|
32
|
+
this.pos += 2;
|
|
33
|
+
}
|
|
34
|
+
else if (val < 0x200000) {
|
|
35
|
+
this.ensureSize(3);
|
|
36
|
+
this.view.setUint8(this.pos, (val >> 14) | 0x80);
|
|
37
|
+
this.view.setUint16(this.pos + 1, (val & 0x7f) | ((val & 0x3f80) << 1) | 0x8000);
|
|
38
|
+
this.pos += 3;
|
|
39
|
+
}
|
|
40
|
+
else if (val < 0x10000000) {
|
|
41
|
+
this.ensureSize(4);
|
|
42
|
+
this.view.setUint32(this.pos, (val & 0x7f) | ((val & 0x3f80) << 1) | ((val & 0x1fc000) << 2) | ((val & 0xfe00000) << 3) | 0x80808000);
|
|
43
|
+
this.pos += 4;
|
|
44
|
+
}
|
|
45
|
+
else if (val < 0x800000000) {
|
|
46
|
+
this.ensureSize(5);
|
|
47
|
+
this.view.setUint8(this.pos, Math.floor(val / Math.pow(2, 28)) | 0x80);
|
|
48
|
+
this.view.setUint32(this.pos + 1, (val & 0x7f) | ((val & 0x3f80) << 1) | ((val & 0x1fc000) << 2) | ((val & 0xfe00000) << 3) | 0x80808000);
|
|
49
|
+
this.pos += 5;
|
|
50
|
+
}
|
|
51
|
+
else if (val < 0x40000000000) {
|
|
52
|
+
this.ensureSize(6);
|
|
53
|
+
const shiftedVal = Math.floor(val / Math.pow(2, 28));
|
|
54
|
+
this.view.setUint16(this.pos, (shiftedVal & 0x7f) | ((shiftedVal & 0x3f80) << 1) | 0x8080);
|
|
55
|
+
this.view.setUint32(this.pos + 2, (val & 0x7f) | ((val & 0x3f80) << 1) | ((val & 0x1fc000) << 2) | ((val & 0xfe00000) << 3) | 0x80808000);
|
|
56
|
+
this.pos += 6;
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
throw new Error("Value out of range");
|
|
60
|
+
}
|
|
61
|
+
return this;
|
|
62
|
+
}
|
|
63
|
+
writeVarint(val) {
|
|
64
|
+
const bigval = BigInt(val);
|
|
65
|
+
this.writeUVarint(Number((bigval >> 63n) ^ (bigval << 1n)));
|
|
66
|
+
return this;
|
|
67
|
+
}
|
|
68
|
+
writeFloat(val) {
|
|
69
|
+
this.ensureSize(4);
|
|
70
|
+
this.view.setFloat32(this.pos, val, true);
|
|
71
|
+
this.pos += 4;
|
|
72
|
+
return this;
|
|
73
|
+
}
|
|
74
|
+
writeBits(bits) {
|
|
75
|
+
for (let i = 0; i < bits.length; i += 8) {
|
|
76
|
+
let byte = 0;
|
|
77
|
+
for (let j = 0; j < 8; j++) {
|
|
78
|
+
if (i + j == bits.length) {
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
byte |= (bits[i + j] ? 1 : 0) << j;
|
|
82
|
+
}
|
|
83
|
+
this.writeUInt8(byte);
|
|
84
|
+
}
|
|
85
|
+
return this;
|
|
86
|
+
}
|
|
87
|
+
writeString(val) {
|
|
88
|
+
if (val.length > 0) {
|
|
89
|
+
const byteSize = (0, utf8_buffer_size_1.default)(val);
|
|
90
|
+
this.writeUVarint(byteSize);
|
|
91
|
+
this.ensureSize(byteSize);
|
|
92
|
+
pack(val, this.bytes, this.pos);
|
|
93
|
+
this.pos += byteSize;
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
this.writeUInt8(0);
|
|
97
|
+
}
|
|
98
|
+
return this;
|
|
99
|
+
}
|
|
100
|
+
writeBuffer(buf) {
|
|
101
|
+
this.ensureSize(buf.length);
|
|
102
|
+
this.bytes.set(buf, this.pos);
|
|
103
|
+
this.pos += buf.length;
|
|
104
|
+
return this;
|
|
105
|
+
}
|
|
106
|
+
toBuffer() {
|
|
107
|
+
return this.bytes.subarray(0, this.pos);
|
|
108
|
+
}
|
|
109
|
+
ensureSize(size) {
|
|
110
|
+
while (this.view.byteLength < this.pos + size) {
|
|
111
|
+
const newView = new DataView(new ArrayBuffer(this.view.byteLength * 2));
|
|
112
|
+
const newBytes = new Uint8Array(newView.buffer);
|
|
113
|
+
newBytes.set(this.bytes);
|
|
114
|
+
this.view = newView;
|
|
115
|
+
this.bytes = newBytes;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
exports.Writer = Writer;
|
|
120
|
+
class Reader {
|
|
121
|
+
pos = 0;
|
|
122
|
+
view;
|
|
123
|
+
bytes;
|
|
124
|
+
constructor(buf) {
|
|
125
|
+
this.view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
126
|
+
this.bytes = new Uint8Array(this.view.buffer, buf.byteOffset, buf.byteLength);
|
|
127
|
+
}
|
|
128
|
+
readUInt8() {
|
|
129
|
+
const val = this.view.getUint8(this.pos);
|
|
130
|
+
this.pos += 1;
|
|
131
|
+
return val;
|
|
132
|
+
}
|
|
133
|
+
readUInt32() {
|
|
134
|
+
const val = this.view.getUint32(this.pos);
|
|
135
|
+
this.pos += 4;
|
|
136
|
+
return val;
|
|
137
|
+
}
|
|
138
|
+
readUVarint() {
|
|
139
|
+
let val = 0;
|
|
140
|
+
while (true) {
|
|
141
|
+
let byte = this.view.getUint8(this.pos++);
|
|
142
|
+
if (byte < 0x80) {
|
|
143
|
+
return val + byte;
|
|
144
|
+
}
|
|
145
|
+
val = (val + (byte & 0x7f)) * 128;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
readVarint() {
|
|
149
|
+
const val = BigInt(this.readUVarint());
|
|
150
|
+
return Number((val >> 1n) ^ -(val & 1n));
|
|
151
|
+
}
|
|
152
|
+
readFloat() {
|
|
153
|
+
const val = this.view.getFloat32(this.pos, true);
|
|
154
|
+
this.pos += 4;
|
|
155
|
+
return val;
|
|
156
|
+
}
|
|
157
|
+
readBits(numBits) {
|
|
158
|
+
const numBytes = Math.ceil(numBits / 8);
|
|
159
|
+
const bytes = this.bytes.slice(this.pos, this.pos + numBytes);
|
|
160
|
+
const bits = [];
|
|
161
|
+
for (const byte of bytes) {
|
|
162
|
+
for (let i = 0; i < 8 && bits.length < numBits; i++) {
|
|
163
|
+
bits.push(((byte >> i) & 1) === 1);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
this.pos += numBytes;
|
|
167
|
+
return bits;
|
|
168
|
+
}
|
|
169
|
+
readString() {
|
|
170
|
+
const len = this.readUVarint();
|
|
171
|
+
if (len === 0) {
|
|
172
|
+
return "";
|
|
173
|
+
}
|
|
174
|
+
const val = unpack(this.bytes, this.pos, this.pos + len);
|
|
175
|
+
this.pos += len;
|
|
176
|
+
return val;
|
|
177
|
+
}
|
|
178
|
+
readBuffer(numBytes) {
|
|
179
|
+
const bytes = this.bytes.slice(this.pos, this.pos + numBytes);
|
|
180
|
+
this.pos += numBytes;
|
|
181
|
+
return bytes;
|
|
182
|
+
}
|
|
183
|
+
remaining() {
|
|
184
|
+
return this.view.byteLength - this.pos;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
exports.Reader = Reader;
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "bin-serde",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A low level library for efficiently writing and reading binary data in javascript",
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"types": "lib/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
9
|
+
"build": "tsc"
|
|
10
|
+
},
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/hathora/bin-serde.git"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"binary",
|
|
17
|
+
"serialization",
|
|
18
|
+
"serde",
|
|
19
|
+
"encode",
|
|
20
|
+
"decode"
|
|
21
|
+
],
|
|
22
|
+
"author": "Harsh Pandey",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/hathora/bin-serde/issues"
|
|
26
|
+
},
|
|
27
|
+
"homepage": "https://github.com/hathora/bin-serde#readme",
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"utf8-buffer": "^1.0.0",
|
|
30
|
+
"utf8-buffer-size": "^0.0.4"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"typescript": "^4.5.5"
|
|
34
|
+
}
|
|
35
|
+
}
|