@@ -22,6 +22,13 @@ const log = getLogger('DVTSecureSocketProxyService');
2222
2323const MIN_ERROR_DESCRIPTION_LENGTH = 20 ;
2424
25+ interface ChannelWaiter {
26+ resolve : ( message : Buffer ) => void ;
27+ reject : ( err : Error ) => void ;
28+ signal ?: AbortSignal ;
29+ onAbort ?: ( ) => void ;
30+ }
31+
2532/**
2633 * DVTSecureSocketProxyService provides access to Apple's DTServiceHub functionality
2734 * This service enables various instruments and debugging capabilities through the DTX protocol
@@ -37,8 +44,18 @@ export class DVTSecureSocketProxyService extends BaseService {
3744 private curMessageId : number = 0 ;
3845 private readonly channelCache : Map < string , Channel > = new Map ( ) ;
3946 private readonly channelMessages : Map < number , ChannelFragmenter > = new Map ( ) ;
47+ private readonly channelWaiters : Map < number , ChannelWaiter [ ] > = new Map ( ) ;
4048 private isHandshakeComplete : boolean = false ;
4149 private readBuffer : Buffer = Buffer . alloc ( 0 ) ;
50+ private readonly pendingChunks : Buffer [ ] = [ ] ;
51+ private bufferedLength : number = 0 ;
52+ private readonly dataWaiters : Array < ( ) => void > = [ ] ;
53+ private socketError : Error | null = null ;
54+ private isPumpRunning : boolean = false ;
55+ private listeningSocket : net . Socket | null = null ;
56+ private readonly boundOnData = ( chunk : Buffer ) : void => this . onSocketData ( chunk ) ;
57+ private readonly boundOnError = ( err : Error ) : void => this . onSocketError ( err ) ;
58+ private readonly boundOnClose = ( ) : void => this . onSocketClose ( ) ;
4259
4360 constructor ( udid : string ) {
4461 super ( udid ) ;
@@ -57,6 +74,7 @@ export class DVTSecureSocketProxyService extends BaseService {
5774 this . connection = await this . startLockdownWithoutCheckin ( DVTSecureSocketProxyService . RSD_SERVICE_NAME ) ;
5875 this . socket = this . connection . getSocket ( ) ;
5976 stripSSL ( this . socket ) ;
77+ this . attachSocketListeners ( this . socket ) ;
6078
6179 await this . performHandshake ( ) ;
6280 }
@@ -252,19 +270,99 @@ export class DVTSecureSocketProxyService extends BaseService {
252270 }
253271
254272 const socketToDestroy = this . socket ;
273+ this . detachSocketListeners ( ) ;
255274 this . connection . close ( ) ;
256275 this . connection = null ;
257276 this . socket = null ;
258277 this . isHandshakeComplete = false ;
278+ this . socketError = null ;
259279 this . channelCache . clear ( ) ;
260280 this . channelMessages . clear ( ) ;
261281 this . channelMessages . set ( DVTSecureSocketProxyService . BROADCAST_CHANNEL , new ChannelFragmenter ( ) ) ;
282+ this . readBuffer = Buffer . alloc ( 0 ) ;
283+ this . pendingChunks . length = 0 ;
284+ this . bufferedLength = 0 ;
285+ this . rejectAllWaiters ( new Error ( 'Service closed' ) ) ;
286+ this . flushDataWaiters ( ) ;
262287
263- // Forcibly destroy the socket so any pending readExact calls unblock
264- // immediately rather than waiting for the remote FIN.
265288 socketToDestroy ?. destroy ( ) ;
266289 }
267290
291+ private attachSocketListeners ( socket : net . Socket ) : void {
292+ if ( this . listeningSocket === socket ) {
293+ return ;
294+ }
295+ this . detachSocketListeners ( ) ;
296+ this . listeningSocket = socket ;
297+ socket . on ( 'data' , this . boundOnData ) ;
298+ socket . on ( 'error' , this . boundOnError ) ;
299+ socket . on ( 'close' , this . boundOnClose ) ;
300+ }
301+
302+ private detachSocketListeners ( ) : void {
303+ if ( ! this . listeningSocket ) {
304+ return ;
305+ }
306+ this . listeningSocket . off ( 'data' , this . boundOnData ) ;
307+ this . listeningSocket . off ( 'error' , this . boundOnError ) ;
308+ this . listeningSocket . off ( 'close' , this . boundOnClose ) ;
309+ this . listeningSocket = null ;
310+ }
311+
312+ private flushDataWaiters ( ) : void {
313+ for ( const waiter of this . dataWaiters . splice ( 0 ) ) {
314+ waiter ( ) ;
315+ }
316+ }
317+
318+ private onSocketData ( chunk : Buffer ) : void {
319+ this . pendingChunks . push ( chunk ) ;
320+ this . bufferedLength += chunk . length ;
321+ if ( this . dataWaiters . length > 0 ) {
322+ this . flushDataWaiters ( ) ;
323+ } else {
324+ this . listeningSocket ?. pause ( ) ;
325+ }
326+ }
327+
328+ private consolidateReadBuffer ( ) : void {
329+ if ( this . pendingChunks . length === 0 ) {
330+ return ;
331+ }
332+ this . readBuffer = Buffer . concat ( [ this . readBuffer , ...this . pendingChunks ] ) ;
333+ this . pendingChunks . length = 0 ;
334+ }
335+
336+ private onSocketError ( err : Error ) : void {
337+ if ( ! this . socketError ) {
338+ this . socketError = err ;
339+ }
340+ this . flushDataWaiters ( ) ;
341+ }
342+
343+ private onSocketClose ( ) : void {
344+ if ( ! this . socketError ) {
345+ this . socketError = new Error ( 'Socket closed' ) ;
346+ }
347+ this . flushDataWaiters ( ) ;
348+ }
349+
350+ private rejectAllWaiters ( error : Error ) : void {
351+ for ( const waiters of this . channelWaiters . values ( ) ) {
352+ for ( const waiter of waiters . splice ( 0 ) ) {
353+ if ( waiter . signal && waiter . onAbort ) {
354+ waiter . signal . removeEventListener ( 'abort' , waiter . onAbort ) ;
355+ }
356+ waiter . reject ( error ) ;
357+ }
358+ }
359+ this . channelWaiters . clear ( ) ;
360+ }
361+
362+ private hasPendingWaiters ( ) : boolean {
363+ return Array . from ( this . channelWaiters . values ( ) ) . some ( ( waiters ) => waiters . length > 0 ) ;
364+ }
365+
268366 /**
269367 * Perform DTX protocol handshake to establish connection and retrieve capabilities
270368 */
@@ -333,6 +431,7 @@ export class DVTSecureSocketProxyService extends BaseService {
333431 * Drain any buffered messages that arrived during handshake
334432 */
335433 private async drainBufferedMessages ( ) : Promise < void > {
434+ this . consolidateReadBuffer ( ) ;
336435 if ( this . readBuffer . length === 0 ) {
337436 return ;
338437 }
@@ -354,28 +453,81 @@ export class DVTSecureSocketProxyService extends BaseService {
354453 } catch ( error ) {
355454 log . debug ( 'Error while draining buffer:' , error ) ;
356455 }
456+ this . bufferedLength = this . readBuffer . length ;
357457 }
358458
359459 /**
360460 * Receive packet fragments until a complete message is available for the specified channel
361461 */
362462 private async recvPacketFragments ( channel : number , signal ?: AbortSignal ) : Promise < Buffer > {
363- while ( true ) {
364- const fragmenter = this . channelMessages . get ( channel ) ;
365- if ( ! fragmenter ) {
366- throw new Error ( `No fragmenter for channel ${ channel } ` ) ;
367- }
463+ const fragmenter = this . channelMessages . get ( channel ) ;
464+ if ( ! fragmenter ) {
465+ throw new Error ( `No fragmenter for channel ${ channel } ` ) ;
466+ }
467+
468+ const queued = fragmenter . get ( ) ;
469+ if ( queued ) {
470+ return queued ;
471+ }
472+
473+ signal ?. throwIfAborted ( ) ;
368474
369- // Check if we have a complete message
370- const message = fragmenter . get ( ) ;
371- if ( message ) {
372- return message ;
475+ const promise = new Promise < Buffer > ( ( resolve , reject ) => {
476+ const waiter : ChannelWaiter = { resolve, reject, signal} ;
477+ if ( signal ) {
478+ const onAbort = ( ) => {
479+ const waiters = this . channelWaiters . get ( channel ) ;
480+ if ( waiters ) {
481+ const idx = waiters . indexOf ( waiter ) ;
482+ if ( idx >= 0 ) {
483+ waiters . splice ( idx , 1 ) ;
484+ }
485+ }
486+ reject ( signal . reason ?? new DOMException ( 'Receive aborted' , 'AbortError' ) ) ;
487+ } ;
488+ waiter . onAbort = onAbort ;
489+ signal . addEventListener ( 'abort' , onAbort , { once : true } ) ;
373490 }
374491
375- // Read next message header
376- const headerData = await this . readExact ( DTX_CONSTANTS . MESSAGE_HEADER_SIZE , signal ) ;
492+ const waiters = this . channelWaiters . get ( channel ) ?? [ ] ;
493+ this . channelWaiters . set ( channel , waiters ) ;
494+ waiters . push ( waiter ) ;
495+ } ) ;
496+
497+ this . ensurePumpRunning ( ) ;
498+ return promise ;
499+ }
500+
501+ private ensurePumpRunning ( ) : void {
502+ if ( this . isPumpRunning ) {
503+ return ;
504+ }
505+ this . isPumpRunning = true ;
506+ void this . runPump ( )
507+ . catch ( ( err : unknown ) => {
508+ this . rejectAllWaiters ( err instanceof Error ? err : new Error ( String ( err ) ) ) ;
509+ } )
510+ . finally ( ( ) => {
511+ this . isPumpRunning = false ;
512+ if ( this . hasPendingWaiters ( ) ) {
513+ this . ensurePumpRunning ( ) ;
514+ }
515+ } ) ;
516+ }
517+
518+ private async runPump ( ) : Promise < void > {
519+ while ( this . hasPendingWaiters ( ) ) {
520+ const headerData = await this . readExact ( DTX_CONSTANTS . MESSAGE_HEADER_SIZE ) ;
377521 const header = DTXMessage . parseMessageHeader ( headerData ) ;
378522
523+ if ( header . magic !== DTX_CONSTANTS . MESSAGE_HEADER_MAGIC ) {
524+ const err = new Error (
525+ `Invalid DTX message header magic: 0x${ header . magic . toString ( 16 ) } (stream desynchronized)` ,
526+ ) ;
527+ this . socketError = err ;
528+ throw err ;
529+ }
530+
379531 const receivedChannel = Math . abs ( header . channelCode ) ;
380532
381533 if ( ! this . channelMessages . has ( receivedChannel ) ) {
@@ -392,80 +544,61 @@ export class DVTSecureSocketProxyService extends BaseService {
392544 continue ;
393545 }
394546
395- // Read message payload
396547 const messageData = await this . readExact ( header . length ) ;
397548
398- // Add fragment to appropriate channel
399549 const targetFragmenter = this . channelMessages . get ( receivedChannel ) ;
400550 if ( ! targetFragmenter ) {
401551 continue ;
402552 }
403553 targetFragmenter . addFragment ( header , messageData ) ;
554+
555+ const waiters = this . channelWaiters . get ( receivedChannel ) ;
556+ if ( waiters && waiters . length > 0 ) {
557+ const completedMessage = targetFragmenter . get ( ) ;
558+ if ( completedMessage ) {
559+ const waiter = waiters . shift ( ) ;
560+ if ( waiter ) {
561+ if ( waiter . signal && waiter . onAbort ) {
562+ waiter . signal . removeEventListener ( 'abort' , waiter . onAbort ) ;
563+ }
564+ waiter . resolve ( completedMessage ) ;
565+ }
566+ }
567+ }
404568 }
405569 }
406570
407571 /**
408572 * Read exact number of bytes from socket with buffering
409573 */
410- private async readExact ( length : number , signal ?: AbortSignal ) : Promise < Buffer > {
411- if ( ! this . socket ) {
574+ private async readExact ( length : number ) : Promise < Buffer > {
575+ const socket = this . socket ;
576+ if ( ! socket ) {
412577 throw new Error ( `${ this . constructor . name } is not initialized. Call connect() before sending messages.` ) ;
413578 }
414579
415- // Keep reading until we have enough data
416- while ( this . readBuffer . length < length ) {
417- // Fast-fail on closed/aborted reads so callers can terminate blocked
418- // iterators instead of hanging until another socket event arrives.
419- if ( this . socket . destroyed ) {
580+ if ( this . listeningSocket !== socket ) {
581+ this . attachSocketListeners ( socket ) ;
582+ }
583+
584+ while ( this . bufferedLength < length ) {
585+ if ( this . socketError ) {
586+ throw this . socketError ;
587+ }
588+ if ( this . socket !== socket || socket . destroyed ) {
420589 throw new Error ( 'Socket is destroyed' ) ;
421590 }
422- signal ?. throwIfAborted ( ) ;
423-
424- const chunk = await new Promise < Buffer > ( ( resolve , reject ) => {
425- const socket = this . socket ;
426- if ( ! socket ) {
427- reject ( new Error ( 'Socket is not available' ) ) ;
428- return ;
429- }
430- const cleanup = ( ) => {
431- socket . off ( 'data' , onData ) ;
432- socket . off ( 'error' , onError ) ;
433- socket . off ( 'close' , onClose ) ;
434- signal ?. removeEventListener ( 'abort' , onAbort ) ;
435- } ;
436-
437- const onData = ( data : Buffer ) => {
438- cleanup ( ) ;
439- resolve ( data ) ;
440- } ;
441591
442- const onError = ( err : Error ) => {
443- cleanup ( ) ;
444- reject ( err ) ;
445- } ;
446-
447- const onClose = ( ) => {
448- cleanup ( ) ;
449- reject ( new Error ( 'Socket closed during read' ) ) ;
450- } ;
451-
452- const onAbort = ( ) => {
453- cleanup ( ) ;
454- reject ( signal ?. reason ?? new DOMException ( 'Read aborted' , 'AbortError' ) ) ;
455- } ;
456-
457- socket . once ( 'data' , onData ) ;
458- socket . once ( 'error' , onError ) ;
459- socket . once ( 'close' , onClose ) ;
460- signal ?. addEventListener ( 'abort' , onAbort , { once : true } ) ;
592+ socket . resume ( ) ;
593+ await new Promise < void > ( ( resolve ) => {
594+ this . dataWaiters . push ( resolve ) ;
461595 } ) ;
462-
463- this . readBuffer = Buffer . concat ( [ this . readBuffer , chunk ] ) ;
464596 }
465597
466- // Extract exact amount requested
598+ this . consolidateReadBuffer ( ) ;
467599 const result = this . readBuffer . subarray ( 0 , length ) ;
468600 this . readBuffer = this . readBuffer . subarray ( length ) ;
601+ this . bufferedLength = this . readBuffer . length ;
469602
470603 return result ;
471604 }
0 commit comments