Class: Protocol::HTTY::Stream

Inherits:
Object
  • Object
show all
Defined in:
lib/protocol/htty/stream.rb

Overview

Transport an opaque byte stream after the HTTY bootstrap handshake.

Constant Summary collapse

ESC =
"\e"
DCS =
"#{ESC}P"
ST =
"#{ESC}\\"
BOOTSTRAP_PREFIX =
"+H"
RAW_MODE =
"raw"
HTTP2_FRAME_HEADER_SIZE =
9

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(input, output) ⇒ Stream

Create a stream on top of raw byte-preserving endpoints.



40
41
42
43
44
45
# File 'lib/protocol/htty/stream.rb', line 40

def initialize(input, output)
	@input = input
	@output = ::IO::Stream(output)
	@frame_remaining = nil
	@local_closed = false
end

Instance Attribute Details

#inputObject (readonly)

Returns the value of attribute input.



47
48
49
# File 'lib/protocol/htty/stream.rb', line 47

def input
  @input
end

#outputObject (readonly)

Returns the value of attribute output.



48
49
50
# File 'lib/protocol/htty/stream.rb', line 48

def output
  @output
end

Class Method Details

.frame_length(buffer) ⇒ Object



143
144
145
146
# File 'lib/protocol/htty/stream.rb', line 143

def self.frame_length(buffer)
	length_high, length_low = buffer.unpack("Cn")
	return (length_high << 16) | length_low
end

.open(input, output, bootstrap: nil, mode: RAW_MODE) ⇒ Object



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/protocol/htty/stream.rb', line 20

def self.open(input, output, bootstrap: nil, mode: RAW_MODE)
	stream = self.new(input, output)
	
	case bootstrap
	when :write
		stream.write_bootstrap(mode)
	when :read
		actual_mode = stream.read_bootstrap
		
		unless actual_mode == mode
			raise ProtocolError, "Expected HTTY bootstrap mode #{mode.inspect}, got #{actual_mode.inspect}"
		end
	end
	
	return stream
end

Instance Method Details

#close_write(error = nil) ⇒ Object Also known as: 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.



120
121
122
123
124
125
# File 'lib/protocol/htty/stream.rb', line 120

def close_write(error = nil)
	unless @local_closed
		@local_closed = true
		@output.flush
	end
end

#closed?Boolean

Check whether the local side of the transport is closed.

Returns:

  • (Boolean)


131
132
133
# File 'lib/protocol/htty/stream.rb', line 131

def closed?
	@local_closed
end

#flushObject

Flush any buffered output through the underlying stream.



113
114
115
# File 'lib/protocol/htty/stream.rb', line 113

def flush
	@output.flush
end

#ioObject

Return the underlying output stream.



51
52
53
# File 'lib/protocol/htty/stream.rb', line 51

def io
	@output
end

#read(length = nil) ⇒ Object

Read application bytes from the HTTY transport.



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/protocol/htty/stream.rb', line 76

def read(length = nil)
	if length == 0
		@frame_remaining = nil if @frame_remaining == 0
		return +"".b
	end
	
	requested_length = length
	length = [length, @frame_remaining].min if length && @frame_remaining && @frame_remaining > 0
	buffer = read_exact(length)
	
	if buffer && requested_length == HTTP2_FRAME_HEADER_SIZE && !@frame_remaining
		if buffer.bytesize == HTTP2_FRAME_HEADER_SIZE
			frame_length = self.class.frame_length(buffer)
			@frame_remaining = frame_length if frame_length > 0
		end
	elsif buffer && @frame_remaining
		@frame_remaining -= buffer.bytesize
		@frame_remaining = nil if @frame_remaining <= 0
	end
	
	return buffer
end

#read_bootstrapObject



60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/protocol/htty/stream.rb', line 60

def read_bootstrap
	while payload = read_payload
		next unless payload.start_with?(BOOTSTRAP_PREFIX)
		mode = payload.delete_prefix(BOOTSTRAP_PREFIX)
		
		unless mode == RAW_MODE
			raise ProtocolError, "Unsupported HTTY bootstrap mode: #{mode.inspect}"
		end
		
		return mode
	end
	
	return nil
end

#readable?Boolean

Check whether the remote side may still provide more data.

Returns:

  • (Boolean)


137
138
139
# File 'lib/protocol/htty/stream.rb', line 137

def readable?
	!(@input.respond_to?(:closed?) && @input.closed?)
end

#write(data, flush: false) ⇒ Object

Write application bytes after bootstrap.

Raises:

  • (IOError)


102
103
104
105
106
107
108
109
# File 'lib/protocol/htty/stream.rb', line 102

def write(data, flush: false)
	raise IOError, "HTTY stream is closed for writing!" if @local_closed
	
	@output.write(data.to_s.b)
	@output.flush if flush
	
	return self
end

#write_bootstrap(mode = RAW_MODE) ⇒ Object



55
56
57
58
# File 'lib/protocol/htty/stream.rb', line 55

def write_bootstrap(mode = RAW_MODE)
	@output.write("#{DCS}#{BOOTSTRAP_PREFIX}#{mode}#{ST}")
	@output.flush
end