1 | 'use strict';
|
2 |
|
3 | module.exports = Protobuf;
|
4 |
|
5 | var Buffer = global.Buffer || require('./buffer');
|
6 |
|
7 | function 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 |
|
13 | Protobuf.Varint = 0;
|
14 | Protobuf.Fixed64 = 1;
|
15 | Protobuf.Bytes = 2;
|
16 | Protobuf.Fixed32 = 5;
|
17 |
|
18 | var SHIFT_LEFT_32 = (1 << 16) * (1 << 16),
|
19 | SHIFT_RIGHT_32 = 1 / SHIFT_LEFT_32;
|
20 |
|
21 | Protobuf.prototype = {
|
22 |
|
23 | destroy: function() {
|
24 | this.buf = null;
|
25 | },
|
26 |
|
27 |
|
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 |
|
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;
|
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 |
|
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 | };
|