import Foundation import NIOCore /// Bidirectional TCP forwarder. Pairs two channels so bytes flow in both directions. final class GlueHandler: ChannelInboundHandler, RemovableChannelHandler { typealias InboundIn = ByteBuffer typealias OutboundOut = ByteBuffer var partner: GlueHandler? private var context: ChannelHandlerContext? private var pendingRead = false func handlerAdded(context: ChannelHandlerContext) { self.context = context ProxyLogger.glue.debug("GlueHandler added to \(context.channel.localAddress?.description ?? "?")") } func handlerRemoved(context: ChannelHandlerContext) { ProxyLogger.glue.debug("GlueHandler removed from \(context.channel.localAddress?.description ?? "?")") self.context = nil self.partner = nil } func channelRead(context: ChannelHandlerContext, data: NIOAny) { let buf = unwrapInboundIn(data) ProxyLogger.glue.debug("GlueHandler read \(buf.readableBytes) bytes, forwarding to partner") partner?.write(buf) } func channelReadComplete(context: ChannelHandlerContext) { partner?.flush() } func channelInactive(context: ChannelHandlerContext) { ProxyLogger.glue.debug("GlueHandler channelInactive — closing partner") partner?.close() } func errorCaught(context: ChannelHandlerContext, error: Error) { ProxyLogger.glue.error("GlueHandler error: \(error.localizedDescription)") context.close(promise: nil) } func channelWritabilityChanged(context: ChannelHandlerContext) { if context.channel.isWritable { partner?.read() } } private func write(_ buffer: ByteBuffer) { context?.write(wrapOutboundOut(buffer), promise: nil) } private func flush() { context?.flush() } private func read() { if let context, !pendingRead { pendingRead = true context.read() pendingRead = false } } private func close() { context?.close(promise: nil) } }