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