Protocol::HTTYSourceProtocolHTTYStream

class Stream

Transport an opaque byte stream over HTTY chunks.

Definitions

PACKET_SIZE = 1024*3

Since base64 encoding adds 33% overhead, we can fit 3KB of binary data into a single HTTY chunk without exceeding the typical MTU of 4KB:

def initialize(input, output = input, packet_size: PACKET_SIZE)

Create a stream on top of HTTY framed input and output.

Signature

parameter input IO | IO::Stream

The source of framed HTTY chunks.

parameter output IO | IO::Stream | Nil

The sink for framed HTTY chunks.

parameter packet_size Integer

The maximum payload size for each chunk.

Implementation

def initialize(input, output = input, packet_size: PACKET_SIZE)
	@framer = Framer.new(::IO::Stream(input), ::IO::Stream(output))
	@packet_size = packet_size
	@buffer = +"".b
	@local_closed = false
	@remote_closed = false
end

def io

Return the writable IO object used by the underlying framer.

Signature

returns IO | IO::Stream

The output side of the framed transport.

Implementation

def io
	@framer.output
end

def read(length = nil)

Read application bytes from the HTTY transport.

Signature

parameter length Integer | Nil

The exact number of bytes to read, or nil for all buffered bytes.

returns String | Nil

The requested bytes, an empty binary string for 0, or nil if more data is required or the remote side is closed.

Implementation

def read(length = nil)
	return +"".b if length == 0
	
	fill(length)
	
	return nil if @buffer.empty? && @remote_closed
	return nil if @buffer.empty?
	return nil if length && @buffer.bytesize < length && !@remote_closed
	
	if length
		return @buffer.slice!(0, length)
	else
		return @buffer.slice!(0, @buffer.bytesize)
	end
end

def write(data)

Write application bytes as one or more HTTY chunks.

Signature

parameter data String | Array(Integer)

The opaque bytes to send.

returns self
raises IOError

If the local side of the transport is closed.

Implementation

def write(data)
	raise IOError, "HTTY stream is closed for writing!" if @local_closed
	
	data = data.to_s.b
	
	until data.empty?
		chunk = data.byteslice(0, @packet_size)
		@framer.write_chunk(chunk)
		data = data.byteslice(chunk.bytesize..)
	end
	
	@framer.flush
	
	return self
end

def flush

Flush any buffered output through the underlying framer.

Signature

returns void

Implementation

def flush
	@framer.flush
end

def close

Close the local write side of this stream abstraction. HTTY does not define a close packet, and closing this object does not close the underlying terminal IO.

Signature

returns void

Implementation

def close
	unless @local_closed
		@local_closed = true
		@framer.flush
	end
end

def closed?

Check whether the local side of the transport is closed.

Signature

returns bool

True if local writes have been closed.

Implementation

def closed?
	@local_closed
end

def readable?

Check whether the remote side may still provide more data.

Signature

returns bool

True if the remote side has not sent or implied a close.

Implementation

def readable?
	!@remote_closed
end