11const { Transform } = require ( 'stream' ) ;
2+ const {
3+ FrameEncoder,
4+ createLibp2pStreamFactory,
5+ encodeFrame
6+ } = require ( './FrameCodecShared' ) ;
27
38const DEBUG_FRAME_DECODER = process . env . FRAME_DECODER_DEBUG === '1' ;
49
5- const varint = {
6- encode : ( n ) => {
7- if ( n < 0 ) throw new RangeError ( 'varint unsigned only' ) ;
8- const o = [ ] ;
9- do {
10- let b = n & 0x7f ;
11- n = Math . floor ( n / 128 ) ;
12- if ( n > 0 ) b |= 0x80 ;
13- o . push ( b ) ;
14- } while ( n > 0 ) ;
15- return Buffer . from ( o ) ;
16- } ,
17- decodeFrom : ( buf , offset = 0 ) => {
18- let r = 0 , s = 0 , i = offset ;
19- for ( ; i < buf . length ; i ++ ) {
20- const b = buf [ i ] ;
21- r |= ( b & 0x7f ) << s ;
22- if ( ( b & 0x80 ) === 0 ) return { value : r , bytes : i - offset + 1 } ;
23- s += 7 ;
24- if ( s > 53 ) break ;
25- }
26- return null ;
27- }
28- } ;
29-
30- class FrameEncoder extends Transform {
31- constructor ( ) {
32- super ( { writableObjectMode : true } ) ;
33- }
34- _transform ( f , e , cb ) {
35- try {
36- if ( ! Buffer . isBuffer ( f ) ) f = Buffer . from ( f ) ;
37- this . push ( Buffer . concat ( [ varint . encode ( f . length ) , f ] ) ) ;
38- cb ( ) ;
39- } catch ( err ) {
40- cb ( err ) ;
41- }
42- }
43- }
44-
4510class FrameDecoder extends Transform {
4611 constructor ( ) {
4712 super ( { readableObjectMode : true } ) ; // object mode ensures zero-length payloads surface as readable chunks
@@ -129,9 +94,25 @@ class FrameDecoder extends Transform {
12994 }
13095
13196 _take ( n , label = 'bytes' ) {
97+ this . _log ( 'take_start' , { label, bytes : n , buffered : this . _l } ) ;
98+
99+ // Zero-copy fast path: single chunk contains all needed bytes
100+ if ( this . _q . length > 0 && this . _q [ 0 ] . length >= n ) {
101+ const head = this . _q [ 0 ] ;
102+ const slice = head . slice ( 0 , n ) ;
103+ this . _l -= n ;
104+ if ( n === head . length ) {
105+ this . _q . shift ( ) ;
106+ } else {
107+ this . _q [ 0 ] = head . slice ( n ) ;
108+ }
109+ this . _log ( 'take_complete' , { label, bytes : n , buffered : this . _l , zeroCopy : true } ) ;
110+ return slice ;
111+ }
112+
113+ // Multi-chunk path: allocate and copy
132114 const f = Buffer . allocUnsafe ( n ) ;
133115 let w = 0 ;
134- this . _log ( 'take_start' , { label, bytes : n , buffered : this . _l } ) ;
135116 while ( w < n && this . _q . length > 0 ) {
136117 const next = this . _q [ 0 ] ;
137118 const t = Math . min ( next . length , n - w ) ;
@@ -145,19 +126,16 @@ class FrameDecoder extends Transform {
145126 }
146127 this . _log ( 'take_progress' , { label, copied : t , written : w , buffered : this . _l } ) ;
147128 }
148- this . _log ( 'take_complete' , { label, bytes : n , buffered : this . _l } ) ;
129+ this . _log ( 'take_complete' , { label, bytes : n , buffered : this . _l , zeroCopy : false } ) ;
149130 return f ;
150131 }
151132}
152133
153134FrameDecoder . _nextId = 1 ;
154135
155- function createLibp2pStream ( socket ) {
156- const d = new FrameDecoder ( ) , e = new FrameEncoder ( ) ;
157- socket . pipe ( d ) ; e . pipe ( socket ) ;
158- const s = { source : ( async function * ( ) { for await ( const c of d ) yield c ; } ) ( ) , sink : async ( src ) => { for await ( const c of src ) { if ( ! e . write ( c ) ) await new Promise ( r => e . once ( 'drain' , r ) ) ; } e . end ( ) ; } } ;
159- s [ Symbol . asyncIterator ] = ( ) => s . source [ Symbol . asyncIterator ] ( ) ;
160- return s ;
161- }
162-
163- module . exports = { FrameEncoder, FrameDecoder, createLibp2pStream, encodeFrame : ( b ) => { const buf = Buffer . isBuffer ( b ) ? b : Buffer . from ( b ) ; return Buffer . concat ( [ varint . encode ( buf . length ) , buf ] ) ; } } ;
136+ module . exports = {
137+ FrameEncoder,
138+ FrameDecoder,
139+ createLibp2pStream : createLibp2pStreamFactory ( ( ) => new FrameDecoder ( ) ) ,
140+ encodeFrame
141+ } ;
0 commit comments