With a bit of experience assembling SPDY frame with the SPDY gem, tonight I will attempt to build additional frames.
Currently, the gem is capable of SYN_STREAM and SYN_REPLY control frames. The other frames in Draft #2 of the SPDY protocol include: RST_STREAM, SETTINGS, NOOP, PING, GOAWAY, HEADERS, and WINDOW_UPDATE.
I start with an easy (but potentially useful) one: PING. The PING frame looks like:
+----------------------------------+Or, in RSpec format:
|1| 1 | 6 |
+----------------------------------+
| 0 (flags) | 4 (length) |
+----------------------------------|
| 32-bit ID |
+----------------------------------+
context "PING" doThe definition of those specify statements can be as follows:
describe "the assembled packet" do
specify "starts with a control bit"
specify "followed by the version (1)"
specify "followed by the type (6)"
specify "followed by flags (0)"
specify "followed by the length (always 4)"
end
end
context "PING" doThe first expectation is a bit of a cheat. The first bit is a control bit (which is on), but I am testing the resulting first two bytes (1000 0000 == 128 = \x80). I need to come up with a better scheme for that (maybe just rely on bindata's implementation?), but that will suffice to drive implementation today. The remaining expectations are straight forward.
describe "the assembled packet" do
before do
@ping = SPDY::Protocol::Control::Ping.new
@ping.create(:stream_id => 1)
@frame = @ping.to_binary_s
end
specify "starts with a control bit" do
@frame[0].should == "\x80"
end
specify "followed by the version (1)" do
@frame[1].should == "\x01"
end
specify "followed by the type (6)" do
@frame[2..3].should == "\x00\x06"
end
specify "followed by flags (0)" do
@frame[4].should == "\x00"
end
specify "followed by the length (always 4)" do
@frame[5..7].should == "\x00\x00\x04"
end
end
end
Implementing those specs is similarly straight forward (largely thanks to the Bindata gem):
class Ping < BinData::RecordUnfortunately, I cannot use the
bit1 :frame, :initial_value => CONTROL_BIT
bit15 :version, :initial_value => VERSION
bit16 :type, :value => 6
bit8 :flags, :value => 0
bit24 :len, :value => 4
bit32 :stream_id
def create(opts = {})
self.stream_id = opts.fetch(:stream_id, 1)
self
end
end
Header
class already in the SPDY gem. It expects the stream ID to be 31 bits. Oddly, the specification for PING packets is 32 bits (all other control frames are 31). Similarly, I cannot use the Helpers
mixin, both because it is coupled with the Header
implementation and because it expects a data section (no data in a PING). Regardless, the Bindata gem makes for a very clean implementation.As for parsing a PING:
context "PING" doWhere the
it "can parse a PING packet" do
ping = SPDY::Protocol::Control::Ping.new
ping.parse(PING)
ping.stream_id.should == 1
ping.type.should == 6
ping.to_binary_s.should == PING
end
PING
constant is defined as:PING = "\x80\x01\x00\x06\x00\x00\x00\x04\x00\x00\x00\x01"The implementation is trivial (again, thanks to Bindata):
def parse(chunk)With that, I have a nice, executable specification for the SPDY PING packet:
self.read(chunk)
self
end
➜ spdy git:(master) rspec ./spec/protocol_spec.rb:149 -cfsI spend a little time knocking out the RST_STREAM frame and then call it a night.
Run filtered using {:line_number=>149}
SPDY::Protocol
PING
can parse a PING packet
the assembled packet
starts with a control bit
followed by the version (1)
followed by the type (6)
followed by flags (0)
followed by the length (always 4)
Finished in 0.00509 seconds
6 examples, 0 failures
Day #4
No comments:
Post a Comment