Class: Protocol::WebSocket::Framer

Inherits:
Object
  • Object
show all
Defined in:
lib/protocol/websocket/framer.rb

Overview

Wraps an underlying Async::IO::Stream for reading and writing binary data into structured frames.

Instance Method Summary collapse

Constructor Details

#initialize(stream, frames = FRAMES) ⇒ Framer

Initialize a new framer wrapping the given stream.



35
36
37
38
# File 'lib/protocol/websocket/framer.rb', line 35

def initialize(stream, frames = FRAMES)
	@stream = stream
	@frames = frames
end

Instance Method Details

#closeObject

Close the underlying stream.



41
42
43
# File 'lib/protocol/websocket/framer.rb', line 41

def close
	@stream.close
end

#flushObject

Flush the underlying stream.



46
47
48
# File 'lib/protocol/websocket/framer.rb', line 46

def flush
	@stream.flush
end

#read_frame(maximum_frame_size = MAXIMUM_ALLOWED_FRAME_SIZE) ⇒ Object

Read a frame from the underlying stream.



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/protocol/websocket/framer.rb', line 52

def read_frame(maximum_frame_size = MAXIMUM_ALLOWED_FRAME_SIZE)
	buffer = @stream.read(2)
	
	unless buffer and buffer.bytesize == 2
		raise EOFError, "Could not read frame header!"
	end
	
	first_byte = buffer.getbyte(0)
	second_byte = buffer.getbyte(1)
	
	finished = (first_byte & 0b1000_0000 != 0)
	flags = (first_byte & 0b0111_0000) >> 4
	opcode = first_byte & 0b0000_1111
	
	if opcode >= 0x3 && opcode <= 0x7
		raise ProtocolError, "Non-control opcode = #{opcode} is reserved!"
	elsif opcode >= 0xB
		raise ProtocolError, "Control opcode = #{opcode} is reserved!"
	end
	
	mask = (second_byte & 0b1000_0000 != 0)
	length = second_byte & 0b0111_1111
	
	if opcode & 0x8 != 0
		if length > 125
			raise ProtocolError, "Invalid control frame payload length: #{length} > 125!"
		elsif !finished
			raise ProtocolError, "Fragmented control frame!"
		end
	end
	
	if length == 126
		if mask
			buffer = @stream.read(6) or raise EOFError, "Could not read length and mask!"
			length = buffer.unpack1("n")
			mask = buffer.byteslice(2, 4)
		else
			buffer = @stream.read(2) or raise EOFError, "Could not read length!"
			length = buffer.unpack1("n")
		end
	elsif length == 127
		if mask
			buffer = @stream.read(12) or raise EOFError, "Could not read length and mask!"
			length = buffer.unpack1("Q>")
			mask = buffer.byteslice(8, 4)
		else
			buffer = @stream.read(8) or raise EOFError, "Could not read length!"
			length = buffer.unpack1("Q>")
		end
	elsif mask
		mask = @stream.read(4) or raise EOFError, "Could not read mask!"
	end
	
	if length > maximum_frame_size
		raise ProtocolError, "Invalid payload length: #{length} > #{maximum_frame_size}!"
	end
	
	payload = @stream.read(length) or raise EOFError, "Could not read payload!"
	
	if payload.bytesize != length
		raise EOFError, "Incorrect payload length: #{length} != #{payload.bytesize}!"
	end
	
	klass = @frames[opcode] || Frame
	return klass.new(finished, payload, flags: flags, opcode: opcode, mask: mask)
end

#write_frame(frame) ⇒ Object

Write a frame to the underlying stream.



122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/protocol/websocket/framer.rb', line 122

def write_frame(frame)
	if frame.mask and frame.mask.bytesize != 4
		raise ProtocolError, "Invalid mask length!"
	end
	
	length = frame.length
	
	if length <= 125
		short_length = length
	elsif length.bit_length <= 16
		short_length = 126
	else
		short_length = 127
	end
	
	buffer = [
		(frame.finished ? 0b1000_0000 : 0) | (frame.flags << 4) | frame.opcode,
		(frame.mask ? 0b1000_0000 : 0) | short_length,
	].pack("CC")
	
	if short_length == 126
		buffer << [length].pack("n")
	elsif short_length == 127
		buffer << [length].pack("Q>")
	end
	
	buffer << frame.mask if frame.mask
	
	@stream.write(buffer)
	@stream.write(frame.payload)
end