Tonight I am back to SPDY gem land. Specifically, I would like to get the remaining SPDY control frames into the gem. Currently supported are:
rspec ./spec/protocol_spec.rb -cfsI love me some good executable documentation.
SPDY::Protocol
NV
SYN_STREAM
SYN_REPLY
PING
DATA
RST_STREAM
According to draft #2 of the SPDY protocol, the supported control frames are:
2.7 Control framesFirst up, a bit of housekeeping. The spec order might as well follow the order laid out in the draft protocol. Also the DATA frames are separate from the control frames. Something like this should do:
2.7.1 SYN_STREAM
2.7.2 SYN_REPLY
2.7.3 RST_STREAM
2.7.4 SETTINGS
2.7.5 NOOP
2.7.6 PING
2.7.7 GOAWAY
2.7.8 HEADERS
2.7.9 WINDOW_UPDATE
SPDY::ProtocolNice. The name-value frames are not technically control frames themselves, but are embedded in SYN_STREAM, SYN_REPLY, and HEADERS frames, so it makes sense to include its specification in there (that is where the draft protocol defines them).
data frames
DATA
control frames
SYN_STREAM
SYN_REPLY
RST_STREAM
PING
NV
So I add describe blocks to
spec/protocol_spec.rb
for the missing sections:describe "SETTINGS"I mark the NOOP frame as not implemented / won't implement. Per the notes for the upcoming draft 3 of the SPDY protocol, NOOP is going away, so there is not much sense in working on adding support for it.
describe "NOOP" do
specify "not implemented (being dropped from protocol)" do
# NOOP
end
end
describe "PING" do
# ... already done
end
describe "GOAWAY"
describe "HEADERS"
That leaves 3 control frames that need to be supported. First up is the SETTINGS frame. SETTINGS are somewhat similar to HTTP cookies in normal HTTP. They store data and can persist. Just like with cookies, the server can set a value in the client and request the client resend it with every request.
SETTINGS differ from cookies in that there are only 5 preset values currently allowed (integer ID): SETTINGS_UPLOAD_BANDWIDTH (1), SETTINGS_DOWNLOAD_BANDWIDTH (2), SETTINGS_ROUND_TRIP_TIME (3), SETTINGS_MAX_CONCURRENT_STREAMS (4), and SETTINGS_CURRENT_CWND (5). It is meant to be extensible in the future, but for now, it is just those 5 possible settings.
From the draft, the packet looks like:
+----------------------------------+In RSpec form, that ought to read something like:
|1| 1 | 4 |
+----------------------------------+
| Flags (8) | Length (24 bits) |
+----------------------------------+
| Number of entries |
+----------------------------------+
| ID/Value Pairs |
| ... |
SETTINGSThe API for a settings beasty ought to take a hash argument with one or more of the five symbols as keys. For example, if I wanted to indicate the round trip time as 300 milliseconds, I might craft the packet as:
the assembled packet
starts with a control bit (PENDING: Not Yet Implemented)
followed by the version (2) (PENDING: Not Yet Implemented)
followed by the type (4) (PENDING: Not Yet Implemented)
followed by flags (PENDING: Not Yet Implemented)
followed by the length (24 bits) (PENDING: Not Yet Implemented)
followed by the number of entries (32 bits) (PENDING: Not Yet Implemented)
followed by ID/Value Pairs (PENDING: Not Yet Implemented)
@settings = SPDY::Protocol::Control::Settings.newThus, to inspect the bytes in the produced frame, my setup block might look like:
@settings.create(
:settings_round_trip_time => 300
)
describe "SETTINGS" doThe first spec for the frame is that it needs to start with a control bit:
describe "the assembled packet" do
before do
@settings = SPDY::Protocol::Control::Settings.new
@settings.create(
:settings_round_trip_time => 300
)
@frame = Array(@settings.to_binary_s.bytes)
end
end
specify "starts with a control bit" doI can make that pass with:
@frame[0].should == 128
end
class Settings < BinData::RecordMost of the next specs are more or less copies of similar fields in other packets. The length fields requires a bit of noodling... actually not too much. The number of bytes after the length field is determined entirely by the ID/Value pairs. Since IDs are always 32 bits and values are 32 bits as well, the number of bytes is
bit1 :frame, :initial_value => CONTROL_BIT
def parse(chunk)
self.read(chunk)
self
end
def create(opts = {})
self
end
end
number of entries * (4 bytes + 4 bytes)
. Since my setup block has one entry, I should expect a length of 8. Similarly, I should expect the length (the number of entries) to be 1:describe "SETTINGS" doTo implement, I can modify the
describe "the assembled packet" do
before do
@settings = SPDY::Protocol::Control::Settings.new
@settings.create(
:settings_round_trip_time => 300
)
@frame = Array(@settings.to_binary_s.bytes)
end
# ...
specify "followed by the length (24 bits)" do
@frame[5..7].should == [0,0,8]
end
specify "followed by the number of entries (32 bits)" do
@frame[8..11].should == [0,0,0,1]
end
end
end
create
method to calculate the length and size:def create(opts = {})With that passing, I can refactor to DRY up the length and size calculations, making use of Bindata's lambdas:
self.len = opts.size * 8
self.number_of_entries = opts.size
self
end
class Settings < BinData::RecordWith that, I am down to one more spec in need of implementation:
bit1 :frame, :initial_value => CONTROL_BIT
bit15 :version, :initial_value => VERSION
bit16 :type, :value => 4
bit8 :flags, :value => 0
bit24 :len, :value => lambda { number_of_entries * 8 }
bit32 :number_of_entries
def parse(chunk)
self.read(chunk)
self
end
def create(opts = {})
self.number_of_entries = opts.size
self
end
end
SPDY::ProtocolGiven that I am creating a RTT of 300 milliseconds ID/Value pair, I should expect the ID/Value frames to be:
data frames
SETTINGS
the assembled packet
starts with a control bit
followed by the version (2)
followed by the type (4)
followed by flags
followed by the length (24 bits)
followed by the number of entries (32 bits)
followed by ID/Value Pairs (PENDING: Not Yet Implemented)
specify "followed by ID/Value Pairs" doThe value
@frame[12..19].should == [0,0,0,3, 0,0,1,44]
end
3
corresponds to SETTINGS_ROUND_TRIP_TIME
, 144 is 300 milliseconds in byte format (300 = 0000 0001 0010 1100 == 1 44). At first, the spec fails because the implementation so far defines nothing after the number of entries. I change the failure message by defining the ID/Value pairs as:array :headers, :initial_length => :number_of_entries doAnd to get the spec to pass, I iterate through the options passed into
bit32 :id
bit32 :data
end
create
, look up the corresponding constant value and add to the ID/Value headers:def create(opts = {})With that, I have completed SETTINGS support in the SPDY gem. Now that I am in back in the flow of this, hopefully I can knock out GOAWAY and HEADERS tomorrow.
self.number_of_entries = opts.size
opts.each do |k, v|
key = SPDY::Protocol.const_get(k.to_s.upcase)
self.headers << { :id => key , :data => v }
end
self
end
Day #13
No comments:
Post a Comment