UNPKG

11.4 kBJavaScriptView Raw
1'use strict';
2
3module.exports = Protobuf;
4
5var Buffer = global.Buffer || require('./buffer');
6
7function Protobuf(buf) {
8 this.buf = !Buffer.isBuffer(buf) ? new Buffer(buf || 0) : buf;
9 this.pos = 0;
10 this.length = this.buf.length;
11}
12
13Protobuf.Varint = 0; // varint: int32, int64, uint32, uint64, sint32, sint64, bool, enum
14Protobuf.Fixed64 = 1; // 64-bit: double, fixed64, sfixed64
15Protobuf.Bytes = 2; // length-delimited: string, bytes, embedded messages, packed repeated fields
16Protobuf.Fixed32 = 5; // 32-bit: float, fixed32, sfixed32
17
18var SHIFT_LEFT_32 = (1 << 16) * (1 << 16),
19 SHIFT_RIGHT_32 = 1 / SHIFT_LEFT_32;
20
21Protobuf.prototype = {
22
23 destroy: function() {
24 this.buf = null;
25 },
26
27 // === READING =================================================================
28
29 readFields: function(readField, result, end) {
30 end = end || this.length;
31
32 while (this.pos < end) {
33 var val = this.readVarint(),
34 tag = val >> 3,
35 startPos = this.pos;
36
37 readField(tag, result, this);
38
39 if (this.pos === startPos) this.skip(val);
40 }
41 return result;
42 },
43
44 readMessage: function(readField, result) {
45 return this.readFields(readField, result, this.readVarint() + this.pos);
46 },
47
48 readFixed32: function() {
49 var val = this.buf.readUInt32LE(this.pos);
50 this.pos += 4;
51 return val;
52 },
53
54 readSFixed32: function() {
55 var val = this.buf.readInt32LE(this.pos);
56 this.pos += 4;
57 return val;
58 },
59
60 // 64-bit int handling is based on github.com/dpw/node-buffer-more-ints (MIT-licensed)
61
62 readFixed64: function() {
63 var val = this.buf.readUInt32LE(this.pos) + this.buf.readUInt32LE(this.pos + 4) * SHIFT_LEFT_32;
64 this.pos += 8;
65 return val;
66 },
67
68 readSFixed64: function() {
69 var val = this.buf.readUInt32LE(this.pos) + this.buf.readInt32LE(this.pos + 4) * SHIFT_LEFT_32;
70 this.pos += 8;
71 return val;
72 },
73
74 readFloat: function() {
75 var val = this.buf.readFloatLE(this.pos);
76 this.pos += 4;
77 return val;
78 },
79
80 readDouble: function() {
81 var val = this.buf.readDoubleLE(this.pos);
82 this.pos += 8;
83 return val;
84 },
85
86 readVarint: function() {
87 var buf = this.buf,
88 val, b, b0, b1, b2, b3;
89
90 b0 = buf[this.pos++]; if (b0 < 0x80) return b0; b0 = b0 & 0x7f;
91 b1 = buf[this.pos++]; if (b1 < 0x80) return b0 | b1 << 7; b1 = (b1 & 0x7f) << 7;
92 b2 = buf[this.pos++]; if (b2 < 0x80) return b0 | b1 | b2 << 14; b2 = (b2 & 0x7f) << 14;
93 b3 = buf[this.pos++]; if (b3 < 0x80) return b0 | b1 | b2 | b3 << 21;
94
95 val = b0 | b1 | b2 | (b3 & 0x7f) << 21;
96
97 b = buf[this.pos++]; val += (b & 0x7f) * 0x10000000; if (b < 0x80) return val;
98 b = buf[this.pos++]; val += (b & 0x7f) * 0x800000000; if (b < 0x80) return val;
99 b = buf[this.pos++]; val += (b & 0x7f) * 0x40000000000; if (b < 0x80) return val;
100 b = buf[this.pos++]; val += (b & 0x7f) * 0x2000000000000; if (b < 0x80) return val;
101 b = buf[this.pos++]; val += (b & 0x7f) * 0x100000000000000; if (b < 0x80) return val;
102 b = buf[this.pos++]; val += (b & 0x7f) * 0x8000000000000000; if (b < 0x80) return val;
103
104 throw new Error('Expected varint not more than 10 bytes');
105 },
106
107 readSVarint: function() {
108 var num = this.readVarint();
109 return num % 2 === 1 ? (num + 1) / -2 : num / 2; // zigzag encoding
110 },
111
112 readBoolean: function() {
113 return Boolean(this.readVarint());
114 },
115
116 readString: function() {
117 var end = this.readVarint() + this.pos,
118 str = this.buf.toString('utf8', this.pos, end);
119 this.pos = end;
120 return str;
121 },
122
123 readBytes: function() {
124 var end = this.readVarint() + this.pos,
125 buffer = this.buf.slice(this.pos, end);
126 this.pos = end;
127 return buffer;
128 },
129
130 readPacked: function(type) {
131 var end = this.readVarint() + this.pos,
132 arr = [];
133 if (type === 'Varint') while (this.pos < end) arr.push(this.readVarint());
134 else if (type === 'SVarint') while (this.pos < end) arr.push(this.readSVarint());
135 else if (type === 'Float') while (this.pos < end) arr.push(this.readFloat());
136 else if (type === 'Double') while (this.pos < end) arr.push(this.readDouble());
137 else if (type === 'Fixed32') while (this.pos < end) arr.push(this.readFixed32());
138 else if (type === 'SFixed32') while (this.pos < end) arr.push(this.readSFixed32());
139 else if (type === 'Fixed64') while (this.pos < end) arr.push(this.readFixed64());
140 else if (type === 'SFixed64') while (this.pos < end) arr.push(this.readSFixed64());
141 return arr;
142 },
143
144 skip: function(val) {
145 var type = val & 0x7;
146 if (type === Protobuf.Varint) while (this.buf[this.pos++] > 0x7f);
147 else if (type === Protobuf.Bytes) this.pos = this.readVarint() + this.pos;
148 else if (type === Protobuf.Fixed32) this.pos += 4;
149 else if (type === Protobuf.Fixed64) this.pos += 8;
150 else throw new Error('Unimplemented type: ' + type);
151 },
152
153 // === WRITING =================================================================
154
155 writeTag: function(tag, type) {
156 this.writeVarint((tag << 3) | type);
157 },
158
159 realloc: function(min) {
160 var length = this.length || 1;
161
162 while (length < this.pos + min) length *= 2;
163
164 if (length != this.length) {
165 var buf = new Buffer(length);
166 this.buf.copy(buf);
167 this.buf = buf;
168 this.length = length;
169 }
170 },
171
172 finish: function() {
173 this.length = this.pos;
174 this.pos = 0;
175 return this.buf.slice(0, this.length);
176 },
177
178 writePacked: function(tag, type, items) {
179 if (!items.length) return;
180
181 var message = new Protobuf(),
182 len = items.length,
183 i = 0;
184
185 if (type === 'Varint') for (; i < len; i++) message.writeVarint(items[i]);
186 else if (type === 'SVarint') for (; i < len; i++) message.writeSVarint(items[i]);
187 else if (type === 'Float') for (; i < len; i++) message.writeFloat(items[i]);
188 else if (type === 'Double') for (; i < len; i++) message.writeDouble(items[i]);
189 else if (type === 'Fixed32') for (; i < len; i++) message.writeFixed32(items[i]);
190 else if (type === 'SFixed32') for (; i < len; i++) message.writeSFixed32(items[i]);
191 else if (type === 'Fixed64') for (; i < len; i++) message.writeFixed64(items[i]);
192 else if (type === 'SFixed64') for (; i < len; i++) message.writeSFixed64(items[i]);
193
194 this.writeMessage(tag, message);
195 },
196
197 writeFixed32: function(val) {
198 this.realloc(4);
199 this.buf.writeUInt32LE(val, this.pos);
200 this.pos += 4;
201 },
202
203 writeSFixed32: function(val) {
204 this.realloc(4);
205 this.buf.writeInt32LE(val, this.pos);
206 this.pos += 4;
207 },
208
209 writeFixed64: function(val) {
210 this.realloc(8);
211 this.buf.writeInt32LE(val & -1, this.pos);
212 this.buf.writeUInt32LE(Math.floor(val * SHIFT_RIGHT_32), this.pos + 4);
213 this.pos += 8;
214 },
215
216 writeSFixed64: function(val) {
217 this.realloc(8);
218 this.buf.writeInt32LE(val & -1, this.pos);
219 this.buf.writeInt32LE(Math.floor(val * SHIFT_RIGHT_32), this.pos + 4);
220 this.pos += 8;
221 },
222
223 writeVarint: function(val) {
224 val = +val;
225
226 if (val <= 0x7f) {
227 this.realloc(1);
228 this.buf[this.pos++] = val;
229 } else if (val <= 0x3fff) {
230 this.realloc(2);
231 this.buf[this.pos++] = 0x80 | ((val >>> 0) & 0x7f);
232 this.buf[this.pos++] = 0x00 | ((val >>> 7) & 0x7f);
233 } else if (val <= 0x1fffff) {
234 this.realloc(3);
235 this.buf[this.pos++] = 0x80 | ((val >>> 0) & 0x7f);
236 this.buf[this.pos++] = 0x80 | ((val >>> 7) & 0x7f);
237 this.buf[this.pos++] = 0x00 | ((val >>> 14) & 0x7f);
238 } else if (val <= 0xfffffff) {
239 this.realloc(4);
240 this.buf[this.pos++] = 0x80 | ((val >>> 0) & 0x7f);
241 this.buf[this.pos++] = 0x80 | ((val >>> 7) & 0x7f);
242 this.buf[this.pos++] = 0x80 | ((val >>> 14) & 0x7f);
243 this.buf[this.pos++] = 0x00 | ((val >>> 21) & 0x7f);
244 } else {
245 var pos = this.pos;
246 while (val >= 0x80) {
247 this.realloc(1);
248 this.buf[this.pos++] = (val & 0xff) | 0x80;
249 val /= 0x80;
250 }
251 this.realloc(1);
252 this.buf[this.pos++] = val | 0;
253 if (this.pos - pos > 10) throw new Error("Given varint doesn't fit into 10 bytes");
254 }
255 },
256
257 writeSVarint: function(val) {
258 this.writeVarint(val < 0 ? -val * 2 - 1 : val * 2);
259 },
260
261 writeBoolean: function(val) {
262 this.writeVarint(Boolean(val));
263 },
264
265 writeString: function(str) {
266 str = String(str);
267 var bytes = Buffer.byteLength(str);
268 this.writeVarint(bytes);
269 this.realloc(bytes);
270 this.buf.write(str, this.pos);
271 this.pos += bytes;
272 },
273
274 writeFloat: function(val) {
275 this.realloc(4);
276 this.buf.writeFloatLE(val, this.pos);
277 this.pos += 4;
278 },
279
280 writeDouble: function(val) {
281 this.realloc(8);
282 this.buf.writeDoubleLE(val, this.pos);
283 this.pos += 8;
284 },
285
286 writeBytes: function(buffer) {
287 var len = buffer.length;
288 this.writeVarint(len);
289 this.realloc(len);
290 for (var i = 0; i < len; i++) {
291 this.buf[this.pos + i] = buffer[i];
292 }
293 this.pos += len;
294 },
295
296 writeMessage: function(tag, protobuf) {
297 this.writeTag(tag, Protobuf.Bytes);
298 this.writeBytes(protobuf.finish());
299 },
300
301 writeBytesField: function(tag, buffer) {
302 this.writeTag(tag, Protobuf.Bytes);
303 this.writeBytes(buffer);
304 },
305
306 writeFixed32Field: function(tag, val) {
307 this.writeTag(tag, Protobuf.Fixed32);
308 this.writeFixed32(val);
309 },
310
311 writeSFixed32Field: function(tag, val) {
312 this.writeTag(tag, Protobuf.Fixed32);
313 this.writeSFixed32(val);
314 },
315
316 writeFixed64Field: function(tag, val) {
317 this.writeTag(tag, Protobuf.Fixed64);
318 this.writeFixed64(val);
319 },
320
321 writeSFixed64Field: function(tag, val) {
322 this.writeTag(tag, Protobuf.Fixed64);
323 this.writeSFixed64(val);
324 },
325
326 writeVarintField: function(tag, val) {
327 this.writeTag(tag, Protobuf.Varint);
328 this.writeVarint(val);
329 },
330
331 writeSVarintField: function(tag, val) {
332 this.writeTag(tag, Protobuf.Varint);
333 this.writeSVarint(val);
334 },
335
336 writeStringField: function(tag, str) {
337 this.writeTag(tag, Protobuf.Bytes);
338 this.writeString(str);
339 },
340
341 writeFloatField: function(tag, val) {
342 this.writeTag(tag, Protobuf.Fixed32);
343 this.writeFloat(val);
344 },
345
346 writeDoubleField: function(tag, val) {
347 this.writeTag(tag, Protobuf.Fixed64);
348 this.writeDouble(val);
349 },
350
351 writeBooleanField: function(tag, val) {
352 this.writeVarintField(tag, Boolean(val));
353 }
354};