Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
SeleniumHQ
GitHub Repository: SeleniumHQ/Selenium
Path: blob/trunk/third_party/closure/goog/net/streams/pbstreamparser.js
1865 views
1
// Copyright 2015 The Closure Library Authors. All Rights Reserved.
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
// http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS-IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
15
/**
16
* @fileoverview The default Protobuf stream parser.
17
*
18
* The default Protobuf parser decodes the input stream (binary) under the
19
* following rules:
20
* 1. The data stream as a whole represents a valid proto message,
21
* defined as following:
22
*
23
* message StreamBody {
24
* repeated bytes messages = 1;
25
* google.rpc.Status status = 2;
26
* repeated bytes padding = 15;
27
* }
28
*
29
* Padding are noop messages may be generated as base64 padding (for
30
* browsers) or as a way to keep the connection alive. Its tag-id is
31
* reserved as the maximum value allowed for a single-byte tag-id.
32
*
33
* 2. The only things that are significant to this parser in the above
34
* definition are the specification of the tag ids and wire types (all fields
35
* having length-delimited wire type). The parser doesn't fail if status
36
* appears more than once, i.e. the validity of StreamBody (other than tag
37
* ids and wire types) is not checked.
38
*
39
* 3. The wire format looks like:
40
*
41
* (<tag-id> <wire-type> <length> <message-bytes>)... EOF
42
*
43
* For details of Protobuf wire format see
44
* https://developers.google.com/protocol-buffers/docs/encoding
45
*
46
* A message with unknown tag or with length larger than 2^32 - 1 will
47
* invalidate the whole stream.
48
*
49
* 4. All decoded messages and status in the buffer will be delivered in
50
* a batch (array), with each constructed as {tag-id: opaque-byte-array}.
51
* No-op data, e.g. padding, will be immediately discarded.
52
*
53
* 5. If a high-level API does not support batch delivery (e.g. grpc), then
54
* a wrapper is expected to deliver individual message separately in order.
55
*/
56
57
goog.provide('goog.net.streams.PbStreamParser');
58
59
goog.require('goog.asserts');
60
goog.require('goog.net.streams.StreamParser');
61
62
goog.scope(function() {
63
64
65
/**
66
* The default Protobuf stream parser.
67
*
68
* @constructor
69
* @struct
70
* @implements {goog.net.streams.StreamParser}
71
* @final
72
*/
73
goog.net.streams.PbStreamParser = function() {
74
/**
75
* The current error message, if any.
76
* @private {?string}
77
*/
78
this.errorMessage_ = null;
79
80
/**
81
* The currently buffered result (parsed messages).
82
* @private {!Array<!Object>}
83
*/
84
this.result_ = [];
85
86
/**
87
* The current position in the streamed data.
88
* @private {number}
89
*/
90
this.streamPos_ = 0;
91
92
/**
93
* The current parser state.
94
* @private {goog.net.streams.PbStreamParser.State_}
95
*/
96
this.state_ = Parser.State_.INIT;
97
98
/**
99
* The tag of the proto message being parsed.
100
* @private {number}
101
*/
102
this.tag_ = 0;
103
104
/**
105
* The length of the proto message being parsed.
106
* @private {number}
107
*/
108
this.length_ = 0;
109
110
/**
111
* Count of processed length bytes.
112
* @private {number}
113
*/
114
this.countLengthBytes_ = 0;
115
116
/**
117
* Raw bytes of the current message. Uses Uint8Array by default. Falls back to
118
* native array when Uint8Array is unsupported.
119
* @private {?Uint8Array|?Array<number>}
120
*/
121
this.messageBuffer_ = null;
122
123
/**
124
* Count of processed message bytes.
125
* @private {number}
126
*/
127
this.countMessageBytes_ = 0;
128
};
129
130
131
var Parser = goog.net.streams.PbStreamParser;
132
133
134
/**
135
* The parser state.
136
* @private @enum {number}
137
*/
138
Parser.State_ = {
139
INIT: 0, // expecting the tag:wire-type byte
140
LENGTH: 1, // expecting more varint bytes of length
141
MESSAGE: 2, // expecting more message bytes
142
INVALID: 3
143
};
144
145
146
/**
147
* Tag of padding messages.
148
* @private @const {number}
149
*/
150
Parser.PADDING_TAG_ = 15;
151
152
153
/**
154
* @override
155
*/
156
goog.net.streams.PbStreamParser.prototype.isInputValid = function() {
157
return this.state_ != Parser.State_.INVALID;
158
};
159
160
161
/**
162
* @override
163
*/
164
goog.net.streams.PbStreamParser.prototype.getErrorMessage = function() {
165
return this.errorMessage_;
166
};
167
168
169
/**
170
* @param {!Uint8Array|!Array<number>} inputBytes The current input buffer
171
* @param {number} pos The position in the current input that triggers the error
172
* @param {string} errorMsg Additional error message
173
* @throws {!Error} Throws an error indicating where the stream is broken
174
* @private
175
*/
176
Parser.prototype.error_ = function(inputBytes, pos, errorMsg) {
177
this.state_ = Parser.State_.INVALID;
178
this.errorMessage_ = 'The stream is broken @' + this.streamPos_ + '/' + pos +
179
'. ' +
180
'Error: ' + errorMsg + '. ' +
181
'With input:\n' + inputBytes;
182
throw Error(this.errorMessage_);
183
};
184
185
186
/**
187
* @throws {!Error} Throws an error message if the input is invalid.
188
* @override
189
*/
190
goog.net.streams.PbStreamParser.prototype.parse = function(input) {
191
goog.asserts.assert(input instanceof Array || input instanceof ArrayBuffer);
192
193
var parser = this;
194
var inputBytes = (input instanceof Array) ? input : new Uint8Array(input);
195
var pos = 0;
196
197
while (pos < inputBytes.length) {
198
switch (parser.state_) {
199
case Parser.State_.INVALID: {
200
parser.error_(inputBytes, pos, 'stream already broken');
201
break;
202
}
203
case Parser.State_.INIT: {
204
processTagByte(inputBytes[pos]);
205
break;
206
}
207
case Parser.State_.LENGTH: {
208
processLengthByte(inputBytes[pos]);
209
break;
210
}
211
case Parser.State_.MESSAGE: {
212
processMessageByte(inputBytes[pos]);
213
break;
214
}
215
default: { throw Error('unexpected parser state: ' + parser.state_); }
216
}
217
218
parser.streamPos_++;
219
pos++;
220
}
221
222
var msgs = parser.result_;
223
parser.result_ = [];
224
return msgs.length > 0 ? msgs : null;
225
226
/**
227
* @param {number} b A tag byte to process
228
*/
229
function processTagByte(b) {
230
if (b & 0x80) {
231
parser.error_(inputBytes, pos, 'invalid tag');
232
}
233
234
var wireType = b & 0x07;
235
if (wireType != 2) {
236
parser.error_(inputBytes, pos, 'invalid wire type');
237
}
238
239
parser.tag_ = b >>> 3;
240
if (parser.tag_ != 1 && parser.tag_ != 2 && parser.tag_ != 15) {
241
parser.error_(inputBytes, pos, 'unexpected tag');
242
}
243
244
parser.state_ = Parser.State_.LENGTH;
245
parser.length_ = 0;
246
parser.countLengthBytes_ = 0;
247
}
248
249
/**
250
* @param {number} b A length byte to process
251
*/
252
function processLengthByte(b) {
253
parser.countLengthBytes_++;
254
if (parser.countLengthBytes_ == 5) {
255
if (b & 0xF0) { // length will not fit in a 32-bit uint
256
parser.error_(inputBytes, pos, 'message length too long');
257
}
258
}
259
parser.length_ |= (b & 0x7F) << ((parser.countLengthBytes_ - 1) * 7);
260
261
if (!(b & 0x80)) { // no more length byte
262
parser.state_ = Parser.State_.MESSAGE;
263
parser.countMessageBytes_ = 0;
264
if (typeof Uint8Array !== 'undefined') {
265
parser.messageBuffer_ = new Uint8Array(parser.length_);
266
} else {
267
parser.messageBuffer_ = new Array(parser.length_);
268
}
269
270
if (parser.length_ == 0) { // empty message
271
finishMessage();
272
}
273
}
274
}
275
276
/**
277
* @param {number} b A message byte to process
278
*/
279
function processMessageByte(b) {
280
parser.messageBuffer_[parser.countMessageBytes_++] = b;
281
if (parser.countMessageBytes_ == parser.length_) {
282
finishMessage();
283
}
284
}
285
286
/**
287
* Finishes up building the current message and resets parser state
288
*/
289
function finishMessage() {
290
if (parser.tag_ < Parser.PADDING_TAG_) {
291
var message = {};
292
message[parser.tag_] = parser.messageBuffer_;
293
parser.result_.push(message);
294
}
295
parser.state_ = Parser.State_.INIT;
296
}
297
};
298
299
300
}); // goog.scope
301
302