Module: BinaryFinery

Defined in:
lib/binary_finery.rb

Overview

In order to be plaftorm agnostic, Binary inspects at load time machine’s capabilities and adjust its own operations accordingly.

BinaryFinary performs operations accepted by the following grammar:

operation     ::= 'read' | 'write'
integer_type  ::= 'uint' | 'int'
bits          ::= '8' | '16' | '32' | '64' | '128' | '256'
endianness    ::= 'native' | 'little' | 'big' | 'network'
OP_INT ::= operation '_' integer_type bits ('_' endianness)?

str_padding      ::= 'null_padded' | 'c_'
string_flavor    ::= 'string' | 'fixed_string' | 'binary_string'
str_preposition  ::= '_of_'
integer          ::= [0-9]+
str_size         ::= 'bytes'
OP_STR ::= operation '_' (str_padding '_')?
           string_flavor '_' str_preposition integer '_' str_size

Constant Summary collapse

OP_RE =
/(read|write)_/
INT_RE =
/(uint|int)(8|16|32|64)_?(native|little|big|network)?/
STR_RE =
/(null_padded|c_)?((binary_|fixed_)?string)(_of_)[0-9]+(bytes)/
OP_INT_RE =
Regexp::compile(OP_RE.source + INT_RE.source)
OP_STR_RE =
Regexp::compile(OP_RE.source + STR_RE.source)
KNOWN_RE =
Regexp::compile(OP_INT_RE.source + OP_STR_RE.source)
NUL =
0.chr
INTEGER_SIZE_IN_BYTES =

Returns the number of bytes used to encode an integer number

module_eval { 1.size }
NATIVE_BYTE_ORDER =

A machine architecture is said to be little endian if puts first the LSB. We evaluate the first byte of the number 1 packed as an integer. While first_byte is a Fixnum in Ruby 1.8.x, it is a string in 1.9.x; in latter case we employ the ord method to obtain the ordinal number associated,if is the case. Underlying machine is l.e. if the LSB is 1.

module_eval do
  first_byte = [1].pack('i')[0]
  first_byte = first_byte.ord if RUBY_VERSION =~ /^1\.9/
  first_byte == 1 ? :little : :big
end
BIT_MASK =
{  4 => 0xF, 8 => 0xFF, 16 => 0xFFFFF,
 32 => 0xFFFFFFFF, 64 => 0xFFFFFFFFFFFFFFFF,
128 => 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF }
DEFAULT_BIT_MASK =
module_eval { (1.size >> 1) * 8 }
@@pack_mappings =
{
    1 => { :uint => { :native => 'C' },
:int  => { :native => 'c' } },
    2 => { :uint => { :native => 'S', :little => 'v', :big => 'n' },
:int  => { :native => 's', :little => 'v', :big => 'n' } },
    4 => { :uint => { :native => 'L', :little => 'V', :big => 'N' },
:int  => { :native => 'l', :little => 'V', :big => 'N' } },
    8 => { :uint => { :native => 'Q' },
:int  => { :native => 'q' } } }

Instance Method Summary collapse

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method_name, *args, &block) ⇒ Object



293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
# File 'lib/binary_finery.rb', line 293

def method_missing(method_name, *args, &block)
  if method_name.to_s =~ OP_INT_RE
    op, type, bits, byte_order = Regexp.last_match[1..4]
    # string → sym
    op, type, byte_order = [op, type].map!(&:to_sym)
    # adjust bits to bytes
    byte_size = (bits.to_i / 8)
    # normalize endianness
    byte_order = byte_order.to_sym unless byte_order.nil?
    byte_order = :big if byte_order == 'network'

    fmt = format(byte_size,type,byte_order)

    case op
    when :read
      self.class.send :define_method, method_name do
        readn_unpack(byte_size, fmt, byte_order)
      end
      self.send method_name
    when :write
      if (args.first.kind_of? Integer) && (args.size == 1)
        self.class.send :define_method, method_name do |value|
          write_pack(value, fmt, byte_order)
        end
        self.send method_name, args.first
      end
    end
  elsif method_name.to_s =~ OP_STR_RE
    op, string_flavor = Regexp.last_match[1..2]
    case op
    when :read
      options = args.first.indexes(:size, :padding)
      self.send :read_string, options
    when :write
      str, options = args.shift, args
      self.send :write_string, str, options
    end
  else
    super
  end
end

Instance Method Details

#big_endian_byte_orderObject Also known as: network_byte_order



52
# File 'lib/binary_finery.rb', line 52

def big_endian_byte_order()    :big end

#big_endian_platform?Boolean Also known as: network_endian_platform?

Returns:

  • (Boolean)


70
# File 'lib/binary_finery.rb', line 70

def big_endian_platform?()    native_byte_order.equal? :big end

#concat(msb, lsb, bits) ⇒ Object



96
97
98
# File 'lib/binary_finery.rb', line 96

def concat(msb, lsb, bits)
  lsb + (msb << bits)
end

#format(byte_size, type, byte_order) ⇒ Object

obtains the correct pack format for the arguments



226
227
228
229
230
# File 'lib/binary_finery.rb', line 226

def format(byte_size, type, byte_order)
  byte_order = :native if byte_order.nil?
  byte_order = :big if byte_order.equal?(:network)
  @@pack_mappings[byte_size][type][byte_order]
end

#integer_size_in_bytesObject Also known as: word_size_in_bytes



48
# File 'lib/binary_finery.rb', line 48

def integer_size_in_bytes() INTEGER_SIZE_IN_BYTES end

#little_endian_byte_orderObject



51
# File 'lib/binary_finery.rb', line 51

def little_endian_byte_order() :little end

#little_endian_platform?Boolean

Returns:

  • (Boolean)


69
# File 'lib/binary_finery.rb', line 69

def little_endian_platform?() native_byte_order.equal? :little end

#lsb(num, bits = DEFAULT_BIT_MASK) ⇒ Object



90
# File 'lib/binary_finery.rb', line 90

def lsb(num, bits=DEFAULT_BIT_MASK) ((num >> bits) & BIT_MASK[bits]) end

#msb(num, bits = DEFAULT_BIT_MASK) ⇒ Object



89
# File 'lib/binary_finery.rb', line 89

def msb(num, bits=DEFAULT_BIT_MASK) (num & BIT_MASK[bits]) end

#native_byte_orderObject



67
# File 'lib/binary_finery.rb', line 67

def native_byte_order() NATIVE_BYTE_ORDER end

#read_c_stringObject



251
252
253
# File 'lib/binary_finery.rb', line 251

def read_c_string
  readline(sep_string = NUL)
end

#read_int128_littleObject



151
152
153
154
# File 'lib/binary_finery.rb', line 151

def read_int128_little
  val = read_uint128_little
  val > (2**128 - 1) ? val : -val
end

#read_int256_littleObject



106
107
108
109
# File 'lib/binary_finery.rb', line 106

def read_int256_little
  val = read_uint256_little
  val > (2**256 - 1) ? val : -val
end

#read_null_padded_string(size) ⇒ Object



246
247
248
249
# File 'lib/binary_finery.rb', line 246

def read_null_padded_string(size)
  str = readn(size)
  str.split(/NUL/).first or str
end

#read_string(opt = {:size => nil, :padding => NUL}) ⇒ Object



255
256
257
258
259
260
261
# File 'lib/binary_finery.rb', line 255

def read_string(opt={:size => nil, :padding => NUL})
  if opt[:size]
    read_fixed_size_string(opt[:size], opt[:padding])
  else
    read_c_string
  end
end

#read_uint128_bigObject Also known as: read_uint128_network



156
157
158
159
160
# File 'lib/binary_finery.rb', line 156

def read_uint128_big
  msb = read_uint64_big
  lsb = read_uint64_big
  concat(msb, lsb, 64)
end

#read_uint128_littleObject



145
146
147
148
149
# File 'lib/binary_finery.rb', line 145

def read_uint128_little
  lsb = read_uint64_little
  msb = read_uint64_little
  concat(msb, lsb, 64)
end

#read_uint128_nativeObject Also known as: read_uint128



163
164
165
# File 'lib/binary_finery.rb', line 163

def read_uint128_native
  little_endian_platform? ? read_uint128_little : read_uint128_big
end

#read_uint256_bigObject Also known as: read_uint256_network



111
112
113
114
115
# File 'lib/binary_finery.rb', line 111

def read_uint256_big
  msb = read_uint64_big
  lsb = read_uint64_big
  concat(msb, lsb, 128)
end

#read_uint256_littleObject



100
101
102
103
104
# File 'lib/binary_finery.rb', line 100

def read_uint256_little
  lsb  = read_uint128_little
  msb  = read_uint128_little
  concat(msb, lsb, 128)
end

#read_uint256_nativeObject Also known as: read_uint256



118
119
120
# File 'lib/binary_finery.rb', line 118

def read_uint256_native
  little_endian_platform? ? read_uint256_little : read_uint256_big
end

#read_uint64_bigObject Also known as: read_uint64_network



196
197
198
199
200
# File 'lib/binary_finery.rb', line 196

def read_uint64_big
  msb = readn_unpack(4, 'L', :big)
  lsb = readn_unpack(4, 'L', :big)
  concat(msb, lsb, 32)
end

#read_uint64_littleObject



190
191
192
193
194
# File 'lib/binary_finery.rb', line 190

def read_uint64_little
  lsb = readn_unpack(4, 'L', :little)
  msb = readn_unpack(4, 'L', :little)
  concat(msb, lsb, 32)
end

#read_uint64_nativeObject Also known as: read_uint64



203
204
205
# File 'lib/binary_finery.rb', line 203

def read_uint64_native
  little_endian_platform? ? read_uint64_little : read_uint64_big
end

#readn(n) ⇒ Object

read exactly n characters from the buffer, otherwise raise an exception.



240
241
242
243
244
# File 'lib/binary_finery.rb', line 240

def readn(n)
  str = read(n)
  raise "couldn't read #{n} characters." if str.nil? or str.size != n
  str
end

#readn_unpack(size, template, byte_order = NATIVE_BYTE_ORDER) ⇒ Object

read n bytes and unpack, swapping bytes as per endianness



233
234
235
236
237
# File 'lib/binary_finery.rb', line 233

def readn_unpack(size, template, byte_order=NATIVE_BYTE_ORDER)
  str = readn(size)
  str.reverse! if not native_byte_order.equal? byte_order # spotted problem in pack
  str.unpack(template).first
end

#recognize?(type) ⇒ Boolean

Returns:

  • (Boolean)


344
345
346
# File 'lib/binary_finery.rb', line 344

def recognize?(type)
  type.to_s =~ Regexp.union(INT_RE,STR_RE)
end

#size_of(type, object = nil) ⇒ Object

Tells the byte size required for the method passed as argument. When recognized.



337
338
339
340
341
342
# File 'lib/binary_finery.rb', line 337

def size_of(type, object=nil)
  case
  when type.to_s =~ INT_RE then Regexp.last_match[2].to_i / 8
  when type.to_s =~ STR_RE then Regexp.last_match[-2].to_i
  end
end

#split_msb_lsb(num, bits) ⇒ Object



92
93
94
# File 'lib/binary_finery.rb', line 92

def split_msb_lsb(num, bits)
  [msb(num, bits), lsb(num,bits) ]
end

#write_c_string(str) ⇒ Object

writes the string and appends NUL

Raises:

  • (ArgumentError)


271
272
273
274
275
276
# File 'lib/binary_finery.rb', line 271

def write_c_string(str)
  #TODO: improve input validation
  raise ArgumentError, "Invalid Ruby string" if str.include?(NUL)
  write(str)
  write(NUL)
end

#write_fixed_size_string(content = "") ⇒ Object



285
286
287
# File 'lib/binary_finery.rb', line 285

def write_fixed_size_string(content="")
  write_string(content, :size => content.size)
end

#write_int128_little(n) ⇒ Object



174
175
176
177
# File 'lib/binary_finery.rb', line 174

def write_int128_little(n)
  n = 2**128 - n
  write_uint128_little(n)
end

#write_int256_little(n) ⇒ Object



129
130
131
132
# File 'lib/binary_finery.rb', line 129

def write_int256_little(n)
  n = 2**256 - n
  write_uint256_little(n)
end

#write_null_padded_string(str, opt = {:size => str.size}) ⇒ Object



289
290
291
# File 'lib/binary_finery.rb', line 289

def write_null_padded_string(str, opt = {:size  => str.size})
  write_string(str, padding => "\000", :size => str.size)
end

#write_pack(number, template, byte_order = NATIVE_BYTE_ORDER) ⇒ Object

writes a number and pack it, swapping bytes as per endianness



264
265
266
267
268
# File 'lib/binary_finery.rb', line 264

def write_pack(number, template, byte_order=NATIVE_BYTE_ORDER)
  str = [number].pack(template)
  str.reverse! if not native_byte_order.equal? byte_order # blame Array#pack
  write(str)
end

#write_string(content, opt = {:padding => nil, :size => content.size}) ⇒ Object



278
279
280
281
282
283
# File 'lib/binary_finery.rb', line 278

def write_string(content, opt = {:padding  => nil, :size => content.size})
  return if not (opt[:size].kind_of? Integer)
  output_string = content[0..opt[:size]]
  output_string = output_string.ljust(opt[:size], opt[:padding]) if opt[:padding]
  write(output_string)
end

#write_uint128_big(n) ⇒ Object



179
180
181
182
183
# File 'lib/binary_finery.rb', line 179

def write_uint128_big(n)
  lsb, msb = split_msb_lsb(n, 64)
  write_uint64_big(msb)
  write_uint64_big(lsb)
end

#write_uint128_little(n) ⇒ Object



168
169
170
171
172
# File 'lib/binary_finery.rb', line 168

def write_uint128_little(n)
  lsb, msb = split_msb_lsb(n, 64)
  write_uint64_little(lsb)
  write_uint64_little(msb)
end

#write_uint128_native(n) ⇒ Object Also known as: write_uint128



185
186
187
# File 'lib/binary_finery.rb', line 185

def write_uint128_native(n)
  little_endian_platform? ? write_uint128_little(n) : write_uint128_big(n)
end

#write_uint256_big(n) ⇒ Object



134
135
136
137
138
# File 'lib/binary_finery.rb', line 134

def write_uint256_big(n)
  lsb, msb = split_msb_lsb(n, 128)
  write_uint128_big(msb)
  write_uint128_big(lsb)
end

#write_uint256_little(n) ⇒ Object



123
124
125
126
127
# File 'lib/binary_finery.rb', line 123

def write_uint256_little(n)
  msb, lsb = split_msb_lsb(n, 128)
  write_uint128_little(msb)
  write_uint128_little(lsb)
end

#write_uint256_native(n) ⇒ Object Also known as: write_uint256



140
141
142
# File 'lib/binary_finery.rb', line 140

def write_uint256_native(n)
  little_endian_platform? ? write_uint256_little(n) : write_uint256_big(n)
end

#write_uint64_big(n) ⇒ Object



214
215
216
217
218
# File 'lib/binary_finery.rb', line 214

def write_uint64_big(n)
  lsb, msb = split_msb_lsb(n,32)
  write_pack(msb, 'L', :big)
  write_pack(lsb, 'L', :big)
end

#write_uint64_little(n) ⇒ Object



208
209
210
211
212
# File 'lib/binary_finery.rb', line 208

def write_uint64_little(n)
  lsb, msb = split_msb_lsb(n,32)
  write_pack(lsb, 'L', :little)
  write_pack(msb, 'L', :little)
end

#write_uint64_native(n) ⇒ Object Also known as: write_uint64



220
221
222
# File 'lib/binary_finery.rb', line 220

def write_uint64_native(n)
  little_endian_platform? ? write_uint64_little(n) : write_uint64_big(n)
end