'use strict'; const {EventEmitter}=require('node:events'); const {TextDecoder}=require('node:util'); const SMALL=65536,LARGE=2097152; // Transport only: the native host and parent must authenticate their pipe peer // before constructing this channel. Framing does not grant policy authority. function createFramedChannel({readable,writable,inboundFirstLimit=SMALL,outboundFirstLimit=SMALL}={}){ if(!readable||!writable||typeof readable.on!=='function'||typeof writable.write!=='function'||![SMALL,LARGE].includes(inboundFirstLimit)||![SMALL,LARGE].includes(outboundFirstLimit))throw Error('broker-channel-invalid'); const channel=new EventEmitter(),decoder=new TextDecoder('utf-8',{fatal:true}); let connected=true,firstRead=true,firstWrite=true,header=Buffer.alloc(4),headerBytes=0,payload,payloadBytes=0,pending=0; const outstanding=new Set(); Object.defineProperty(channel,'connected',{get:()=>connected}); function disconnect(){ if(!connected)return;connected=false;header.fill(0);payload?.fill(0);payload=undefined; readable.off('data',data);readable.off('end',disconnect);readable.off('close',disconnect); // Keep bounded error listeners until destroyed streams settle. readable.destroy?.();if(writable!==readable)writable.destroy?.(); for(const record of outstanding)record.notify(true); channel.emit('disconnect'); } function data(chunk){ if(!connected)return; try{ if(!Buffer.isBuffer(chunk))throw Error(); let offset=0; while(offsetlimit)throw Error(); payload=Buffer.alloc(length);payloadBytes=0;headerBytes=0;header.fill(0); } const take=Math.min(payload.length-payloadBytes,chunk.length-offset);chunk.copy(payload,payloadBytes,offset,offset+take);payloadBytes+=take;offset+=take; if(payloadBytes===payload.length){ const completed=payload;payload=undefined;payloadBytes=0;firstRead=false; let message;try{message=JSON.parse(decoder.decode(completed));}finally{completed.fill(0);} if(!message||typeof message!=='object'||Array.isArray(message))throw Error(); channel.emit('message',message); } } }catch{disconnect();} } channel.send=(message,callback=()=>{})=>{ if(typeof callback!=='function')throw Error('broker-channel-invalid'); const notifyFailure=()=>{disconnect();try{callback(Error('broker-channel-unavailable'));}catch{}}; if(!connected||pending>=8){notifyFailure();return;} let frame; try{ if(!message||typeof message!=='object'||Array.isArray(message))throw Error(); const text=JSON.stringify(message),bytes=Buffer.byteLength(text),limit=firstWrite?outboundFirstLimit:SMALL; if(bytes<1||bytes>limit)throw Error(); frame=Buffer.alloc(bytes+4);frame.writeUInt32LE(bytes);frame.write(text,4);firstWrite=false;pending++; }catch{notifyFailure();return;} let settled=false,notified=false; const record={notify(error){if(notified)return;notified=true;try{callback(error?Error('broker-channel-unavailable'):undefined);}catch{disconnect();}}}; outstanding.add(record); // Completion of the underlying write owns buffer release. Disconnect notifies // failure immediately, but cannot claim that an outstanding write has settled. const done=error=>{if(settled)return;settled=true;pending--;outstanding.delete(record);frame.fill(0);if(error)disconnect();record.notify(error||!connected);}; try{writable.write(frame,done);}catch{done(Error());} }; channel.disconnect=disconnect; readable.on('data',data);readable.once('end',disconnect);readable.once('close',disconnect);readable.on('error',disconnect); if(writable!==readable){writable.once('close',disconnect);writable.on('error',disconnect);} return channel; } module.exports={createFramedChannel};