I'm looking at constructing a UDP packet as per a protocol that specifies the layout of the packet to the bit level, and I'm not sure how to go about this in Ruby. My understanding is the pack
and unpack
functions for arrays would be the most appropriate.
http://ruby-doc.org/core-2.2.0/Array.html#method-i-pack
The array documentation lists a whole bunch of different arguments that can be used with pack
, but I'm not sure how to pack a boolean value as a single bit.
The format I'm trying to achieve looks like that.
My understanding was that I would be able to do something like:
[size_int, origin_as_int, tagged, addressable, protocol_int, source_int].pack "v1????V1"
Where the questions marks indicate where I'm not sure how to represent the fields. I could do something like
binary_string = ""
binary_string += "%02b" % origin_as_int
binary_string += (tagged ? "1" : "0")
binary_string += (addressable ? "1" : "0")
binary_string += "%012b" % protocol_int
munged_stuff = binary_string.to_i(2)
[size_int, munged_stuff, source_int].pack "v2V1"
I suppose? Feels kind of bad, though.
I've never used the headache-inducing methods Array#pack and String#unpack, but after reading their docs they actually seem quite straightforward, at least for what I assume are the more commonly-used directives, such as 'C'
, 'S'
, 'L'
, 'Q'
, 'B'
, 'b'
, 'A'
and 'a'
.
I believe you can do something like this:
size_int = 123 # C (0 to 2**8-1 => 255)
origin_as_int = "10" # B2
tagged = "1" # B1
addressable = "0" # B1
protocol_int = "010011001011" # B12
source_int = 2361 # S (0 to 2**16-1 => 65535)
template = %| C B2 B1 B1 B12 S |.delete(' ')
#=> "CB2B1B1B12S"
s = [size_int, origin_as_int, tagged, addressable, protocol_int,
source_int].pack(template)
#=> "{\x80\x80\x00L\xB09\t"
s.unpack(template)
#=> [123, "10", "1", "0", "010011001011", 2361]