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 bytes; if it is in fact in bits, you should pass :bit to the :unit option (see the example). Note that variable-length fields must have whole-byte sizes, though they need not be byte-aligned.
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.
-
.maybe_valid?(string) ⇒ Boolean
Determines whether a string is at least the minimum correct length and matches the checksum.
-
.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.
253 254 255 256 257 258 |
# File 'lib/rubybits.rb', line 253 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.
131 132 133 134 135 136 137 138 139 |
# File 'lib/rubybits.rb', line 131 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
145 |
# File 'lib/rubybits.rb', line 145 def checksum_field; @_checksum_field; end |
.fields ⇒ Object
A list of the fields in the class
142 |
# File 'lib/rubybits.rb', line 142 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
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 |
# File 'lib/rubybits.rb', line 181 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 |
.maybe_valid?(string) ⇒ Boolean
Determines whether a string is at least the minimum correct length and matches the checksum. This method is less correct than valid_message? but considerably faster.
161 162 163 164 165 166 167 168 169 170 |
# File 'lib/rubybits.rb', line 161 def maybe_valid? string if string.size >= @_size_sum if self.class.checksum_field checksum = self.class.checksum_field[1].call(string) else return true end end return false 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).
214 215 216 217 218 219 220 221 222 223 |
# File 'lib/rubybits.rb', line 214 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
150 151 152 |
# File 'lib/rubybits.rb', line 150 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
271 272 273 274 275 276 277 278 279 280 |
# File 'lib/rubybits.rb', line 271 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.
263 264 265 266 267 268 |
# File 'lib/rubybits.rb', line 263 def to_s if self.class.checksum_field && !@_checksum_cached self.calculate_checksum end to_s_without_checksum end |