Class: RubyBits::Structure
- Inherits:
-
Object
- Object
- RubyBits::Structure
- Defined in:
- lib/rubybits.rb
Overview
You can subclass RubyBits::Strcuture to define new binary formats. This can be used for lots of purposes: reading binary data, communicating in binary formats (like TCP/IP, http, etc).
Currently, three field types are supported: unsigned, signed and variable. Unsigned and signed fields are big-endian and can be any number of bits in size. Unsigned integers are assumed to be encoded with two’s complement. Variable fields are binary strings with their size defined by the value of another field (given by passing that field’s name to the :length option). This size is assumed to be in bits; if it is in fact in bytes, you should pass :byte to the :unit option (see the example).
Class Method Summary collapse
-
.checksum(field) {|bytes| ... } ⇒ Object
Sets the checksum field.
-
.checksum_field ⇒ Object
The checksum field.
-
.fields ⇒ Object
A list of the fields in the class.
-
.from_string(string) ⇒ Array<Structure, string>
Parses a message from the binary string assuming that the message starts at the first byte of the string.
-
.parse(string) ⇒ Array<Array<Structure>, String>
Parses out all of the messages in a given string assuming that the first message starts at the first byte, and there are no bytes between messages (though messages are not allowed to span bytes; i.e., all messages must be byte-aligned).
-
.valid_message?(string) ⇒ Boolean
Determines whether a string is a valid message.
Instance Method Summary collapse
-
#calculate_checksum ⇒ Object
Calculates and sets the checksum bit according to the checksum field defined by #checksum.
-
#initialize(values = {}) ⇒ Structure
constructor
Creates a new instance of the class.
-
#to_s ⇒ String
Returns a binary string representation of the structure according to the fields defined and their current values.
Constructor Details
#initialize(values = {}) ⇒ Structure
Creates a new instance of the class. You can pass in field names to initialize to set their values.
221 222 223 224 225 226 |
# File 'lib/rubybits.rb', line 221 def initialize(values={}) values.each{|key, value| self.send "#{key}=", value } @_checksum_cached = false end |
Class Method Details
.checksum(field) {|bytes| ... } ⇒ Object
Sets the checksum field. Setting a checksum field alters the functionality in several ways: the checksum is automatically calculated and set, and #parse will only consider a bitstring to be a valid instance of the structure if it has a checksum appropriate to its data.
125 126 127 128 129 130 131 132 133 |
# File 'lib/rubybits.rb', line 125 def checksum field, &block @_checksum_field = [field, block] self.class_eval %{ def #{field} calculate_checksum unless @_calculating_checksum || @_checksum_cached @__#{field} end } end |
.checksum_field ⇒ Object
The checksum field
139 |
# File 'lib/rubybits.rb', line 139 def checksum_field; @_checksum_field; end |
.fields ⇒ Object
A list of the fields in the class
136 |
# File 'lib/rubybits.rb', line 136 def fields; @_fields; end |
.from_string(string) ⇒ Array<Structure, string>
Parses a message from the binary string assuming that the message starts at the first byte of the string
155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 |
# File 'lib/rubybits.rb', line 155 def from_string(string) = self.new iter = 0 checksum = nil fields.each{|field| kind, name, size, description, = field ||= {} size = (kind == :variable) ? .send([:length]) : size size *= 8 if [:unit] == :byte begin value = FIELD_TYPES[kind][:unpack].call(string, iter, size, ) .send("#{name}=", value) checksum = value if checksum_field && name == checksum_field[0] rescue StopIteration, FieldValueException => e return [nil, string] end iter += size } # if there's a checksum, make sure the provided one is valid return [nil, string] unless .checksum == checksum if checksum_field [, string[((iter/8.0).ceil)..-1]] end |
.parse(string) ⇒ Array<Array<Structure>, String>
Parses out all of the messages in a given string assuming that the first message starts at the first byte, and there are no bytes between messages (though messages are not allowed to span bytes; i.e., all messages must be byte-aligned).
185 186 187 188 189 190 191 192 193 194 |
# File 'lib/rubybits.rb', line 185 def parse(string) = [] = true while , string = from_string(string) #puts "Found message: #{last_message.to_s.bytes.to_a}, string=#{string.bytes.to_a.inspect}" << if end [, string] end |
.valid_message?(string) ⇒ Boolean
Determines whether a string is a valid message
144 145 146 |
# File 'lib/rubybits.rb', line 144 def string !!from_string(string)[0] end |
Instance Method Details
#calculate_checksum ⇒ Object
Calculates and sets the checksum bit according to the checksum field defined by #checksum
239 240 241 242 243 244 245 246 247 248 |
# File 'lib/rubybits.rb', line 239 def calculate_checksum if self.class.checksum_field @_calculating_checksum = true self.send("#{self.class.checksum_field[0]}=", 0) checksum = self.class.checksum_field[1].call(self.to_s_without_checksum.bytes.to_a) self.send("#{self.class.checksum_field[0]}=", checksum) @_checksum_cached = true @_calculating_checksum = false end end |
#to_s ⇒ String
Returns a binary string representation of the structure according to the fields defined and their current values.
231 232 233 234 235 236 |
# File 'lib/rubybits.rb', line 231 def to_s if self.class.checksum_field && !@_checksum_cached self.calculate_checksum end to_s_without_checksum end |