1
+ interface Field {
2
+ key : number ,
3
+ value : any
4
+ }
5
+
6
+ class Protobuf {
7
+ TYPE : number ;
8
+ NUMBER : number ;
9
+ MSB : number ;
10
+ VALUE : number ;
11
+ offset : number ;
12
+ LENGTH : number ;
13
+ data : ( Int8Array | Uint8Array ) ;
14
+
15
+ constructor ( data : ( Int8Array | Uint8Array ) ) {
16
+ this . data = data ;
17
+
18
+ // Set up masks
19
+ this . TYPE = 0x07 ;
20
+ this . NUMBER = 0x78 ;
21
+ this . MSB = 0x80 ;
22
+ this . VALUE = 0x7f ;
23
+
24
+ // Declare offset and length
25
+ this . offset = 0 ;
26
+ this . LENGTH = data . length ;
27
+ }
28
+
29
+
30
+ static decode ( input : ( Int8Array | Uint8Array ) ) {
31
+ const pb = new Protobuf ( input ) ;
32
+ return pb . _parse ( ) ;
33
+ }
34
+
35
+ _parse ( ) {
36
+ let object = { } ;
37
+ // Continue reading whilst we still have data
38
+ while ( this . offset < this . LENGTH ) {
39
+ const field = this . _parseField ( ) ;
40
+ object = this . _addField ( field , object ) ;
41
+ }
42
+ // Throw an error if we have gone beyond the end of the data
43
+ if ( this . offset > this . LENGTH ) {
44
+ throw new Error ( "Exhausted Buffer" ) ;
45
+ }
46
+ return object ;
47
+ }
48
+
49
+ _addField ( field : Field , object : any ) {
50
+ // Get the field key/values
51
+ const key = field . key ;
52
+ const value = field . value ;
53
+ object [ key ] = Object . prototype . hasOwnProperty . call ( object , key ) ?
54
+ object [ key ] instanceof Array ?
55
+ object [ key ] . concat ( [ value ] ) :
56
+ [ object [ key ] , value ] :
57
+ value ;
58
+ return object ;
59
+ }
60
+
61
+ _parseField ( ) {
62
+ // Get the field headers
63
+ const header = this . _fieldHeader ( ) ;
64
+ const type = header . type ;
65
+ const key = header . key ;
66
+ switch ( type ) {
67
+ // varint
68
+ case 0 :
69
+ return { "key" : key , "value" : this . _varInt ( ) } ;
70
+ // fixed 64
71
+ case 1 :
72
+ return { "key" : key , "value" : this . _uint64 ( ) } ;
73
+ // length delimited
74
+ case 2 :
75
+ return { "key" : key , "value" : this . _lenDelim ( ) } ;
76
+ // fixed 32
77
+ case 5 :
78
+ return { "key" : key , "value" : this . _uint32 ( ) } ;
79
+ // unknown type
80
+ default :
81
+ throw new Error ( "Unknown type 0x" + type . toString ( 16 ) ) ;
82
+ }
83
+ }
84
+
85
+ _fieldHeader ( ) {
86
+ // Make sure we call type then number to preserve offset
87
+ return { "type" : this . _fieldType ( ) , "key" : this . _fieldNumber ( ) } ;
88
+ }
89
+
90
+ _fieldType ( ) {
91
+ // Field type stored in lower 3 bits of tag byte
92
+ return this . data [ this . offset ] & this . TYPE ;
93
+ }
94
+
95
+ _fieldNumber ( ) {
96
+ let shift = - 3 ;
97
+ let fieldNumber = 0 ;
98
+ do {
99
+ fieldNumber += shift < 28 ?
100
+ shift === - 3 ?
101
+ ( this . data [ this . offset ] & this . NUMBER ) >> - shift :
102
+ ( this . data [ this . offset ] & this . VALUE ) << shift :
103
+ ( this . data [ this . offset ] & this . VALUE ) * Math . pow ( 2 , shift ) ;
104
+ shift += 7 ;
105
+ } while ( ( this . data [ this . offset ++ ] & this . MSB ) === this . MSB ) ;
106
+ return fieldNumber ;
107
+ }
108
+
109
+ _varInt ( ) {
110
+ let value = 0 ;
111
+ let shift = 0 ;
112
+ // Keep reading while upper bit set
113
+ do {
114
+ value += shift < 28 ?
115
+ ( this . data [ this . offset ] & this . VALUE ) << shift :
116
+ ( this . data [ this . offset ] & this . VALUE ) * Math . pow ( 2 , shift ) ;
117
+ shift += 7 ;
118
+ } while ( ( this . data [ this . offset ++ ] & this . MSB ) === this . MSB ) ;
119
+ return value ;
120
+ }
121
+ _uint64 ( ) {
122
+ // Read off a Uint64
123
+ let num = this . data [ this . offset ++ ] * 0x1000000 + ( this . data [ this . offset ++ ] << 16 ) + ( this . data [ this . offset ++ ] << 8 ) + this . data [ this . offset ++ ] ;
124
+ num = num * 0x100000000 + this . data [ this . offset ++ ] * 0x1000000 + ( this . data [ this . offset ++ ] << 16 ) + ( this . data [ this . offset ++ ] << 8 ) + this . data [ this . offset ++ ] ;
125
+ return num ;
126
+ }
127
+ _lenDelim ( ) {
128
+ // Read off the field length
129
+ const length = this . _varInt ( ) ;
130
+ const fieldBytes = this . data . slice ( this . offset , this . offset + length ) ;
131
+ let field ;
132
+ try {
133
+ // Attempt to parse as a new Protobuf Object
134
+ const pbObject = new Protobuf ( fieldBytes ) ;
135
+ field = pbObject . _parse ( ) ;
136
+ } catch ( err ) {
137
+ // Otherwise treat as bytes
138
+ field = this . _byteArrayToChars ( fieldBytes ) ;
139
+ }
140
+ // Move the offset and return the field
141
+ this . offset += length ;
142
+ return field ;
143
+ }
144
+ _uint32 ( ) {
145
+ // Use a dataview to read off the integer
146
+ const dataview = new DataView ( new Uint8Array ( this . data . slice ( this . offset , this . offset + 4 ) ) . buffer ) ;
147
+ const value = dataview . getUint32 ( 0 ) ;
148
+ this . offset += 4 ;
149
+ return value ;
150
+ }
151
+ _byteArrayToChars ( byteArray : ( Int8Array | Uint8Array ) ) {
152
+ if ( ! byteArray ) return "" ;
153
+ let str = "" ;
154
+ // String concatenation appears to be faster than an array join
155
+ for ( let i = 0 ; i < byteArray . length ; ) {
156
+ str += String . fromCharCode ( byteArray [ i ++ ] ) ;
157
+ }
158
+ return str ;
159
+ }
160
+ }
161
+
162
+ export default Protobuf ;
0 commit comments