bindata-2.3.5/0000755000004100000410000000000013042501567013156 5ustar www-datawww-databindata-2.3.5/Rakefile0000644000004100000410000000035113042501567014622 0ustar www-datawww-datarequire 'bundler' Bundler.setup Bundler::GemHelper.install_tasks require 'rake/clean' require 'rake/testtask' task :clobber do rm_rf 'pkg' end Rake::TestTask.new do |t| t.pattern = "test/**/*_test.rb" end task default: :test bindata-2.3.5/Gemfile0000644000004100000410000000004613042501567014451 0ustar www-datawww-datasource "https://rubygems.org" gemspec bindata-2.3.5/examples/0000755000004100000410000000000013042501567014774 5ustar www-datawww-databindata-2.3.5/examples/ip_address.rb0000644000004100000410000000110213042501567017430 0ustar www-datawww-datarequire 'bindata' # A custom type representing an IP address. # The underlying binary representation is a sequence of four octets. # The human accessible representation is a dotted quad. class IPAddr < BinData::Primitive array :octets, type: :uint8, initial_length: 4 def set(val) self.octets = val.split(/\./).map(&:to_i) end def get self.octets.map(&:to_s).join(".") end end ip = IPAddr.new("127.0.0.1") puts "human readable value: #{ip}" #=> 127.0.0.1 puts "binary representation: #{ip.to_binary_s.inspect}" #=> "\177\000\000\001" bindata-2.3.5/examples/gzip.rb0000644000004100000410000000752513042501567016303 0ustar www-datawww-datarequire 'bindata' require 'zlib' # An example of a reader / writer for the GZIP file format as per rfc1952. # See notes at the end of this file for implementation discussions. class Gzip < BinData::Record # Binary representation of a ruby Time object class Mtime < BinData::Primitive uint32le :time def set(val) self.time = val.to_i end def get Time.at(time) end end # Known compression methods DEFLATE = 8 endian :little uint16 :ident, asserted_value: 0x8b1f uint8 :compression_method, initial_value: DEFLATE bit3 :freserved, asserted_value: 0 bit1 :fcomment, value: -> { comment.length > 0 ? 1 : 0 } bit1 :ffile_name, value: -> { file_name.length > 0 ? 1 : 0 } bit1 :fextra, value: -> { extra.len > 0 ? 1 : 0 } bit1 :fcrc16, value: 0 # see note at end of file bit1 :ftext mtime :mtime uint8 :extra_flags uint8 :os, initial_value: 255 # unknown OS # The following fields are optional depending on the bits in flags struct :extra, onlyif: -> { fextra.nonzero? } do uint16 :len, length: -> { data.length } string :data, read_length: :len end stringz :file_name, onlyif: -> { ffile_name.nonzero? } stringz :comment, onlyif: -> { fcomment.nonzero? } uint16 :crc16, onlyif: -> { fcrc16.nonzero? } # The length of compressed data must be calculated from the current file offset count_bytes_remaining :bytes_remaining string :compressed_data, read_length: -> { bytes_remaining - footer.num_bytes } struct :footer do uint32 :crc32 uint32 :uncompressed_size end def data=(data) # Zlib.deflate includes a header + footer which we must discard self.compressed_data = Zlib::Deflate.deflate(data)[2..-5] self.footer.crc32 = Zlib::crc32(data) self.footer.uncompressed_size = data.size end end if __FILE__ == $0 # Write a gzip file. print "Creating a gzip file ... " g = Gzip.new g.data = "the cat sat on the mat" g.file_name = "poetry" g.mtime = Time.now g.comment = "A stunning piece of prose" File.open("poetry.gz", "w") do |io| g.write(io) end puts "done." puts # Read the created gzip file. print "Reading newly created gzip file ... " g = Gzip.new File.open("poetry.gz", "r") do |io| g.read(io) end puts "done." puts puts "Printing gzip file details in the format of gzip -l -v" # compression ratio ratio = 100.0 * (g.footer.uncompressed_size - g.compressed_data.size) / g.footer.uncompressed_size comp_meth = (g.compression_method == Gzip::DEFLATE) ? "defla" : "" # Output using the same format as gzip -l -v puts "method crc date time compressed " + "uncompressed ratio uncompressed_name" puts "%5s %08x %6s %5s %19s %19s %5.1f%% %s" % [comp_meth, g.footer.crc32, g.mtime.strftime('%b %d'), g.mtime.strftime('%H:%M'), g.num_bytes, g.footer.uncompressed_size, ratio, g.file_name] puts "Comment: #{g.comment}" if g.comment? puts puts "Executing gzip -l -v" puts `gzip -l -v poetry.gz` end # Notes: # # Mtime: A convenience wrapper that allow a ruby Time object to be used instead # of manually dealing with the raw form (seconds since 1 Jan 1970) # # rfc1952 specifies an optional crc16 field. The gzip command line client # uses this field for multi-part gzip. Hence we ignore this. # We are cheating and using the Zlib library for compression. We can't use # this library for decompression as zlib requires an adler32 checksum while # gzip uses crc32. bindata-2.3.5/examples/NBT.txt0000644000004100000410000001275613042501567016173 0ustar www-datawww-dataNamed Binary Tag specification NBT (Named Binary Tag) is a tag based binary format designed to carry large amounts of binary data with smaller amounts of additional data. An NBT file consists of a single GZIPped Named Tag of type TAG_Compound. A Named Tag has the following format: byte tagType TAG_String name [payload] The tagType is a single byte defining the contents of the payload of the tag. The name is a descriptive name, and can be anything (eg "cat", "banana", "Hello World!"). It has nothing to do with the tagType. The purpose for this name is to name tags so parsing is easier and can be made to only look for certain recognized tag names. Exception: If tagType is TAG_End, the name is skipped and assumed to be "". The [payload] varies by tagType. Note that ONLY Named Tags carry the name and tagType data. Explicitly identified Tags (such as TAG_String above) only contains the payload. The tag types and respective payloads are: TYPE: 0 NAME: TAG_End Payload: None. Note: This tag is used to mark the end of a list. Cannot be named! If type 0 appears where a Named Tag is expected, the name is assumed to be "". (In other words, this Tag is always just a single 0 byte when named, and nothing in all other cases) TYPE: 1 NAME: TAG_Byte Payload: A single signed byte (8 bits) TYPE: 2 NAME: TAG_Short Payload: A signed short (16 bits, big endian) TYPE: 3 NAME: TAG_Int Payload: A signed short (32 bits, big endian) TYPE: 4 NAME: TAG_Long Payload: A signed long (64 bits, big endian) TYPE: 5 NAME: TAG_Float Payload: A floating point value (32 bits, big endian, IEEE 754-2008, binary32) TYPE: 6 NAME: TAG_Double Payload: A floating point value (64 bits, big endian, IEEE 754-2008, binary64) TYPE: 7 NAME: TAG_Byte_Array Payload: TAG_Int length An array of bytes of unspecified format. The length of this array is bytes TYPE: 8 NAME: TAG_String Payload: TAG_Short length An array of bytes defining a string in UTF-8 format. The length of this array is bytes TYPE: 9 NAME: TAG_List Payload: TAG_Byte tagId TAG_Int length A sequential list of Tags (not Named Tags), of type . The length of this array is Tags Notes: All tags share the same type. TYPE: 10 NAME: TAG_Compound Payload: A sequential list of Named Tags. This array keeps going until a TAG_End is found. TAG_End end Notes: If there's a nested TAG_Compound within this tag, that one will also have a TAG_End, so simply reading until the next TAG_End will not work. The names of the named tags have to be unique within each TAG_Compound The order of the tags is not guaranteed. Decoding example: (Use http://www.minecraft.net/docs/test.nbt to test your implementation) First we start by reading a Named Tag. After unzipping the stream, the first byte is a 10. That means the tag is a TAG_Compound (as expected by the specification). The next two bytes are 0 and 11, meaning the name string consists of 11 UTF-8 characters. In this case, they happen to be "hello world". That means our root tag is named "hello world". We can now move on to the payload. From the specification, we see that TAG_Compound consists of a series of Named Tags, so we read another byte to find the tagType. It happens to be an 8. The name is 4 letters long, and happens to be "name". Type 8 is TAG_String, meaning we read another two bytes to get the length, then read that many bytes to get the contents. In this case, it's "Bananrama". So now we know the TAG_Compound contains a TAG_String named "name" with the content "Bananrama" We move on to reading the next Named Tag, and get a 0. This is TAG_End, which always has an implied name of "". That means that the list of entries in the TAG_Compound is over, and indeed all of the NBT file. So we ended up with this: TAG_Compound("hello world"): 1 entries { TAG_String("name"): Bananrama } For a slightly longer test, download http://www.minecraft.net/docs/bigtest.nbt You should end up with this: TAG_Compound("Level"): 11 entries { TAG_Short("shortTest"): 32767 TAG_Long("longTest"): 9223372036854775807 TAG_Float("floatTest"): 0.49823147 TAG_String("stringTest"): HELLO WORLD THIS IS A TEST STRING ÅÄÖ! TAG_Int("intTest"): 2147483647 TAG_Compound("nested compound test"): 2 entries { TAG_Compound("ham"): 2 entries { TAG_String("name"): Hampus TAG_Float("value"): 0.75 } TAG_Compound("egg"): 2 entries { TAG_String("name"): Eggbert TAG_Float("value"): 0.5 } } TAG_List("listTest (long)"): 5 entries of type TAG_Long { TAG_Long: 11 TAG_Long: 12 TAG_Long: 13 TAG_Long: 14 TAG_Long: 15 } TAG_Byte("byteTest"): 127 TAG_List("listTest (compound)"): 2 entries of type TAG_Compound { TAG_Compound: 2 entries { TAG_String("name"): Compound tag #0 TAG_Long("created-on"): 1264099775885 } TAG_Compound: 2 entries { TAG_String("name"): Compound tag #1 TAG_Long("created-on"): 1264099775885 } } TAG_Byte_Array("byteArrayTest (the first 1000 values of (n*n*255+n*7)%100, starting with n=0 (0, 62, 34, 16, 8, ...))"): [1000 bytes] TAG_Double("doubleTest"): 0.4931287132182315 } bindata-2.3.5/examples/tcp_ip.rb0000644000004100000410000000640713042501567016606 0ustar www-datawww-datarequire 'bindata' # This is a simple protocol analyser for examining IPv4 packets # captured with libpcap on an ethernet network. # # The dump file can be obtained like this: # # sudo tcpdump -i eth0 -s 0 -n -w dump.pcap # # Present MAC addresses in a human readable way class MacAddr < BinData::Primitive array :octets, type: :uint8, initial_length: 6 def set(val) self.octets = val.split(/\./).collect(&:to_i) end def get self.octets.collect { |octet| "%02x" % octet }.join(":") end end # Present IP addresses in a human readable way class IPAddr < BinData::Primitive array :octets, type: :uint8, initial_length: 4 def set(val) self.octets = val.split(/\./).collect(&:to_i) end def get self.octets.collect { |octet| "%d" % octet }.join(".") end end # TCP Protocol Data Unit class TCP_PDU < BinData::Record endian :big uint16 :src_port uint16 :dst_port uint32 :seq uint32 :ack_seq bit4 :doff bit4 :res1 bit2 :res2 bit1 :urg bit1 :ack bit1 :psh bit1 :rst bit1 :syn bit1 :fin uint16 :window uint16 :checksum uint16 :urg_ptr string :options, read_length: :options_length_in_bytes rest :payload def options_length_in_bytes (doff - 5 ) * 4 end end # UDP Protocol Data Unit class UDP_PDU < BinData::Record endian :big uint16 :src_port uint16 :dst_port uint16 :len uint16 :checksum rest :payload end # IP Protocol Data Unit class IP_PDU < BinData::Record endian :big bit4 :version, asserted_value: 4 bit4 :header_length uint8 :tos uint16 :total_length uint16 :ident bit3 :flags bit13 :frag_offset uint8 :ttl uint8 :protocol uint16 :checksum ip_addr :src_addr ip_addr :dest_addr string :options, read_length: :options_length_in_bytes buffer :payload, length: :payload_length_in_bytes do choice :payload, selection: :protocol do tcp_pdu 6 udp_pdu 17 rest :default end end def header_length_in_bytes header_length * 4 end def options_length_in_bytes header_length_in_bytes - options.rel_offset end def payload_length_in_bytes total_length - header_length_in_bytes end end # Ethernet Frame - NOTE only ipv4 is supported class Ether < BinData::Record IPV4 = 0x0800 endian :big mac_addr :dst mac_addr :src uint16 :ether_type choice :payload, selection: :ether_type do ip_pdu IPV4 rest :default end end class Pcap def initialize(filename) @filename = filename end def each_record File.open(@filename) do |io| file = PcapFile.read(io) file.records.each do |rec| yield rec.data end end end class PcapFile < BinData::Record endian :little struct :head do uint32 :magic uint16 :major uint16 :minor int32 :this_zone uint32 :sig_figs uint32 :snaplen uint32 :linktype end array :records, read_until: :eof do uint32 :ts_sec uint32 :ts_usec uint32 :incl_len uint32 :orig_len string :data, length: :incl_len end end end require 'pp' unless File.exist?('dump.pcap') puts "No dump file found. Create one with: sudo tcpdump -i eth0 -s 0 -n -w dump.pcap" exit 1 end cap = Pcap.new('dump.pcap') cap.each_record do |str| pp Ether.read(str) end bindata-2.3.5/examples/list.rb0000644000004100000410000000546213042501567016303 0ustar www-datawww-datarequire 'bindata' # An example of a recursively defined data format. # # This example format describes atoms and lists. # It is recursive because lists can contain other lists. # # Atoms - contain a single integer # Lists - contain a mixture of atoms and lists # # The binary representation is: # # Atoms - A single byte 'a' followed by an int32 containing the value. # Lists - A single byte 'l' followed by an int32 denoting the number of # items in the list. This is followed by all the items in the list. # # All integers are big endian. # # # A first attempt at a declaration would be: # # class Atom < BinData::Record # string :tag, length: 1, assert: 'a' # int32be :val # end # # class List < BinData::Record # string :tag, length: 1, assert: 'l' # int32be :num, value: -> { vals.length } # array :vals, initial_length: :num do # choice selection: ??? do # atom # list # end # end # end # # Notice how we get stuck on attemping to write a declaration for # the contents of the list. We can't determine if the list item is # an atom or list because we haven't read it yet. It appears that # we can't proceed. # # The cause of the problem is that the tag identifying the type is # coupled with that type. # # The solution is to decouple the tag from the type. We introduce a # new type 'Term' that is a thin container around the tag plus the # type (atom or list). # # The declaration then becomes: # # class Term < BinData::Record; end # forward declaration # # class Atom < BinData::Int32be # end # # class List < BinData::Record # int32be :num, value: -> { vals.length } # array :vals, type: :term, initial_length: :num # end # # class Term < BinData::Record # string :tag, length: 1 # choice :term, selection: :tag do # atom 'a' # list 'l' # end # end class Term < BinData::Record; end # Forward declaration class Atom < BinData::Int32be def decode snapshot end def self.encode(val) Atom.new(val) end end class List < BinData::Record int32be :num, value: -> { vals.length } array :vals, initial_length: :num, type: :term def decode vals.collect(&:decode) end def self.encode(val) List.new(vals: val.collect { |v| Term.encode(v) }) end end class Term < BinData::Record string :tag, length: 1 choice :term, selection: :tag do atom 'a' list 'l' end def decode term.decode end def self.encode(val) if Fixnum === val Term.new(tag: 'a', term: Atom.encode(val)) else Term.new(tag: 'l', term: List.encode(val)) end end end puts "A single Atom" p Term.encode(4) p Term.encode(4).decode puts puts "A nested List" p Term.encode([1, [2, 3], 4]) p Term.encode([1, [2, 3], 4]).decode bindata-2.3.5/examples/nbt.rb0000644000004100000410000001234013042501567016104 0ustar www-datawww-datarequire 'bindata' # An example reader for Minecraft's NBT format. # http://www.minecraft.net/docs/NBT.txt # # This is an example of how to write a BinData # declaration for a recursively defined file format. module Nbt TAG_NAMES = { 0 => "End", 1 => "Byte", 2 => "Short", 3 => "Int", 4 => "Long", 5 => "Float", 6 => "Double", 7 => "Byte_Array", 8 => "String", 9 => "List", 10 => "Compound" } # NBT.txt line 25 class TagEnd < BinData::Primitive def get; ""; end def set(v); end def to_formatted_s(indent = 0); to_s; end end # NBT.txt line 31 class TagByte < BinData::Int8 def to_formatted_s(indent = 0); to_s; end end # NBT.txt line 34 class TagShort < BinData::Int16be def to_formatted_s(indent = 0); to_s; end end # NBT.txt line 37 class TagInt < BinData::Int32be def to_formatted_s(indent = 0); to_s; end end # NBT.txt line 40 class TagLong < BinData::Int64be def to_formatted_s(indent = 0); to_s; end end # NBT.txt line 43 class TagFloat < BinData::FloatBe def to_formatted_s(indent = 0); to_s; end end # NBT.txt line 46 class TagDouble < BinData::DoubleBe def to_formatted_s(indent = 0); to_s; end end # NBT.txt line 49 class TagByteArray < BinData::Record int32be :len, value: -> { data.length } string :data, read_length: :len def to_formatted_s(indent = 0) "[#{len} bytes]" end end # NBT.txt line 53 class TagString < BinData::Primitive int16be :len, value: -> { data.length } string :data, read_length: :len def get self.data end def set(v) self.data = v end def to_formatted_s(indent = 0); to_s; end end ## Payload is the most important class to understand. ## This abstraction allows recursive formats. ## eg. lists can contain lists can contain lists. # Forward references used by Payload class TagCompound < BinData::Record; end class TagList < BinData::Record; end # NBT.txt line 10 class Payload < BinData::Choice tag_end 0 tag_byte 1 tag_short 2 tag_int 3 tag_long 4 tag_float 5 tag_double 6 tag_byte_array 7 tag_string 8 tag_list 9 tag_compound 10 end # NBT.txt line 6, 27 class NamedTag < BinData::Record int8 :tag_id tag_string :name, onlyif: :not_end_tag? payload :payload, onlyif: :not_end_tag?, selection: :tag_id def not_end_tag? tag_id != 0 end def to_formatted_s(indent = 0) " " * indent + "TAG_#{TAG_NAMES[tag_id]}(\"#{name}\"): " + payload.to_formatted_s(indent) + "\n" end end # NBT.txt line 57 class TagList < BinData::Record int8 :tag_id int32be :len, value: -> { data.length } array :data, initial_length: :len do payload selection: :tag_id end def to_formatted_s(indent = 0) pre = " " * indent tag_type = "TAG_#{TAG_NAMES[tag_id]}" "#{len} entries of type #{tag_type}\n" + pre + "{\n" + data.collect { |el| " #{pre}#{tag_type}: #{el.to_formatted_s(indent + 1)}\n" }.join("") + pre + "}" end end # NBT.txt line 63 class TagCompound < BinData::Record array :data, read_until: -> { element.tag_id == 0 } do named_tag end def to_formatted_s(indent = 0) pre = " " * indent "#{data.length - 1} entries\n" + pre + "{\n" + data[0..-2].collect { |el| el.to_formatted_s(indent + 1) }.join("") + pre + "}" end end # NBT.txt line 3 class Nbt < NamedTag def self.read(io) require 'zlib' super(Zlib::GzipReader.new(io)) end end end if $0 == __FILE__ require 'stringio' bigtest_nbt = StringIO.new "\037\213\b\000\000\000\000\000\000\003\355T\317O\032A\024~\302\002\313\226\202\261\304\020c\314\253\265\204\245\333\315B\021\211\261\210\026,\232\r\032\330\2501\206\270+\303\202.\273fw\260\361\324K{lz\353?\323#\177C\317\275\366\277\240\303/{i\317\275\3602\311\367\346\275o\346{o&y\002\004TrO,\016x\313\261M\215x\364\343pb>\b{\035\307\245\223\030\017\202G\335\356\204\002b\265\242\252\307xv\\W\313\250U\017\e\310\326\036j\225\206\206\r\255~X{\217\203\317\203O\203o\317\003\020n[\216>\276\2458Ld\375\020\352\332t\246\#@\334f.i\341\265\323\273s\372v\v)\333\v\340\357\350=\0368[\357\021\bV\365\336]\337\v@\340^\267\372d\267\004\000\214ALs\306\bUL\323 .}\244\300\310\302\020\263\272\336X\vS\243\356D\216E\0030\261'S\214L\361\351\024\243S\214\205\341\331\237\343\263\362D\201\245|3\335\330\273\307\252u\023_(\034\b\327.\321Y?\257\035\e`!Y\337\372\361\005\376\301\316\374\235\275\000\274\361@\311\370\205B@F\376\236\353\352\017\223:h\207`\273\35327\243(\n\216\273\365\320ic\312N\333\351\354\346\346+;\275%\276dI\t=\252\273\224\375\030~\350\322\016\332o\025L\261h>+\341\233\234\204\231\274\204\005\teY\026E\000\377/(\256/\362\302\262\244.\035 wZ;\271\214\312\347)\337QA\311\026\265\305m\241*\255,\3051\177\272z\222\216^\235_\370\022\005#\e\321\366\267w\252\315\225r\274\236\337X]K\227\256\222\027\271D\320\200\310\372>\277\263\334T\313\aun\243\266vY\222\223\251\334QP\231k\3145\346\032\377W#\bB\313\351\e\326x\302\354\376\374z\373}x\323\204\337\324\362\244\373\b\006\000\000" nbt = Nbt::Nbt.read(bigtest_nbt) puts nbt.to_formatted_s end bindata-2.3.5/ChangeLog.rdoc0000644000004100000410000002225013042501567015657 0ustar www-datawww-data= BinData Changelog == Version 2.3.5 (2017-01-19) * Enforce Integer#nbits > 0. Reported by Keenan Tims. * Fix auto_call_delayed_io crash. Reported by Michael Petter. == Version 2.3.4 (2016-10-17) * Memoize dynamic methods for primitives. Thanks to hiroeorz. == Version 2.3.3 (2016-09-07) * Fix bug #80. Thanks to Michael Petter. * Add Buffer#raw_num_bytes. == Version 2.3.2 (2016-09-02) * IO#num_bytes_remaining now works inside Buffers. * Added ability to skip to arbitrary byte patterns. Requested by Stefan Kolb. == Version 2.3.1 (2016-06-17) * Improve list of reserved words. Thanks to Claudius Coenen. * Fix virtual fields to be bit aligned. Thanks to hopesea. == Version 2.3.0 (2016-03-25) * Add :to_abs_offset to Skip. * Added backwards seeking via multi pass I/O. See DelayedIO. * Removed #offset, which was deprecated in 2.1.0. * Removed :adjust_offset. See NEWS.rdoc for details. == Version 2.2.0 (2016-01-30) * Warn if String has :value but no :read_length. Requested by Michael Genereux. * Prevent running under Ruby 2.1.0p0 due to ruby bug 44525. * Added #to_hex convenience method. Thanks to Gregory Romé. * Added namespacing via search_prefix. Requested by sumofparts. == Version 2.1.0 (2014-04-16) * Performance improvements. * Removed deprecated parameters. * Code refactored to use Ruby 1.9 features. * #eval_parameters can now call private methods. Requested by Ole Rasmussen. * Can now determine state of :onlyif fields. Requested by Ole Rasmussen. * Renamed #offset to #abs_offset for clarity. #offset is now deprecated. * Support :byte_align for fields in structs. Requested by Igor Yamolov. * Added bit fields with dynamic length. Requested by Jacob Dam. * Added "endian :big_and_little" option which creates :big and :little versions of the class. Thanks to Jacob Lukas for the prototype. == Version 2.0.0 (2014-02-02) * Ruby 1.8 now has its own separate branch. * Struct now uses symbols for field names instead of strings. * Added signed bitfields. Requested by redood. * Virtual fields can now have names. * Bug fixes. == Version 1.8.0 (2014-01-06) * Allow custom types to have endian, not just numerics. * Added missing field to TCP example. Thanks to Bertrand Paquet. * Made tests compatible with JRuby. Thanks to Charles Oliver Nutter. * Support travis ci for those that use it. Thanks to Charles Oliver Nutter. * Added Buffer for easier handling of nested streams. * Added Virtual field. == Version 1.6.0 (2013-09-02) * Added license to .gemspec * Moved test suite from RSpec to Minitest. * Added :assert and :asserted_value. * :check_value has been deprecated. Use :assert instead. == Version 1.5.1 (2013-08-16) * Rework build system and include .gemspec. Requested by Simon Shortman. * Fixed bug when Choice#clear didn't clear everything. Thanks to Simon Shortman for the bug report. * Moved BinData::VERSION into its own file. Thanks to John Van Enk. == Version 1.5.0 (2013-05-21) * Moved to github. * Updated to Ruby 2.0 * Arrays now accept BinData object factories for :type (feature request by Matt Dainty). * Dynamically generated BinData objects can use the :name parameter to register themselves. * Remove functionality that has been deprecated for two years. == Version 1.4.5 (2012-07-24) * Added the :pad_front option for padding to occur at the front of a String. (suggested by Eduardo Mourão). == Version 1.4.4 (2012-06-21) * Fixed bug where user defined boolean primitive wouldn't set its value to false (reported by Frank Roland). * Fixed infinite looping bug caused by nested Choices. * Renamed String parameter :pad_char to :pad_byte. * Updated manual. == Version 1.4.3 (2011-10-01) * Exceptions no longer ignored inside lambdas when reading until eof in an array (thanks John Labovitz). * Fixed interaction bug between choices and records (reported by Refrigerator Johnny). == Version 1.4.2 (2011-08-06) * \=~ now works for strings. == Version 1.4.1 (2011-06-20) * Added :default option for Choices. * Added count_bytes_remaining keyword. * Increased speed of lazy evaluation. == Version 1.4.0 (2011-06-14) * Record#snapshot now returns fields in order. * Removed the need to call #register_self in subclasses. * Wrapper is now deprecated. Use subclassing instead. * Updated license to mimic changes to the Ruby License. * Refactoring to reduce memory usage. == Version 1.3.1 (2011-01-25) * Fixed file permissions problem. == Version 1.3.0 (2011-01-25) * BinData objects can now assign values when instantiating. * Improved support for bit-based alignment. * Updated reference manual. * Added examples for declaring recursive structures. * Objects deriving from BinData::Base should no longer override #initialize. * Added BinData::Base(#new, #initialize_instance) to speed up instantiation of multiple objects. * Updated specs to rspec-1.3.0 * BinData::Struct.hide now expects symbols instead of strings. == Version 1.2.2 (2010-12-14) * Added Base.bindata_name method. * Removed Base#done_read to reduce memory usage and cpu usage. * Derived classes should now use do_read et al, instead of _do_read. * Added predefinition of record fields to improve speed. * Made compatible with ruby 1.9.2. Thanks to Andrew Watts. == Version 1.2.1 (2010-07-20) * Updated specs to be compatible with ruby 1.9.1 == Version 1.2.0 (2010-07-09) * Deprecated Base#register. Use #register_self or #register_subclasses instead. * Syntax improvement. Array, Structs and Choices can now use blocks to specify fields. * Reduced startup time (suggestion courtesy of Mike Ryan). == Version 1.1.0 (2009-11-24) * Allow anonymous fields in Records and Primitives. * Add the ability to skip over unused data. * Allow Records, Primitives and Wrappers to be derived from. * Classes for integers are now defined on demand. == Version 1.0.0 (2009-09-13) * Is now compatible with Ruby 1.9 * Added reference manual. * Added #rel_offset to Base. * Removed support for deprecated functionality. == Version 0.11.1 (2009-08-28) * Allow wrapped types to work with struct's :onlyif parameter * Use Array#index instead of #find_index for compatibility with Ruby 1.8.6 (patch courtesy of Joe Rozner). == Version 0.11.0 (2009-06-28) * Sanitizing code was refactored for speed. * Arbitrary sized integers and bit fields are now automatically instantiated. * Add ability to wrap existing types and override their parameters. == Version 0.10.0 (2009-04-17) * Arbitrary byte sized integers are now supported (e.g. 24bit, 808bit). * Renamed String :trim_value parameter to :trim_padding. * BinData::Array now behaves more like Ruby's Array. * Added debug_name * Added ability to trace reading * Primitives now behave as their value. Calling #value is no longer needed. * Renamed #to_s -> #to_binary_s to avoid confusion with Ruby's #to_s. * Added #assign as the generic way to assign values to objects. * Added :copy_on_change parameter to Choice. * Implement #offset for all objects. * Renamed Single -> BasePrimitive. * Renamed SingleValue -> Primitive. * Renamed MultiValue -> Record. * The :onlyif parameter now only applies to fields inside Structs. * LazyEvaluator can now supply arguments when invoking methods == Version 0.9.3 (2008-12-03) * Arrays can now :read_until => :eof * TCPSocket and UDPSocket can now be used as input streams (patch courtesy of Peter Suschlik). * Added 128 bit integers. * Significant memory usage reduction. * Added custom mandatory and default parameters for user defined MultiValues. == Version 0.9.2 (2008-07-18) * Added lazy instantiation to allow recursive definitions. * Array elements can be appended at any position. * Deprecated the :readwrite parameter. * Removed feature where Struct fields names could be nil. * Reworked sanitizing system. == Version 0.9.1 (2008-06-15) * Implemented bit fields. * Added :onlyif parameter to Base for specifying optional fields. * Fixed IO offset bug with SingleValues. == Version 0.9.0 (2008-06-02) * Added :adjust_offset option to automatically seek to a given offset. * Modified #read to accept strings as well as IO streams. * Choice now accepts sparse arrays and hashes as :choice. * Added BinData::Rest to help with debugging. * Major internal restructuring - memory usage is much better. * Improved documentation. == Version 0.8.1 (2008-01-14) * Reduced memory consumption. * Increased execution speed. * Deprecated BinData::Base.parameters. * Fixed spec syntax (thanks to David Goodlad). == Version 0.8.0 (2007-10-14) * Add reserved field names to Struct. * Prevent warnings about method redefinition. * Allow Struct to masquerade as one of its fields. * Renamed String param :initial_length to :read_length. * BinData::Array now behaves more like the internal Ruby array. == Version 0.7.0 (2007-08-26) * Arrays now support terminating conditions as well as fixed length reads. * Updated specs to new rspec syntax (0.9). * Added scoped resolution of variables in lambdas. * Added ability to append elements to arrays. == Version 0.6.0 (2007-03-28) * Added 64 bit integers. * Added floating point numbers. * Added endian keyword to Struct to reduce the amount of typing needed. == Version 0.5.1 (2007-03-21) * Updated documentation. * Struct now raises an error if a field name shadows an existing method. == Version 0.5.0 (2007-03-14) * Initial public release. bindata-2.3.5/NEWS.rdoc0000644000004100000410000001464313042501567014613 0ustar www-datawww-data= 2.3.0 This release adds I/O features. Seeking forward to an arbitrary offset is achieved with Skip's :to_abs_offset parameter. Multi pass I/O has been added. BinData performs I/O in a single pass. See DelayedIO to perform multi pass (backwards seeking) I/O. The experimental :adjust_offset feature has been removed. If you have existing code that uses this feature, you need to explicitly require 'bindata/offset' to retain this functionality. = 2.1.0 Several new features in this release. * Fields can be aligned on n-bytes boundaries. This make it easier to write parsers for aligned structures. * The endian value :big_and_little can be used to automatically create :big and :little endian versions of the class. * Bit fields can be defined to have their length determined at runtime. * The state of :onlyif fields can be determined by #field? == Deprecations The #offset method has been renamed to #abs_offset. This make an obvious distinction to #rel_offset. = 2.0.0 BinData 2.0.0 requires ruby 1.9 or greater. Support for ruby 1.8 is found in BinData versions 1.8.x. Prior to version 2.0.0, BinData used the Ruby 1.8 conversion of strings for method names. As of 2.0.0, BinData uses symbols. class A < BinData::Record uint8 :a uint8 :b end # BinData 1.8.x A.read "\001\002" #=> {"a"=>1, "b"=>2} # BinData >= 2.0.0 A.read "\001\002" #=> {:a=>1, :b=>2} = 1.6.0 Added :assert as a replacement for :check_value. Note that :assert performs the checking on assignment as well as reading. The parameter :asserted_value is a new shortcut for combining :assert and :value int8 :magic, :assert => 42, :value => 42 Can be written more concisely as int8 :magic, :asserted_value => 42 = 1.5.0 Finally moved the source code to github. Deprecated functionality has been removed to cleanup the codebase. = 1.4.4 == Deprecations The BinData::String parameter :pad_char has been renamed to :pad_byte. This aids readibilty as Ruby 1.9 characters are not necessarily one byte. = 1.4.0 == Deprecations There is no longer any need to call #register_self when inheriting from BinData::Base. BinData::Wrapper is deprecated. You should use direct inheritance instead. If your code looks like: class Uint8Array < BinData::Wrapper default_parameter :initial_element_value => 0 array :initial_length => 2 do uint8 :initial_value => :initial_element_value end end It should be changed to: class Uint8Array < BinData::Array default_parameter :initial_element_value => 0 default_parameter :initial_length => 2 uint8 :initial_value => :initial_element_value end See http://bindata.rubyforge.org/manual.html#extending_existing_types for details. = 1.3.0 == New features You can now assign values to BinData objects when instantiating them. Previously: obj = BinData::Stringz.new(:max_length => 10) obj.assign("abc") Now: obj = BinData::Stringz.new("abc", :max_length => 10) == Backwards incompatible changes This version makes some backwards incompatible changes for more advanced users of BinData. This only affects you if you have created your own custom types by subclassing BinData::Base or BinData::BasePrimitive. All instance variables must now be initialized in #initialize_instance. Implementing #initialize is no longer allowed. Run your existing code in $VERBOSE mode ("ruby -w"), and BinData will inform you of any changes you need to make to your code. If your code previously looked like: class MyType < BinData::Base register_self def initialize(parameters = {}, parent = nil) super @var1 = ... @var2 = ... end ... end You must change it to look like: class MyType < BinData::Base register_self def initialize_instance @var1 = ... @var2 = ... end ... end = 1.0.0 Version 1.0.0 removes all deprecated features. If you are upgrading from a version prior to 0.10.0 you will need to make changes to your code before it will work with BinData 1.0.0 or later. It is recommended to first upgrade to 0.11.1, and run your code with the -w command line switch. This will turn on warning messages that should give you hints about which parts of your code need changing. See the NEWS for 0.10.0 for details of the deprecated features. = 0.10.0 There are several new features in this release. The major ones are: * Debugging declarations is now easier with the ability to trace values when reading. BinData::trace_reading(STDERR) do obj_not_quite_working_correctly.read(io) end Will result in a debugging trace written to STDERR. * Support for arbitrary sized integers and bit fields. * Struct/Array field/element access now returns a BinData object, rather than the underlying Fixnum or String. The BinData object will behave like it's underlying primitive so existing code should continue to work. This allows for an improved syntax in your declarations. If your code requires access to the underlying primitive, you can access it with the #value method. There are several deprecations and one backwards incompatible change in this release. Turn on $VERBOSE mode (-w command line switch) to see warnings regarding these deprecations. == IMPORTANT - Ruby does not warn you about this change! * The #to_s method for getting the binary string representation of a data object has been renamed to #to_binary_s. If you use this method you must manually change your code or you will get strange results. == Deprecations. Ruby will warn you of these when in $VERBOSE mode. Code using these deprecated features will still work in this release but will fail to work in some future release. Change your code now. * BinData::SingleValue has been renamed to BinData::Primitive. * BinData::MultiValue has been renamed to BinData::Record. * String :trim_value parameter has been renamed to :trim_padding. * Registry.instance should be replaced with RegisteredClasses. * struct.offset_of("field") should be replaced with struct.field.offset. * struct.clear("field") should be replaced with struct.field.clear. * struct.clear?("field") should be replaced with struct.field.clear?. * struct.num_bytes("field") should be replaced with struct.field.num_bytes. * array.clear(n) should be replaced with array[n].clear. * array.clear?(n) should be replaced with array[n].clear?. * array.num_bytes(n) should be replaced with array[n].num_bytes. bindata-2.3.5/.travis.yml0000644000004100000410000000022013042501567015261 0ustar www-datawww-datalanguage: ruby rvm: - 2.0 - 2.1.10 - 2.2 - 2.3 - jruby - ruby-head matrix: allow_failures: - rvm: jruby - rvm: ruby-head bindata-2.3.5/bindata.gemspec0000644000004100000410000000212713042501567016127 0ustar www-datawww-data$LOAD_PATH.unshift File.expand_path("../lib", __FILE__) require 'bindata/version' Gem::Specification.new do |s| s.name = 'bindata' s.version = BinData::VERSION s.platform = Gem::Platform::RUBY s.summary = 'A declarative way to read and write binary file formats' s.author = 'Dion Mendel' s.email = 'bindata@dm9.info' s.homepage = 'http://github.com/dmendel/bindata' s.rubyforge_project = 'bindata' s.require_path = 'lib' s.has_rdoc = true s.extra_rdoc_files = ['NEWS.rdoc'] s.rdoc_options << '--main' << 'NEWS.rdoc' s.files = `git ls-files`.split("\n") s.license = 'Ruby' s.add_development_dependency('rake') s.add_development_dependency('minitest', "> 5.0.0") s.add_development_dependency('coveralls') s.description = <<-END.gsub(/^ +/, "") BinData is a declarative way to read and write binary file formats. This means the programmer specifies *what* the format of the binary data is, and BinData works out *how* to read and write data in this format. It is an easier ( and more readable ) alternative to ruby's #pack and #unpack methods. END end bindata-2.3.5/lib/0000755000004100000410000000000013042501567013724 5ustar www-datawww-databindata-2.3.5/lib/bindata.rb0000644000004100000410000000216013042501567015652 0ustar www-datawww-data# BinData -- Binary data manipulator. # Copyright (c) 2007 - 2016 Dion Mendel. if RUBY_VERSION <= "1.9" fail "BinData requires ruby >= 1.9.3. Use BinData version 1.8.x instead" end if RUBY_VERSION == "2.1.0" and RUBY_PATCHLEVEL == "0" fail "Ruby 2.1.0p0 has a bug that causes BinData to fail. Upgrade your ruby version" end require 'bindata/version' require 'bindata/array' require 'bindata/bits' require 'bindata/buffer' require 'bindata/choice' require 'bindata/count_bytes_remaining' require 'bindata/delayed_io' require 'bindata/float' require 'bindata/int' require 'bindata/primitive' require 'bindata/record' require 'bindata/rest' require 'bindata/skip' require 'bindata/string' require 'bindata/stringz' require 'bindata/struct' require 'bindata/trace' require 'bindata/virtual' require 'bindata/alignment' require 'bindata/warnings' # = BinData # # A declarative way to read and write structured binary data. # # A full reference manual is available online at # http://bindata.rubyforge.org/manual.html # # == License # # BinData is released under the same license as Ruby. # # Copyright (c) 2007 - 2016 Dion Mendel. bindata-2.3.5/lib/bindata/0000755000004100000410000000000013042501567015326 5ustar www-datawww-databindata-2.3.5/lib/bindata/buffer.rb0000644000004100000410000000563313042501567017133 0ustar www-datawww-datarequire 'bindata/base' require 'bindata/dsl' module BinData # A Buffer is conceptually a substream within a data stream. It has a # defined size and it will always read or write the exact number of bytes to # fill the buffer. Short reads will skip over unused bytes and short writes # will pad the substream with "\0" bytes. # # require 'bindata' # # obj = BinData::Buffer.new(length: 5, type: [:string, {value: "abc"}]) # obj.to_binary_s #=> "abc\000\000" # # # class MyBuffer < BinData::Buffer # default_parameter length: 8 # # endian :little # # uint16 :num1 # uint16 :num2 # # padding occurs here # end # # obj = MyBuffer.read("\001\000\002\000\000\000\000\000") # obj.num1 #=> 1 # obj.num1 #=> 2 # obj.raw_num_bytes #=> 4 # obj.num_bytes #=> 8 # # # class StringTable < BinData::Record # endian :little # # uint16 :table_size_in_bytes # buffer :strings, length: :table_size_in_bytes do # array read_until: :eof do # uint8 :len # string :str, length: :len # end # end # end # # # == Parameters # # Parameters may be provided at initialisation to control the behaviour of # an object. These params are: # # :length:: The number of bytes in the buffer. # :type:: The single type inside the buffer. Use a struct if # multiple fields are required. class Buffer < BinData::Base extend DSLMixin dsl_parser :buffer arg_processor :buffer mandatory_parameters :length, :type def initialize_instance @type = get_parameter(:type).instantiate(nil, self) end # The number of bytes used, ignoring the padding imposed by the buffer. def raw_num_bytes @type.num_bytes end def clear? @type.clear? end def assign(val) @type.assign(val) end def snapshot @type.snapshot end def respond_to?(symbol, include_private = false) #:nodoc: @type.respond_to?(symbol, include_private) || super end def method_missing(symbol, *args, &block) #:nodoc: @type.__send__(symbol, *args, &block) end def do_read(io) #:nodoc: io.with_buffer(eval_parameter(:length)) do @type.do_read(io) end end def do_write(io) #:nodoc: io.with_buffer(eval_parameter(:length)) do @type.do_write(io) end end def do_num_bytes #:nodoc: eval_parameter(:length) end end class BufferArgProcessor < BaseArgProcessor include MultiFieldArgSeparator def sanitize_parameters!(obj_class, params) params.merge!(obj_class.dsl_params) params.must_be_integer(:length) if params.needs_sanitizing?(:type) el_type, el_params = params[:type] params[:type] = params.create_sanitized_object_prototype(el_type, el_params) end end end end bindata-2.3.5/lib/bindata/string.rb0000644000004100000410000001075513042501567017171 0ustar www-datawww-datarequire "bindata/base_primitive" module BinData # A String is a sequence of bytes. This is the same as strings in Ruby 1.8. # The issue of character encoding is ignored by this class. # # require 'bindata' # # data = "abcdefghij" # # obj = BinData::String.new(read_length: 5) # obj.read(data) # obj #=> "abcde" # # obj = BinData::String.new(length: 6) # obj.read(data) # obj #=> "abcdef" # obj.assign("abcdefghij") # obj #=> "abcdef" # obj.assign("abcd") # obj #=> "abcd\000\000" # # obj = BinData::String.new(length: 6, trim_padding: true) # obj.assign("abcd") # obj #=> "abcd" # obj.to_binary_s #=> "abcd\000\000" # # obj = BinData::String.new(length: 6, pad_byte: 'A') # obj.assign("abcd") # obj #=> "abcdAA" # obj.to_binary_s #=> "abcdAA" # # == Parameters # # String objects accept all the params that BinData::BasePrimitive # does, as well as the following: # # :read_length:: The length in bytes to use when reading a value. # :length:: The fixed length of the string. If a shorter # string is set, it will be padded to this length. # :pad_byte:: The byte to use when padding a string to a # set length. Valid values are Integers and # Strings of length 1. "\0" is the default. # :pad_front:: Signifies that the padding occurs at the front # of the string rather than the end. Default # is false. # :trim_padding:: Boolean, default false. If set, #value will # return the value with all pad_bytes trimmed # from the end of the string. The value will # not be trimmed when writing. class String < BinData::BasePrimitive arg_processor :string optional_parameters :read_length, :length, :trim_padding, :pad_front, :pad_left default_parameters pad_byte: "\0" mutually_exclusive_parameters :read_length, :length mutually_exclusive_parameters :length, :value def initialize_shared_instance if (has_parameter?(:value) || has_parameter?(:asserted_value)) && !has_parameter?(:read_length) extend WarnNoReadLengthPlugin end super end def assign(val) super(binary_string(val)) end def snapshot # override to trim padding snap = super snap = clamp_to_length(snap) if get_parameter(:trim_padding) trim_padding(snap) else snap end end #--------------- private def clamp_to_length(str) str = binary_string(str) len = eval_parameter(:length) || str.length if str.length == len str elsif str.length > len str.slice(0, len) else padding = (eval_parameter(:pad_byte) * (len - str.length)) if get_parameter(:pad_front) padding + str else str + padding end end end def trim_padding(str) if get_parameter(:pad_front) str.sub(/\A#{eval_parameter(:pad_byte)}*/, "") else str.sub(/#{eval_parameter(:pad_byte)}*\z/, "") end end def value_to_binary_string(val) clamp_to_length(val) end def read_and_return_value(io) len = eval_parameter(:read_length) || eval_parameter(:length) || 0 io.readbytes(len) end def sensible_default "" end end class StringArgProcessor < BaseArgProcessor def sanitize_parameters!(obj_class, params) params.warn_replacement_parameter(:initial_length, :read_length) params.must_be_integer(:read_length, :length) if params.has_parameter?(:pad_left) params[:pad_front] = params.delete(:pad_left) end if params.has_parameter?(:pad_byte) byte = params[:pad_byte] params[:pad_byte] = sanitized_pad_byte(byte) end end #------------- private def sanitized_pad_byte(byte) pad_byte = byte.is_a?(Integer) ? byte.chr : byte.to_s if pad_byte.bytesize > 1 raise ArgumentError, ":pad_byte must not contain more than 1 byte" end pad_byte end end # Warns when reading if :value && no :read_length module WarnNoReadLengthPlugin def read_and_return_value(io) warn "#{debug_name} does not have a :read_length parameter - returning empty string" "" end end end bindata-2.3.5/lib/bindata/skip.rb0000644000004100000410000000742413042501567016630 0ustar www-datawww-datarequire "bindata/base_primitive" module BinData # Skip will skip over bytes from the input stream. If the stream is not # seekable, then the bytes are consumed and discarded. # # When writing, skip will write the appropriate number of zero bytes. # # require 'bindata' # # class A < BinData::Record # skip length: 5 # string :a, read_length: 5 # end # # obj = A.read("abcdefghij") # obj.a #=> "fghij" # # # class B < BinData::Record # skip until_valid: [:string, {read_length: 2, assert: "ef"} ] # string :b, read_length: 5 # end # # obj = B.read("abcdefghij") # obj.b #=> "efghi" # # # == Parameters # # Skip objects accept all the params that BinData::BasePrimitive # does, as well as the following: # # :length:: The number of bytes to skip. # :to_abs_offset:: Skips to the given absolute offset. # :until_valid:: Skips untils a given byte pattern is matched. # This parameter contains a type that will raise # a BinData::ValidityError unless an acceptable byte # sequence is found. The type is represented by a # Symbol, or if the type is to have params # # passed to it, then it should be provided as # # [type_symbol, hash_params]. # class Skip < BinData::BasePrimitive arg_processor :skip optional_parameters :length, :to_abs_offset, :until_valid mutually_exclusive_parameters :length, :to_abs_offset, :until_valid def initialize_shared_instance extend SkipLengthPlugin if has_parameter?(:length) extend SkipToAbsOffsetPlugin if has_parameter?(:to_abs_offset) extend SkipUntilValidPlugin if has_parameter?(:until_valid) super end #--------------- private def value_to_binary_string(val) len = skip_length if len < 0 raise ValidityError, "#{debug_name} attempted to seek backwards by #{len.abs} bytes" end "\000" * skip_length end def read_and_return_value(io) len = skip_length if len < 0 raise ValidityError, "#{debug_name} attempted to seek backwards by #{len.abs} bytes" end io.seekbytes(len) "" end def sensible_default "" end end class SkipArgProcessor < BaseArgProcessor def sanitize_parameters!(obj_class, params) unless params.has_parameter?(:length) || params.has_parameter?(:to_abs_offset) || params.has_parameter?(:until_valid) raise ArgumentError, "#{obj_class} requires either :length, :to_abs_offset or :until_valid" end params.must_be_integer(:to_abs_offset, :length) if params.needs_sanitizing?(:until_valid) el_type, el_params = params[:until_valid] params[:until_valid] = params.create_sanitized_object_prototype(el_type, el_params) end end end # Logic for the :length parameter module SkipLengthPlugin def skip_length eval_parameter(:length) end end # Logic for the :to_abs_offset parameter module SkipToAbsOffsetPlugin def skip_length eval_parameter(:to_abs_offset) - abs_offset end end # Logic for the :until_valid parameter module SkipUntilValidPlugin def skip_length # no skipping when writing 0 end def read_and_return_value(io) prototype = get_parameter(:until_valid) validator = prototype.instantiate(nil, self) valid = false until valid begin io.with_readahead do validator.read(io) valid = true end rescue ValidityError io.readbytes(1) end end end end end bindata-2.3.5/lib/bindata/primitive.rb0000644000004100000410000000656613042501567017700 0ustar www-datawww-datarequire 'bindata/base_primitive' require 'bindata/dsl' require 'bindata/struct' module BinData # A Primitive is a declarative way to define a new BinData data type. # The data type must contain a primitive value only, i.e numbers or strings. # For new data types that contain multiple values see BinData::Record. # # To define a new data type, set fields as if for Record and add a # #get and #set method to extract / convert the data between the fields # and the #value of the object. # # require 'bindata' # # class PascalString < BinData::Primitive # uint8 :len, value: -> { data.length } # string :data, read_length: :len # # def get # self.data # end # # def set(v) # self.data = v # end # end # # ps = PascalString.new(initial_value: "hello") # ps.to_binary_s #=> "\005hello" # ps.read("\003abcde") # ps #=> "abc" # # # Unsigned 24 bit big endian integer # class Uint24be < BinData::Primitive # uint8 :byte1 # uint8 :byte2 # uint8 :byte3 # # def get # (self.byte1 << 16) | (self.byte2 << 8) | self.byte3 # end # # def set(v) # v = 0 if v < 0 # v = 0xffffff if v > 0xffffff # # self.byte1 = (v >> 16) & 0xff # self.byte2 = (v >> 8) & 0xff # self.byte3 = v & 0xff # end # end # # u24 = Uint24be.new # u24.read("\x12\x34\x56") # "0x%x" % u24 #=> 0x123456 # # == Parameters # # Primitive objects accept all the parameters that BinData::BasePrimitive do. # class Primitive < BasePrimitive extend DSLMixin unregister_self dsl_parser :primitive arg_processor :primitive mandatory_parameter :struct_params def initialize_instance super @struct = BinData::Struct.new(get_parameter(:struct_params), self) end def respond_to?(symbol, include_private = false) #:nodoc: @struct.respond_to?(symbol, include_private) || super end def method_missing(symbol, *args, &block) #:nodoc: if @struct.respond_to?(symbol) @struct.__send__(symbol, *args, &block) else super end end def assign(val) super(val) set(_value) @value = get end def debug_name_of(child) #:nodoc: debug_name + "-internal-" end def do_write(io) set(_value) @struct.do_write(io) end def do_num_bytes set(_value) @struct.do_num_bytes end #--------------- private def sensible_default get end def read_and_return_value(io) @struct.do_read(io) get end ########################################################################### # To be implemented by subclasses # Extracts the value for this data object from the fields of the # internal struct. def get raise NotImplementedError end # Sets the fields of the internal struct to represent +v+. def set(v) raise NotImplementedError end # To be implemented by subclasses ########################################################################### end class PrimitiveArgProcessor < BaseArgProcessor def sanitize_parameters!(obj_class, params) params[:struct_params] = params.create_sanitized_params(obj_class.dsl_params, BinData::Struct) end end end bindata-2.3.5/lib/bindata/name.rb0000644000004100000410000000157113042501567016577 0ustar www-datawww-datamodule BinData # == Parameters # # Parameters may be provided at initialisation to control the behaviour of # an object. These parameters are: # # :name:: The name that this object can be referred to may be # set explicitly. This is only useful when dynamically # generating types. #
  #                    BinData::Struct.new(name: :my_struct, fields: ...)
  #                    array = BinData::Array.new(type: :my_struct)
  #                  
module RegisterNamePlugin def self.included(base) #:nodoc: # The registered name may be provided explicitly. base.optional_parameter :name end def initialize_shared_instance if has_parameter?(:name) RegisteredClasses.register(get_parameter(:name), self) end super end end end bindata-2.3.5/lib/bindata/alignment.rb0000644000004100000410000000313413042501567017632 0ustar www-datawww-datarequire 'bindata/base_primitive' module BinData # Resets the stream alignment to the next byte. This is # only useful when using bit-based primitives. # # class MyRec < BinData::Record # bit4 :a # resume_byte_alignment # bit4 :b # end # # MyRec.read("\x12\x34") #=> {"a" => 1, "b" => 3} # class ResumeByteAlignment < BinData::Base def clear?; true; end def assign(val); end def snapshot; nil; end def do_num_bytes; 0; end def do_read(io) io.reset_read_bits end def do_write(io) io.flushbits end end # A monkey patch to force byte-aligned primitives to # become bit-aligned. This allows them to be used at # non byte based boundaries. # # class BitString < BinData::String # bit_aligned # end # # class MyRecord < BinData::Record # bit4 :preamble # bit_string :str, length: 2 # end # module BitAligned class BitAlignedIO def initialize(io) @io = io end def readbytes(n) n.times.inject("") do |bytes, _| bytes << @io.readbits(8, :big).chr end end end def bit_aligned? true end def read_and_return_value(io) super(BitAlignedIO.new(io)) end def do_num_bytes super.to_f end def do_write(io) value_to_binary_string(_value).each_byte { |v| io.writebits(v, 8, :big) } end end def BasePrimitive.bit_aligned include BitAligned end def Primitive.bit_aligned fail "'bit_aligned' is not needed for BinData::Primitives" end end bindata-2.3.5/lib/bindata/float.rb0000644000004100000410000000411313042501567016757 0ustar www-datawww-datarequire 'bindata/base_primitive' module BinData # Defines a number of classes that contain a floating point number. # The float is defined by precision and endian. module FloatingPoint #:nodoc: all class << self PRECISION = { single: 4, double: 8, } PACK_CODE = { [:single, :little] => 'e', [:single, :big] => 'g', [:double, :little] => 'E', [:double, :big] => 'G', } def define_methods(float_class, precision, endian) float_class.module_eval <<-END def do_num_bytes #{create_num_bytes_code(precision)} end #--------------- private def sensible_default 0.0 end def value_to_binary_string(val) #{create_to_binary_s_code(precision, endian)} end def read_and_return_value(io) #{create_read_code(precision, endian)} end END end def create_num_bytes_code(precision) PRECISION[precision] end def create_read_code(precision, endian) nbytes = PRECISION[precision] unpack = PACK_CODE[[precision, endian]] "io.readbytes(#{nbytes}).unpack('#{unpack}').at(0)" end def create_to_binary_s_code(precision, endian) pack = PACK_CODE[[precision, endian]] "[val].pack('#{pack}')" end end end # Single precision floating point number in little endian format class FloatLe < BinData::BasePrimitive FloatingPoint.define_methods(self, :single, :little) end # Single precision floating point number in big endian format class FloatBe < BinData::BasePrimitive FloatingPoint.define_methods(self, :single, :big) end # Double precision floating point number in little endian format class DoubleLe < BinData::BasePrimitive FloatingPoint.define_methods(self, :double, :little) end # Double precision floating point number in big endian format class DoubleBe < BinData::BasePrimitive FloatingPoint.define_methods(self, :double, :big) end end bindata-2.3.5/lib/bindata/offset.rb0000644000004100000410000000605313042501567017145 0ustar www-datawww-datamodule BinData # WARNING: THIS IS UNSUPPORTED!! # # This was a (failed) experimental feature that allowed seeking within the # input stream. It remains here for backwards compatability for the few # people that used it. # # The official way to skip around the stream is to use BinData::Skip with # the `:to_abs_offset` parameter. # # == Parameters # # Parameters may be provided at initialisation to control the behaviour of # an object. These parameters are: # # [:check_offset] Raise an error if the current IO offset doesn't # meet this criteria. A boolean return indicates # success or failure. Any other return is compared # to the current offset. The variable +offset+ # is made available to any lambda assigned to # this parameter. This parameter is only checked # before reading. # [:adjust_offset] Ensures that the current IO offset is at this # position before reading. This is like # :check_offset, except that it will # adjust the IO offset instead of raising an error. module CheckOrAdjustOffsetPlugin def self.included(base) #:nodoc: base.optional_parameters :check_offset, :adjust_offset base.mutually_exclusive_parameters :check_offset, :adjust_offset end def initialize_shared_instance extend CheckOffsetMixin if has_parameter?(:check_offset) extend AdjustOffsetMixin if has_parameter?(:adjust_offset) super end module CheckOffsetMixin def do_read(io) #:nodoc: check_offset(io) super(io) end #--------------- private def check_offset(io) actual_offset = io.offset expected = eval_parameter(:check_offset, offset: actual_offset) if !expected raise ValidityError, "offset not as expected for #{debug_name}" elsif actual_offset != expected && expected != true raise ValidityError, "offset is '#{actual_offset}' but " + "expected '#{expected}' for #{debug_name}" end end end module AdjustOffsetMixin def do_read(io) #:nodoc: adjust_offset(io) super(io) end #--------------- private def adjust_offset(io) actual_offset = io.offset expected = eval_parameter(:adjust_offset) if actual_offset != expected begin seek = expected - actual_offset io.seekbytes(seek) warn "adjusting stream position by #{seek} bytes" if $VERBOSE rescue raise ValidityError, "offset is '#{actual_offset}' but couldn't seek to " + "expected '#{expected}' for #{debug_name}" end end end end end # Add these offset options to Base class Base include CheckOrAdjustOffsetPlugin end end bindata-2.3.5/lib/bindata/struct.rb0000644000004100000410000002732313042501567017206 0ustar www-datawww-datarequire 'bindata/base' module BinData class Base optional_parameter :onlyif, :byte_align # Used by Struct end # A Struct is an ordered collection of named data objects. # # require 'bindata' # # class Tuple < BinData::Record # int8 :x # int8 :y # int8 :z # end # # obj = BinData::Struct.new(hide: :a, # fields: [ [:int32le, :a], # [:int16le, :b], # [:tuple, :s] ]) # obj.field_names =># [:b, :s] # # # == Parameters # # Parameters may be provided at initialisation to control the behaviour of # an object. These params are: # # :fields:: An array specifying the fields for this struct. # Each element of the array is of the form [type, name, # params]. Type is a symbol representing a registered # type. Name is the name of this field. Params is an # optional hash of parameters to pass to this field # when instantiating it. If name is "" or nil, then # that field is anonymous and behaves as a hidden field. # :hide:: A list of the names of fields that are to be hidden # from the outside world. Hidden fields don't appear # in #snapshot or #field_names but are still accessible # by name. # :endian:: Either :little or :big. This specifies the default # endian of any numerics in this struct, or in any # nested data objects. # :search_prefix:: Allows abbreviated type names. If a type is # unrecognised, then each prefix is applied until # a match is found. # # == Field Parameters # # Fields may have have extra parameters as listed below: # # [:onlyif] Used to indicate a data object is optional. # if +false+, this object will not be included in any # calls to #read, #write, #num_bytes or #snapshot. # [:byte_align] This field's rel_offset must be a multiple of # :byte_align. class Struct < BinData::Base arg_processor :struct mandatory_parameter :fields optional_parameters :endian, :search_prefix, :hide # These reserved words may not be used as field names RESERVED = Hash[* (Hash.instance_methods + %w{alias and begin break case class def defined do else elsif end ensure false for if in module next nil not or redo rescue retry return self super then true undef unless until when while yield} + %w{array element index value} + %w{type initial_length read_until} + %w{fields endian search_prefix hide only_if byte_align} + %w{choices selection copy_on_change} + %w{read_abs_offset struct_params}).collect(&:to_sym). uniq.collect { |key| [key, true] }.flatten ] def initialize_shared_instance fields = get_parameter(:fields) @field_names = fields.field_names.freeze extend ByteAlignPlugin if fields.any_field_has_parameter?(:byte_align) define_field_accessors super end def initialize_instance @field_objs = [] end def clear #:nodoc: @field_objs.each { |f| f.clear unless f.nil? } end def clear? #:nodoc: @field_objs.all? { |f| f.nil? || f.clear? } end def assign(val) clear assign_fields(val) end def snapshot snapshot = Snapshot.new field_names.each do |name| obj = find_obj_for_name(name) snapshot[name] = obj.snapshot if include_obj?(obj) end snapshot end # Returns a list of the names of all fields accessible through this # object. +include_hidden+ specifies whether to include hidden names # in the listing. def field_names(include_hidden = false) if include_hidden @field_names.compact else hidden = get_parameter(:hide) || [] @field_names.compact - hidden end end def debug_name_of(child) #:nodoc: field_name = @field_names[find_index_of(child)] "#{debug_name}.#{field_name}" end def offset_of(child) #:nodoc: instantiate_all_objs sum = sum_num_bytes_below_index(find_index_of(child)) child.bit_aligned? ? sum.floor : sum.ceil end def do_read(io) #:nodoc: instantiate_all_objs @field_objs.each { |f| f.do_read(io) if include_obj?(f) } end def do_write(io) #:nodoc instantiate_all_objs @field_objs.each { |f| f.do_write(io) if include_obj?(f) } end def do_num_bytes #:nodoc: instantiate_all_objs sum_num_bytes_for_all_fields end def [](key) find_obj_for_name(key) end def []=(key, value) obj = find_obj_for_name(key) if obj obj.assign(value) end end def key?(key) @field_names.index(base_field_name(key)) end def each_pair @field_names.compact.each do |name| yield [name, find_obj_for_name(name)] end end #--------------- private def define_field_accessors get_parameter(:fields).each_with_index do |field, i| name = field.name_as_sym define_field_accessors_for(name, i) if name end end def define_field_accessors_for(name, index) define_singleton_method(name) do instantiate_obj_at(index) if @field_objs[index].nil? @field_objs[index] end define_singleton_method("#{name}=") do |*vals| instantiate_obj_at(index) if @field_objs[index].nil? @field_objs[index].assign(*vals) end define_singleton_method("#{name}?") do instantiate_obj_at(index) if @field_objs[index].nil? include_obj?(@field_objs[index]) end end def find_index_of(obj) @field_objs.index { |el| el.equal?(obj) } end def find_obj_for_name(name) index = @field_names.index(base_field_name(name)) if index instantiate_obj_at(index) @field_objs[index] else nil end end def base_field_name(name) name.to_s.sub(/(=|\?)\z/, "").to_sym end def instantiate_all_objs @field_names.each_index { |i| instantiate_obj_at(i) } end def instantiate_obj_at(index) if @field_objs[index].nil? field = get_parameter(:fields)[index] @field_objs[index] = field.instantiate(nil, self) end end def assign_fields(val) src = as_stringified_hash(val) @field_names.compact.each do |name| obj = find_obj_for_name(name) if obj && src.key?(name) obj.assign(src[name]) end end end def as_stringified_hash(val) if BinData::Struct === val val elsif val.nil? {} else hash = Snapshot.new val.each_pair { |k,v| hash[k] = v } hash end end def sum_num_bytes_for_all_fields sum_num_bytes_below_index(@field_objs.length) end def sum_num_bytes_below_index(index) (0...index).inject(0) do |sum, i| obj = @field_objs[i] if include_obj?(obj) nbytes = obj.do_num_bytes (nbytes.is_a?(Integer) ? sum.ceil : sum) + nbytes else sum end end end def include_obj?(obj) !obj.has_parameter?(:onlyif) || obj.eval_parameter(:onlyif) end # A hash that can be accessed via attributes. class Snapshot < ::Hash #:nodoc: def []=(key, value) super unless value.nil? end def respond_to?(symbol, include_private = false) key?(symbol) || super end def method_missing(symbol, *args) self[symbol] || super end end end # Align fields to a multiple of :byte_align module ByteAlignPlugin def do_read(io) initial_offset = io.offset instantiate_all_objs @field_objs.each do |f| if include_obj?(f) if align_obj?(f) io.seekbytes(bytes_to_align(f, io.offset - initial_offset)) end f.do_read(io) end end end def do_write(io) initial_offset = io.offset instantiate_all_objs @field_objs.each do |f| if include_obj?(f) if align_obj?(f) io.writebytes("\x00" * bytes_to_align(f, io.offset - initial_offset)) end f.do_write(io) end end end def sum_num_bytes_below_index(index) sum = 0 (0...@field_objs.length).each do |i| obj = @field_objs[i] if include_obj?(obj) sum = sum.ceil + bytes_to_align(obj, sum.ceil) if align_obj?(obj) break if i >= index nbytes = obj.do_num_bytes sum = (nbytes.is_a?(Integer) ? sum.ceil : sum) + nbytes end end sum end def bytes_to_align(obj, rel_offset) align = obj.eval_parameter(:byte_align) (align - (rel_offset % align)) % align end def align_obj?(obj) obj.has_parameter?(:byte_align) end end class StructArgProcessor < BaseArgProcessor def sanitize_parameters!(obj_class, params) sanitize_endian(params) sanitize_search_prefix(params) sanitize_fields(obj_class, params) sanitize_hide(params) end #------------- private def sanitize_endian(params) if params.needs_sanitizing?(:endian) endian = params.create_sanitized_endian(params[:endian]) params[:endian] = endian end end def sanitize_search_prefix(params) if params.needs_sanitizing?(:search_prefix) search_prefix = [] Array(params[:search_prefix]).each do |prefix| prefix = prefix.to_s.chomp("_") search_prefix << prefix if prefix != "" end params[:search_prefix] = search_prefix end end def sanitize_fields(obj_class, params) if params.needs_sanitizing?(:fields) fields = params[:fields] params[:fields] = params.create_sanitized_fields fields.each do |ftype, fname, fparams| params[:fields].add_field(ftype, fname, fparams) end field_names = sanitized_field_names(params[:fields]) ensure_field_names_are_valid(obj_class, field_names) end end def sanitize_hide(params) if params.needs_sanitizing?(:hide) && params.has_parameter?(:fields) field_names = sanitized_field_names(params[:fields]) hfield_names = hidden_field_names(params[:hide]) params[:hide] = (hfield_names & field_names) end end def sanitized_field_names(sanitized_fields) sanitized_fields.field_names.compact end def hidden_field_names(hidden) (hidden || []).collect(&:to_sym) end def ensure_field_names_are_valid(obj_class, field_names) reserved_names = BinData::Struct::RESERVED field_names.each do |name| if obj_class.method_defined?(name) raise NameError.new("Rename field '#{name}' in #{obj_class}, " \ "as it shadows an existing method.", name) end if reserved_names.include?(name) raise NameError.new("Rename field '#{name}' in #{obj_class}, " \ "as it is a reserved name.", name) end if field_names.count(name) != 1 raise NameError.new("field '#{name}' in #{obj_class}, " \ "is defined multiple times.", name) end end end end end bindata-2.3.5/lib/bindata/virtual.rb0000644000004100000410000000226313042501567017344 0ustar www-datawww-datarequire "bindata/base" module BinData # A virtual field is one that is neither read, written nor occupies space in # the data stream. It is used to make assertions or as a convenient label # for determining offsets or storing values. # # require 'bindata' # # class A < BinData::Record # string :a, read_length: 5 # string :b, read_length: 5 # virtual :c, assert: -> { a == b } # end # # obj = A.read("abcdeabcde") # obj.a #=> "abcde" # obj.c.offset #=> 10 # # obj = A.read("abcdeABCDE") #=> BinData::ValidityError: assertion failed for obj.c # # == Parameters # # Parameters may be provided at initialisation to control the behaviour of # an object. These params include those for BinData::Base as well as: # # [:assert] Raise an error when reading or assigning if the value # of this evaluated parameter is false. # [:value] The virtual object will always have this value. # class Virtual < BinData::BasePrimitive def do_read(io) end def do_write(io) end def do_num_bytes 0.0 end def sensible_default nil end end end bindata-2.3.5/lib/bindata/record.rb0000644000004100000410000000075713042501567017142 0ustar www-datawww-datarequire 'bindata/dsl' require 'bindata/struct' module BinData # A Record is a declarative wrapper around Struct. # # See +Struct+ for more info. class Record < BinData::Struct extend DSLMixin unregister_self dsl_parser :struct arg_processor :record end class RecordArgProcessor < StructArgProcessor include MultiFieldArgSeparator def sanitize_parameters!(obj_class, params) super(obj_class, params.merge!(obj_class.dsl_params)) end end end bindata-2.3.5/lib/bindata/version.rb0000644000004100000410000000004713042501567017341 0ustar www-datawww-datamodule BinData VERSION = "2.3.5" end bindata-2.3.5/lib/bindata/choice.rb0000644000004100000410000001300313042501567017102 0ustar www-datawww-datarequire 'bindata/base' require 'bindata/dsl' module BinData # A Choice is a collection of data objects of which only one is active # at any particular time. Method calls will be delegated to the active # choice. # # require 'bindata' # # type1 = [:string, {value: "Type1"}] # type2 = [:string, {value: "Type2"}] # # choices = {5 => type1, 17 => type2} # a = BinData::Choice.new(choices: choices, selection: 5) # a # => "Type1" # # choices = [ type1, type2 ] # a = BinData::Choice.new(choices: choices, selection: 1) # a # => "Type2" # # choices = [ nil, nil, nil, type1, nil, type2 ] # a = BinData::Choice.new(choices: choices, selection: 3) # a # => "Type1" # # # Chooser = Struct.new(:choice) # mychoice = Chooser.new # mychoice.choice = 'big' # # choices = {'big' => :uint16be, 'little' => :uint16le} # a = BinData::Choice.new(choices: choices, copy_on_change: true, # selection: -> { mychoice.choice }) # a.assign(256) # a.to_binary_s #=> "\001\000" # # mychoice.choice = 'little' # a.to_binary_s #=> "\000\001" # # == Parameters # # Parameters may be provided at initialisation to control the behaviour of # an object. These params are: # # :choices:: Either an array or a hash specifying the possible # data objects. The format of the # array/hash.values is a list of symbols # representing the data object type. If a choice # is to have params passed to it, then it should # be provided as [type_symbol, hash_params]. An # implementation constraint is that the hash may # not contain symbols as keys, with the exception # of :default. :default is to be used when then # :selection does not exist in the :choices hash. # :selection:: An index/key into the :choices array/hash which # specifies the currently active choice. # :copy_on_change:: If set to true, copy the value of the previous # selection to the current selection whenever the # selection changes. Default is false. class Choice < BinData::Base extend DSLMixin dsl_parser :choice arg_processor :choice mandatory_parameters :choices, :selection optional_parameter :copy_on_change def initialize_shared_instance extend CopyOnChangePlugin if eval_parameter(:copy_on_change) == true super end def initialize_instance @choices = {} @last_selection = nil end # Returns the current selection. def selection selection = eval_parameter(:selection) if selection.nil? raise IndexError, ":selection returned nil for #{debug_name}" end selection end def respond_to?(symbol, include_private = false) #:nodoc: current_choice.respond_to?(symbol, include_private) || super end def method_missing(symbol, *args, &block) #:nodoc: current_choice.__send__(symbol, *args, &block) end %w(clear? assign snapshot do_read do_write do_num_bytes).each do |m| module_eval <<-END def #{m}(*args) current_choice.#{m}(*args) end END end #--------------- private def current_choice current_selection = selection @choices[current_selection] ||= instantiate_choice(current_selection) end def instantiate_choice(selection) prototype = get_parameter(:choices)[selection] if prototype.nil? raise IndexError, "selection '#{selection}' does not exist in :choices for #{debug_name}" end prototype.instantiate(nil, self) end end class ChoiceArgProcessor < BaseArgProcessor def sanitize_parameters!(obj_class, params) #:nodoc: params.merge!(obj_class.dsl_params) if params.needs_sanitizing?(:choices) choices = choices_as_hash(params[:choices]) ensure_valid_keys(choices) params[:choices] = params.create_sanitized_choices(choices) end end #------------- private def choices_as_hash(choices) if choices.respond_to?(:to_ary) key_array_by_index(choices.to_ary) else choices end end def key_array_by_index(array) result = {} array.each_with_index do |el, i| result[i] = el unless el.nil? end result end def ensure_valid_keys(choices) if choices.key?(nil) raise ArgumentError, ":choices hash may not have nil key" end if choices.keys.detect { |key| key.is_a?(Symbol) && key != :default } raise ArgumentError, ":choices hash may not have symbols for keys" end end end # Logic for the :copy_on_change parameter module CopyOnChangePlugin def current_choice obj = super copy_previous_value(obj) obj end def copy_previous_value(obj) current_selection = selection prev = get_previous_choice(current_selection) obj.assign(prev) unless prev.nil? remember_current_selection(current_selection) end def get_previous_choice(selection) if @last_selection && selection != @last_selection @choices[@last_selection] end end def remember_current_selection(selection) @last_selection = selection end end end bindata-2.3.5/lib/bindata/lazy.rb0000644000004100000410000000552613042501567016642 0ustar www-datawww-datamodule BinData # A LazyEvaluator is bound to a data object. The evaluator will evaluate # lambdas in the context of this data object. These lambdas # are those that are passed to data objects as parameters, e.g.: # # BinData::String.new(value: -> { %w(a test message).join(" ") }) # # As a shortcut, :foo is the equivalent of lambda { foo }. # # When evaluating lambdas, unknown methods are resolved in the context of the # parent of the bound data object. Resolution is attempted firstly as keys # in #parameters, and secondly as methods in this parent. This # resolution propagates up the chain of parent data objects. # # An evaluation will recurse until it returns a result that is not # a lambda or a symbol. # # This resolution process makes the lambda easier to read as we just write # field instead of obj.field. class LazyEvaluator # Creates a new evaluator. All lazy evaluation is performed in the # context of +obj+. def initialize(obj) @obj = obj end def lazy_eval(val, overrides = nil) @overrides = overrides if overrides if val.is_a? Symbol __send__(val) elsif val.respond_to? :arity instance_exec(&val) else val end end # Returns a LazyEvaluator for the parent of this data object. def parent if @obj.parent @obj.parent.lazy_evaluator else nil end end # Returns the index of this data object inside it's nearest container # array. def index return @overrides[:index] if defined?(@overrides) && @overrides.key?(:index) child = @obj parent = @obj.parent while parent if parent.respond_to?(:find_index_of) return parent.find_index_of(child) end child = parent parent = parent.parent end raise NoMethodError, "no index found" end def method_missing(symbol, *args) return @overrides[symbol] if defined?(@overrides) && @overrides.key?(symbol) if @obj.parent eval_symbol_in_parent_context(symbol, args) else super end end #--------------- private def eval_symbol_in_parent_context(symbol, args) result = resolve_symbol_in_parent_context(symbol, args) recursively_eval(result, args) end def resolve_symbol_in_parent_context(symbol, args) obj_parent = @obj.parent if obj_parent.has_parameter?(symbol) obj_parent.get_parameter(symbol) elsif obj_parent.safe_respond_to?(symbol, true) obj_parent.__send__(symbol, *args) else symbol end end def recursively_eval(val, args) if val.is_a?(Symbol) parent.__send__(val, *args) elsif val.respond_to?(:arity) parent.instance_exec(&val) else val end end end end bindata-2.3.5/lib/bindata/rest.rb0000644000004100000410000000123513042501567016631 0ustar www-datawww-datarequire "bindata/base_primitive" module BinData # Rest will consume the input stream from the current position to the end of # the stream. This will mainly be useful for debugging and developing. # # require 'bindata' # # class A < BinData::Record # string :a, read_length: 5 # rest :rest # end # # obj = A.read("abcdefghij") # obj.a #=> "abcde" # obj.rest #=" "fghij" # class Rest < BinData::BasePrimitive #--------------- private def value_to_binary_string(val) val end def read_and_return_value(io) io.read_all_bytes end def sensible_default "" end end end bindata-2.3.5/lib/bindata/bits.rb0000644000004100000410000001063013042501567016614 0ustar www-datawww-datarequire 'bindata/base_primitive' module BinData # Defines a number of classes that contain a bit based integer. # The integer is defined by endian and number of bits. module BitField #:nodoc: all class << self def define_class(name, nbits, endian, signed = :unsigned) unless BinData.const_defined?(name) BinData.module_eval <<-END class #{name} < BinData::BasePrimitive BitField.define_methods(self, #{nbits.inspect}, #{endian.inspect}, #{signed.inspect}) end END end BinData.const_get(name) end def define_methods(bit_class, nbits, endian, signed) bit_class.module_eval <<-END #{create_params_code(nbits)} def assign(val) #{create_nbits_code(nbits)} #{create_clamp_code(nbits, signed)} super(val) end def do_write(io) #{create_nbits_code(nbits)} val = _value #{create_int2uint_code(nbits, signed)} io.writebits(val, #{nbits}, :#{endian}) end def do_num_bytes #{create_nbits_code(nbits)} #{create_do_num_bytes_code(nbits)} end def bit_aligned? true end #--------------- private def read_and_return_value(io) #{create_nbits_code(nbits)} val = io.readbits(#{nbits}, :#{endian}) #{create_uint2int_code(nbits, signed)} val end def sensible_default 0 end END end def create_params_code(nbits) if nbits == :nbits "mandatory_parameter :nbits" else "" end end def create_nbits_code(nbits) if nbits == :nbits "nbits = eval_parameter(:nbits)" else "" end end def create_do_num_bytes_code(nbits) if nbits == :nbits "nbits / 8.0" else nbits / 8.0 end end def create_clamp_code(nbits, signed) if nbits == :nbits create_dynamic_clamp_code(signed) else create_fixed_clamp_code(nbits, signed) end end def create_dynamic_clamp_code(signed) if signed == :signed max = "max = (1 << (nbits - 1)) - 1" min = "min = -(max + 1)" else max = "max = (1 << nbits) - 1" min = "min = 0" end "#{max}; #{min}; val = (val < min) ? min : (val > max) ? max : val" end def create_fixed_clamp_code(nbits, signed) if nbits == 1 && signed == :signed raise "signed bitfield must have more than one bit" end if signed == :signed max = (1 << (nbits - 1)) - 1 min = -(max + 1) else min = 0 max = (1 << nbits) - 1 end clamp = "(val < #{min}) ? #{min} : (val > #{max}) ? #{max} : val" if nbits == 1 # allow single bits to be used as booleans clamp = "(val == true) ? 1 : (not val) ? 0 : #{clamp}" end "val = #{clamp}" end def create_int2uint_code(nbits, signed) if signed != :signed "" elsif nbits == :nbits "val &= (1 << nbits) - 1" else "val &= #{(1 << nbits) - 1}" end end def create_uint2int_code(nbits, signed) if signed != :signed "" elsif nbits == :nbits "val -= (1 << nbits) if (val >= (1 << (nbits - 1)))" else "val -= #{1 << nbits} if (val >= #{1 << (nbits - 1)})" end end end end # Create classes for dynamic bitfields { "Bit" => :big, "BitLe" => :little, "Sbit" => [:big, :signed], "SbitLe" => [:little, :signed], }.each_pair { |name, args| BitField.define_class(name, :nbits, *args) } # Create classes on demand module BitFieldFactory def const_missing(name) mappings = { /^Bit(\d+)$/ => :big, /^Bit(\d+)le$/ => :little, /^Sbit(\d+)$/ => [:big, :signed], /^Sbit(\d+)le$/ => [:little, :signed] } mappings.each_pair do |regex, args| if regex =~ name.to_s nbits = $1.to_i return BitField.define_class(name, nbits, *args) end end super(name) end end BinData.extend BitFieldFactory end bindata-2.3.5/lib/bindata/array.rb0000644000004100000410000002134313042501567016774 0ustar www-datawww-datarequire 'bindata/base' require 'bindata/dsl' module BinData # An Array is a list of data objects of the same type. # # require 'bindata' # # data = "\x03\x04\x05\x06\x07\x08\x09" # # obj = BinData::Array.new(type: :int8, initial_length: 6) # obj.read(data) #=> [3, 4, 5, 6, 7, 8] # # obj = BinData::Array.new(type: :int8, # read_until: -> { index == 1 }) # obj.read(data) #=> [3, 4] # # obj = BinData::Array.new(type: :int8, # read_until: -> { element >= 6 }) # obj.read(data) #=> [3, 4, 5, 6] # # obj = BinData::Array.new(type: :int8, # read_until: -> { array[index] + array[index - 1] == 13 }) # obj.read(data) #=> [3, 4, 5, 6, 7] # # obj = BinData::Array.new(type: :int8, read_until: :eof) # obj.read(data) #=> [3, 4, 5, 6, 7, 8, 9] # # == Parameters # # Parameters may be provided at initialisation to control the behaviour of # an object. These params are: # # :type:: The symbol representing the data type of the # array elements. If the type is to have params # passed to it, then it should be provided as # [type_symbol, hash_params]. # :initial_length:: The initial length of the array. # :read_until:: While reading, elements are read until this # condition is true. This is typically used to # read an array until a sentinel value is found. # The variables +index+, +element+ and +array+ # are made available to any lambda assigned to # this parameter. If the value of this parameter # is the symbol :eof, then the array will read # as much data from the stream as possible. # # Each data object in an array has the variable +index+ made available # to any lambda evaluated as a parameter of that data object. class Array < BinData::Base extend DSLMixin include Enumerable dsl_parser :array arg_processor :array mandatory_parameter :type optional_parameters :initial_length, :read_until mutually_exclusive_parameters :initial_length, :read_until def initialize_shared_instance @element_prototype = get_parameter(:type) if get_parameter(:read_until) == :eof extend ReadUntilEOFPlugin elsif has_parameter?(:read_until) extend ReadUntilPlugin elsif has_parameter?(:initial_length) extend InitialLengthPlugin end super end def initialize_instance @element_list = nil end def clear? @element_list.nil? || elements.all?(&:clear?) end def assign(array) raise ArgumentError, "can't set a nil value for #{debug_name}" if array.nil? @element_list = to_storage_formats(array.to_ary) end def snapshot elements.collect(&:snapshot) end def find_index(obj) elements.index(obj) end alias index find_index # Returns the first index of +obj+ in self. # # Uses equal? for the comparator. def find_index_of(obj) elements.index { |el| el.equal?(obj) } end def push(*args) insert(-1, *args) self end alias << push def unshift(*args) insert(0, *args) self end def concat(array) insert(-1, *array.to_ary) self end def insert(index, *objs) extend_array(index - 1) elements.insert(index, *to_storage_formats(objs)) self end # Returns the element at +index+. def [](arg1, arg2 = nil) if arg1.respond_to?(:to_int) && arg2.nil? slice_index(arg1.to_int) elsif arg1.respond_to?(:to_int) && arg2.respond_to?(:to_int) slice_start_length(arg1.to_int, arg2.to_int) elsif arg1.is_a?(Range) && arg2.nil? slice_range(arg1) else raise TypeError, "can't convert #{arg1} into Integer" unless arg1.respond_to?(:to_int) raise TypeError, "can't convert #{arg2} into Integer" unless arg2.respond_to?(:to_int) end end alias slice [] def slice_index(index) extend_array(index) at(index) end def slice_start_length(start, length) elements[start, length] end def slice_range(range) elements[range] end private :slice_index, :slice_start_length, :slice_range # Returns the element at +index+. Unlike +slice+, if +index+ is out # of range the array will not be automatically extended. def at(index) elements[index] end # Sets the element at +index+. def []=(index, value) extend_array(index) elements[index].assign(value) end # Returns the first element, or the first +n+ elements, of the array. # If the array is empty, the first form returns nil, and the second # form returns an empty array. def first(n = nil) if n.nil? && empty? # explicitly return nil as arrays grow automatically nil elsif n.nil? self[0] else self[0, n] end end # Returns the last element, or the last +n+ elements, of the array. # If the array is empty, the first form returns nil, and the second # form returns an empty array. def last(n = nil) if n.nil? self[-1] else n = length if n > length self[-n, n] end end def length elements.length end alias size length def empty? length.zero? end # Allow this object to be used in array context. def to_ary collect { |el| el } end def each elements.each { |el| yield el } end def debug_name_of(child) #:nodoc: index = find_index_of(child) "#{debug_name}[#{index}]" end def offset_of(child) #:nodoc: index = find_index_of(child) sum = sum_num_bytes_below_index(index) child.bit_aligned? ? sum.floor : sum.ceil end def do_write(io) #:nodoc: elements.each { |el| el.do_write(io) } end def do_num_bytes #:nodoc: sum_num_bytes_for_all_elements end #--------------- private def extend_array(max_index) max_length = max_index + 1 while elements.length < max_length append_new_element end end def to_storage_formats(els) els.collect { |el| new_element(el) } end def elements @element_list ||= [] end def append_new_element element = new_element elements << element element end def new_element(value = nil) @element_prototype.instantiate(value, self) end def sum_num_bytes_for_all_elements sum_num_bytes_below_index(length) end def sum_num_bytes_below_index(index) (0...index).inject(0) do |sum, i| nbytes = elements[i].do_num_bytes if nbytes.is_a?(Integer) sum.ceil + nbytes else sum + nbytes end end end end class ArrayArgProcessor < BaseArgProcessor def sanitize_parameters!(obj_class, params) #:nodoc: unless params.has_parameter?(:initial_length) || params.has_parameter?(:read_until) # ensure one of :initial_length and :read_until exists params[:initial_length] = 0 end params.warn_replacement_parameter(:length, :initial_length) params.warn_replacement_parameter(:read_length, :initial_length) params.must_be_integer(:initial_length) params.merge!(obj_class.dsl_params) if params.needs_sanitizing?(:type) el_type, el_params = params[:type] params[:type] = params.create_sanitized_object_prototype(el_type, el_params) end end end # Logic for the :read_until parameter module ReadUntilPlugin def do_read(io) loop do element = append_new_element element.do_read(io) variables = { index: self.length - 1, element: self.last, array: self } break if eval_parameter(:read_until, variables) end end end # Logic for the read_until: :eof parameter module ReadUntilEOFPlugin def do_read(io) loop do element = append_new_element begin element.do_read(io) rescue EOFError, IOError elements.pop break end end end end # Logic for the :initial_length parameter module InitialLengthPlugin def do_read(io) elements.each { |el| el.do_read(io) } end def elements if @element_list.nil? @element_list = [] eval_parameter(:initial_length).times do @element_list << new_element end end @element_list end end end bindata-2.3.5/lib/bindata/registry.rb0000644000004100000410000000623413042501567017530 0ustar www-datawww-datamodule BinData class UnRegisteredTypeError < StandardError ; end # This registry contains a register of name -> class mappings. # # Numerics (integers and floating point numbers) have an endian property as # part of their name (e.g. int32be, float_le). # # Classes can be looked up based on their full name or an abbreviated +name+ # with +hints+. # # There are two hints supported, :endian and :search_prefix. # # #lookup("int32", { endian: :big }) will return Int32Be. # # #lookup("my_type", { search_prefix: :ns }) will return NsMyType. # # Names are stored in under_score_style, not camelCase. class Registry def initialize @registry = {} end def register(name, class_to_register) return if class_to_register.nil? formatted_name = underscore_name(name) warn_if_name_is_already_registered(formatted_name, class_to_register) @registry[formatted_name] = class_to_register end def unregister(name) @registry.delete(underscore_name(name)) end def lookup(name, hints = {}) key = normalize_name(name, hints) @registry[key] || raise(UnRegisteredTypeError, name.to_s) end # Convert CamelCase +name+ to underscore style. def underscore_name(name) name.to_s.sub(/.*::/, ""). gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2'). gsub(/([a-z\d])([A-Z])/, '\1_\2'). tr("-", "_"). downcase end #--------------- private def normalize_name(name, hints) name = underscore_name(name) if !registered?(name) search_prefix = [""].concat(Array(hints[:search_prefix])) search_prefix.each do |prefix| nwp = name_with_prefix(name, prefix) if registered?(nwp) name = nwp break end nwe = name_with_endian(nwp, hints[:endian]) if registered?(nwe) name = nwe break end end end name end def name_with_prefix(name, prefix) prefix = prefix.to_s.chomp("_") if prefix == "" name else "#{prefix}_#{name}" end end def name_with_endian(name, endian) return name if endian.nil? suffix = (endian == :little) ? "le" : "be" if /^u?int\d+$/ =~ name name + suffix else name + "_" + suffix end end def registered?(name) register_dynamic_class(name) unless @registry.key?(name) @registry.key?(name) end def register_dynamic_class(name) if /^u?int\d+(le|be)$/ =~ name || /^s?bit\d+(le)?$/ =~ name class_name = name.gsub(/(?:^|_)(.)/) { $1.upcase } begin BinData.const_get(class_name) rescue NameError end end end def warn_if_name_is_already_registered(name, class_to_register) prev_class = @registry[name] if $VERBOSE && prev_class && prev_class != class_to_register warn "warning: replacing registered class #{prev_class} " \ "with #{class_to_register}" end end end # A singleton registry of all registered classes. RegisteredClasses = Registry.new end bindata-2.3.5/lib/bindata/trace.rb0000644000004100000410000000406513042501567016756 0ustar www-datawww-datamodule BinData # reference to the current tracer @tracer ||= nil class Tracer #:nodoc: def initialize(io) @trace_io = io end def trace(msg) @trace_io.puts(msg) end def trace_obj(obj_name, val) if val.length > 30 val = val.slice(0..30) + "..." end trace "#{obj_name} => #{val}" end end # Turn on trace information when reading a BinData object. # If +block+ is given then the tracing only occurs for that block. # This is useful for debugging a BinData declaration. def trace_reading(io = STDERR) @tracer = Tracer.new(io) [BasePrimitive, Choice].each(&:turn_on_tracing) if block_given? begin yield ensure [BasePrimitive, Choice].each(&:turn_off_tracing) @tracer = nil end end end def trace_message #:nodoc: yield @tracer if @tracer end module_function :trace_reading, :trace_message class BasePrimitive < BinData::Base class << self def turn_on_tracing alias_method :do_read_without_hook, :do_read alias_method :do_read, :do_read_with_hook end def turn_off_tracing alias_method :do_read, :do_read_without_hook end end def do_read_with_hook(io) do_read_without_hook(io) trace_value end def trace_value BinData.trace_message do |tracer| value_string = _value.inspect tracer.trace_obj(debug_name, value_string) end end end class Choice < BinData::Base class << self def turn_on_tracing alias_method :do_read_without_hook, :do_read alias_method :do_read, :do_read_with_hook end def turn_off_tracing alias_method :do_read, :do_read_without_hook end end def do_read_with_hook(io) trace_selection do_read_without_hook(io) end def trace_selection BinData.trace_message do |tracer| selection_string = eval_parameter(:selection).inspect tracer.trace_obj("#{debug_name}-selection-", selection_string) end end end end bindata-2.3.5/lib/bindata/delayed_io.rb0000644000004100000410000001215213042501567017752 0ustar www-datawww-datarequire 'bindata/base' require 'bindata/dsl' module BinData # BinData declarations are evaluated in a single pass. # However, some binary formats require multi pass processing. A common # reason is seeking backwards in the input stream. # # DelayedIO supports multi pass processing. It works by ignoring the normal # #read or #write calls. The user must explicitly call the #read_now! or # #write_now! methods to process an additional pass. This additional pass # must specify the abs_offset of the I/O operation. # # require 'bindata' # # obj = BinData::DelayedIO.new(read_abs_offset: 3, type: :uint16be) # obj.read("\x00\x00\x00\x11\x12") # obj #=> 0 # # obj.read_now! # obj #=> 0x1112 # # - OR - # # obj.read("\x00\x00\x00\x11\x12") { obj.read_now! } #=> 0x1122 # # obj.to_binary_s { obj.write_now! } #=> "\x00\x00\x00\x11\x12" # # You can use the +auto_call_delayed_io+ keyword to cause #read and #write to # automatically perform the extra passes. # # class ReversePascalString < BinData::Record # auto_call_delayed_io # # delayed_io :str, read_abs_offset: 0 do # string read_length: :len # end # count_bytes_remaining :total_size # skip to_abs_offset: -> { total_size - 1 } # uint8 :len, value: -> { str.length } # end # # s = ReversePascalString.read("hello\x05") # s.to_binary_s #=> "hello\x05" # # # == Parameters # # Parameters may be provided at initialisation to control the behaviour of # an object. These params are: # # :read_abs_offset:: The abs_offset to start reading at. # :type:: The single type inside the delayed io. Use # a struct if multiple fields are required. class DelayedIO < BinData::Base extend DSLMixin dsl_parser :delayed_io arg_processor :delayed_io mandatory_parameters :read_abs_offset, :type def initialize_instance @type = get_parameter(:type).instantiate(nil, self) @abs_offset = nil @read_io = nil @write_io = nil end def clear? @type.clear? end def assign(val) @type.assign(val) end def snapshot @type.snapshot end def num_bytes @type.num_bytes end def respond_to?(symbol, include_private = false) #:nodoc: @type.respond_to?(symbol, include_private) || super end def method_missing(symbol, *args, &block) #:nodoc: @type.__send__(symbol, *args, &block) end def abs_offset @abs_offset || eval_parameter(:read_abs_offset) end # Sets the +abs_offset+ to use when writing this object. def abs_offset=(offset) @abs_offset = offset end def rel_offset abs_offset end def do_read(io) #:nodoc: @read_io = io end def do_write(io) #:nodoc: @write_io = io end def do_num_bytes #:nodoc: 0 end # DelayedIO objects aren't read when #read is called. # The reading is delayed until this method is called. def read_now! raise IOError, "read from where?" unless @read_io @read_io.seekbytes(abs_offset - @read_io.offset) start_read do @type.do_read(@read_io) end end # DelayedIO objects aren't written when #write is called. # The writing is delayed until this method is called. def write_now! raise IOError, "write to where?" unless @write_io @write_io.seekbytes(abs_offset - @write_io.offset) @type.do_write(@write_io) end end class DelayedIoArgProcessor < BaseArgProcessor include MultiFieldArgSeparator def sanitize_parameters!(obj_class, params) params.merge!(obj_class.dsl_params) params.must_be_integer(:read_abs_offset) if params.needs_sanitizing?(:type) el_type, el_params = params[:type] params[:type] = params.create_sanitized_object_prototype(el_type, el_params) end end end # Add +auto_call_delayed_io+ keyword to BinData::Base. class Base class << self # The +auto_call_delayed_io+ keyword sets a data object tree to perform # multi pass I/O automatically. def auto_call_delayed_io include AutoCallDelayedIO return if DelayedIO.method_defined? :initialize_instance_without_record_io DelayedIO.send(:alias_method, :initialize_instance_without_record_io, :initialize_instance) DelayedIO.send(:define_method, :initialize_instance) do if @parent && !defined? @delayed_io_recorded @delayed_io_recorded = true list = top_level_get(:delayed_ios) list << self if list end initialize_instance_without_record_io end end end module AutoCallDelayedIO def initialize_shared_instance top_level_set(:delayed_ios, []) super end def read(io) super(io) { top_level_get(:delayed_ios).each(&:read_now!) } end def write(io, *_) super(io) { top_level_get(:delayed_ios).each(&:write_now!) } end def num_bytes to_binary_s.size end end end end bindata-2.3.5/lib/bindata/count_bytes_remaining.rb0000644000004100000410000000133513042501567022244 0ustar www-datawww-datarequire "bindata/base_primitive" module BinData # Counts the number of bytes remaining in the input stream from the current # position to the end of the stream. This only makes sense for seekable # streams. # # require 'bindata' # # class A < BinData::Record # count_bytes_remaining :bytes_remaining # string :all_data, read_length: :bytes_remaining # end # # obj = A.read("abcdefghij") # obj.all_data #=> "abcdefghij" # class CountBytesRemaining < BinData::BasePrimitive #--------------- private def value_to_binary_string(val) "" end def read_and_return_value(io) io.num_bytes_remaining end def sensible_default 0 end end end bindata-2.3.5/lib/bindata/base.rb0000644000004100000410000002111513042501567016565 0ustar www-datawww-datarequire 'bindata/framework' require 'bindata/io' require 'bindata/lazy' require 'bindata/name' require 'bindata/params' require 'bindata/registry' require 'bindata/sanitize' module BinData # This is the abstract base class for all data objects. class Base extend AcceptedParametersPlugin include Framework include RegisterNamePlugin class << self # Instantiates this class and reads from +io+, returning the newly # created data object. +args+ will be used when instantiating. def read(io, *args, &block) obj = self.new(*args) obj.read(io, &block) obj end # The arg processor for this class. def arg_processor(name = nil) @arg_processor ||= nil if name @arg_processor = "#{name}_arg_processor".gsub(/(?:^|_)(.)/) { $1.upcase }.to_sym elsif @arg_processor.is_a? Symbol @arg_processor = BinData.const_get(@arg_processor).new elsif @arg_processor.nil? @arg_processor = superclass.arg_processor else @arg_processor end end # The name of this class as used by Records, Arrays etc. def bindata_name RegisteredClasses.underscore_name(name) end # Call this method if this class is abstract and not to be used. def unregister_self RegisteredClasses.unregister(name) end # Registers all subclasses of this class for use def register_subclasses #:nodoc: singleton_class.send(:undef_method, :inherited) define_singleton_method(:inherited) do |subclass| RegisteredClasses.register(subclass.name, subclass) register_subclasses end end private :unregister_self, :register_subclasses end # Register all subclasses of this class. register_subclasses # Set the initial arg processor. arg_processor :base # Creates a new data object. # # Args are optional, but if present, must be in the following order. # # +value+ is a value that is +assign+ed immediately after initialization. # # +parameters+ is a hash containing symbol keys. Some parameters may # reference callable objects (methods or procs). # # +parent+ is the parent data object (e.g. struct, array, choice) this # object resides under. # def initialize(*args) value, @params, @parent = extract_args(args) initialize_shared_instance initialize_instance assign(value) if value end attr_accessor :parent protected :parent= # Creates a new data object based on this instance. # # All parameters will be be duplicated. Use this method # when creating multiple objects with the same parameters. def new(value = nil, parent = nil) obj = clone obj.parent = parent if parent obj.initialize_instance obj.assign(value) if value obj end # Returns the result of evaluating the parameter identified by +key+. # # +overrides+ is an optional +parameters+ like hash that allow the # parameters given at object construction to be overridden. # # Returns nil if +key+ does not refer to any parameter. def eval_parameter(key, overrides = nil) value = get_parameter(key) if value.is_a?(Symbol) || value.respond_to?(:arity) lazy_evaluator.lazy_eval(value, overrides) else value end end # Returns a lazy evaluator for this object. def lazy_evaluator #:nodoc: @lazy ||= LazyEvaluator.new(self) end # Returns the parameter referenced by +key+. # Use this method if you are sure the parameter is not to be evaluated. # You most likely want #eval_parameter. def get_parameter(key) @params[key] end # Returns whether +key+ exists in the +parameters+ hash. def has_parameter?(key) @params.has_parameter?(key) end # Resets the internal state to that of a newly created object. def clear initialize_instance end # Reads data into this data object. def read(io, &block) io = BinData::IO::Read.new(io) unless BinData::IO::Read === io start_read do clear do_read(io) end block.call(self) if block_given? self end # Writes the value for this data object to +io+. def write(io, &block) io = BinData::IO::Write.new(io) unless BinData::IO::Write === io do_write(io) io.flush block.call(self) if block_given? self end # Returns the number of bytes it will take to write this data object. def num_bytes do_num_bytes.ceil end # Returns the string representation of this data object. def to_binary_s(&block) io = BinData::IO.create_string_io write(io, &block) io.rewind io.read end # Returns the hexadecimal string representation of this data object. def to_hex(&block) to_binary_s(&block).unpack('H*')[0] end # Return a human readable representation of this data object. def inspect snapshot.inspect end # Return a string representing this data object. def to_s snapshot.to_s end # Work with Ruby's pretty-printer library. def pretty_print(pp) #:nodoc: pp.pp(snapshot) end # Override and delegate =~ as it is defined in Object. def =~(other) snapshot =~ other end # Returns a user friendly name of this object for debugging purposes. def debug_name if @parent @parent.debug_name_of(self) else "obj" end end # Returns the offset (in bytes) of this object with respect to its most # distant ancestor. def abs_offset if @parent @parent.abs_offset + @parent.offset_of(self) else 0 end end # Returns the offset (in bytes) of this object with respect to its parent. def rel_offset if @parent @parent.offset_of(self) else 0 end end def ==(other) #:nodoc: # double dispatch other == snapshot end # A version of +respond_to?+ used by the lazy evaluator. It doesn't # reinvoke the evaluator so as to avoid infinite evaluation loops. def safe_respond_to?(symbol, include_private = false) #:nodoc: base_respond_to?(symbol, include_private) end alias base_respond_to? respond_to? #--------------- private def extract_args(args) self.class.arg_processor.extract_args(self.class, args) end def start_read top_level_set(:in_read, true) yield ensure top_level_set(:in_read, false) end # Is this object tree currently being read? Used by BasePrimitive. def reading? top_level_get(:in_read) end def top_level_set(sym, value) top_level.instance_variable_set("@tl_#{sym}", value) end def top_level_get(sym) tl = top_level tl.instance_variable_defined?("@tl_#{sym}") && tl.instance_variable_get("@tl_#{sym}") end def top_level if parent.nil? tl = self else tl = parent tl = tl.parent while tl.parent end tl end def binary_string(str) str.to_s.dup.force_encoding(Encoding::BINARY) end end # ArgProcessors process the arguments passed to BinData::Base.new into # the form required to initialise the BinData object. # # Any passed parameters are sanitized so the BinData object doesn't # need to perform error checking on the parameters. class BaseArgProcessor @@empty_hash = Hash.new.freeze # Takes the arguments passed to BinData::Base.new and # extracts [value, sanitized_parameters, parent]. def extract_args(obj_class, obj_args) value, params, parent = separate_args(obj_class, obj_args) sanitized_params = SanitizedParameters.sanitize(params, obj_class) [value, sanitized_params, parent] end # Separates the arguments passed to BinData::Base.new into # [value, parameters, parent]. Called by #extract_args. def separate_args(_obj_class, obj_args) args = obj_args.dup value = parameters = parent = nil if args.length > 1 && args.last.is_a?(BinData::Base) parent = args.pop end if args.length > 0 && args.last.is_a?(Hash) parameters = args.pop end if args.length > 0 value = args.pop end parameters ||= @@empty_hash [value, parameters, parent] end # Performs sanity checks on the given parameters. # This method converts the parameters to the form expected # by the data object. def sanitize_parameters!(obj_class, obj_params) end end end bindata-2.3.5/lib/bindata/int.rb0000644000004100000410000001323013042501567016444 0ustar www-datawww-datarequire 'bindata/base_primitive' module BinData # Defines a number of classes that contain an integer. The integer # is defined by endian, signedness and number of bytes. module Int #:nodoc: all class << self def define_class(name, nbits, endian, signed) unless BinData.const_defined?(name) BinData.module_eval <<-END class #{name} < BinData::BasePrimitive Int.define_methods(self, #{nbits}, :#{endian}, :#{signed}) end END end BinData.const_get(name) end def define_methods(int_class, nbits, endian, signed) raise "nbits must be divisible by 8" unless (nbits % 8).zero? int_class.module_eval <<-END def assign(val) #{create_clamp_code(nbits, signed)} super(val) end def do_num_bytes #{nbits / 8} end #--------------- private def sensible_default 0 end def value_to_binary_string(val) #{create_clamp_code(nbits, signed)} #{create_to_binary_s_code(nbits, endian, signed)} end def read_and_return_value(io) #{create_read_code(nbits, endian, signed)} end END end #------------- private def create_clamp_code(nbits, signed) if signed == :signed max = (1 << (nbits - 1)) - 1 min = -(max + 1) else max = (1 << nbits) - 1 min = 0 end "val = (val < #{min}) ? #{min} : (val > #{max}) ? #{max} : val" end def create_read_code(nbits, endian, signed) read_str = create_raw_read_code(nbits, endian, signed) if need_signed_conversion_code?(nbits, signed) "val = #{read_str} ; #{create_uint2int_code(nbits)}" else read_str end end def create_raw_read_code(nbits, endian, signed) # special case 8bit integers for speed if nbits == 8 "io.readbytes(1).ord" else unpack_str = create_read_unpack_code(nbits, endian, signed) assemble_str = create_read_assemble_code(nbits, endian, signed) "(#{unpack_str} ; #{assemble_str})" end end def create_read_unpack_code(nbits, endian, signed) nbytes = nbits / 8 pack_directive = pack_directive(nbits, endian, signed) "ints = io.readbytes(#{nbytes}).unpack('#{pack_directive}')" end def create_read_assemble_code(nbits, endian, signed) nwords = nbits / bits_per_word(nbits) idx = (0...nwords).to_a idx.reverse! if endian == :big parts = (0...nwords).collect do |i| "(ints.at(#{idx[i]}) << #{bits_per_word(nbits) * i})" end parts[0].sub!(/ << 0\b/, "") # Remove " << 0" for optimisation parts.join(" + ") end def create_to_binary_s_code(nbits, endian, signed) # special case 8bit integers for speed return "(val & 0xff).chr" if nbits == 8 pack_directive = pack_directive(nbits, endian, signed) words = val_as_packed_words(nbits, endian, signed) pack_str = "[#{words}].pack('#{pack_directive}')" if need_signed_conversion_code?(nbits, signed) "#{create_int2uint_code(nbits)} ; #{pack_str}" else pack_str end end def val_as_packed_words(nbits, endian, signed) nwords = nbits / bits_per_word(nbits) mask = (1 << bits_per_word(nbits)) - 1 vals = (0...nwords).collect { |i| "val >> #{bits_per_word(nbits) * i}" } vals[0].sub!(/ >> 0\b/, "") # Remove " >> 0" for optimisation vals.reverse! if (endian == :big) vals = vals.collect { |val| "#{val} & #{mask}" } # TODO: "& mask" is needed to work around jruby bug. Remove this line when fixed. vals.join(",") end def create_int2uint_code(nbits) "val &= #{(1 << nbits) - 1}" end def create_uint2int_code(nbits) "(val >= #{1 << (nbits - 1)}) ? val - #{1 << nbits} : val" end def bits_per_word(nbits) (nbits % 64).zero? ? 64 : (nbits % 32).zero? ? 32 : (nbits % 16).zero? ? 16 : 8 end def pack_directive(nbits, endian, signed) nwords = nbits / bits_per_word(nbits) directives = { 8 => "C", 16 => "S", 32 => "L", 64 => "Q" } d = directives[bits_per_word(nbits)] d << ((endian == :big) ? ">" : "<") unless d == "C" if signed == :signed && directives.key?(nbits) (d * nwords).downcase else d * nwords end end def need_signed_conversion_code?(nbits, signed) signed == :signed && ![64, 32, 16].include?(nbits) end end end # Unsigned 1 byte integer. class Uint8 < BinData::BasePrimitive Int.define_methods(self, 8, :little, :unsigned) end # Signed 1 byte integer. class Int8 < BinData::BasePrimitive Int.define_methods(self, 8, :little, :signed) end # Create classes on demand module IntFactory def const_missing(name) mappings = { /^Uint(\d+)be$/ => [:big, :unsigned], /^Uint(\d+)le$/ => [:little, :unsigned], /^Int(\d+)be$/ => [:big, :signed], /^Int(\d+)le$/ => [:little, :signed], } mappings.each_pair do |regex, args| if regex =~ name.to_s nbits = $1.to_i if nbits > 0 && (nbits % 8).zero? return Int.define_class(name, nbits, *args) end end end super end end BinData.extend IntFactory end bindata-2.3.5/lib/bindata/stringz.rb0000644000004100000410000000431713042501567017360 0ustar www-datawww-datarequire "bindata/base_primitive" module BinData # A BinData::Stringz object is a container for a zero ("\0") terminated # string. # # For convenience, the zero terminator is not necessary when setting the # value. Likewise, the returned value will not be zero terminated. # # require 'bindata' # # data = "abcd\x00efgh" # # obj = BinData::Stringz.new # obj.read(data) # obj.snapshot #=> "abcd" # obj.num_bytes #=> 5 # obj.to_binary_s #=> "abcd\000" # # == Parameters # # Stringz objects accept all the params that BinData::BasePrimitive # does, as well as the following: # # :max_length:: The maximum length of the string including the zero # byte. class Stringz < BinData::BasePrimitive optional_parameters :max_length def assign(val) super(binary_string(val)) end def snapshot # override to always remove trailing zero bytes result = super trim_and_zero_terminate(result).chomp("\0") end #--------------- private def value_to_binary_string(val) trim_and_zero_terminate(val) end def read_and_return_value(io) max_length = eval_parameter(:max_length) str = "" i = 0 ch = nil # read until zero byte or we have read in the max number of bytes while ch != "\0" && i != max_length ch = io.readbytes(1) str << ch i += 1 end trim_and_zero_terminate(str) end def sensible_default "" end def trim_and_zero_terminate(str) result = binary_string(str) truncate_after_first_zero_byte!(result) trim_to!(result, eval_parameter(:max_length)) append_zero_byte_if_needed!(result) result end def truncate_after_first_zero_byte!(str) str.sub!(/([^\0]*\0).*/, '\1') end def trim_to!(str, max_length = nil) if max_length max_length = 1 if max_length < 1 str.slice!(max_length) if str.length == max_length && str[-1, 1] != "\0" str[-1, 1] = "\0" end end end def append_zero_byte_if_needed!(str) if str.length == 0 || str[-1, 1] != "\0" str << "\0" end end end end bindata-2.3.5/lib/bindata/sanitize.rb0000644000004100000410000002037613042501567017511 0ustar www-datawww-datarequire 'bindata/registry' module BinData # Subclasses of this are sanitized class SanitizedParameter; end class SanitizedPrototype < SanitizedParameter def initialize(obj_type, obj_params, hints) raw_hints = hints.dup if raw_hints[:endian].respond_to?(:endian) raw_hints[:endian] = raw_hints[:endian].endian end obj_params ||= {} if BinData::Base === obj_type obj_class = obj_type else obj_class = RegisteredClasses.lookup(obj_type, raw_hints) end if BinData::Base === obj_class @factory = obj_class else @obj_class = obj_class @obj_params = SanitizedParameters.new(obj_params, @obj_class, hints) end end def has_parameter?(param) if defined? @factory @factory.has_parameter?(param) else @obj_params.has_parameter?(param) end end def instantiate(value = nil, parent = nil) @factory ||= @obj_class.new(@obj_params) @factory.new(value, parent) end end #---------------------------------------------------------------------------- class SanitizedField < SanitizedParameter def initialize(name, field_type, field_params, hints) @name = name @prototype = SanitizedPrototype.new(field_type, field_params, hints) end attr_reader :prototype def name_as_sym @name.nil? ? nil : @name.to_sym end def name @name end def has_parameter?(param) @prototype.has_parameter?(param) end def instantiate(value = nil, parent = nil) @prototype.instantiate(value, parent) end end #---------------------------------------------------------------------------- class SanitizedFields < SanitizedParameter include Enumerable def initialize(hints) @fields = [] @hints = hints end attr_reader :fields def add_field(type, name, params) name = nil if name == "" @fields << SanitizedField.new(name, type, params, @hints) end def [](idx) @fields[idx] end def empty? @fields.empty? end def length @fields.length end def each(&block) @fields.each(&block) end def field_names @fields.collect(&:name_as_sym) end def field_name?(name) @fields.detect { |f| f.name_as_sym == name.to_sym } end def all_field_names_blank? @fields.all? { |f| f.name.nil? } end def no_field_names_blank? @fields.all? { |f| f.name != nil } end def any_field_has_parameter?(parameter) @fields.any? { |f| f.has_parameter?(parameter) } end def copy_fields(other) @fields.concat(other.fields) end end #---------------------------------------------------------------------------- class SanitizedChoices < SanitizedParameter def initialize(choices, hints) @choices = {} choices.each_pair do |key, val| if SanitizedParameter === val prototype = val else type, param = val prototype = SanitizedPrototype.new(type, param, hints) end if key == :default @choices.default = prototype else @choices[key] = prototype end end end def [](key) @choices[key] end end #---------------------------------------------------------------------------- class SanitizedBigEndian < SanitizedParameter def endian :big end end class SanitizedLittleEndian < SanitizedParameter def endian :little end end #---------------------------------------------------------------------------- # BinData objects are instantiated with parameters to determine their # behaviour. These parameters must be sanitized to ensure their values # are valid. When instantiating many objects with identical parameters, # such as an array of records, there is much duplicated sanitizing. # # The purpose of the sanitizing code is to eliminate the duplicated # validation. # # SanitizedParameters is a hash-like collection of parameters. Its purpose # is to recursively sanitize the parameters of an entire BinData object chain # at a single time. class SanitizedParameters < Hash # Memoized constants BIG_ENDIAN = SanitizedBigEndian.new LITTLE_ENDIAN = SanitizedLittleEndian.new class << self def sanitize(parameters, the_class) if SanitizedParameters === parameters parameters else SanitizedParameters.new(parameters, the_class, {}) end end end def initialize(parameters, the_class, hints) parameters.each_pair { |key, value| self[key.to_sym] = value } @the_class = the_class if hints[:endian] self[:endian] ||= hints[:endian] end if hints[:search_prefix] && !hints[:search_prefix].empty? self[:search_prefix] = Array(self[:search_prefix]).concat(Array(hints[:search_prefix])) end sanitize! end alias_method :has_parameter?, :key? def needs_sanitizing?(key) parameter = self[key] parameter && !parameter.is_a?(SanitizedParameter) end def warn_replacement_parameter(bad_key, suggested_key) if has_parameter?(bad_key) Kernel.warn ":#{bad_key} is not used with #{@the_class}. " \ "You probably want to change this to :#{suggested_key}" end end # def warn_renamed_parameter(old_key, new_key) # val = delete(old_key) # if val # self[new_key] = val # Kernel.warn ":#{old_key} has been renamed to :#{new_key} in #{@the_class}. " \ # "Using :#{old_key} is now deprecated and will be removed in the future" # end # end def must_be_integer(*keys) keys.each do |key| if has_parameter?(key) parameter = self[key] unless Symbol === parameter || parameter.respond_to?(:arity) || parameter.respond_to?(:to_int) raise ArgumentError, "parameter '#{key}' in #{@the_class} must " \ "evaluate to an integer, got #{parameter.class}" end end end end def hints { endian: self[:endian], search_prefix: self[:search_prefix] } end def create_sanitized_endian(endian) if endian == :big BIG_ENDIAN elsif endian == :little LITTLE_ENDIAN elsif endian == :big_and_little raise ArgumentError, "endian: :big or endian: :little is required" else raise ArgumentError, "unknown value for endian '#{endian}'" end end def create_sanitized_params(params, the_class) SanitizedParameters.new(params, the_class, hints) end def create_sanitized_choices(choices) SanitizedChoices.new(choices, hints) end def create_sanitized_fields SanitizedFields.new(hints) end def create_sanitized_object_prototype(obj_type, obj_params) SanitizedPrototype.new(obj_type, obj_params, hints) end #--------------- private def sanitize! ensure_no_nil_values merge_default_parameters! @the_class.arg_processor.sanitize_parameters!(@the_class, self) ensure_mandatory_parameters_exist ensure_mutual_exclusion_of_parameters end def ensure_no_nil_values each do |key, value| if value.nil? raise ArgumentError, "parameter '#{key}' has nil value in #{@the_class}" end end end def merge_default_parameters! @the_class.default_parameters.each do |key, value| self[key] ||= value end end def ensure_mandatory_parameters_exist @the_class.mandatory_parameters.each do |key| unless has_parameter?(key) raise ArgumentError, "parameter '#{key}' must be specified in #{@the_class}" end end end def ensure_mutual_exclusion_of_parameters return if length < 2 @the_class.mutually_exclusive_parameters.each do |key1, key2| if has_parameter?(key1) && has_parameter?(key2) raise ArgumentError, "params '#{key1}' and '#{key2}' " \ "are mutually exclusive in #{@the_class}" end end end end #---------------------------------------------------------------------------- end bindata-2.3.5/lib/bindata/dsl.rb0000644000004100000410000003312713042501567016443 0ustar www-datawww-datamodule BinData # Extracts args for Records and Buffers. # # Foo.new(bar: "baz) is ambiguous as to whether :bar is a value or parameter. # # BaseArgExtractor always assumes :bar is parameter. This extractor correctly # identifies it as value or parameter. module MultiFieldArgSeparator def separate_args(obj_class, obj_args) value, parameters, parent = super(obj_class, obj_args) if parameters_is_value?(obj_class, value, parameters) value = parameters parameters = {} end [value, parameters, parent] end def parameters_is_value?(obj_class, value, parameters) if value.nil? && !parameters.empty? field_names_in_parameters?(obj_class, parameters) else false end end def field_names_in_parameters?(obj_class, parameters) field_names = obj_class.fields.field_names param_keys = parameters.keys !(field_names & param_keys).empty? end end # BinData classes that are part of the DSL must be extended by this. module DSLMixin def dsl_parser(parser_type = nil) @dsl_parser ||= begin parser_type ||= superclass.dsl_parser.parser_type DSLParser.new(self, parser_type) end end def method_missing(symbol, *args, &block) #:nodoc: dsl_parser.__send__(symbol, *args, &block) end # Assert object is not an array or string. def to_ary; nil; end def to_str; nil; end # A DSLParser parses and accumulates field definitions of the form # # type name, params # # where: # * +type+ is the under_scored name of a registered type # * +name+ is the (possible optional) name of the field # * +params+ is a hash containing any parameters # class DSLParser def initialize(the_class, parser_type) raise "unknown parser type #{parser_type}" unless parser_abilities[parser_type] @the_class = the_class @parser_type = parser_type @validator = DSLFieldValidator.new(the_class, self) @endian = nil end attr_reader :parser_type def endian(endian = nil) if endian set_endian(endian) elsif @endian.nil? set_endian(parent_attribute(:endian)) end @endian end def search_prefix(*args) @search_prefix ||= parent_attribute(:search_prefix, []).dup prefix = args.collect(&:to_sym).compact unless prefix.empty? if fields? dsl_raise SyntaxError, "search_prefix must be called before defining fields" end @search_prefix = prefix.concat(@search_prefix) end @search_prefix end def hide(*args) if option?(:hidden_fields) @hide ||= parent_attribute(:hide, []).dup hidden = args.collect(&:to_sym).compact @hide.concat(hidden) @hide end end def fields @fields ||= begin SanitizedFields.new(hints).tap do |san_fields| san_fields.copy_fields(parent_fields) if parent_fields end end end def dsl_params abilities = parser_abilities[@parser_type] send(abilities.at(0), abilities.at(1)) end def method_missing(*args, &block) ensure_hints parse_and_append_field(*args, &block) end #------------- private def parser_abilities @abilities ||= { struct: [:to_struct_params, :struct, [:multiple_fields, :optional_fieldnames, :hidden_fields]], array: [:to_object_params, :type, [:multiple_fields, :optional_fieldnames]], buffer: [:to_object_params, :type, [:multiple_fields, :optional_fieldnames, :hidden_fields]], choice: [:to_choice_params, :choices, [:multiple_fields, :all_or_none_fieldnames, :fieldnames_are_values]], delayed_io: [:to_object_params, :type, [:multiple_fields, :optional_fieldnames, :hidden_fields]], primitive: [:to_struct_params, :struct, [:multiple_fields, :optional_fieldnames]], skip: [:to_object_params, :until_valid, [:multiple_fields, :optional_fieldnames]], } end def option?(opt) parser_abilities[@parser_type].at(2).include?(opt) end def ensure_hints endian search_prefix end def hints { endian: endian, search_prefix: search_prefix } end def set_endian(endian) if endian if fields? dsl_raise SyntaxError, "endian must be called before defining fields" end if !valid_endian?(endian) dsl_raise ArgumentError, "unknown value for endian '#{endian}'" end if endian == :big_and_little DSLBigAndLittleEndianHandler.handle(@the_class) end @endian = endian end end def valid_endian?(endian) [:big, :little, :big_and_little].include?(endian) end def parent_fields parent_attribute(:fields) end def fields? defined?(@fields) && !@fields.empty? end def parse_and_append_field(*args, &block) parser = DSLFieldParser.new(hints, *args, &block) begin @validator.validate_field(parser.name) append_field(parser.type, parser.name, parser.params) rescue Exception => err dsl_raise err.class, err.message end end def append_field(type, name, params) fields.add_field(type, name, params) rescue BinData::UnRegisteredTypeError => err raise TypeError, "unknown type '#{err.message}'" end def parent_attribute(attr, default = nil) parent = @the_class.superclass parser = parent.respond_to?(:dsl_parser) ? parent.dsl_parser : nil if parser && parser.respond_to?(attr) parser.send(attr) else default end end def dsl_raise(exception, msg) backtrace = caller backtrace.shift while %r{bindata/dsl.rb} =~ backtrace.first raise exception, "#{msg} in #{@the_class}", backtrace end def to_object_params(key) case fields.length when 0 {} when 1 {key => fields[0].prototype} else {key=> [:struct, to_struct_params]} end end def to_choice_params(key) if fields.empty? {} elsif fields.all_field_names_blank? {key => fields.collect(&:prototype)} else choices = {} fields.each { |f| choices[f.name] = f.prototype } {key => choices} end end def to_struct_params(*unused) result = {fields: fields} if !endian.nil? result[:endian] = endian end if !search_prefix.empty? result[:search_prefix] = search_prefix end if option?(:hidden_fields) && !hide.empty? result[:hide] = hide end result end end # Handles the :big_and_little endian option. # This option creates two subclasses, each handling # :big or :little endian. class DSLBigAndLittleEndianHandler class << self def handle(bnl_class) make_class_abstract(bnl_class) create_subclasses_with_endian(bnl_class) override_new_in_class(bnl_class) delegate_field_creation(bnl_class) fixup_subclass_hierarchy(bnl_class) end def make_class_abstract(bnl_class) bnl_class.send(:unregister_self) end def create_subclasses_with_endian(bnl_class) instance_eval "class ::#{bnl_class}Be < ::#{bnl_class}; endian :big; end" instance_eval "class ::#{bnl_class}Le < ::#{bnl_class}; endian :little; end" end def override_new_in_class(bnl_class) endian_classes = { big: class_with_endian(bnl_class, :big), little: class_with_endian(bnl_class, :little), } bnl_class.define_singleton_method(:new) do |*args| if self == bnl_class _, options, _ = arg_processor.separate_args(self, args) delegate = endian_classes[options[:endian]] return delegate.new(*args) if delegate end super(*args) end end def delegate_field_creation(bnl_class) endian_classes = { big: class_with_endian(bnl_class, :big), little: class_with_endian(bnl_class, :little), } parser = bnl_class.dsl_parser parser.define_singleton_method(:parse_and_append_field) do |*args, &block| endian_classes[:big].send(*args, &block) endian_classes[:little].send(*args, &block) end end def fixup_subclass_hierarchy(bnl_class) parent = bnl_class.superclass if obj_attribute(parent, :endian) == :big_and_little be_subclass = class_with_endian(bnl_class, :big) be_parent = class_with_endian(parent, :big) be_fields = obj_attribute(be_parent, :fields) le_subclass = class_with_endian(bnl_class, :little) le_parent = class_with_endian(parent, :little) le_fields = obj_attribute(le_parent, :fields) be_subclass.dsl_parser.define_singleton_method(:parent_fields) do be_fields end le_subclass.dsl_parser.define_singleton_method(:parent_fields) do le_fields end end end def class_with_endian(class_name, endian) hints = { endian: endian, search_prefix: class_name.dsl_parser.search_prefix, } RegisteredClasses.lookup(class_name, hints) end def obj_attribute(obj, attr) obj.dsl_parser.send(attr) end end end # Extracts the details from a field declaration. class DSLFieldParser def initialize(hints, symbol, *args, &block) @hints = hints @type = symbol @name = name_from_field_declaration(args) @params = params_from_field_declaration(args, &block) end attr_reader :type, :name, :params def name_from_field_declaration(args) name, _ = args if name == "" || name.is_a?(Hash) nil else name end end def params_from_field_declaration(args, &block) params = params_from_args(args) if block_given? params.merge(params_from_block(&block)) else params end end def params_from_args(args) name, params = args params = name if name.is_a?(Hash) params || {} end def params_from_block(&block) bindata_classes = { array: BinData::Array, buffer: BinData::Buffer, choice: BinData::Choice, delayed_io: BinData::DelayedIO, skip: BinData::Skip, struct: BinData::Struct, } if bindata_classes.include?(@type) parser = DSLParser.new(bindata_classes[@type], @type) parser.endian(@hints[:endian]) parser.search_prefix(*@hints[:search_prefix]) parser.instance_eval(&block) parser.dsl_params else {} end end end # Validates a field defined in a DSLMixin. class DSLFieldValidator def initialize(the_class, parser) @the_class = the_class @dsl_parser = parser end def validate_field(name) if must_not_have_a_name_failed?(name) raise SyntaxError, "field must not have a name" end if all_or_none_names_failed?(name) raise SyntaxError, "fields must either all have names, or none must have names" end if must_have_a_name_failed?(name) raise SyntaxError, "field must have a name" end ensure_valid_name(name) end def ensure_valid_name(name) if name && !option?(:fieldnames_are_values) if malformed_name?(name) raise NameError.new("", name), "field '#{name}' is an illegal fieldname" end if duplicate_name?(name) raise SyntaxError, "duplicate field '#{name}'" end if name_shadows_method?(name) raise NameError.new("", name), "field '#{name}' shadows an existing method" end if name_is_reserved?(name) raise NameError.new("", name), "field '#{name}' is a reserved name" end end end def must_not_have_a_name_failed?(name) option?(:no_fieldnames) && !name.nil? end def must_have_a_name_failed?(name) option?(:mandatory_fieldnames) && name.nil? end def all_or_none_names_failed?(name) if option?(:all_or_none_fieldnames) && !fields.empty? all_names_blank = fields.all_field_names_blank? no_names_blank = fields.no_field_names_blank? (!name.nil? && all_names_blank) || (name.nil? && no_names_blank) else false end end def malformed_name?(name) /^[a-z_]\w*$/ !~ name.to_s end def duplicate_name?(name) fields.field_name?(name) end def name_shadows_method?(name) @the_class.method_defined?(name) end def name_is_reserved?(name) BinData::Struct::RESERVED.include?(name.to_sym) end def fields @dsl_parser.fields end def option?(opt) @dsl_parser.send(:option?, opt) end end end end bindata-2.3.5/lib/bindata/base_primitive.rb0000644000004100000410000001542013042501567020657 0ustar www-datawww-datarequire 'bindata/base' module BinData # A BinData::BasePrimitive object is a container for a value that has a # particular binary representation. A value corresponds to a primitive type # such as as integer, float or string. Only one value can be contained by # this object. This value can be read from or written to an IO stream. # # require 'bindata' # # obj = BinData::Uint8.new(initial_value: 42) # obj #=> 42 # obj.assign(5) # obj #=> 5 # obj.clear # obj #=> 42 # # obj = BinData::Uint8.new(value: 42) # obj #=> 42 # obj.assign(5) # obj #=> 42 # # obj = BinData::Uint8.new(assert: 3) # obj.read("\005") #=> BinData::ValidityError: value is '5' but expected '3' # # obj = BinData::Uint8.new(assert: -> { value < 5 }) # obj.read("\007") #=> BinData::ValidityError: value not as expected # # == Parameters # # Parameters may be provided at initialisation to control the behaviour of # an object. These params include those for BinData::Base as well as: # # [:initial_value] This is the initial value to use before one is # either #read or explicitly set with #value=. # [:value] The object will always have this value. # Calls to #value= are ignored when # using this param. While reading, #value # will return the value of the data read from the # IO, not the result of the :value param. # [:assert] Raise an error unless the value read or assigned # meets this criteria. The variable +value+ is # made available to any lambda assigned to this # parameter. A boolean return indicates success # or failure. Any other return is compared to # the value just read in. # [:asserted_value] Equivalent to :assert and :value. # class BasePrimitive < BinData::Base unregister_self optional_parameters :initial_value, :value, :assert, :asserted_value mutually_exclusive_parameters :initial_value, :value mutually_exclusive_parameters :asserted_value, :value, :assert def initialize_shared_instance extend InitialValuePlugin if has_parameter?(:initial_value) extend ValuePlugin if has_parameter?(:value) extend AssertPlugin if has_parameter?(:assert) extend AssertedValuePlugin if has_parameter?(:asserted_value) super end def initialize_instance @value = nil end def clear? #:nodoc: @value.nil? end def assign(val) raise ArgumentError, "can't set a nil value for #{debug_name}" if val.nil? raw_val = val.respond_to?(:snapshot) ? val.snapshot : val @value = begin raw_val.dup rescue TypeError # can't dup Fixnums raw_val end end def snapshot _value end def value snapshot end def value=(val) assign(val) end def respond_to?(symbol, include_private = false) #:nodoc: child = snapshot child.respond_to?(symbol, include_private) || super end def method_missing(symbol, *args, &block) #:nodoc: child = snapshot if child.respond_to?(symbol) self.class.class_eval "def #{symbol}(*args, &block);" \ " snapshot.#{symbol}(*args, &block);" \ "end" child.__send__(symbol, *args, &block) else super end end def <=>(other) snapshot <=> other end def eql?(other) # double dispatch other.eql?(snapshot) end def hash snapshot.hash end def do_read(io) #:nodoc: @value = read_and_return_value(io) end def do_write(io) #:nodoc: io.writebytes(value_to_binary_string(_value)) end def do_num_bytes #:nodoc: value_to_binary_string(_value).length end #--------------- private # The unmodified value of this data object. Note that #snapshot calls this # method. This indirection is so that #snapshot can be overridden in # subclasses to modify the presentation value. def _value @value != nil ? @value : sensible_default end # Logic for the :value parameter module ValuePlugin def assign(val) # Ignored end def _value reading? ? @value : eval_parameter(:value) end end # Logic for the :initial_value parameter module InitialValuePlugin def _value @value != nil ? @value : eval_parameter(:initial_value) end end # Logic for the :assert parameter module AssertPlugin def assign(val) super(val) assert! end def do_read(io) #:nodoc: super(io) assert! end def assert! current_value = snapshot expected = eval_parameter(:assert, value: current_value) msg = if !expected "value '#{current_value}' not as expected" elsif expected != true && current_value != expected "value is '#{current_value}' but expected '#{expected}'" else nil end raise ValidityError, "#{msg} for #{debug_name}" if msg end end # Logic for the :asserted_value parameter module AssertedValuePlugin def assign(val) assert_value(val) super(val) end def _value reading? ? @value : eval_parameter(:asserted_value) end def do_read(io) #:nodoc: super(io) assert! end def assert! assert_value(snapshot) end def assert_value(current_value) expected = eval_parameter(:asserted_value, value: current_value) if current_value != expected raise ValidityError, "value is '#{current_value}' but " \ "expected '#{expected}' for #{debug_name}" end end end ########################################################################### # To be implemented by subclasses # Return the string representation that +val+ will take when written. def value_to_binary_string(val) raise NotImplementedError end # Read a number of bytes from +io+ and return the value they represent. def read_and_return_value(io) raise NotImplementedError end # Return a sensible default for this data. def sensible_default raise NotImplementedError end # To be implemented by subclasses ########################################################################### end end bindata-2.3.5/lib/bindata/io.rb0000644000004100000410000002771413042501567016275 0ustar www-datawww-datarequire 'stringio' module BinData # A wrapper around an IO object. The wrapper provides a consistent # interface for BinData objects to use when accessing the IO. module IO # Common operations for both Read and Write. module Common def initialize(io) if self.class === io raise ArgumentError, "io must not be a #{self.class}" end # wrap strings in a StringIO if io.respond_to?(:to_str) io = BinData::IO.create_string_io(io.to_str) end @raw_io = io @buffer_end_points = nil extend seekable? ? SeekableStream : UnSeekableStream stream_init end #------------- private def seekable? @raw_io.pos rescue NoMethodError, Errno::ESPIPE, Errno::EPIPE nil end def seek(n) seek_raw(buffer_limited_n(n)) end def buffer_limited_n(n) if @buffer_end_points if n.nil? || n > 0 max = @buffer_end_points[1] - offset n = max if n.nil? || n > max else min = @buffer_end_points[0] - offset n = min if n < min end end n end def with_buffer_common(n) prev = @buffer_end_points if prev avail = prev[1] - offset n = avail if n > avail end @buffer_end_points = [offset, offset + n] begin yield(*@buffer_end_points) ensure @buffer_end_points = prev end end # Use #seek and #pos on seekable streams module SeekableStream # The number of bytes remaining in the input stream. def num_bytes_remaining start_mark = @raw_io.pos @raw_io.seek(0, ::IO::SEEK_END) end_mark = @raw_io.pos if @buffer_end_points if @buffer_end_points[1] < end_mark end_mark = @buffer_end_points[1] end end bytes_remaining = end_mark - start_mark @raw_io.seek(start_mark, ::IO::SEEK_SET) bytes_remaining end # All io calls in +block+ are rolled back after this # method completes. def with_readahead mark = @raw_io.pos begin yield ensure @raw_io.seek(mark, ::IO::SEEK_SET) end end #----------- private def stream_init @initial_pos = @raw_io.pos end def offset_raw @raw_io.pos - @initial_pos end def seek_raw(n) @raw_io.seek(n, ::IO::SEEK_CUR) end def read_raw(n) @raw_io.read(n) end def write_raw(data) @raw_io.write(data) end end # Manually keep track of offset for unseekable streams. module UnSeekableStream def offset_raw @offset end # The number of bytes remaining in the input stream. def num_bytes_remaining raise IOError, "stream is unseekable" end # All io calls in +block+ are rolled back after this # method completes. def with_readahead mark = @offset @read_data = "" @in_readahead = true class << self alias_method :read_raw_without_readahead, :read_raw alias_method :read_raw, :read_raw_with_readahead end begin yield ensure @offset = mark @in_readahead = false end end #----------- private def stream_init @offset = 0 end def read_raw(n) data = @raw_io.read(n) @offset += data.size if data data end def read_raw_with_readahead(n) data = "" unless @read_data.empty? || @in_readahead bytes_to_consume = [n, @read_data.length].min data << @read_data.slice!(0, bytes_to_consume) n -= bytes_to_consume if @read_data.empty? class << self alias_method :read_raw, :read_raw_without_readahead end end end raw_data = @raw_io.read(n) data << raw_data if raw_data if @in_readahead @read_data << data end @offset += data.size data end def write_raw(data) @offset += data.size @raw_io.write(data) end def seek_raw(n) raise IOError, "stream is unseekable" if n < 0 # NOTE: how do we seek on a writable stream? # skip over data in 8k blocks while n > 0 bytes_to_read = [n, 8192].min read_raw(bytes_to_read) n -= bytes_to_read end end end end # Creates a StringIO around +str+. def self.create_string_io(str = "") StringIO.new(str.dup.force_encoding(Encoding::BINARY)) end # Create a new IO Read wrapper around +io+. +io+ must provide #read, # #pos if reading the current stream position and #seek if setting the # current stream position. If +io+ is a string it will be automatically # wrapped in an StringIO object. # # The IO can handle bitstreams in either big or little endian format. # # M byte1 L M byte2 L # S 76543210 S S fedcba98 S # B B B B # # In big endian format: # readbits(6), readbits(5) #=> [765432, 10fed] # # In little endian format: # readbits(6), readbits(5) #=> [543210, a9876] # class Read include Common def initialize(io) super(io) # bits when reading @rnbits = 0 @rval = 0 @rendian = nil end # Sets a buffer of +n+ bytes on the io stream. Any reading or seeking # calls inside the +block+ will be contained within this buffer. def with_buffer(n) with_buffer_common(n) do yield read end end # Returns the current offset of the io stream. Offset will be rounded # up when reading bitfields. def offset offset_raw end # Seek +n+ bytes from the current position in the io stream. def seekbytes(n) reset_read_bits seek(n) end # Reads exactly +n+ bytes from +io+. # # If the data read is nil an EOFError is raised. # # If the data read is too short an IOError is raised. def readbytes(n) reset_read_bits str = read(n) raise EOFError, "End of file reached" if str.nil? raise IOError, "data truncated" if str.size < n str end # Reads all remaining bytes from the stream. def read_all_bytes reset_read_bits read end # Reads exactly +nbits+ bits from the stream. +endian+ specifies whether # the bits are stored in +:big+ or +:little+ endian format. def readbits(nbits, endian) if @rendian != endian # don't mix bits of differing endian reset_read_bits @rendian = endian end if endian == :big read_big_endian_bits(nbits) else read_little_endian_bits(nbits) end end # Discards any read bits so the stream becomes aligned at the # next byte boundary. def reset_read_bits @rnbits = 0 @rval = 0 end #--------------- private def read(n = nil) read_raw(buffer_limited_n(n)) end def read_big_endian_bits(nbits) while @rnbits < nbits accumulate_big_endian_bits end val = (@rval >> (@rnbits - nbits)) & mask(nbits) @rnbits -= nbits @rval &= mask(@rnbits) val end def accumulate_big_endian_bits byte = read(1) raise EOFError, "End of file reached" if byte.nil? byte = byte.unpack('C').at(0) & 0xff @rval = (@rval << 8) | byte @rnbits += 8 end def read_little_endian_bits(nbits) while @rnbits < nbits accumulate_little_endian_bits end val = @rval & mask(nbits) @rnbits -= nbits @rval >>= nbits val end def accumulate_little_endian_bits byte = read(1) raise EOFError, "End of file reached" if byte.nil? byte = byte.unpack('C').at(0) & 0xff @rval = @rval | (byte << @rnbits) @rnbits += 8 end def mask(nbits) (1 << nbits) - 1 end end # Create a new IO Write wrapper around +io+. +io+ must provide #write. # If +io+ is a string it will be automatically wrapped in an StringIO # object. # # The IO can handle bitstreams in either big or little endian format. # # See IO::Read for more information. class Write include Common def initialize(io) super(io) @wnbits = 0 @wval = 0 @wendian = nil end # Sets a buffer of +n+ bytes on the io stream. Any writes inside the # +block+ will be contained within this buffer. If less than +n+ bytes # are written inside the block, the remainder will be padded with '\0' # bytes. def with_buffer(n) with_buffer_common(n) do |_buf_start, buf_end| yield write("\0" * (buf_end - offset)) end end # Returns the current offset of the io stream. Offset will be rounded # up when writing bitfields. def offset offset_raw + (@wnbits > 0 ? 1 : 0) end # Seek +n+ bytes from the current position in the io stream. def seekbytes(n) flushbits seek(n) end # Writes the given string of bytes to the io stream. def writebytes(str) flushbits write(str) end # Writes +nbits+ bits from +val+ to the stream. +endian+ specifies whether # the bits are to be stored in +:big+ or +:little+ endian format. def writebits(val, nbits, endian) if @wendian != endian # don't mix bits of differing endian flushbits @wendian = endian end clamped_val = val & mask(nbits) if endian == :big write_big_endian_bits(clamped_val, nbits) else write_little_endian_bits(clamped_val, nbits) end end # To be called after all +writebits+ have been applied. def flushbits raise "Internal state error nbits = #{@wnbits}" if @wnbits >= 8 if @wnbits > 0 writebits(0, 8 - @wnbits, @wendian) end end alias flush flushbits #--------------- private def write(data) n = buffer_limited_n(data.size) if n < data.size data = data[0, n] end write_raw(data) end def write_big_endian_bits(val, nbits) while nbits > 0 bits_req = 8 - @wnbits if nbits >= bits_req msb_bits = (val >> (nbits - bits_req)) & mask(bits_req) nbits -= bits_req val &= mask(nbits) @wval = (@wval << bits_req) | msb_bits write(@wval.chr) @wval = 0 @wnbits = 0 else @wval = (@wval << nbits) | val @wnbits += nbits nbits = 0 end end end def write_little_endian_bits(val, nbits) while nbits > 0 bits_req = 8 - @wnbits if nbits >= bits_req lsb_bits = val & mask(bits_req) nbits -= bits_req val >>= bits_req @wval = @wval | (lsb_bits << @wnbits) write(@wval.chr) @wval = 0 @wnbits = 0 else @wval = @wval | (val << @wnbits) @wnbits += nbits nbits = 0 end end end def mask(nbits) (1 << nbits) - 1 end end end end bindata-2.3.5/lib/bindata/params.rb0000644000004100000410000000725713042501567017151 0ustar www-datawww-datarequire 'bindata/lazy' module BinData module AcceptedParametersPlugin # Mandatory parameters must be present when instantiating a data object. def mandatory_parameters(*args) accepted_parameters.mandatory(*args) end # Optional parameters may be present when instantiating a data object. def optional_parameters(*args) accepted_parameters.optional(*args) end # Default parameters can be overridden when instantiating a data object. def default_parameters(*args) accepted_parameters.default(*args) end # Mutually exclusive parameters may not all be present when # instantiating a data object. def mutually_exclusive_parameters(*args) accepted_parameters.mutually_exclusive(*args) end alias mandatory_parameter mandatory_parameters alias optional_parameter optional_parameters alias default_parameter default_parameters def accepted_parameters #:nodoc: @accepted_parameters ||= begin ancestor_params = superclass.respond_to?(:accepted_parameters) ? superclass.accepted_parameters : nil AcceptedParameters.new(ancestor_params) end end # BinData objects accept parameters when initializing. AcceptedParameters # allow a BinData class to declaratively identify accepted parameters as # mandatory, optional, default or mutually exclusive. class AcceptedParameters def initialize(ancestor_parameters = nil) if ancestor_parameters @mandatory = ancestor_parameters.mandatory.dup @optional = ancestor_parameters.optional.dup @default = ancestor_parameters.default.dup @mutually_exclusive = ancestor_parameters.mutually_exclusive.dup else @mandatory = [] @optional = [] @default = Hash.new @mutually_exclusive = [] end end def mandatory(*args) unless args.empty? @mandatory.concat(to_syms(args)) @mandatory.uniq! end @mandatory end def optional(*args) unless args.empty? @optional.concat(to_syms(args)) @optional.uniq! end @optional end def default(args = nil) if args to_syms(args.keys) # call for side effect of validating names args.each_pair do |param, value| @default[param.to_sym] = value end end @default end def mutually_exclusive(*args) arg1 = args.shift until args.empty? args.each do |arg2| @mutually_exclusive.push([arg1.to_sym, arg2.to_sym]) @mutually_exclusive.uniq! end arg1 = args.shift end @mutually_exclusive end def all (@mandatory + @optional + @default.keys).uniq end #--------------- private def to_syms(args) syms = args.collect(&:to_sym) ensure_valid_names(syms) syms end def ensure_valid_names(names) invalid_names = self.class.invalid_parameter_names names.each do |name| if invalid_names.include?(name) raise NameError.new("Rename parameter '#{name}' " \ "as it shadows an existing method.", name) end end end def self.invalid_parameter_names @invalid_names ||= begin all_names = LazyEvaluator.instance_methods(true) + Kernel.methods allowed_names = [:name, :type] invalid_names = (all_names - allowed_names).uniq Hash[*invalid_names.collect { |key| [key.to_sym, true] }.flatten] end end end end end bindata-2.3.5/lib/bindata/framework.rb0000644000004100000410000000447613042501567017663 0ustar www-datawww-datamodule BinData # Error raised when unexpected results occur when reading data from IO. class ValidityError < StandardError ; end # All methods provided by the framework are to be implemented or overridden # by subclasses of BinData::Base. module Framework # Initializes the state of the object. All instance variables that # are used by the object must be initialized here. def initialize_instance end # Initialises state that is shared by objects with the same parameters. # # This should only be used when optimising for performance. Instance # variables set here, and changes to the singleton class will be shared # between all objects that are initialized with the same parameters. # This method is called only once for a particular set of parameters. def initialize_shared_instance end # Returns true if the object has not been changed since creation. def clear? raise NotImplementedError end # Assigns the value of +val+ to this data object. Note that +val+ must # always be deep copied to ensure no aliasing problems can occur. def assign(val) raise NotImplementedError end # Returns a snapshot of this data object. def snapshot raise NotImplementedError end # Returns the debug name of +child+. This only needs to be implemented # by objects that contain child objects. def debug_name_of(child) #:nodoc: debug_name end # Returns the offset of +child+. This only needs to be implemented # by objects that contain child objects. def offset_of(child) #:nodoc: 0 end # Is this object aligned on non-byte boundaries? def bit_aligned? false end # Reads the data for this data object from +io+. def do_read(io) #:nodoc: raise NotImplementedError end # Writes the value for this data to +io+. def do_write(io) #:nodoc: raise NotImplementedError end # Returns the number of bytes it will take to write this data. def do_num_bytes #:nodoc: raise NotImplementedError end # Set visibility requirements of methods to implement public :clear?, :assign, :snapshot, :debug_name_of, :offset_of protected :initialize_instance, :initialize_shared_instance protected :do_read, :do_write, :do_num_bytes end end bindata-2.3.5/lib/bindata/warnings.rb0000644000004100000410000000235213042501567017505 0ustar www-datawww-datamodule BinData class Base # Don't override initialize. If you are defining a new kind of datatype # (list, array, choice etc) then put your initialization code in # #initialize_instance. BinData objects might be initialized as prototypes # and your initialization code may not be called. # # If you're subclassing BinData::Record, you are definitely doing the wrong # thing. Read the documentation on how to use BinData. # http://github.com/dmendel/bindata/wiki/Records alias_method :initialize_without_warning, :initialize def initialize_with_warning(*args) owner = method(:initialize).owner if owner != BinData::Base msg = "Don't override #initialize on #{owner}." if %w(BinData::Base BinData::BasePrimitive).include? self.class.superclass.name msg += "\nrename #initialize to #initialize_instance." end fail msg end initialize_without_warning(*args) end alias initialize initialize_with_warning def initialize_instance(*args) unless args.empty? fail "#{caller[0]} remove the call to super in #initialize_instance" end end end class Struct # has_key? is deprecated alias has_key? key? end end bindata-2.3.5/INSTALL0000644000004100000410000000044513042501567014212 0ustar www-datawww-dataThis package is designed to be installed with rubygems. $ gem install bindata If you are using ruby 1.8 $ gem install bindata -v '~> 1.8.0' If you are not using rubygems, you may like to install BinData with Minero Aoki's setup.rb found at: https://github.com/rubyworks/setup bindata-2.3.5/test/0000755000004100000410000000000013042501567014135 5ustar www-datawww-databindata-2.3.5/test/io_test.rb0000755000004100000410000003310213042501567016132 0ustar www-datawww-data#!/usr/bin/env ruby require File.expand_path(File.join(File.dirname(__FILE__), "test_helper")) describe BinData::IO::Read, "reading from non seekable stream" do before do @rd, @wr = IO::pipe @io = BinData::IO::Read.new(@rd) @wr.write "a" * 2000 @wr.write "b" * 2000 @wr.close end after do @rd.close end it "has correct offset" do @io.readbytes(10) @io.offset.must_equal 10 end it "seeks correctly" do @io.seekbytes(1999) @io.readbytes(5).must_equal "abbbb" end it "#num_bytes_remaining raises IOError" do lambda { @io.num_bytes_remaining }.must_raise IOError end end describe BinData::IO::Read, "when reading" do let(:stream) { StringIO.new "abcdefghij" } let(:io) { BinData::IO::Read.new(stream) } it "raises error when io is BinData::IO::Read" do lambda { BinData::IO::Read.new(BinData::IO::Read.new("")) }.must_raise ArgumentError end it "returns correct offset" do stream.seek(3, IO::SEEK_CUR) io.offset.must_equal 0 io.readbytes(4).must_equal "defg" io.offset.must_equal 4 end it "seeks correctly" do io.seekbytes(2) io.readbytes(4).must_equal "cdef" end it "reads all bytes" do io.read_all_bytes.must_equal "abcdefghij" end it "returns number of bytes remaining" do stream_length = io.num_bytes_remaining io.readbytes(4) io.num_bytes_remaining.must_equal stream_length - 4 end it "raises error when reading at eof" do io.seekbytes(10) lambda { io.readbytes(3) }.must_raise EOFError end it "raises error on short reads" do lambda { io.readbytes(20) }.must_raise IOError end end describe BinData::IO::Read, "#with_buffer" do let(:stream) { StringIO.new "abcdefghijklmnopqrst" } let(:io) { BinData::IO::Read.new(stream) } it "consumes entire buffer on short reads" do io.with_buffer(10) do io.readbytes(4).must_equal "abcd" end io.offset.must_equal(10) end it "consumes entire buffer on read_all_bytes" do io.with_buffer(10) do io.read_all_bytes.must_equal "abcdefghij" end io.offset.must_equal(10) end it "restricts large reads" do io.with_buffer(10) do lambda { io.readbytes(15) }.must_raise IOError end end it "is nestable" do io.with_buffer(10) do io.readbytes(2).must_equal "ab" io.with_buffer(5) do io.read_all_bytes.must_equal "cdefg" end io.offset.must_equal(2 + 5) end io.offset.must_equal(10) end it "restricts large nested buffers" do io.with_buffer(10) do io.readbytes(2).must_equal "ab" io.with_buffer(20) do io.read_all_bytes.must_equal "cdefghij" io.offset.must_equal(10) end end io.offset.must_equal(10) end it "restricts large seeks" do io.with_buffer(10) do io.seekbytes(15) end io.offset.must_equal(10) end it "restricts large -ve seeks" do io.readbytes(2) io.with_buffer(10) do io.seekbytes(-1) io.offset.must_equal(2) end end it "greater than stream size consumes all bytes" do io.with_buffer(30) do io.readbytes(4).must_equal "abcd" end io.offset.must_equal(20) end it "restricts #num_bytes_remaining" do io.with_buffer(10) do io.readbytes(2) io.num_bytes_remaining.must_equal 8 end end it "greater than stream size doesn't restrict #num_bytes_remaining" do io.with_buffer(30) do io.readbytes(2) io.num_bytes_remaining.must_equal 18 end end end module IOReadWithReadahead def test_rolls_back_short_reads io.readbytes(2).must_equal "ab" io.with_readahead do io.readbytes(4).must_equal "cdef" end io.offset.must_equal 2 end def test_rolls_back_read_all_bytes io.readbytes(3).must_equal "abc" io.with_readahead do io.read_all_bytes.must_equal "defghijklmnopqrst" end io.offset.must_equal 3 end def test_inside_buffer_rolls_back_reads io.with_buffer(10) do io.with_readahead do io.readbytes(4).must_equal "abcd" end io.offset.must_equal 0 end io.offset.must_equal 10 end def test_outside_buffer_rolls_back_reads io.with_readahead do io.with_buffer(10) do io.readbytes(4).must_equal "abcd" end io.offset.must_equal 10 end io.offset.must_equal 0 end end describe BinData::IO::Read, "#with_readahead" do let(:stream) { StringIO.new "abcdefghijklmnopqrst" } let(:io) { BinData::IO::Read.new(stream) } include IOReadWithReadahead end describe BinData::IO::Read, "unseekable stream #with_readahead" do let(:stream) { io = StringIO.new "abcdefghijklmnopqrst" def io.pos raise Errno::EPIPE end io } let(:io) { BinData::IO::Read.new(stream) } include IOReadWithReadahead end describe BinData::IO::Write, "writing to non seekable stream" do before do @rd, @wr = IO::pipe @io = BinData::IO::Write.new(@wr) end after do @rd.close @wr.close end it "writes data" do @io.writebytes("1234567890") @rd.read(10).must_equal "1234567890" end it "has correct offset" do @io.writebytes("1234567890") @io.offset.must_equal 10 end it "does not seek backwards" do @io.writebytes("1234567890") lambda { @io.seekbytes(-5) }.must_raise IOError end it "does not seek forwards" do lambda { @io.seekbytes(5) }.must_raise IOError end it "#num_bytes_remaining raises IOError" do lambda { @io.num_bytes_remaining }.must_raise IOError end end describe BinData::IO::Write, "when writing" do let(:stream) { StringIO.new } let(:io) { BinData::IO::Write.new(stream) } it "raises error when io is BinData::IO" do lambda { BinData::IO::Write.new(BinData::IO::Write.new("")) }.must_raise ArgumentError end it "writes correctly" do io.writebytes("abcd") stream.value.must_equal "abcd" end it "has #offset" do io.offset.must_equal 0 io.writebytes("abcd") io.offset.must_equal 4 io.writebytes("ABCD") io.offset.must_equal 8 end it "rounds up #offset when writing bits" do io.writebits(123, 9, :little) io.offset.must_equal 2 end it "flushes" do io.writebytes("abcd") io.flush stream.value.must_equal "abcd" end end describe BinData::IO::Write, "#with_buffer" do let(:stream) { StringIO.new } let(:io) { BinData::IO::Write.new(stream) } it "pads entire buffer on short reads" do io.with_buffer(10) do io.writebytes "abcde" end stream.value.must_equal "abcde\0\0\0\0\0" end it "discards excess on large writes" do io.with_buffer(5) do io.writebytes "abcdefghij" end stream.value.must_equal "abcde" end it "is nestable" do io.with_buffer(10) do io.with_buffer(5) do io.writebytes "abc" end io.writebytes "de" end stream.value.must_equal "abc\0\0de\0\0\0" end it "restricts large seeks" do io.with_buffer(10) do io.seekbytes(15) end io.offset.must_equal(10) end it "restricts large -ve seeks" do io.writebytes("12") io.with_buffer(10) do io.seekbytes(-1) io.offset.must_equal(2) end end end describe BinData::IO::Read, "reading bits in big endian" do let(:b1) { 0b1111_1010 } let(:b2) { 0b1100_1110 } let(:b3) { 0b0110_1010 } let(:io) { BinData::IO::Read.new([b1, b2, b3].pack("CCC")) } it "reads a bitfield less than 1 byte" do io.readbits(3, :big).must_equal 0b111 end it "reads a bitfield more than 1 byte" do io.readbits(10, :big).must_equal 0b1111_1010_11 end it "reads a bitfield more than 2 bytes" do io.readbits(17, :big).must_equal 0b1111_1010_1100_1110_0 end it "reads two bitfields totalling less than 1 byte" do io.readbits(5, :big).must_equal 0b1111_1 io.readbits(2, :big).must_equal 0b01 end it "reads two bitfields totalling more than 1 byte" do io.readbits(6, :big).must_equal 0b1111_10 io.readbits(8, :big).must_equal 0b10_1100_11 end it "reads two bitfields totalling more than 2 bytes" do io.readbits(7, :big).must_equal 0b1111_101 io.readbits(12, :big).must_equal 0b0_1100_1110_011 end it "ignores unused bits when reading bytes" do io.readbits(3, :big).must_equal 0b111 io.readbytes(1).must_equal [b2].pack("C") io.readbits(2, :big).must_equal 0b01 end it "resets read bits to realign stream to next byte" do io.readbits(3, :big).must_equal 0b111 io.reset_read_bits io.readbits(3, :big).must_equal 0b110 end end describe BinData::IO::Read, "reading bits in little endian" do let(:b1) { 0b1111_1010 } let(:b2) { 0b1100_1110 } let(:b3) { 0b0110_1010 } let(:io) { BinData::IO::Read.new([b1, b2, b3].pack("CCC")) } it "reads a bitfield less than 1 byte" do io.readbits(3, :little).must_equal 0b010 end it "reads a bitfield more than 1 byte" do io.readbits(10, :little).must_equal 0b10_1111_1010 end it "reads a bitfield more than 2 bytes" do io.readbits(17, :little).must_equal 0b0_1100_1110_1111_1010 end it "reads two bitfields totalling less than 1 byte" do io.readbits(5, :little).must_equal 0b1_1010 io.readbits(2, :little).must_equal 0b11 end it "reads two bitfields totalling more than 1 byte" do io.readbits(6, :little).must_equal 0b11_1010 io.readbits(8, :little).must_equal 0b00_1110_11 end it "reads two bitfields totalling more than 2 bytes" do io.readbits(7, :little).must_equal 0b111_1010 io.readbits(12, :little).must_equal 0b010_1100_1110_1 end it "ignores unused bits when reading bytes" do io.readbits(3, :little).must_equal 0b010 io.readbytes(1).must_equal [b2].pack("C") io.readbits(2, :little).must_equal 0b10 end it "resets read bits to realign stream to next byte" do io.readbits(3, :little).must_equal 0b010 io.reset_read_bits io.readbits(3, :little).must_equal 0b110 end end class BitWriterHelper def initialize @stringio = BinData::IO.create_string_io @io = BinData::IO::Write.new(@stringio) end def writebits(val, nbits, endian) @io.writebits(val, nbits, endian) end def writebytes(val) @io.writebytes(val) end def value @io.flushbits @stringio.rewind @stringio.read end end describe BinData::IO::Write, "writing bits in big endian" do let(:io) { BitWriterHelper.new } it "writes a bitfield less than 1 byte" do io.writebits(0b010, 3, :big) io.value.must_equal [0b0100_0000].pack("C") end it "writes a bitfield more than 1 byte" do io.writebits(0b10_1001_1101, 10, :big) io.value.must_equal [0b1010_0111, 0b0100_0000].pack("CC") end it "writes a bitfield more than 2 bytes" do io.writebits(0b101_1000_0010_1001_1101, 19, :big) io.value.must_equal [0b1011_0000, 0b0101_0011, 0b1010_0000].pack("CCC") end it "writes two bitfields totalling less than 1 byte" do io.writebits(0b1_1001, 5, :big) io.writebits(0b00, 2, :big) io.value.must_equal [0b1100_1000].pack("C") end it "writes two bitfields totalling more than 1 byte" do io.writebits(0b01_0101, 6, :big) io.writebits(0b001_1001, 7, :big) io.value.must_equal [0b0101_0100, 0b1100_1000].pack("CC") end it "writes two bitfields totalling more than 2 bytes" do io.writebits(0b01_0111, 6, :big) io.writebits(0b1_0010_1001_1001, 13, :big) io.value.must_equal [0b0101_1110, 0b0101_0011, 0b0010_0000].pack("CCC") end it "pads unused bits when writing bytes" do io.writebits(0b101, 3, :big) io.writebytes([0b1011_1111].pack("C")) io.writebits(0b01, 2, :big) io.value.must_equal [0b1010_0000, 0b1011_1111, 0b0100_0000].pack("CCC") end end describe BinData::IO::Write, "writing bits in little endian" do let(:io) { BitWriterHelper.new } it "writes a bitfield less than 1 byte" do io.writebits(0b010, 3, :little) io.value.must_equal [0b0000_0010].pack("C") end it "writes a bitfield more than 1 byte" do io.writebits(0b10_1001_1101, 10, :little) io.value.must_equal [0b1001_1101, 0b0000_0010].pack("CC") end it "writes a bitfield more than 2 bytes" do io.writebits(0b101_1000_0010_1001_1101, 19, :little) io.value.must_equal [0b1001_1101, 0b1000_0010, 0b0000_0101].pack("CCC") end it "writes two bitfields totalling less than 1 byte" do io.writebits(0b1_1001, 5, :little) io.writebits(0b00, 2, :little) io.value.must_equal [0b0001_1001].pack("C") end it "writes two bitfields totalling more than 1 byte" do io.writebits(0b01_0101, 6, :little) io.writebits(0b001_1001, 7, :little) io.value.must_equal [0b0101_0101, 0b0000_0110].pack("CC") end it "writes two bitfields totalling more than 2 bytes" do io.writebits(0b01_0111, 6, :little) io.writebits(0b1_0010_1001_1001, 13, :little) io.value.must_equal [0b0101_0111, 0b1010_0110, 0b0000_0100].pack("CCC") end it "pads unused bits when writing bytes" do io.writebits(0b101, 3, :little) io.writebytes([0b1011_1111].pack("C")) io.writebits(0b01, 2, :little) io.value.must_equal [0b0000_0101, 0b1011_1111, 0b0000_0001].pack("CCC") end end describe BinData::IO::Read, "with changing endian" do it "does not mix different endianess when reading" do b1 = 0b0110_1010 b2 = 0b1110_0010 str = [b1, b2].pack("CC") io = BinData::IO::Read.new(str) io.readbits(3, :big).must_equal 0b011 io.readbits(4, :little).must_equal 0b0010 end end describe BinData::IO::Write, "with changing endian" do it "does not mix different endianess when writing" do io = BitWriterHelper.new io.writebits(0b110, 3, :big) io.writebits(0b010, 3, :little) io.value.must_equal [0b1100_0000, 0b0000_0010].pack("CC") end end bindata-2.3.5/test/float_test.rb0000755000004100000410000000241413042501567016632 0ustar www-datawww-data#!/usr/bin/env ruby require File.expand_path(File.join(File.dirname(__FILE__), "test_helper")) module FloatTest def test_num_bytes @obj.num_bytes.must_equal 4 end def test_writing_then_reading @obj.value_read_from_written.must_be_close_to Math::PI, 0.000001 end end module DoubleTest def test_num_bytes @obj.num_bytes.must_equal 8 end def test_writing_then_reading @obj.value_read_from_written.must_be_close_to Math::PI, 0.0000000000000001 end end describe "A FloatLe" do include FloatTest before do @obj = BinData::FloatLe.new(Math::PI) end it "#to_binary_s" do @obj.to_binary_s.must_equal_binary [Math::PI].pack('e') end end describe "A FloatBe" do include FloatTest before do @obj = BinData::FloatBe.new(Math::PI) end it "#to_binary_s" do @obj.to_binary_s.must_equal_binary [Math::PI].pack('g') end end describe "A DoubleLe" do include DoubleTest before do @obj = BinData::DoubleLe.new(Math::PI) end it "#to_binary_s" do @obj.to_binary_s.must_equal_binary [Math::PI].pack('E') end end describe "A DoubleBe" do include DoubleTest before do @obj = BinData::DoubleBe.new(Math::PI) end it "#to_binary_s" do @obj.to_binary_s.must_equal_binary [Math::PI].pack('G') end end bindata-2.3.5/test/choice_test.rb0000755000004100000410000001273713042501567016770 0ustar www-datawww-data#!/usr/bin/env ruby require File.expand_path(File.join(File.dirname(__FILE__), "test_helper")) class Chooser attr_accessor :choice end class BinData::Choice def set_chooser(chooser) @chooser = chooser end def choice=(s) @chooser.choice = s end end def create_choice(choices, options = {}) chooser = Chooser.new params = {choices: choices, selection: -> { chooser.choice } }.merge(options) choice = BinData::Choice.new(params) choice.set_chooser(chooser) choice end describe BinData::Choice, "when instantiating" do it "ensures mandatory parameters are supplied" do args = {} lambda { BinData::Choice.new(args) }.must_raise ArgumentError args = {selection: 1} lambda { BinData::Choice.new(args) }.must_raise ArgumentError args = {choices: []} lambda { BinData::Choice.new(args) }.must_raise ArgumentError end it "fails when a given type is unknown" do args = {choices: [:does_not_exist], selection: 0} lambda { BinData::Choice.new(args) }.must_raise BinData::UnRegisteredTypeError end it "fails when a given type is unknown" do args = {choices: {0 => :does_not_exist}, selection: 0} lambda { BinData::Choice.new(args) }.must_raise BinData::UnRegisteredTypeError end it "fails when :choices Hash has a symbol as key" do args = {choices: {a: :uint8}, selection: 0} lambda { BinData::Choice.new(args) }.must_raise ArgumentError end it "fails when :choices Hash has a nil key" do args = {choices: {nil => :uint8}, selection: 0} lambda { BinData::Choice.new(args) }.must_raise ArgumentError end end module ChoiceInitializedWithArrayOrHash def test_can_select_the_choice obj.choice = 3 obj.must_equal 30 end def test_shows_the_current_selection obj.choice = 3 obj.selection.must_equal 3 end def test_forwards_snapshot obj.choice = 3 obj.snapshot.must_equal 30 end def test_can_change_the_choice obj.choice = 3 obj.choice = 7 obj.must_equal 70 end def test_fails_if_no_choice_has_been_set lambda { obj.to_s }.must_raise IndexError end def test_wont_select_an_invalid_choice obj.choice = 99 lambda { obj.to_s }.must_raise IndexError end def test_wont_select_a_nil_choice obj.choice = 1 lambda { obj.to_s }.must_raise IndexError end def test_handles_missing_methods_correctly obj.choice = 3 obj.must_respond_to :value obj.wont_respond_to :does_not_exist lambda { obj.does_not_exist }.must_raise NoMethodError end def test_delegates_methods_to_the_selected_single_choice obj.choice = 5 obj.num_bytes.must_equal 1 end end describe BinData::Choice, "with sparse choices array" do include ChoiceInitializedWithArrayOrHash let(:obj) { choices = [nil, nil, nil, [:uint8, {value: 30}], nil, [:uint8, {value: 50}], nil, [:uint8, {value: 70}]] create_choice(choices) } end describe BinData::Choice, "with choices hash" do include ChoiceInitializedWithArrayOrHash let(:obj) { choices = {3 => [:uint8, {value: 30}], 5 => [:uint8, {value: 50}], 7 => [:uint8, {value: 70}]} create_choice(choices) } end describe BinData::Choice, "with single values" do let(:obj) { create_choice({3 => :uint8, 5 => :uint8, 7 => :uint8}) } it "assigns raw values" do obj.choice = 3 obj.assign(254) obj.must_equal 254 end it "assigns BinData values" do data = BinData::Uint8.new(11) obj.choice = 3 obj.assign(data) obj.must_equal 11 end it "clears" do obj.choice = 3 obj.assign(254) obj.clear obj.must_equal 0 end it "clears all possible choices" do obj.choice = 3 obj.assign(10) obj.choice = 5 obj.assign(11) obj.clear obj.choice = 3 obj.must_equal 0 end it "is clear on initialisation" do obj.choice = 3 assert obj.clear? end it "is not clear after assignment" do obj.choice = 3 obj.assign(254) refute obj.clear? end it "does not copy value when changing selection" do obj.choice = 3 obj.assign(254) obj.choice = 7 obj.wont_equal 254 end it "behaves as value" do obj.choice = 3 obj.assign(5) (obj + 1).must_equal 6 (1 + obj).must_equal 6 end end describe BinData::Choice, "with copy_on_change => true" do let(:obj) { choices = {3 => :uint8, 5 => :uint8, 7 => :uint8} create_choice(choices, copy_on_change: true) } it "copies value when changing selection" do obj.choice = 3 obj.assign(254) obj.choice = 7 obj.must_equal 254 end end describe BinData::Choice, "with :default" do let(:choices) { { "a" => :int8, default: :int16be } } it "selects for existing case" do obj = BinData::Choice.new(selection: "a", choices: choices) obj.num_bytes.must_equal 1 end it "selects for default case" do obj = BinData::Choice.new(selection: "other", choices: choices) obj.num_bytes.must_equal 2 end end describe BinData::Choice, "subclassed with default parameters" do class DerivedChoice < BinData::Choice endian :big default_parameter selection: 'a' uint16 'a' uint32 'b' uint64 :default end it "sets initial selection" do obj = DerivedChoice.new obj.num_bytes.must_equal 2 end it "overides default parameter" do obj = DerivedChoice.new(selection: 'b') obj.num_bytes.must_equal 4 end it "selects default selection" do obj = DerivedChoice.new(selection: 'z') obj.num_bytes.must_equal 8 end end bindata-2.3.5/test/record_test.rb0000755000004100000410000003716613042501567017017 0ustar www-datawww-data#!/usr/bin/env ruby require File.expand_path(File.join(File.dirname(__FILE__), "test_helper")) describe BinData::Record do it "is not registered" do lambda { BinData::RegisteredClasses.lookup("Record") }.must_raise BinData::UnRegisteredTypeError end end describe BinData::Record, "when defining with errors" do it "fails on non registered types" do lambda { class BadTypeRecord < BinData::Record non_registered_type :a end }.must_raise_on_line TypeError, 2, "unknown type 'non_registered_type' in BadTypeRecord" end it "gives correct error message for non registered nested types" do lambda { class BadNestedTypeRecord < BinData::Record array :a, type: :non_registered_type end }.must_raise_on_line TypeError, 2, "unknown type 'non_registered_type' in BadNestedTypeRecord" end it "gives correct error message for non registered nested types in blocks" do lambda { class BadNestedTypeInBlockRecord < BinData::Record array :a do non_registered_type end end }.must_raise_on_line TypeError, 3, "unknown type 'non_registered_type' in BinData::Array" end it "fails on nested choice when missing names" do lambda { class MissingChoiceNamesRecord < BinData::Record choice do int8 :a int8 end end }.must_raise_on_line SyntaxError, 4, "fields must either all have names, or none must have names in BinData::Choice" end it "fails on malformed names" do lambda { class MalformedNameRecord < BinData::Record int8 :a int8 "45" end }.must_raise_on_line NameError, 3, "field '45' is an illegal fieldname in MalformedNameRecord" end it "fails on duplicate names" do lambda { class DuplicateNameRecord < BinData::Record int8 :a int8 :b int8 :a end }.must_raise_on_line SyntaxError, 4, "duplicate field 'a' in DuplicateNameRecord" end it "fails on reserved names" do lambda { class ReservedNameRecord < BinData::Record int8 :a int8 :invert # from Hash.instance_methods end }.must_raise_on_line NameError, 3, "field 'invert' is a reserved name in ReservedNameRecord" end it "fails when field name shadows an existing method" do lambda { class ExistingNameRecord < BinData::Record int8 :object_id end }.must_raise_on_line NameError, 2, "field 'object_id' shadows an existing method in ExistingNameRecord" end it "fails on unknown endian" do lambda { class BadEndianRecord < BinData::Record endian 'a bad value' end }.must_raise_on_line ArgumentError, 2, "unknown value for endian 'a bad value' in BadEndianRecord" end it "fails when endian is after a field" do lambda { class BadEndianPosRecord < BinData::Record string :a endian :little end }.must_raise_on_line SyntaxError, 3, "endian must be called before defining fields in BadEndianPosRecord" end it "fails when search_prefix is after a field" do lambda { class BadSearchPrefixPosRecord < BinData::Record string :a search_prefix :pre end }.must_raise_on_line SyntaxError, 3, "search_prefix must be called before defining fields in BadSearchPrefixPosRecord" end end describe BinData::Record, "with anonymous fields" do class AnonymousRecord < BinData::Record int8 'a', initial_value: 10 int8 '' int8 nil int8 int8 value: :a end let(:obj) { AnonymousRecord.new } it "only shows non anonymous fields" do obj.field_names.must_equal [:a] end it "does not include anonymous fields in snapshot" do obj.a = 5 obj.snapshot.must_equal({a: 5}) end it "writes anonymous fields" do str = "\001\002\003\004\005" obj.read(str) obj.a.clear obj.to_binary_s.must_equal_binary "\012\002\003\004\012" end end describe BinData::Record, "with hidden fields" do class HiddenRecord < BinData::Record hide :b, :c int8 :a int8 'b', initial_value: 10 int8 :c int8 :d, value: :b end let(:obj) { HiddenRecord.new } it "only shows fields that aren't hidden" do obj.field_names.must_equal [:a, :d] end it "accesses hidden fields directly" do obj.b.must_equal 10 obj.c = 15 obj.c.must_equal 15 obj.must_respond_to :b= end it "does not include hidden fields in snapshot" do obj.b = 5 obj.snapshot.must_equal({a: 0, d: 5}) end end describe BinData::Record, "with multiple fields" do class MultiFieldRecord < BinData::Record int8 :a int8 :b end let(:obj) { MultiFieldRecord.new(a: 1, b: 2) } it "returns num_bytes" do obj.a.num_bytes.must_equal 1 obj.b.num_bytes.must_equal 1 obj.num_bytes.must_equal 2 end it "identifies accepted parameters" do BinData::Record.accepted_parameters.all.must_include :hide BinData::Record.accepted_parameters.all.must_include :endian end it "clears" do obj.a = 6 obj.clear assert obj.clear? end it "clears individual elements" do obj.a = 6 obj.b = 7 obj.a.clear assert obj.a.clear? refute obj.b.clear? end it "writes ordered" do obj.to_binary_s.must_equal_binary "\x01\x02" end it "reads ordered" do obj.read("\x03\x04") obj.a.must_equal 3 obj.b.must_equal 4 end it "returns a snapshot" do snap = obj.snapshot snap.a.must_equal 1 snap.b.must_equal 2 snap.must_equal({ a: 1, b: 2 }) end it "returns field_names" do obj.field_names.must_equal [:a, :b] end it "fails on unknown method call" do lambda { obj.does_not_exist }.must_raise NoMethodError end end describe BinData::Record, "with nested structs" do class NestedStructRecord < BinData::Record int8 :a, initial_value: 6 struct :b, the_val: :a do hide :w int8 :w, initial_value: 3 int8 :x, value: :the_val end struct :c do int8 :y, value: -> { b.w } int8 :z end end let(:obj) { NestedStructRecord.new } it "includes nested field names" do obj.field_names.must_equal [:a, :b, :c] end it "hides nested field names" do obj.b.field_names.must_equal [:x] end it "accesses nested fields" do obj.a.must_equal 6 obj.b.w.must_equal 3 obj.b.x.must_equal 6 obj.c.y.must_equal 3 end it "returns correct abs_offset" do obj.abs_offset.must_equal 0 obj.b.abs_offset.must_equal 1 obj.b.w.abs_offset.must_equal 1 obj.c.abs_offset.must_equal 3 obj.c.z.abs_offset.must_equal 4 end it "returns correct rel_offset" do obj.rel_offset.must_equal 0 obj.b.rel_offset.must_equal 1 obj.b.w.rel_offset.must_equal 0 obj.c.rel_offset.must_equal 3 obj.c.z.rel_offset.must_equal 1 end it "assigns nested fields" do obj.assign(a: 2, b: {w: 4}) obj.a.must_equal 2 obj.b.w.must_equal 4 obj.b.x.must_equal 2 obj.c.y.must_equal 4 end end describe BinData::Record, "with nested array of primitives" do class NestedPrimitiveArrayRecord < BinData::Record array :a, initial_length: 3 do uint8 value: -> { index } end end let(:obj) { NestedPrimitiveArrayRecord.new } it "uses block as :type" do obj.snapshot.must_equal({a: [0, 1, 2]}) end end describe BinData::Record, "with nested array of structs" do class NestedStructArrayRecord < BinData::Record array :a do uint8 :b uint8 :c end end let(:obj) { NestedStructArrayRecord.new } it "uses block as struct for :type" do obj.a[0].b = 2 obj.snapshot.must_equal({a: [{b: 2, c: 0}]}) end end describe BinData::Record, "with nested choice with implied keys" do class NestedChoiceWithImpliedKeysRecord < BinData::Record choice :a, selection: 1 do uint8 value: 1 uint8 value: 2 end end let(:obj) { NestedChoiceWithImpliedKeysRecord.new } specify { obj.a.must_equal 2 } end describe BinData::Record, "with nested choice with explicit keys" do class NestedChoiceWithKeysRecord < BinData::Record choice :a, selection: 5 do uint8 3, value: 1 uint8 5, value: 2 end end let(:obj) { NestedChoiceWithKeysRecord.new } specify { obj.a.must_equal 2 } end describe BinData::Record, "with nested choice with names" do class NestedChoiceWithNamesRecord < BinData::Record choice :a, selection: "b" do uint8 "b", value: 1 uint8 "c", value: 2 end end let(:obj) { NestedChoiceWithNamesRecord.new } specify { obj.a.must_equal 1 } end describe BinData::Record, "with an endian defined" do class RecordWithEndian < BinData::Record endian :little uint16 :a float :b array :c, initial_length: 2 do int8 end choice :d, selection: 1 do uint16 uint32 end struct :e do uint16 :f uint32be :g end struct :h do endian :big struct :i do uint16 :j end end end let(:obj) { RecordWithEndian.new } it "uses correct endian" do obj.a = 1 obj.b = 2.0 obj.c[0] = 3 obj.c[1] = 4 obj.d = 5 obj.e.f = 6 obj.e.g = 7 obj.h.i.j = 8 lambdaed = [1, 2.0, 3, 4, 5, 6, 7, 8].pack('veCCVvNn') obj.to_binary_s.must_equal_binary lambdaed end end describe BinData::Record, "with search_prefix" do class ASprefix < BinData::Int8; end class BSprefix < BinData::Int8; end class RecordWithSearchPrefix < BinData::Record search_prefix :a sprefix :f end class RecordWithParentSearchPrefix < BinData::Record search_prefix :a struct :s do sprefix :f end end class RecordWithNestedSearchPrefix < BinData::Record search_prefix :a struct :s do search_prefix :x sprefix :f end end class RecordWithPrioritisedNestedSearchPrefix < BinData::Record search_prefix :b struct :s do search_prefix :a sprefix :f end end it "uses search_prefix" do obj = RecordWithSearchPrefix.new obj.f.class.name.must_equal "ASprefix" end it "uses parent search_prefix" do obj = RecordWithParentSearchPrefix.new obj.s.f.class.name.must_equal "ASprefix" end it "uses nested search_prefix" do obj = RecordWithNestedSearchPrefix.new obj.s.f.class.name.must_equal "ASprefix" end it "uses prioritised nested search_prefix" do obj = RecordWithPrioritisedNestedSearchPrefix.new obj.s.f.class.name.must_equal "ASprefix" end end describe BinData::Record, "with endian :big_and_little" do class RecordWithBnLEndian < BinData::Record endian :big_and_little int16 :a, value: 1 end it "is not registered" do lambda { BinData::RegisteredClasses.lookup("RecordWithBnLEndian") }.must_raise BinData::UnRegisteredTypeError end it "creates big endian version" do obj = RecordWithBnLEndianBe.new obj.to_binary_s.must_equal_binary "\x00\x01" end it "creates little endian version" do obj = RecordWithBnLEndianLe.new obj.to_binary_s.must_equal_binary "\x01\x00" end it "requires :endian as argument" do lambda { RecordWithBnLEndian.new }.must_raise ArgumentError end it "accepts :endian as argument" do obj = RecordWithBnLEndian.new(endian: :little) obj.to_binary_s.must_equal_binary "\x01\x00" end end describe BinData::Record, "with endian :big_and_little and search_prefix" do class NsBNLIntBe < BinData::Int16be; end class NsBNLIntLe < BinData::Int16le; end class RecordWithBnLEndianAndSearchPrefix < BinData::Record endian :big_and_little search_prefix :ns bnl_int :a, value: 1 end it "creates big endian version" do obj = RecordWithBnLEndianAndSearchPrefixBe.new obj.to_binary_s.must_equal_binary "\x00\x01" end it "creates little endian version" do obj = RecordWithBnLEndianAndSearchPrefixLe.new obj.to_binary_s.must_equal_binary "\x01\x00" end end describe BinData::Record, "with endian :big_and_little when subclassed" do class ARecordWithBnLEndian < BinData::Record endian :big_and_little int16 :a, value: 1 end class BRecordWithBnLEndian < ARecordWithBnLEndian int16 :b, value: 2 end it "is not registered" do lambda { BinData::RegisteredClasses.lookup("BRecordWithBnLEndian") }.must_raise BinData::UnRegisteredTypeError end it "creates big endian version" do obj = BRecordWithBnLEndianBe.new obj.to_binary_s.must_equal_binary "\x00\x01\x00\x02" end it "creates little endian version" do obj = BRecordWithBnLEndianLe.new obj.to_binary_s.must_equal_binary "\x01\x00\x02\x00" end it "requires :endian as argument" do lambda { BRecordWithBnLEndian.new }.must_raise ArgumentError end it "accepts :endian as argument" do obj = BRecordWithBnLEndian.new(endian: :little) obj.to_binary_s.must_equal_binary "\x01\x00\x02\x00" end end describe BinData::Record, "defined recursively" do class RecursiveRecord < BinData::Record endian :big uint16 :val uint8 :has_nxt, value: -> { nxt.clear? ? 0 : 1 } recursive_record :nxt, onlyif: -> { has_nxt > 0 } end it "can be created" do RecursiveRecord.new end it "reads" do str = "\x00\x01\x01\x00\x02\x01\x00\x03\x00" obj = RecursiveRecord.read(str) obj.val.must_equal 1 obj.nxt.val.must_equal 2 obj.nxt.nxt.val.must_equal 3 end it "is assignable on demand" do obj = RecursiveRecord.new obj.val = 13 obj.nxt.val = 14 obj.nxt.nxt.val = 15 end it "writes" do obj = RecursiveRecord.new obj.val = 5 obj.nxt.val = 6 obj.nxt.nxt.val = 7 obj.to_binary_s.must_equal_binary "\x00\x05\x01\x00\x06\x01\x00\x07\x00" end end describe BinData::Record, "with custom mandatory parameters" do class MandatoryRecord < BinData::Record mandatory_parameter :arg1 uint8 :a, value: :arg1 end it "raises error if mandatory parameter is not supplied" do lambda { MandatoryRecord.new }.must_raise ArgumentError end it "uses mandatory parameter" do obj = MandatoryRecord.new(arg1: 5) obj.a.must_equal 5 end end describe BinData::Record, "with custom default parameters" do class DefaultRecord < BinData::Record default_parameter arg1: 5 uint8 :a, value: :arg1 uint8 :b end it "uses default parameter" do obj = DefaultRecord.new obj.a.must_equal 5 end it "overrides default parameter" do obj = DefaultRecord.new(arg1: 7) obj.a.must_equal 7 end it "accepts values" do obj = DefaultRecord.new(b: 2) obj.b.must_equal 2 end it "accepts values and parameters" do obj = DefaultRecord.new({b: 2}, arg1: 3) obj.a.must_equal 3 obj.b.must_equal 2 end end describe BinData::Record, "with :onlyif" do class OnlyIfRecord < BinData::Record uint8 :a, initial_value: 3 uint8 :b, initial_value: 5, onlyif: -> { a == 3 } uint8 :c, initial_value: 7, onlyif: -> { a != 3 } end let(:obj) { OnlyIfRecord.new } it "initial state" do obj.num_bytes.must_equal 2 obj.snapshot.must_equal({a: 3, b: 5}) obj.to_binary_s.must_equal_binary "\x03\x05" end it "identifies if fields are included" do obj.a?.must_equal true obj.b?.must_equal true obj.c?.must_equal false end it "reads as lambdaed" do obj.read("\x01\x02") obj.snapshot.must_equal({a: 1, c: 2}) end end describe BinData::Record, "derived classes" do class ParentRecord < BinData::Record uint8 :a end class Child1Record < ParentRecord uint8 :b end class Child2Record < Child1Record uint8 :c end it "does not affect parent" do ParentRecord.new.field_names.must_equal [:a] end it "inherits fields for first child" do Child1Record.new.field_names.must_equal [:a, :b] end it "inherits fields for second child" do Child2Record.new.field_names.must_equal [:a, :b, :c] end end bindata-2.3.5/test/alignment_test.rb0000755000004100000410000000253413042501567017506 0ustar www-datawww-data#!/usr/bin/env ruby require File.expand_path(File.join(File.dirname(__FILE__), "test_helper")) describe BinData::ResumeByteAlignment do class ResumeAlignmentRecord < BinData::Record bit4 :a resume_byte_alignment bit4 :b end let(:obj) { ResumeAlignmentRecord.new } it "resets read alignment" do obj.read "\x12\x34" obj.a.must_equal 1 obj.b.must_equal 3 end it "resets write alignment" do obj.assign(a: 2, b: 7) obj.to_binary_s.must_equal_binary "\x20\x70" end end describe BinData::BitAligned do it "does not apply to BinData::Primitives" do lambda { class BitAlignedPrimitive < BinData::Primitive bit_aligned end }.must_raise RuntimeError end class BitString < BinData::String bit_aligned end class BitAlignedRecord < BinData::Record bit4 :preamble bit_string :str, length: 2 bit4 :afterward end let(:obj) { BitAlignedRecord.new } it "#num_bytes as expected" do obj.num_bytes.must_equal 3 end it "has expected abs_offset" do obj.str.abs_offset.must_equal 0 end it "reads as expected" do obj.read("\x56\x36\x42") obj.snapshot.must_equal({preamble: 5, str: "cd", afterward: 2}) end it "writes as expected" do obj.assign(preamble: 5, str: "ab", afterward: 1) obj.to_binary_s.must_equal_binary "\x56\x16\x21" end end bindata-2.3.5/test/stringz_test.rb0000755000004100000410000000537413042501567017235 0ustar www-datawww-data#!/usr/bin/env ruby require File.expand_path(File.join(File.dirname(__FILE__), "test_helper")) describe BinData::Stringz, "when empty" do let(:obj) { BinData::Stringz.new } it "initial state" do obj.value.must_equal "" obj.num_bytes.must_equal 1 obj.to_binary_s.must_equal_binary "\0" end end describe BinData::Stringz, "with value set" do let(:obj) { BinData::Stringz.new("abcd") } it "initial state" do obj.value.must_equal "abcd" obj.num_bytes.must_equal 5 obj.to_binary_s.must_equal_binary "abcd\0" end end describe BinData::Stringz, "when reading" do let(:obj) { BinData::Stringz.new } it "stops at the first zero byte" do io = StringIO.new("abcd\0xyz\0") obj.read(io) io.pos.must_equal 5 obj.must_equal "abcd" end it "handles a zero length string" do io = StringIO.new("\0abcd") obj.read(io) io.pos.must_equal 1 obj.must_equal "" end it "fails if no zero byte is found" do lambda {obj.read("abcd") }.must_raise EOFError end end describe BinData::Stringz, "when setting the value" do let(:obj) { BinData::Stringz.new } it "includes the zero byte in num_bytes total" do obj.assign("abcd") obj.num_bytes.must_equal 5 end it "accepts empty strings" do obj.assign("") obj.must_equal "" end it "accepts strings that aren't zero terminated" do obj.assign("abcd") obj.must_equal "abcd" end it "accepts strings that are zero terminated" do obj.assign("abcd\0") obj.must_equal "abcd" end it "accepts up to the first zero byte" do obj.assign("abcd\0xyz\0") obj.must_equal "abcd" end end describe BinData::Stringz, "with max_length" do let(:obj) { BinData::Stringz.new(max_length: 5) } it "reads less than max_length" do io = StringIO.new("abc\0xyz") obj.read(io) obj.must_equal "abc" end it "reads exactly max_length" do io = StringIO.new("abcd\0xyz") obj.read(io) obj.must_equal "abcd" end it "reads no more than max_length" do io = StringIO.new("abcdefg\0xyz") obj.read(io) io.pos.must_equal 5 obj.must_equal "abcd" end it "accepts values less than max_length" do obj.assign("abc") obj.must_equal "abc" end it "accepts values exactly max_length" do obj.assign("abcd") obj.must_equal "abcd" end it "trims values greater than max_length" do obj.assign("abcde") obj.must_equal "abcd" end it "writes values greater than max_length" do obj.assign("abcde") obj.to_binary_s.must_equal_binary "abcd\0" end it "writes values less than max_length" do obj.assign("abc") obj.to_binary_s.must_equal_binary "abc\0" end it "writes values exactly max_length" do obj.assign("abcd") obj.to_binary_s.must_equal_binary "abcd\0" end end bindata-2.3.5/test/base_primitive_test.rb0000755000004100000410000001536713042501567020542 0ustar www-datawww-data#!/usr/bin/env ruby require File.expand_path(File.join(File.dirname(__FILE__), "test_helper")) class ExampleSingle < BinData::BasePrimitive def self.io_with_value(val) StringIO.new([val].pack("V")) end private def value_to_binary_string(val) [val].pack("V") end def read_and_return_value(io) io.readbytes(4).unpack("V").at(0) end def sensible_default 0 end end describe BinData::BasePrimitive do it "is not registered" do lambda { BinData::RegisteredClasses.lookup("BasePrimitive") }.must_raise BinData::UnRegisteredTypeError end end describe BinData::BasePrimitive, "all subclasses" do class SubClassOfBasePrimitive < BinData::BasePrimitive expose_methods_for_testing end let(:obj) { SubClassOfBasePrimitive.new } it "raise errors on unimplemented methods" do lambda { obj.value_to_binary_string(nil) }.must_raise NotImplementedError lambda { obj.read_and_return_value(nil) }.must_raise NotImplementedError lambda { obj.sensible_default }.must_raise NotImplementedError end end describe ExampleSingle do let(:obj) { ExampleSingle.new(5) } it "fails when assigning nil values" do lambda { obj.assign(nil) }.must_raise ArgumentError end it "sets and retrieves values" do obj.assign(7) obj.must_equal 7 end it "sets and retrieves BinData::BasePrimitives" do obj.assign(ExampleSingle.new(7)) obj.must_equal 7 end it "responds to known methods" do obj.must_respond_to :num_bytes end it "responds to known methods in #snapshot" do obj.must_respond_to :div end it "does not respond to unknown methods in self or #snapshot" do obj.wont_respond_to :does_not_exist end it "behaves as #snapshot" do (obj + 1).must_equal 6 (1 + obj).must_equal 6 end it "is equal to other ExampleSingle" do obj.must_equal ExampleSingle.new(5) end it "is equal to raw values" do obj.must_equal 5 5.must_equal obj end it "can be used as a hash key" do hash = {5 => 17} hash[obj].must_equal 17 end it "is sortable" do [ExampleSingle.new(5), ExampleSingle.new(3)].sort.must_equal [3, 5] end end describe BinData::BasePrimitive, "after initialisation" do let(:obj) { ExampleSingle.new } it "does not allow both :initial_value and :value" do params = {initial_value: 1, value: 2} lambda { ExampleSingle.new(params) }.must_raise ArgumentError end it "initial state" do assert obj.clear? obj.value.must_equal 0 obj.num_bytes.must_equal 4 end it "has symmetric IO" do obj.assign(42) written = obj.to_binary_s ExampleSingle.read(written).must_equal 42 end it "sets and retrieves values" do obj.value = 5 obj.value.must_equal 5 end it "is not clear after setting value" do obj.assign(5) refute obj.clear? end it "is not clear after reading" do obj.read("\x11\x22\x33\x44") refute obj.clear? end it "returns a snapshot" do obj.assign(5) obj.snapshot.must_equal 5 end end describe BinData::BasePrimitive, "with :initial_value" do let(:obj) { ExampleSingle.new(initial_value: 5) } it "initial state" do obj.value.must_equal 5 end it "forgets :initial_value after being set" do obj.assign(17) obj.wont_equal 5 end it "forgets :initial_value after reading" do obj.read("\x11\x22\x33\x44") obj.wont_equal 5 end it "remembers :initial_value after being cleared" do obj.assign(17) obj.clear obj.must_equal 5 end end describe BinData::BasePrimitive, "with :value" do let(:obj) { ExampleSingle.new(value: 5) } let(:io) { ExampleSingle.io_with_value(56) } it "initial state" do obj.value.must_equal 5 end it "changes during reading" do obj.read(io) obj.stub :reading?, true do obj.must_equal 56 end end it "does not change after reading" do obj.read(io) obj.must_equal 5 end it "is unaffected by assigning" do obj.assign(17) obj.must_equal 5 end end describe BinData::BasePrimitive, "asserting value" do let(:io) { ExampleSingle.io_with_value(12) } describe ":assert is non boolean" do it "asserts sensible value" do data = ExampleSingle.new(assert: 0) data.assert! data.value.must_equal 0 end it "succeeds when assert is correct" do data = ExampleSingle.new(assert: 12) data.read(io) data.value.must_equal 12 end it "fails when assert is incorrect" do data = ExampleSingle.new(assert: -> { 99 }) lambda { data.read(io) }.must_raise BinData::ValidityError end end describe ":assert is boolean" do it "succeeds when assert is true" do data = ExampleSingle.new(assert: -> { value < 20 }) data.read(io) data.value.must_equal 12 end it "fails when assert is false" do data = ExampleSingle.new(assert: -> { value > 20 }) lambda { data.read(io) }.must_raise BinData::ValidityError end end describe "assigning with :assert" do it "succeeds when assert is correct" do data = ExampleSingle.new(assert: 12) data.assign(12) data.value.must_equal 12 end it "fails when assert is incorrect" do data = ExampleSingle.new(assert: 12) lambda { data.assign(99) }.must_raise BinData::ValidityError end end end describe BinData::BasePrimitive, ":asserted_value" do it "has :value" do data = ExampleSingle.new(asserted_value: -> { 12 }) data.value.must_equal 12 end describe "assigning with :assert" do it "succeeds when assert is correct" do data = ExampleSingle.new(asserted_value: -> { 12 }) data.assign(12) data.value.must_equal 12 end it "fails when assert is incorrect" do data = ExampleSingle.new(asserted_value: -> { 12 }) lambda { data.assign(99) }.must_raise BinData::ValidityError end end end describe BinData::BasePrimitive do it "conforms to rule 1 for returning a value" do data = ExampleSingle.new(value: 5) data.must_equal 5 end it "conforms to rule 2 for returning a value" do io = ExampleSingle.io_with_value(42) data = ExampleSingle.new(value: 5) data.read(io) data.stub :reading?, true do data.must_equal 42 end end it "conforms to rule 3 for returning a value" do data = ExampleSingle.new(initial_value: 5) assert data.clear? data.must_equal 5 end it "conforms to rule 4 for returning a value" do data = ExampleSingle.new(initial_value: 5) data.assign(17) refute data.clear? data.must_equal 17 end it "conforms to rule 5 for returning a value" do data = ExampleSingle.new assert data.clear? data.must_equal 0 end it "conforms to rule 6 for returning a value" do data = ExampleSingle.new data.assign(8) refute data.clear? data.must_equal 8 end end bindata-2.3.5/test/bits_test.rb0000755000004100000410000001065213042501567016471 0ustar www-datawww-data#!/usr/bin/env ruby require File.expand_path(File.join(File.dirname(__FILE__), "test_helper")) module AllBitfields def test_has_a_sensible_value_of_zero all_objects do |obj, nbits| obj.must_equal 0 end end def test_avoids_underflow all_objects do |obj, nbits| obj.assign(min_value - 1) obj.must_equal min_value end end def test_avoids_overflow all_objects do |obj, nbits| obj.assign(max_value + 1) obj.must_equal max_value end end def test_assign_values all_objects do |obj, nbits| some_values_within_range.each do |val| obj.assign(val) obj.must_equal val end end end def test_assign_values_from_other_bit_objects all_objects do |obj, nbits| some_values_within_range.each do |val| obj.assign(obj.new(val)) obj.must_equal val end end end def test_symmetrically_read_and_write all_objects do |obj, nbits| some_values_within_range.each do |val| obj.assign(val) other = obj.new other.read(obj.to_binary_s) other.must_equal obj end end end def all_objects(&block) @bits.each do |obj, nbits| @nbits = nbits yield obj, nbits end end def min_value if @signed -max_value - 1 else 0 end end def max_value if @signed (1 << (@nbits - 1)) - 1 else (1 << @nbits) - 1 end end def some_values_within_range lo = min_value + 1 mid = (min_value + max_value) / 2 hi = max_value - 1 [lo, mid, hi].select { |val| value_within_range?(val) } end def value_within_range?(val) (min_value .. max_value).include?(val) end end def generate_bit_classes_to_test(endian, signed) bits = [] if signed base = "Sbit" start = 2 else base = "Bit" start = 1 end (start .. 64).each do |nbits| name = "#{base}#{nbits}" name << "le" if endian == :little obj = BinData.const_get(name).new bits << [obj, nbits] end (start .. 64).each do |nbits| name = "#{base}" name << "Le" if endian == :little obj = BinData.const_get(name).new(nbits: nbits) bits << [obj, nbits] end bits end describe "Unsigned big endian bitfields" do include AllBitfields before do @signed = false @bits = generate_bit_classes_to_test(:big, @signed) end it "read big endian values" do all_objects do |obj, nbits| nbytes = (nbits + 7) / 8 str = [0b1000_0000].pack("C") + "\000" * (nbytes - 1) obj.read(str) obj.must_equal 1 << (nbits - 1) end end end describe "Signed big endian bitfields" do include AllBitfields before do @signed = true @bits = generate_bit_classes_to_test(:big, @signed) end it "read big endian values" do all_objects do |obj, nbits| nbytes = (nbits + 7) / 8 str = [0b0100_0000].pack("C") + "\000" * (nbytes - 1) obj.read(str) obj.must_equal 1 << (nbits - 2) end end end describe "Unsigned little endian bitfields" do include AllBitfields before do @signed = false @bits = generate_bit_classes_to_test(:little, @signed) end it "read little endian values" do all_objects do |obj, nbits| nbytes = (nbits + 7) / 8 str = [0b0000_0001].pack("C") + "\000" * (nbytes - 1) obj.read(str) obj.must_equal 1 end end end describe "Signed little endian bitfields" do include AllBitfields before do @signed = true @bits = generate_bit_classes_to_test(:little, @signed) end it "read little endian values" do all_objects do |obj, nbits| nbytes = (nbits + 7) / 8 str = [0b0000_0001].pack("C") + "\000" * (nbytes - 1) obj.read(str) obj.must_equal 1 end end end describe "Bits of size 1" do let(:bit_classes) { [BinData::Bit1, BinData::Bit1le] } it "accept true as value" do bit_classes.each do |bit_class| obj = bit_class.new obj.assign(true) obj.must_equal 1 end end it "accept false as value" do bit_classes.each do |bit_class| obj = bit_class.new obj.assign(false) obj.must_equal 0 end end it "accept nil as value" do bit_classes.each do |bit_class| obj = bit_class.new obj.assign(nil) obj.must_equal 0 end end it "must not be signed" do lambda { BinData::Sbit1 }.must_raise RuntimeError lambda { BinData::Sbit1le }.must_raise RuntimeError end end bindata-2.3.5/test/array_test.rb0000755000004100000410000002275213042501567016652 0ustar www-datawww-data#!/usr/bin/env ruby require File.expand_path(File.join(File.dirname(__FILE__), "test_helper")) describe BinData::Array, "when instantiating" do describe "with no mandatory parameters supplied" do it "raises an error" do args = {} lambda { BinData::Array.new(args) }.must_raise ArgumentError end end describe "with some but not all mandatory parameters supplied" do it "raises an error" do args = {initial_length: 3} lambda { BinData::Array.new(args) }.must_raise ArgumentError end end it "warns about :length" do Kernel.must_warn ":length is not used with BinData::Array. You probably want to change this to :initial_length" do obj = BinData::Array.new(type: :uint8, length: 3) obj.read "123" end end it "warns about :read_length" do Kernel.must_warn ":read_length is not used with BinData::Array. You probably want to change this to :initial_length" do obj = BinData::Array.new(type: :uint8, read_length: 3) obj.read "123" end end it "fails if a given type is unknown" do args = {type: :does_not_exist, initial_length: 3} lambda { BinData::Array.new(args) }.must_raise BinData::UnRegisteredTypeError end it "fails if :initial_length is not an integer" do args = {type: :uint8, initial_length: "3"} lambda { BinData::Array.new(args) }.must_raise ArgumentError end it "does not allow both :initial_length and :read_until" do args = {initial_length: 3, read_until: -> { false } } lambda { BinData::Array.new(args) }.must_raise ArgumentError end it "accepts BinData::Base as :type" do obj = BinData::Int8.new(initial_value: 5) array = BinData::Array.new(type: obj, initial_length: 1) array.must_equal [5] end end describe BinData::Array, "with no elements" do let(:obj) { BinData::Array.new(type: :uint32le) } it "initial state" do assert obj.clear? obj.must_be_empty obj.length.must_equal 0 obj.first.must_be_nil obj.last.must_be_nil end it "returns [] for the first n elements" do obj.first(3).must_equal [] end it "returns [] for the last n elements" do obj.last(3).must_equal [] end end describe BinData::Array, "with several elements" do let(:obj) { type = [:uint32le, {initial_value: -> { index + 1 }}] BinData::Array.new(type: type, initial_length: 5) } it "initial state" do assert obj.clear? obj.wont_be_empty obj.size.must_equal 5 obj.length.must_equal 5 obj.snapshot.must_equal [1, 2, 3, 4, 5] obj.inspect.must_equal "[1, 2, 3, 4, 5]" end it "coerces to ::Array if required" do [0].concat(obj).must_equal [0, 1, 2, 3, 4, 5] end it "uses methods from Enumerable" do obj.select { |x| (x % 2) == 0 }.must_equal [2, 4] end it "assigns primitive values" do obj.assign([4, 5, 6]) obj.must_equal [4, 5, 6] end it "assigns bindata objects" do obj.assign([BinData::Uint32le.new(4), BinData::Uint32le.new(5), BinData::Uint32le.new(6)]) obj.must_equal [4, 5, 6] end it "assigns a bindata array" do array = BinData::Array.new([4, 5, 6], type: :uint32le) obj.assign(array) obj.must_equal [4, 5, 6] end it "returns the first element" do obj.first.must_equal 1 end it "returns the first n elements" do obj[0...3].must_equal [1, 2, 3] obj.first(3).must_equal [1, 2, 3] obj.first(99).must_equal [1, 2, 3, 4, 5] end it "returns the last element" do obj.last.must_equal 5 obj[-1].must_equal 5 end it "returns the last n elements" do obj.last(3).must_equal [3, 4, 5] obj.last(99).must_equal [1, 2, 3, 4, 5] obj[-3, 100].must_equal [3, 4, 5] end it "clears all" do obj[1] = 8 obj.clear obj.must_equal [1, 2, 3, 4, 5] end it "clears a single element" do obj[1] = 8 obj[1].clear obj[1].must_equal 2 end it "is clear if all elements are clear" do obj[1] = 8 obj[1].clear assert obj.clear? end it "tests clear status of individual elements" do obj[1] = 8 assert obj[0].clear? refute obj[1].clear? end it "directly accesses elements" do obj[1] = 8 obj[1].must_equal 8 end it "symmetrically reads and writes" do obj[1] = 8 str = obj.to_binary_s obj.clear obj[1].must_equal 2 obj.read(str) obj[1].must_equal 8 end it "identifies index of elements" do obj.index(3).must_equal 2 end it "returns nil for index of non existent element" do obj.index(42).must_be_nil end it "has correct debug name" do obj[2].debug_name.must_equal "obj[2]" end it "has correct offset" do obj[2].rel_offset.must_equal 2 * 4 end it "has correct num_bytes" do obj.num_bytes.must_equal 5 * 4 end it "has correct num_bytes for individual elements" do obj[0].num_bytes.must_equal 4 end end describe BinData::Array, "when accessing elements" do let(:obj) { type = [:uint32le, {initial_value: -> { index + 1 }}] data = BinData::Array.new(type: type, initial_length: 5) data.assign([1, 2, 3, 4, 5]) data } it "inserts with positive indexes" do obj.insert(2, 30, 40) obj.snapshot.must_equal [1, 2, 30, 40, 3, 4, 5] end it "inserts with negative indexes" do obj.insert(-2, 30, 40) obj.snapshot.must_equal [1, 2, 3, 4, 30, 40, 5] end it "pushes" do obj.push(30, 40) obj.snapshot.must_equal [1, 2, 3, 4, 5, 30, 40] end it "concats" do obj.concat([30, 40]) obj.snapshot.must_equal [1, 2, 3, 4, 5, 30, 40] end it "unshifts" do obj.unshift(30, 40) obj.snapshot.must_equal [30, 40, 1, 2, 3, 4, 5] end it "automatically extends on [index]" do obj[9].must_equal 10 obj.snapshot.must_equal [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] end it "automatically extends on []=" do obj[9] = 30 obj.snapshot.must_equal [1, 2, 3, 4, 5, 6, 7, 8, 9, 30] end it "automatically extends on insert" do obj.insert(7, 30, 40) obj.snapshot.must_equal [1, 2, 3, 4, 5, 6, 7, 30, 40] end it "does not extend on at" do obj.at(9).must_be_nil obj.length.must_equal 5 end it "does not extend on [start, length]" do obj[9, 2].must_be_nil obj.length.must_equal 5 end it "does not extend on [range]" do obj[9 .. 10].must_be_nil obj.length.must_equal 5 end it "raises error on bad input to []" do lambda { obj["a"] }.must_raise TypeError lambda { obj[1, "a"] }.must_raise TypeError end end describe BinData::Array, "with :read_until" do describe "containing +element+" do it "reads until the sentinel is reached" do read_until = lambda { element == 5 } obj = BinData::Array.new(type: :int8, read_until: read_until) obj.read "\x01\x02\x03\x04\x05\x06\x07\x08" obj.must_equal [1, 2, 3, 4, 5] end end describe "containing +array+ and +index+" do it "reads until the sentinel is reached" do read_until = lambda { index >= 2 and array[index - 2] == 5 } obj = BinData::Array.new(type: :int8, read_until: read_until) obj.read "\x01\x02\x03\x04\x05\x06\x07\x08" obj.must_equal [1, 2, 3, 4, 5, 6, 7] end end describe ":eof" do it "reads records until eof" do obj = BinData::Array.new(type: :int8, read_until: :eof) obj.read "\x01\x02\x03" obj.must_equal [1, 2, 3] end it "reads records until eof, ignoring partial records" do obj = BinData::Array.new(type: :int16be, read_until: :eof) obj.read "\x00\x01\x00\x02\x03" obj.must_equal [1, 2] end it "reports exceptions" do array_type = [:string, {read_length: -> { unknown_variable }}] obj = BinData::Array.new(type: array_type, read_until: :eof) lambda { obj.read "\x00\x01\x00\x02\x03" }.must_raise NoMethodError end end end describe BinData::Array, "nested within an Array" do let(:obj) { nested_array_params = { type: [:int8, { initial_value: :index }], initial_length: -> { index + 1 } } BinData::Array.new(type: [:array, nested_array_params], initial_length: 3) } it "#snapshot" do obj.snapshot.must_equal [ [0], [0, 1], [0, 1, 2] ] end it "maintains structure when reading" do obj.read "\x04\x05\x06\x07\x08\x09" obj.must_equal [ [4], [5, 6], [7, 8, 9] ] end end describe BinData::Array, "subclassed" do class IntArray < BinData::Array endian :big default_parameter initial_element_value: 0 uint16 initial_value: :initial_element_value end it "forwards parameters" do obj = IntArray.new(initial_length: 7) obj.length.must_equal 7 end it "overrides default parameters" do obj = IntArray.new(initial_length: 3, initial_element_value: 5) obj.to_binary_s.must_equal_binary "\x00\x05\x00\x05\x00\x05" end end describe BinData::Array, "of bits" do let(:obj) { BinData::Array.new(type: :bit1, initial_length: 15) } it "reads" do str = [0b0001_0100, 0b1000_1000].pack("CC") obj.read(str) obj[0].must_equal 0 obj[1].must_equal 0 obj[2].must_equal 0 obj[3].must_equal 1 obj[4].must_equal 0 obj[5].must_equal 1 obj[6].must_equal 0 obj[7].must_equal 0 obj[8].must_equal 1 obj[9].must_equal 0 obj[10].must_equal 0 obj[11].must_equal 0 obj[12].must_equal 1 obj[13].must_equal 0 obj[14].must_equal 0 end it "writes" do obj[3] = 1 obj.to_binary_s.must_equal_binary [0b0001_0000, 0b0000_0000].pack("CC") end it "returns num_bytes" do obj.num_bytes.must_equal 2 end it "has correct offset" do obj[7].rel_offset.must_equal 0 obj[8].rel_offset.must_equal 1 end end bindata-2.3.5/test/skip_test.rb0000755000004100000410000000706213042501567016477 0ustar www-datawww-data#!/usr/bin/env ruby require File.expand_path(File.join(File.dirname(__FILE__), "test_helper")) describe BinData::Skip, "when instantiating" do describe "with no mandatory parameters supplied" do it "raises an error" do args = {} lambda { BinData::Skip.new(args) }.must_raise ArgumentError end end end describe BinData::Skip, "with :length" do let(:obj) { BinData::Skip.new(length: 5) } let(:io) { StringIO.new("abcdefghij") } it "initial state" do obj.must_equal "" obj.to_binary_s.must_equal_binary "\000" * 5 end it "skips bytes" do obj.read(io) io.pos.must_equal 5 end it "has expected binary representation after setting value" do obj.assign("123") obj.to_binary_s.must_equal_binary "\000" * 5 end it "has expected binary representation after reading" do obj.read(io) obj.to_binary_s.must_equal_binary "\000" * 5 end end describe BinData::Skip, "with :to_abs_offset" do BinData::Struct.new(fields: [ [:skip, :f, { to_abs_offset: 5 } ] ]) let(:skip_obj) { [:skip, :f, { to_abs_offset: 5 } ] } let(:io) { StringIO.new("abcdefghij") } it "reads skipping forward" do fields = [ skip_obj ] obj = BinData::Struct.new(fields: fields) obj.read(io) io.pos.must_equal 5 end it "reads skipping in place" do fields = [ [:string, :a, { read_length: 5 }], skip_obj ] obj = BinData::Struct.new(fields: fields) obj.read(io) io.pos.must_equal 5 end it "does not read skipping backwards" do fields = [ [:string, :a, { read_length: 10 }], skip_obj ] obj = BinData::Struct.new(fields: fields) lambda { obj.read(io) }.must_raise BinData::ValidityError end it "writes skipping forward" do fields = [ skip_obj ] obj = BinData::Struct.new(fields: fields) obj.to_binary_s.must_equal "\000\000\000\000\000" end it "reads skipping in place" do fields = [ [:string, :a, { value: "abcde" }], skip_obj ] obj = BinData::Struct.new(fields: fields) obj.to_binary_s.must_equal "abcde" end it "does not write skipping backwards" do fields = [ [:string, :a, { value: "abcdefghij" }], skip_obj ] obj = BinData::Struct.new(fields: fields) lambda { obj.to_binary_s }.must_raise BinData::ValidityError end end describe BinData::Skip, "with :until_valid" do let(:io) { StringIO.new("abcdefghij") } it "skips to valid match" do skip_obj = [:string, { read_length: 1, assert: "f" }] fields = [ [:skip, :s, { until_valid: skip_obj }] ] obj = BinData::Struct.new(fields: fields) obj.read(io) io.pos.must_equal 5 end it "doesn't skip when validator doesn't assert" do skip_obj = [:string, { read_length: 1 }] fields = [ [:skip, :s, { until_valid: skip_obj }] ] obj = BinData::Struct.new(fields: fields) obj.read(io) io.pos.must_equal 0 end it "raises EOFError when no match" do skip_obj = [:string, { read_length: 1, assert: "X" }] fields = [ [:skip, :s, { until_valid: skip_obj }] ] obj = BinData::Struct.new(fields: fields) lambda { obj.read(io) }.must_raise EOFError end it "raises IOError when validator reads beyond stream" do skip_obj = [:string, { read_length: 30 }] fields = [ [:skip, :s, { until_valid: skip_obj }] ] obj = BinData::Struct.new(fields: fields) lambda { obj.read(io) }.must_raise IOError end class DSLSkip < BinData::Record skip :s do string read_length: 1, assert: "f" end string :a, read_length: 1 end it "uses block form" do obj = DSLSkip.read(io) obj.a.must_equal "f" end end bindata-2.3.5/test/buffer_test.rb0000755000004100000410000000714413042501567017003 0ustar www-datawww-data#!/usr/bin/env ruby require File.expand_path(File.join(File.dirname(__FILE__), "test_helper")) describe BinData::Buffer, "when instantiating" do describe "with no mandatory parameters supplied" do it "raises an error" do args = {} lambda { BinData::Buffer.new(args) }.must_raise ArgumentError end end describe "with some but not all mandatory parameters supplied" do it "raises an error" do args = {length: 3} lambda { BinData::Buffer.new(args) }.must_raise ArgumentError end end it "fails if a given type is unknown" do args = {type: :does_not_exist, length: 3} lambda { BinData::Buffer.new(args) }.must_raise BinData::UnRegisteredTypeError end it "accepts BinData::Base as :type" do obj = BinData::Int8.new(initial_value: 5) array = BinData::Buffer.new(type: obj, length: 3) array.must_equal 5 end end describe BinData::Buffer, "subclassed with a single type" do class IntBuffer < BinData::Buffer endian :big default_parameter length: 5 uint16 end it "behaves as type" do obj = IntBuffer.new(3) obj.must_equal 3 end it "reads data" do obj = IntBuffer.read "\001\002\003\004\005" obj.must_equal 0x0102 end it "writes data" do obj = IntBuffer.new(3) obj.to_binary_s.must_equal_binary "\000\003\000\000\000" end it "has total num_bytes" do obj = IntBuffer.new assert obj.clear? obj.num_bytes.must_equal 5 end it "has raw_num_bytes" do obj = IntBuffer.new assert obj.clear? obj.raw_num_bytes.must_equal 2 end end describe BinData::Buffer, "subclassed with multiple types" do class TupleBuffer < BinData::Buffer endian :big default_parameter length: 5 uint16 :a uint16 :b end it "behaves as type" do obj = TupleBuffer.new(a: 1, b: 2) obj.a.must_equal 1 obj.b.must_equal 2 end it "has total num_bytes" do obj = TupleBuffer.new obj.num_bytes.must_equal 5 end it "has raw_num_bytes" do obj = TupleBuffer.new obj.raw_num_bytes.must_equal 4 end it "reads data" do obj = TupleBuffer.read "\001\002\003\004\005" obj.a.must_equal 0x0102 obj.b.must_equal 0x0304 end it "writes data" do obj = TupleBuffer.new(a: 1, b: 2) obj.to_binary_s.must_equal_binary "\000\001\000\002\000" end end describe BinData::Buffer, "inside a Record" do class BufferRecord < BinData::Record endian :little uint16 :buffer_length, value: -> { 2 * list.length + 1 } buffer :list, length: :buffer_length do array type: :int16, read_until: :eof end string :footer, read_length: 2, asserted_value: "ZZ" end it "reads" do obj = BufferRecord.read "\007\000\004\000\005\000\006\000\000ZZ" obj.list.must_equal [4, 5, 6] end it "writes" do obj = BufferRecord.new(list: [1, 2, 3, 4, 5]) obj.to_binary_s.must_equal_binary "\013\000\001\000\002\000\003\000\004\000\005\000\000ZZ" end end describe BinData::Buffer, "nested buffers" do class NestedBufferRecord < BinData::Record buffer :a, length: 10 do buffer :aa, length: 5 do string read_length: 5 end buffer :bb, length: 20 do string read_length: 5 end end string :b, read_length: 5 end it "restricts large nested buffer" do obj = NestedBufferRecord.read "abcdefghijklmnopqrst" obj.a.aa.must_equal "abcde" obj.a.bb.must_equal "fghij" obj.b.must_equal "klmno" end it "restricts oversize writes" do obj = NestedBufferRecord.new obj.a.aa = "abcdefghij" obj.a.bb = "ABCDEFGHIJ" obj.b = "12345" obj.to_binary_s.must_equal_binary "abcdeABCDE12345" end end bindata-2.3.5/test/offset_test.rb0000755000004100000410000000450613042501567017017 0ustar www-datawww-data#!/usr/bin/env ruby require File.expand_path(File.join(File.dirname(__FILE__), "test_helper")) require 'bindata/offset' describe BinData::Base, "offsets" do class ThreeByteReader < BinData::Base def do_read(io) @val = io.readbytes(3) end def snapshot @val end end class TenByteOffsetBase < BinData::Base def self.create(params) obj = self.new obj.initialize_child(params) obj end def initialize_child(params) @child = ThreeByteReader.new(params, self) end def snapshot @child.snapshot end def do_read(io) io.seekbytes(10) @child.do_read(io) end def clear end end let(:data) { "0123456789abcdefghijk" } let(:io) { StringIO.new(data) } describe "with :check_offset" do it "fails when offset is incorrect" do io.seek(2) obj = TenByteOffsetBase.create(check_offset: 10 - 4) lambda { obj.read(io) }.must_raise BinData::ValidityError end it "succeeds when offset is correct" do io.seek(3) obj = TenByteOffsetBase.create(check_offset: 10) obj.read(io).snapshot.must_equal data[3 + 10, 3] end it "fails when :check_offset fails" do io.seek(4) obj = TenByteOffsetBase.create(check_offset: -> { offset == 10 + 1 } ) lambda { obj.read(io) }.must_raise BinData::ValidityError end it "succeeds when :check_offset succeeds" do io.seek(5) obj = TenByteOffsetBase.create(check_offset: -> { offset == 10 } ) obj.read(io).snapshot.must_equal data[5 + 10, 3] end end describe "with :adjust_offset" do it "is mutually exclusive with :check_offset" do params = { check_offset: 8, adjust_offset: 8 } lambda { TenByteOffsetBase.create(params) }.must_raise ArgumentError end it "adjust offset when incorrect" do io.seek(2) obj = TenByteOffsetBase.create(adjust_offset: 13) obj.read(io).snapshot.must_equal data[2 + 13, 3] end it "succeeds when offset is correct" do io.seek(3) obj = TenByteOffsetBase.create(adjust_offset: 10) obj.read(io).snapshot.must_equal data[3 + 10, 3] end it "fails if cannot adjust offset" do io.seek(4) obj = TenByteOffsetBase.create(adjust_offset: -5) lambda { obj.read(io) }.must_raise BinData::ValidityError end end end bindata-2.3.5/test/test_helper.rb0000644000004100000410000000270413042501567017003 0ustar www-datawww-datarequire 'rubygems' require 'coveralls' Coveralls.wear! require 'minitest/autorun' require 'stringio' $LOAD_PATH.unshift File.expand_path("../lib", File.dirname(__FILE__)) require 'bindata' class StringIO # Returns the value that was written to the io def value rewind read end end module Kernel def expose_methods_for_testing cls = (Class === self) ? self : (class << self ; self; end) private_method_names = cls.private_instance_methods - Object.private_instance_methods cls.send(:public, *private_method_names) protected_method_names = cls.protected_instance_methods - Object.protected_instance_methods cls.send(:public, *protected_method_names) end def value_read_from_written self.class.read(self.to_binary_s) end def must_equal_binary(expected) must_equal expected.dup.force_encoding(Encoding::BINARY) end def must_raise_on_line(exp, line, msg = nil) ex = self.must_raise exp ex.message.must_equal msg if msg idx = ex.backtrace.find_index { |bt| /:in `must_raise_on_line'$/ =~ bt } line_num_regex = /.*:(\d+)(:.*|$)/ err_line = line_num_regex.match(ex.backtrace[0])[1].to_i ref_line = line_num_regex.match(ex.backtrace[idx + 1])[1].to_i (err_line - ref_line).must_equal line end def must_warn(msg, &block) result = "" callable = proc { |str| result = str } self.stub(:warn, callable) do block.call end result.must_equal msg end end bindata-2.3.5/test/params_test.rb0000755000004100000410000000713613042501567017016 0ustar www-datawww-data#!/usr/bin/env ruby require File.expand_path(File.join(File.dirname(__FILE__), "test_helper")) describe BinData::Base, "parameters" do it "fails when parameter name is invalid" do lambda { class InvalidParameterNameBase < BinData::Base optional_parameter :eval # i.e. Kernel#eval end }.must_raise NameError end it "fails when parameter has nil value" do lambda { BinData::Base.new(a: nil) }.must_raise ArgumentError end end describe BinData::Base, "#has_parameter?" do it "true for existing parameters" do obj = BinData::Base.new(a: 3) assert obj.has_parameter?(:a) end it "false for non-existing parameters" do obj = BinData::Base.new refute obj.has_parameter?(:a) end end describe BinData::Base, "#get_parameter" do it "retrieves parameter values" do obj = BinData::Base.new(a: 3) obj.get_parameter(:a).must_equal 3 end it "retrieves parameter values with string keys" do obj = BinData::Base.new('a' => 3) obj.get_parameter(:a).must_equal 3 end it "returns nil for non existing parameters" do obj = BinData::Base.new obj.get_parameter(:a).must_be_nil end it "wont eval parameters" do obj = BinData::Base.new(a: -> { 3 }) assert_kind_of Proc, obj.get_parameter(:a) end end describe BinData::Base, "#eval_parameter" do it "evals the parameter" do obj = BinData::Base.new(a: -> { 3 }) obj.eval_parameter(:a).must_equal 3 end it "returns nil for a non existing parameter" do obj = BinData::Base.new obj.eval_parameter(:a).must_be_nil end end describe BinData::Base, ".mandatory_parameters" do class MandatoryBase < BinData::Base mandatory_parameter :p1 mandatory_parameter :p2 end it "fails when not all mandatory parameters are present" do params = {p1: "a", xx: "b" } lambda { MandatoryBase.new(params) }.must_raise ArgumentError end it "fails when no mandatory parameters are present" do lambda { MandatoryBase.new() }.must_raise ArgumentError end end describe BinData::Base, ".default_parameters" do class DefaultBase < BinData::Base default_parameter p1: "a" end it "uses default parameters when not specified" do obj = DefaultBase.new obj.eval_parameter(:p1).must_equal "a" end it "can override default parameters" do obj = DefaultBase.new(p1: "b") obj.eval_parameter(:p1).must_equal "b" end end describe BinData::Base, ".mutually_exclusive_parameters" do class MutexParamBase < BinData::Base optional_parameters :p1, :p2, :p3 mutually_exclusive_parameters :p1, :p2, :p3 end it "fails when any two of those parameters are present" do lambda { MutexParamBase.new(p1: "a", p2: "b") }.must_raise ArgumentError lambda { MutexParamBase.new(p1: "a", p3: "c") }.must_raise ArgumentError lambda { MutexParamBase.new(p2: "b", p3: "c") }.must_raise ArgumentError end end describe BinData::Base, "subclassing" do class ParamLevel1Base < BinData::Base optional_parameter :p1 end class ParamLevel2Base < ParamLevel1Base optional_parameter :p2 end it "inherits parameters" do accepted = ParamLevel2Base.accepted_parameters.all accepted.must_include :p1 accepted.must_include :p2 end end describe BinData::Base, "subclassing when skipping a level" do class ParamLevel1Base < BinData::Base optional_parameter :p1 end class ParamLevel2Base < ParamLevel1Base end class ParamLevel3Base < ParamLevel2Base optional_parameter :p2 end it "inherits parameters" do accepted = ParamLevel3Base.accepted_parameters.all accepted.must_include :p1 accepted.must_include :p2 end end bindata-2.3.5/test/warnings_test.rb0000755000004100000410000000127313042501567017357 0ustar www-datawww-data#!/usr/bin/env ruby require File.expand_path(File.join(File.dirname(__FILE__), "test_helper")) describe BinData::Base, "when defining" do it "fails if #initialize is overridden" do class BaseWithInitialize < BinData::Base def initialize(params = {}, parent = nil) super end end lambda { BaseWithInitialize.new }.must_raise RuntimeError end it "handles if #initialize is naively renamed to #initialize_instance" do class BaseWithInitializeInstance < BinData::Base def initialize_instance(params = {}, parent = nil) super end end lambda { BaseWithInitializeInstance.new }.must_raise RuntimeError end end bindata-2.3.5/test/registry_test.rb0000755000004100000410000001122113042501567017371 0ustar www-datawww-data#!/usr/bin/env ruby require File.expand_path(File.join(File.dirname(__FILE__), "test_helper")) describe BinData::Registry do A = Class.new B = Class.new C = Class.new D = Class.new let(:r) { BinData::Registry.new } it "lookups registered names" do r.register('ASubClass', A) r.register('AnotherSubClass', B) r.lookup('ASubClass').must_equal A r.lookup('a_sub_class').must_equal A r.lookup('AnotherSubClass').must_equal B r.lookup('another_sub_class').must_equal B end it "does not lookup unregistered names" do lambda { r.lookup('a_non_existent_sub_class') }.must_raise BinData::UnRegisteredTypeError end it "unregisters names" do r.register('ASubClass', A) r.unregister('ASubClass') lambda { r.lookup('ASubClass') }.must_raise BinData::UnRegisteredTypeError end it "allows overriding of registered classes" do r.register('A', A) r.register('A', B) r.lookup('a').must_equal B end it "converts CamelCase to underscores" do r.underscore_name('CamelCase').must_equal 'camel_case' end it "converts adjacent caps camelCase to underscores" do r.underscore_name('XYZCamelCase').must_equal 'xyz_camel_case' end it "ignores the outer nestings of classes" do r.underscore_name('A::B::C').must_equal 'c' end end describe BinData::Registry, "with numerics" do let(:r) { BinData::RegisteredClasses } it "lookup integers with endian" do r.lookup("int24", {endian: :big}).to_s.must_equal "BinData::Int24be" r.lookup("int24", {endian: :little}).to_s.must_equal "BinData::Int24le" r.lookup("uint24", {endian: :big}).to_s.must_equal "BinData::Uint24be" r.lookup("uint24", {endian: :little}).to_s.must_equal "BinData::Uint24le" end it "does not lookup integers without endian" do lambda { r.lookup("int24") }.must_raise BinData::UnRegisteredTypeError end it "does not lookup non byte based integers" do lambda { r.lookup("int3") }.must_raise BinData::UnRegisteredTypeError lambda { r.lookup("int3", {endian: :big}) }.must_raise BinData::UnRegisteredTypeError lambda { r.lookup("int3", {endian: :little}) }.must_raise BinData::UnRegisteredTypeError end it "lookup floats with endian" do r.lookup("float", {endian: :big}).to_s.must_equal "BinData::FloatBe" r.lookup("float", {endian: :little}).to_s.must_equal "BinData::FloatLe" r.lookup("double", {endian: :big}).to_s.must_equal "BinData::DoubleBe" r.lookup("double", {endian: :little}).to_s.must_equal "BinData::DoubleLe" end it "lookup bits" do r.lookup("bit5").to_s.must_equal "BinData::Bit5" r.lookup("sbit5").to_s.must_equal "BinData::Sbit5" r.lookup("bit6le").to_s.must_equal "BinData::Bit6le" end it "lookup bits by ignoring endian" do r.lookup("bit2", {endian: :big}).to_s.must_equal "BinData::Bit2" r.lookup("bit3le", {endian: :big}).to_s.must_equal "BinData::Bit3le" r.lookup("bit2", {endian: :little}).to_s.must_equal "BinData::Bit2" r.lookup("bit3le", {endian: :little}).to_s.must_equal "BinData::Bit3le" end it "lookup signed bits by ignoring endian" do r.lookup("sbit2", {endian: :big}).to_s.must_equal "BinData::Sbit2" r.lookup("sbit3le", {endian: :big}).to_s.must_equal "BinData::Sbit3le" r.lookup("sbit2", {endian: :little}).to_s.must_equal "BinData::Sbit2" r.lookup("sbit3le", {endian: :little}).to_s.must_equal "BinData::Sbit3le" end end describe BinData::Registry, "with endian specific types" do let(:r) { BinData::Registry.new } before do r.register('a_le', A) r.register('b_be', B) end it "lookup little endian types" do r.lookup('a', {endian: :little}).must_equal A end it "lookup big endian types" do r.lookup('b', {endian: :big}).must_equal B end it "does not lookup types with non existent endian" do lambda { r.lookup('a', {endian: :big}) }.must_raise BinData::UnRegisteredTypeError end it "lookup prefers exact type" do r.register('c', C) r.register('c_le', D) r.lookup('c', {endian: :little}).must_equal C end end describe BinData::Registry, "with search_prefix" do let(:r) { BinData::Registry.new } before do r.register('a_f', A) r.register('b_f', B) end it "lookup single search_prefix" do r.lookup('f', {search_prefix: :a}).must_equal A end it "lookup single search_prefix with endian" do r.lookup('f', {search_prefix: :a, endian: :little}).must_equal A end it "lookup multiple search_prefix" do r.lookup('f', {search_prefix: [:x, :a]}).must_equal A end it "lookup first match in search_prefix" do r.lookup('f', {search_prefix: [:a, :b]}).must_equal A end end bindata-2.3.5/test/base_test.rb0000755000004100000410000001101613042501567016435 0ustar www-datawww-data#!/usr/bin/env ruby require File.expand_path(File.join(File.dirname(__FILE__), "test_helper")) describe "BinData::Base", "framework" do class FrameworkBase < BinData::Base class << self attr_accessor :calls def record_calls(&block) self.calls = [] block.call end end def initialize_instance self.class.calls << :initialize_instance end def initialize_shared_instance self.class.calls << :initialize_shared_instance end expose_methods_for_testing end let(:obj) do FrameworkBase.record_calls { FrameworkBase.new } end it "raises errors on unimplemented methods" do lambda { obj.clear? }.must_raise NotImplementedError lambda { obj.assign(nil) }.must_raise NotImplementedError lambda { obj.snapshot }.must_raise NotImplementedError lambda { obj.do_read(nil) }.must_raise NotImplementedError lambda { obj.do_write(nil) }.must_raise NotImplementedError lambda { obj.do_num_bytes }.must_raise NotImplementedError end it "calls initialize methods in order" do FrameworkBase.record_calls { FrameworkBase.new } FrameworkBase.calls.must_equal [:initialize_shared_instance, :initialize_instance] end it "does not call #initialize_shared_instance for prototypes" do prototype = obj FrameworkBase.record_calls { prototype.new } FrameworkBase.calls.must_equal [:initialize_instance] end end describe BinData::Base, "ArgExtractor" do class ParamParserBase < BinData::Base attr_reader :params attr_reader :val def assign(v) @val = v end end it "parses parameters" do par = BinData::Base.new data = [ [[3 ], 3, [], nil], [[3, par], 3, [], par], [[ {a: 1} ], nil, [:a], nil], [[ {a: 1}, par], nil, [:a], par], [[3, {a: 1} ], 3, [:a], nil], [[3, {a: 1}, par], 3, [:a], par], ] data.each do |el| args, val, param_keys, parent = *el obj = ParamParserBase.new(*args) obj.val.must_be_same_as val obj.params.keys.must_equal param_keys obj.parent.must_be_same_as parent end end end describe BinData::Base do class BaseStub < BinData::Base # Override to avoid NotImplemented errors def clear?; end def assign(x); end def snapshot; end def do_read(io) end def do_write(io) end def do_num_bytes; end end let(:obj) { BaseStub.new } it "::bindata_name returns lowercased name" do BaseStub.bindata_name.must_equal "base_stub" end it "::read instantiates self" do BaseStub.read("").must_be_instance_of BaseStub end it "#read returns self" do obj.read("").must_equal obj end it "#write returns self" do obj.write("").must_equal obj end it "#to_hex uses #to_binary_s representation" do obj.stub :to_binary_s, "\x01\xab\xCD" do obj.to_hex.must_equal "01abcd" end end it "#inspect is forwarded to snapshot" do obj.stub :snapshot, [1, 2, 3] do obj.inspect.must_equal obj.snapshot.inspect end end it "#to_s is forwarded to snapshot" do obj.stub :snapshot, [1, 2, 3] do obj.to_s.must_equal obj.snapshot.to_s end end it "pretty prints object as snapshot" do actual_io = StringIO.new expected_io = StringIO.new obj.stub :snapshot, [1, 2, 3] do require 'pp' PP.pp(obj, actual_io) PP.pp(obj.snapshot, expected_io) end actual_io.value.must_equal expected_io.value end it "#write writes the same as #to_binary_s" do class WriteToSBase < BaseStub def do_write(io) io.writebytes("abc"); end end obj = WriteToSBase.new io = StringIO.new obj.write(io) io.value.must_equal obj.to_binary_s end it "#read is forwarded to #do_read" do calls = [] called_clear = lambda { |*a| calls << :clear } called_do_read = lambda { |*a| calls << :do_read } obj.stub :clear, called_clear do obj.stub :do_read, called_do_read do obj.read(nil) end end calls.must_equal [:clear, :do_read] end it "#write is forwarded to #do_write" do calls = [] called_do_write = lambda { |*a| calls << :do_write } obj.stub :do_write, called_do_write do obj.write(nil) end calls.must_equal [:do_write] end it "#num_bytes is forwarded to #do_num_bytes" do obj.stub :do_num_bytes, 42 do obj.num_bytes.must_equal 42 end end it "#num_bytes rounds up fractional values" do obj.stub :do_num_bytes, 42.1 do obj.num_bytes.must_equal 43 end end end bindata-2.3.5/test/primitive_test.rb0000755000004100000410000001174713042501567017546 0ustar www-datawww-data#!/usr/bin/env ruby require File.expand_path(File.join(File.dirname(__FILE__), "test_helper")) describe BinData::Primitive do it "is not registered" do lambda { BinData::RegisteredClasses.lookup("Primitive") }.must_raise BinData::UnRegisteredTypeError end end describe BinData::Primitive, "all subclasses" do class SubClassOfPrimitive < BinData::Primitive expose_methods_for_testing end let(:obj) { SubClassOfPrimitive.new } it "raise errors on unimplemented methods" do lambda { obj.set(nil) }.must_raise NotImplementedError lambda { obj.get }.must_raise NotImplementedError end end describe BinData::Primitive, "when defining with errors" do it "fails on non registered types" do lambda { class BadTypePrimitive < BinData::Primitive non_registered_type :a end }.must_raise_on_line TypeError, 2, "unknown type 'non_registered_type' in BadTypePrimitive" end it "fails on duplicate names" do lambda { class DuplicateNamePrimitive < BinData::Primitive int8 :a int8 :b int8 :a end }.must_raise_on_line SyntaxError, 4, "duplicate field 'a' in DuplicateNamePrimitive" end it "fails when field name shadows an existing method" do lambda { class ExistingNamePrimitive < BinData::Primitive int8 :object_id end }.must_raise_on_line NameError, 2, "field 'object_id' shadows an existing method in ExistingNamePrimitive" end it "fails on unknown endian" do lambda { class BadEndianPrimitive < BinData::Primitive endian 'a bad value' end }.must_raise_on_line ArgumentError, 2, "unknown value for endian 'a bad value' in BadEndianPrimitive" end end describe BinData::Primitive do class PrimitiveWithEndian < BinData::Primitive endian :little int16 :a def get; self.a; end def set(v); self.a = v; end end let(:obj) { PrimitiveWithEndian.new } it "assigns value" do obj.value = 5 obj.value.must_equal 5 end it "produces binary string" do obj.assign(5) obj.to_binary_s.must_equal_binary "\x05\x00" end it "reads value" do obj.read("\x00\x01") obj.must_equal 0x100 end it "accepts standard parameters" do obj = PrimitiveWithEndian.new(initial_value: 2) obj.to_binary_s.must_equal_binary "\x02\x00" end it "returns num_bytes" do obj.num_bytes.must_equal 2 end it "raises error on missing methods" do lambda { obj.does_not_exist }.must_raise NoMethodError end it "uses read value whilst reading" do obj = PrimitiveWithEndian.new(value: 2) obj.read "\x05\x00" obj.must_equal 2 obj.stub :reading?, true do obj.must_equal 5 end end it "behaves as primitive" do obj.assign(5) (2 + obj).must_equal 7 end end describe BinData::Primitive, "requiring custom parameters" do class PrimitiveWithCustom < BinData::Primitive int8 :a, initial_value: :iv def get; self.a; end def set(v); self.a = v; end end it "passes parameters correctly" do obj = PrimitiveWithCustom.new(iv: 5) obj.must_equal 5 end end describe BinData::Primitive, "with custom mandatory parameters" do class MandatoryPrimitive < BinData::Primitive mandatory_parameter :arg1 uint8 :a, value: :arg1 def get; self.a; end def set(v); self.a = v; end end it "raises error if mandatory parameter is not supplied" do lambda { MandatoryPrimitive.new }.must_raise ArgumentError end it "uses mandatory parameter" do obj = MandatoryPrimitive.new(arg1: 5) obj.must_equal 5 end end describe BinData::Primitive, "with custom default parameters" do class DefaultPrimitive < BinData::Primitive default_parameter arg1: 5 uint8 :a, value: :arg1 def get; self.a; end def set(v); self.a = v; end end it "uses default parameter" do obj = DefaultPrimitive.new obj.must_equal 5 end it "overrides default parameter" do obj = DefaultPrimitive.new(arg1: 7) obj.must_equal 7 end end describe BinData::Primitive, "subclassed with default parameter" do class ParentDerivedPrimitive < BinData::Primitive uint16be :a def get; self.a; end def set(v); self.a = v; end end class ChildDerivedPrimitive < ParentDerivedPrimitive default_parameter initial_value: 5 end it "overrides initial_value" do a = ChildDerivedPrimitive.new(initial_value: 7) a.to_binary_s.must_equal_binary "\000\007" end it "uses default parameter" do a = ChildDerivedPrimitive.new a.to_binary_s.must_equal_binary "\000\005" end end describe BinData::Primitive, "with mutating #get and #set" do class MutatingPrimitive < BinData::Primitive uint16le :a def get; self.a; end def set(v); self.a = v.abs; end end it "#assign applies mutator" do obj = MutatingPrimitive.new obj.assign(-50) obj.snapshot.must_equal 50 end it "#to_binary_s applies mutator" do obj = MutatingPrimitive.new obj.assign(-50) obj.to_binary_s.must_equal_binary "\062\000" end end bindata-2.3.5/test/system_test.rb0000755000004100000410000001763713042501567017066 0ustar www-datawww-data#!/usr/bin/env ruby require File.expand_path(File.join(File.dirname(__FILE__), "test_helper")) describe "lambdas with index" do class NestedLambdaWithIndex < BinData::Record uint8 :a, value: -> { index * 10 } end it "uses index of containing array" do arr = BinData::Array.new(type: [:uint8, { value: -> { index * 10 } }], initial_length: 3) arr.snapshot.must_equal [0, 10, 20] end it "uses index of nearest containing array" do arr = BinData::Array.new(type: :nested_lambda_with_index, initial_length: 3) arr.snapshot.must_equal [{a: 0}, {a: 10}, {a: 20}] end it "fails if there is no containing array" do obj = NestedLambdaWithIndex.new lambda { obj.a.to_s }.must_raise NoMethodError end end describe "lambdas with parent" do it "accesses immediate parent when no parent is specified" do class NestedLambdaWithoutParent < BinData::Record int8 :a, value: 5 int8 :b, value: -> { a } end class TestLambdaWithoutParent < BinData::Record int8 :a, value: 3 nested_lambda_without_parent :x end obj = TestLambdaWithoutParent.new obj.x.b.must_equal 5 end it "accesses parent's parent when parent is specified" do class NestedLambdaWithParent < BinData::Record int8 :a, value: 5 int8 :b, value: -> { parent.a } end class TestLambdaWithParent < BinData::Record int8 :a, value: 3 nested_lambda_with_parent :x end obj = TestLambdaWithParent.new obj.x.b.must_equal 3 end end describe BinData::Record, "with choice field" do class TupleRecord < BinData::Record uint8 :a, value: 3 uint8 :b, value: 5 end class RecordWithChoiceField < BinData::Record choice :x, selection: 0 do tuple_record end end class RecordWithNestedChoiceField < BinData::Record uint8 :sel, value: 0 choice :x, selection: 0 do choice selection: :sel do tuple_record end end end it "treats choice object transparently " do obj = RecordWithChoiceField.new obj.x.a.must_equal 3 end it "treats nested choice object transparently " do obj = RecordWithNestedChoiceField.new obj.x.a.must_equal 3 end it "has correct offset" do obj = RecordWithNestedChoiceField.new obj.x.b.abs_offset.must_equal 2 end end describe BinData::Record, "containing bitfields" do class BCD < BinData::Primitive bit4 :d1 bit4 :d2 bit4 :d3 def set(v) self.d1 = (v / 100) % 10 self.d2 = (v / 10) % 10 self.d3 = v % 10 end def get() d1 * 100 + d2 * 10 + d3 end end class BitfieldRecord < BinData::Record struct :a do bit4 :w end array :b, type: :bit1, initial_length: 9 struct :c do bit2 :x end bcd :d bit6 :e end let(:obj) { BitfieldRecord.new } it "has correct num_bytes" do obj.num_bytes.must_equal 5 end it "reads across bitfield boundaries" do obj.read [0b0111_0010, 0b0110_0101, 0b0010_1010, 0b1000_0101, 0b1000_0000].pack("CCCCC") obj.a.w.must_equal 7 obj.b.must_equal [0, 0, 1, 0, 0, 1, 1, 0, 0] obj.c.x.must_equal 2 obj.d.must_equal 954 obj.e.must_equal 11 end it "writes across bitfield boundaries" do obj.a.w = 3 obj.b[2] = 1 obj.b[5] = 1 obj.c.x = 1 obj.d = 850 obj.e = 35 obj.to_binary_s.must_equal_binary [0b0011_0010, 0b0100_0011, 0b0000_1010, 0b0001_0001, 0b1000_0000].pack("CCCCC") end end describe "Objects with debug_name" do it "haves default name of obj" do el = BinData::Uint8.new el.debug_name.must_equal "obj" end it "includes array index" do arr = BinData::Array.new(type: :uint8, initial_length: 2) arr[2].debug_name.must_equal "obj[2]" end it "includes field name" do s = BinData::Struct.new(fields: [[:uint8, :a]]) s.a.debug_name.must_equal "obj.a" end it "delegates to choice" do choice_params = {choices: [:uint8], selection: 0} s = BinData::Struct.new(fields: [[:choice, :a, choice_params]]) s.a.debug_name.must_equal "obj.a" end it "nests" do nested_struct_params = {fields: [[:uint8, :c]]} struct_params = {fields: [[:struct, :b, nested_struct_params]]} s = BinData::Struct.new(fields: [[:struct, :a, struct_params]]) s.a.b.c.debug_name.must_equal "obj.a.b.c" end end describe "Tracing" do it "should trace arrays" do arr = BinData::Array.new(type: :int8, initial_length: 5) io = StringIO.new BinData::trace_reading(io) { arr.read("\x01\x02\x03\x04\x05") } expected = (0..4).collect { |i| "obj[#{i}] => #{i + 1}\n" }.join("") io.value.must_equal expected end it "traces custom single values" do class DebugNamePrimitive < BinData::Primitive int8 :ex def get; self.ex; end def set(val) self.ex = val; end end obj = DebugNamePrimitive.new io = StringIO.new BinData::trace_reading(io) { obj.read("\x01") } io.value.must_equal ["obj-internal-.ex => 1\n", "obj => 1\n"].join("") end it "traces choice selection" do obj = BinData::Choice.new(choices: [:int8, :int16be], selection: 0) io = StringIO.new BinData::trace_reading(io) { obj.read("\x01") } io.value.must_equal ["obj-selection- => 0\n", "obj => 1\n"].join("") end it "trims long trace values" do obj = BinData::String.new(read_length: 40) io = StringIO.new BinData::trace_reading(io) { obj.read("0000000000111111111122222222223333333333") } io.value.must_equal "obj => \"000000000011111111112222222222...\n" end end describe "Forward referencing with Primitive" do class FRPrimitive < BinData::Record uint8 :len, value: -> { data.length } string :data, read_length: :len end let(:obj) { FRPrimitive.new } it "initialises" do obj.snapshot.must_equal({len: 0, data: ""}) end it "reads" do obj.read("\x04test") obj.snapshot.must_equal({len: 4, data: "test"}) end it "sets value" do obj.data = "hello" obj.snapshot.must_equal({len: 5, data: "hello"}) end end describe "Forward referencing with Array" do class FRArray < BinData::Record uint8 :len, value: -> { data.length } array :data, type: :uint8, initial_length: :len end let(:obj) { FRArray.new } it "initialises" do obj.snapshot.must_equal({len: 0, data: []}) end it "reads" do obj.read("\x04\x01\x02\x03\x04") obj.snapshot.must_equal({len: 4, data: [1, 2, 3, 4]}) end it "sets value" do obj.data = [1, 2, 3] obj.snapshot.must_equal({len: 3, data: [1, 2, 3]}) end end describe "Evaluating custom parameters" do class CustomParameterRecord < BinData::Record mandatory_parameter :zz uint8 :a, value: :zz uint8 :b, value: :a uint8 :c, custom: :b end it "recursively evaluates parameter" do obj = CustomParameterRecord.new(zz: 5) obj.c.eval_parameter(:custom).must_equal 5 end end describe BinData::Record, "with custom sized integers" do class CustomIntRecord < BinData::Record int40be :a end it "reads as expected" do str = "\x00\x00\x00\x00\x05" CustomIntRecord.read(str).snapshot.must_equal({a: 5}) end end describe BinData::Record, "with choice field" do class ChoiceFieldRecord < BinData::Record int8 :a choice :b, selection: :a do struct 1, fields: [[:int8, :v]] end end it "assigns" do obj = BinData::Array.new(type: :choice_field_record) data = ChoiceFieldRecord.new(a: 1, b: {v: 3}) obj.assign([data]) end end describe BinData::Primitive, "representing a string" do class PascalStringPrimitive < BinData::Primitive uint8 :len, value: -> { data.length } string :data, read_length: :len def get; self.data; end def set(v) self.data = v; end end let(:obj) { PascalStringPrimitive.new("testing") } it "compares to regexp" do (obj =~ /es/).must_equal 1 end it "compares to regexp" do (/es/ =~ obj).must_equal 1 end end bindata-2.3.5/test/rest_test.rb0000755000004100000410000000120013042501567016472 0ustar www-datawww-data#!/usr/bin/env ruby require File.expand_path(File.join(File.dirname(__FILE__), "test_helper")) describe BinData::Rest do let(:obj) { BinData::Rest.new } it "initial state" do obj.must_equal "" end it "reads till end of stream" do data = "abcdefghij" obj.read(data).must_equal data end it "allows setting value for completeness" do obj.assign("123") obj.must_equal "123" obj.to_binary_s.must_equal_binary "123" end it "accepts BinData::BasePrimitive parameters" do rest = BinData::Rest.new(assert: "abc") lambda { rest.read("xyz") }.must_raise BinData::ValidityError end end bindata-2.3.5/test/count_bytes_remaining_test.rb0000755000004100000410000000161413042501567022115 0ustar www-datawww-data#!/usr/bin/env ruby require File.expand_path(File.join(File.dirname(__FILE__), "test_helper")) describe BinData::CountBytesRemaining do let(:obj) { BinData::CountBytesRemaining.new } it "initial state" do obj.must_equal 0 obj.num_bytes.must_equal 0 end it "counts till end of stream" do data = "abcdefghij" obj.read(data).must_equal 10 end it "does not read any data" do io = StringIO.new "abcdefghij" obj.read(io) io.pos.must_equal 0 end it "does not write any data" do obj.to_binary_s.must_equal_binary "" end it "allows setting value for completeness" do obj.assign("123") obj.must_equal "123" obj.to_binary_s.must_equal_binary "" end it "accepts BinData::BasePrimitive parameters" do count = BinData::CountBytesRemaining.new(assert: 2) lambda { count.read("xyz") }.must_raise BinData::ValidityError end end bindata-2.3.5/test/struct_test.rb0000755000004100000410000002632313042501567017056 0ustar www-datawww-data#!/usr/bin/env ruby require File.expand_path(File.join(File.dirname(__FILE__), "test_helper")) describe BinData::Struct, "when initializing" do it "fails on non registered types" do params = {fields: [[:non_registered_type, :a]]} lambda { BinData::Struct.new(params) }.must_raise BinData::UnRegisteredTypeError end it "fails on duplicate names" do params = {fields: [[:int8, :a], [:int8, :b], [:int8, :a]]} lambda { BinData::Struct.new(params) }.must_raise NameError end it "fails on reserved names" do # note that #invert is from Hash.instance_methods params = {fields: [[:int8, :a], [:int8, :invert]]} lambda { BinData::Struct.new(params) }.must_raise NameError end it "fails when field name shadows an existing method" do params = {fields: [[:int8, :object_id]]} lambda { BinData::Struct.new(params) }.must_raise NameError end it "fails on unknown endian" do params = {endian: 'bad value', fields: []} lambda { BinData::Struct.new(params) }.must_raise ArgumentError end end describe BinData::Struct, "with anonymous fields" do let(:obj) { params = { fields: [ [:int8, :a, {initial_value: 5}], [:int8, nil], [:int8, '', {value: :a}] ] } BinData::Struct.new(params) } it "only shows non anonymous fields" do obj.field_names.must_equal [:a] end it "does not include anonymous fields in snapshot" do obj.a = 5 obj.snapshot.must_equal({a: 5}) end it "writes anonymous fields" do obj.read("\001\002\003") obj.a.clear obj.to_binary_s.must_equal_binary "\005\002\005" end end describe BinData::Struct, "with hidden fields" do let(:obj) { params = { hide: [:b, :c], fields: [ [:int8, :a], [:int8, 'b', {initial_value: 5}], [:int8, :c], [:int8, :d, {value: :b}]] } BinData::Struct.new(params) } it "only shows fields that aren't hidden" do obj.field_names.must_equal [:a, :d] end it "shows all fields when requested" do obj.field_names(true).must_equal [:a, :b, :c, :d] end it "accesses hidden fields directly" do obj.b.must_equal 5 obj.c = 15 obj.c.must_equal 15 obj.must_respond_to :b= end it "does not include hidden fields in snapshot" do obj.b = 7 obj.snapshot.must_equal({a: 0, d: 7}) end it "detects hidden fields with has_key?" do assert obj.has_key?("b") end end describe BinData::Struct, "with multiple fields" do let(:params) { { fields: [ [:int8, :a], [:int8, :b] ] } } let(:obj) { BinData::Struct.new({a: 1, b: 2}, params) } specify { obj.field_names.must_equal [:a, :b] } specify { obj.to_binary_s.must_equal_binary "\x01\x02" } it "returns num_bytes" do obj.a.num_bytes.must_equal 1 obj.b.num_bytes.must_equal 1 obj.num_bytes.must_equal 2 end it "identifies accepted parameters" do BinData::Struct.accepted_parameters.all.must_include :fields BinData::Struct.accepted_parameters.all.must_include :hide BinData::Struct.accepted_parameters.all.must_include :endian end it "clears" do obj.a = 6 obj.clear assert obj.clear? end it "clears individual elements" do obj.a = 6 obj.b = 7 obj.a.clear assert obj.a.clear? refute obj.b.clear? end it "reads elements dynamically" do obj[:a].must_equal 1 end it "handles not existing elements" do obj[:does_not_exist].must_be_nil end it "writes elements dynamically" do obj[:a] = 2 obj.a.must_equal 2 end it "implements has_key?" do assert obj.has_key?("a") end it "reads ordered" do obj.read("\x03\x04") obj.a.must_equal 3 obj.b.must_equal 4 end it "returns a snapshot" do snap = obj.snapshot assert snap.respond_to?(:a) snap.a.must_equal 1 snap.b.must_equal 2 snap.must_equal({ a: 1, b: 2 }) end it "assigns from partial hash" do obj.assign(a: 3) obj.a.must_equal 3 obj.b.must_equal 0 end it "assigns from hash" do obj.assign(a: 3, b: 4) obj.a.must_equal 3 obj.b.must_equal 4 end it "assigns from nil" do obj.assign(nil) assert obj.clear? end it "assigns from Struct" do src = BinData::Struct.new(params) src.a = 3 src.b = 4 obj.assign(src) obj.a.must_equal 3 obj.b.must_equal 4 end it "assigns from snapshot" do src = BinData::Struct.new(params) src.a = 3 src.b = 4 obj.assign(src.snapshot) obj.a.must_equal 3 obj.b.must_equal 4 end it "fails on unknown method call" do lambda { obj.does_not_exist }.must_raise NoMethodError end describe "#snapshot" do it "has ordered #keys" do obj.snapshot.keys.must_equal [:a, :b] end it "has ordered #each" do keys = [] obj.snapshot.each { |el| keys << el[0] } keys.must_equal [:a, :b] end it "has ordered #each_pair" do keys = [] obj.snapshot.each_pair { |k, v| keys << k } keys.must_equal [:a, :b] end end end describe BinData::Struct, "with nested structs" do let(:obj) { inner1 = [ [:int8, :w, {initial_value: 3}], [:int8, :x, {value: :the_val}] ] inner2 = [ [:int8, :y, {value: -> { parent.b.w }}], [:int8, :z] ] params = { fields: [ [:int8, :a, {initial_value: 6}], [:struct, :b, {fields: inner1, the_val: :a}], [:struct, :c, {fields: inner2}]] } BinData::Struct.new(params) } specify { obj.field_names.must_equal [:a, :b, :c] } it "returns num_bytes" do obj.b.num_bytes.must_equal 2 obj.c.num_bytes.must_equal 2 obj.num_bytes.must_equal 5 end it "accesses nested fields" do obj.a.must_equal 6 obj.b.w.must_equal 3 obj.b.x.must_equal 6 obj.c.y.must_equal 3 obj.c.z.must_equal 0 end it "returns correct abs_offset" do obj.b.abs_offset.must_equal 1 obj.b.w.abs_offset.must_equal 1 obj.c.abs_offset.must_equal 3 obj.c.z.abs_offset.must_equal 4 end end describe BinData::Struct, "with an endian defined" do let(:obj) { BinData::Struct.new(endian: :little, fields: [ [:uint16, :a], [:float, :b], [:array, :c, {type: :int8, initial_length: 2}], [:choice, :d, {choices: [[:uint16], [:uint32]], selection: 1}], [:struct, :e, {fields: [[:uint16, :f], [:uint32be, :g]]}], [:struct, :h, {fields: [ [:struct, :i, {fields: [[:uint16, :j]]}]]}] ]) } it "uses correct endian" do obj.a = 1 obj.b = 2.0 obj.c[0] = 3 obj.c[1] = 4 obj.d = 5 obj.e.f = 6 obj.e.g = 7 obj.h.i.j = 8 expected = [1, 2.0, 3, 4, 5, 6, 7, 8].pack('veCCVvNv') obj.to_binary_s.must_equal_binary expected end end describe BinData::Struct, "with bit fields" do let(:obj) { params = { fields: [ [:bit1le, :a], [:bit2le, :b], [:uint8, :c], [:bit1le, :d] ] } BinData::Struct.new({a: 1, b: 2, c: 3, d: 1}, params) } specify { obj.num_bytes.must_equal 3 } specify { obj.to_binary_s.must_equal_binary [0b0000_0101, 3, 1].pack("C*") } it "reads" do str = [0b0000_0110, 5, 0].pack("C*") obj.read(str) obj.a.must_equal 0 obj.b.must_equal 3 obj.c.must_equal 5 obj.d.must_equal 0 end it "has correct offsets" do obj.a.rel_offset.must_equal 0 obj.b.rel_offset.must_equal 0 obj.c.rel_offset.must_equal 1 obj.d.rel_offset.must_equal 2 end end describe BinData::Struct, "with nested endian" do it "uses correct endian" do nested_params = { endian: :little, fields: [[:int16, :b], [:int16, :c]] } params = { endian: :big, fields: [[:int16, :a], [:struct, :s, nested_params], [:int16, :d]] } obj = BinData::Struct.new(params) obj.read("\x00\x01\x02\x00\x03\x00\x00\x04") obj.a.must_equal 1 obj.s.b.must_equal 2 obj.s.c.must_equal 3 obj.d.must_equal 4 end end describe BinData::Struct, "with a search_prefix" do class AShort < BinData::Uint8; end class BShort < BinData::Uint8; end it "searches symbol prefixes" do obj = BinData::Struct.new(search_prefix: :a, fields: [ [:short, :f] ]) obj.f.class.name.must_equal "AShort" end it "searches string prefixes" do obj = BinData::Struct.new(search_prefix: "a", fields: [ [:short, :f] ]) obj.f.class.name.must_equal "AShort" end it "searches string prefixes with optional underscore" do obj = BinData::Struct.new(search_prefix: "a_", fields: [ [:short, :f] ]) obj.f.class.name.must_equal "AShort" end it "searches multiple prefixes" do obj = BinData::Struct.new(search_prefix: [:x, :a], fields: [ [:short, :f] ]) obj.f.class.name.must_equal "AShort" end it "uses parent search_prefix" do nested_params = { fields: [[:short, :f]] } obj = BinData::Struct.new(search_prefix: :a, fields: [[:struct, :s, nested_params]]) obj.s.f.class.name.must_equal "AShort" end it "searches parent search_prefix" do nested_params = { search_prefix: :x, fields: [[:short, :f]] } obj = BinData::Struct.new(search_prefix: :a, fields: [[:struct, :s, nested_params]]) obj.s.f.class.name.must_equal "AShort" end it "prioritises nested search_prefix" do nested_params = { search_prefix: :a, fields: [[:short, :f]] } obj = BinData::Struct.new(search_prefix: :b, fields: [[:struct, :s, nested_params]]) obj.s.f.class.name.must_equal "AShort" end end describe BinData::Struct, "with byte_align" do let(:obj) { params = { fields: [[:int8, :a], [:int8, :b, byte_align: 5], [:bit2, :c], [:int8, :d, byte_align: 3]] } BinData::Struct.new(params) } it "has #num_bytes" do obj.num_bytes.must_equal 10 end it "reads" do obj.read("\x01\x00\x00\x00\x00\x02\xc0\x00\x00\x04") obj.snapshot.must_equal({ a: 1, b: 2, c: 3, d: 4 }) end it "writes" do obj.assign(a: 1, b: 2, c: 3, d: 4) obj.to_binary_s.must_equal_binary "\x01\x00\x00\x00\x00\x02\xc0\x00\x00\x04" end it "has correct offsets" do obj.a.rel_offset.must_equal 0 obj.b.rel_offset.must_equal 5 obj.c.rel_offset.must_equal 6 obj.d.rel_offset.must_equal 9 end end describe BinData::Struct, "with dynamically named types" do it "instantiates" do _ = BinData::Struct.new(name: :my_struct, fields: [[:int8, :a, {initial_value: 3}]]) obj = BinData::Struct.new(fields: [[:my_struct, :v]]) obj.v.a.must_equal 3 end end bindata-2.3.5/test/int_test.rb0000755000004100000410000001046013042501567016317 0ustar www-datawww-data#!/usr/bin/env ruby require File.expand_path(File.join(File.dirname(__FILE__), "test_helper")) module AllIntegers def test_have_correct_num_bytes all_classes do |int_class| int_class.new.num_bytes.must_equal @nbytes end end def test_have_a_sensible_value_of_zero all_classes do |int_class| int_class.new.must_equal 0 end end def test_avoid_underflow all_classes do |int_class| subject = int_class.new subject.assign(min_value - 1) subject.must_equal min_value end end def test_avoid_overflow all_classes do |int_class| subject = int_class.new subject.assign(max_value + 1) subject.must_equal max_value end end def test_assign_values all_classes do |int_class| subject = int_class.new test_int = gen_test_int subject.assign(test_int) subject.must_equal test_int end end def test_assign_values_from_other_int_objects all_classes do |int_class| src = int_class.new src.assign(gen_test_int) subject = int_class.new subject.assign(src) subject.must_equal src end end def test_symmetrically_read_and_write_a_positive_number all_classes do |int_class| subject = int_class.new subject.assign(gen_test_int) subject.value_read_from_written.must_equal subject end end def test_symmetrically_read_and_write_a_negative_number all_classes do |int_class| if @signed subject = int_class.new subject.assign(-gen_test_int) subject.value_read_from_written.must_equal subject end end end def test_convert_a_positive_number_to_string all_classes do |int_class| val = gen_test_int subject = int_class.new subject.assign(val) subject.to_binary_s.must_equal_binary int_to_binary_str(val) end end def test_convert_a_negative_number_to_string all_classes do |int_class| if @signed val = -gen_test_int subject = int_class.new subject.assign(val) subject.to_binary_s.must_equal_binary int_to_binary_str(val) end end end def all_classes(&block) @ints.each_pair do |int_class, nbytes| @nbytes = nbytes yield int_class end end def min_value if @signed -max_value - 1 else 0 end end def max_value if @signed (1 << (@nbytes * 8 - 1)) - 1 else (1 << (@nbytes * 8)) - 1 end end def gen_test_int # resulting int is guaranteed to be +ve for signed or unsigned integers (0 ... @nbytes).inject(0) { |val, i| (val << 8) | ((val + 0x11) % 0x100) } end def int_to_binary_str(val) str = "".force_encoding(Encoding::BINARY) v = val & ((1 << (@nbytes * 8)) - 1) @nbytes.times do str.concat(v & 0xff) v >>= 8 end (@endian == :little) ? str : str.reverse end def create_mapping_of_class_to_nbits(endian, signed) base = signed ? "Int" : "Uint" endian_str = (endian == :little) ? "le" : "be" result = {} result[BinData.const_get("#{base}8")] = 1 (1 .. 20).each do |nbytes| nbits = nbytes * 8 class_name = "#{base}#{nbits}#{endian_str}" result[BinData.const_get(class_name)] = nbytes end result end end describe "All signed big endian integers" do include AllIntegers before do @endian = :big @signed = true @ints = create_mapping_of_class_to_nbits(@endian, @signed) end end describe "All unsigned big endian integers" do include AllIntegers before do @endian = :big @signed = false @ints = create_mapping_of_class_to_nbits(@endian, @signed) end end describe "All signed little endian integers" do include AllIntegers before do @endian = :little @signed = true @ints = create_mapping_of_class_to_nbits(@endian, @signed) end end describe "All unsigned little endian integers" do include AllIntegers before do @endian = :little @signed = false @ints = create_mapping_of_class_to_nbits(@endian, @signed) end end describe "Custom defined integers" do it "fail unless bits are a multiple of 8" do lambda { BinData::Uint7le }.must_raise NameError lambda { BinData::Uint7be }.must_raise NameError lambda { BinData::Int7le }.must_raise NameError lambda { BinData::Int7be }.must_raise NameError end end bindata-2.3.5/test/lazy_test.rb0000755000004100000410000001307213042501567016506 0ustar www-datawww-data#!/usr/bin/env ruby require File.expand_path(File.join(File.dirname(__FILE__), "test_helper")) # A mock data object with customizable fields. class MockBinDataObject def initialize(methods = {}, params = {}, parent = nil) meta = class << self ; self; end methods.each do |k,v| meta.send(:define_method, k.to_sym) { v } end @parameters = params @parent = parent end attr_accessor :parent def has_parameter?(key) @parameters.has_key?(key) end def get_parameter(key) @parameters[key] end def lazy_evaluator BinData::LazyEvaluator.new(self) end alias_method :safe_respond_to?, :respond_to? end def lazy_eval(*rest) subject.lazy_evaluator.lazy_eval(*rest) end describe BinData::LazyEvaluator, "with no parents" do subject { methods = {m1: 'm1', com: 'mC'} params = {p1: 'p1', com: 'pC'} MockBinDataObject.new(methods, params) } it "evaluates raw value when instantiated" do lazy_eval(5).must_equal 5 end it "evaluates raw value" do lazy_eval(5).must_equal 5 end it "evaluates value" do lazy_eval(lambda { 5 }).must_equal 5 end it "evaluates overrides" do lazy_eval(lambda { o1 }, o1: 'o1').must_equal 'o1' end it "does not resolve any unknown methods" do lambda { lazy_eval(lambda { unknown }) }.must_raise NameError lambda { lazy_eval(lambda { m1 }) }.must_raise NameError lambda { lazy_eval(lambda { p1 }) }.must_raise NameError end it "does not have a parent" do lazy_eval(lambda { parent }).must_be_nil end it "does not resolve #index" do lambda { lazy_eval(lambda { index }) }.must_raise NoMethodError end end describe BinData::LazyEvaluator, "with one parent" do subject { parent_methods = {m1: 'Pm1', m2: 'Pm2', com1: 'PmC', mm: 3} parent_params = {p1: 'Pp1', com1: 'PpC'} parent_obj = MockBinDataObject.new(parent_methods, parent_params) class << parent_obj def echo(a1, a2) [a1, a2] end private :m2 end methods = {m1: 'm1', com1: 'mC'} params = {p1: 'p1', com1: 'pC'} MockBinDataObject.new(methods, params, parent_obj) } it "evaluates raw value" do lazy_eval(5).must_equal 5 end it "evaluates value" do lazy_eval(lambda { 5 }).must_equal 5 end it "evaluates overrides before params" do lazy_eval(lambda { p1 }, p1: 'o1').must_equal 'o1' end it "evaluates overrides before methods" do lazy_eval(lambda { m1 }, m1: 'o1').must_equal 'o1' end it "does not resolve any unknown methods" do lambda { lazy_eval(lambda { unknown }) }.must_raise NoMethodError end it "resolves parameters in the parent" do lazy_eval(lambda { p1 }).must_equal 'Pp1' end it "resolves methods in the parent" do lazy_eval(lambda { m1 }).must_equal 'Pm1' end it "invokes methods in the parent" do lazy_eval(lambda { echo(p1, m1) }).must_equal ['Pp1', 'Pm1'] end it "invokes private methods in the parent" do lazy_eval(lambda { m2 }).must_equal 'Pm2' end it "resolves parameters in preference to methods in the parent" do lazy_eval(lambda { com1 }).must_equal 'PpC' end it "has a parent" do lazy_eval(lambda { parent }).wont_be_nil end it "does not resolve #index" do lambda { lazy_eval(lambda { index }) }.must_raise NoMethodError end end describe BinData::LazyEvaluator, "with nested parents" do subject { pparent_methods = {m1: 'PPm1', m2: 'PPm2', com1: 'PPmC'} pparent_params = {p1: 'PPp1', p2: 'PPp2', com1: 'PPpC'} pparent_obj = MockBinDataObject.new(pparent_methods, pparent_params) def pparent_obj.echo(arg) ["PP", arg] end def pparent_obj.echo2(arg) ["PP2", arg] end parent_methods = {m1: 'Pm1', com1: 'PmC', sym1: :m2, sym2: -> { m2 }} parent_params = {p1: 'Pp1', com1: 'PpC'} parent_obj = MockBinDataObject.new(parent_methods, parent_params, pparent_obj) def parent_obj.echo(arg) ["P", arg] end methods = {m1: 'm1', com1: 'mC'} params = {p1: 'p1', com1: 'pC'} MockBinDataObject.new(methods, params, parent_obj) } it "accepts symbols as a shortcut to lambdas" do lazy_eval(:p1).must_equal 'Pp1' lazy_eval(:p2).must_equal 'PPp2' lazy_eval(:m1).must_equal 'Pm1' lazy_eval(:m2).must_equal 'PPm2' end it "does not resolve any unknown methods" do lambda { lazy_eval(lambda { unknown }) }.must_raise NoMethodError end it "resolves parameters in the parent" do lazy_eval(lambda { p1 }).must_equal 'Pp1' end it "resolves methods in the parent" do lazy_eval(lambda { m1 }).must_equal 'Pm1' end it "resolves parameters in the parent's parent" do lazy_eval(lambda { p2 }).must_equal 'PPp2' end it "resolves methods in the parent's parent" do lazy_eval(lambda { m2 }).must_equal 'PPm2' end it "invokes methods in the parent" do lazy_eval(lambda { echo(m1) }).must_equal ['P', 'Pm1'] end it "invokes methods in the parent's parent" do lazy_eval(lambda { parent.echo(m1) }, { m1: 'o1'}).must_equal ['PP', 'o1'] end it "invokes methods in the parent's parent" do lazy_eval(lambda { echo2(m1) }).must_equal ['PP2', 'Pm1'] end it "resolves parameters in preference to methods in the parent" do lazy_eval(lambda { com1 }).must_equal 'PpC' end it "resolves methods in the parent explicitly" do lazy_eval(lambda { parent.m1 }).must_equal 'PPm1' end it "cascades lambdas " do lazy_eval(lambda { sym1 }).must_equal 'PPm2' lazy_eval(lambda { sym2 }).must_equal 'PPm2' end it "does not resolve #index" do lambda { lazy_eval(lambda { index }) }.must_raise NoMethodError end end bindata-2.3.5/test/virtual_test.rb0000755000004100000410000000175513042501567017222 0ustar www-datawww-data#!/usr/bin/env ruby require File.expand_path(File.join(File.dirname(__FILE__), "test_helper")) describe BinData::Virtual do let(:stream) { StringIO.new "abcdefghij" } it "must not read from any stream" do BinData::Virtual.read(stream) stream.pos.must_equal 0 end it "must not write to a stream" do obj = BinData::Virtual.new obj.to_binary_s.must_equal_binary "" end it "occupies no space" do obj = BinData::Virtual.new obj.num_bytes.must_equal 0 end it "asserts on #read" do data = [] obj = BinData::Virtual.new(assert: -> { data << 1; true }) obj.read "" data.must_equal [1] end it "asserts on #assign" do data = [] obj = BinData::Virtual.new(assert: -> { data << 1; true }) obj.assign("foo") data.must_equal [1] end it "assigns a value" do obj = BinData::Virtual.new(3) obj.must_equal 3 end it "accepts the :value parameter" do obj = BinData::Virtual.new(value: 3) obj.must_equal 3 end end bindata-2.3.5/test/string_test.rb0000755000004100000410000001732613042501567017043 0ustar www-datawww-data#!/usr/bin/env ruby require File.expand_path(File.join(File.dirname(__FILE__), "test_helper")) describe BinData::String, "with mutually exclusive parameters" do it ":value and :initial_value" do params = {value: "", initial_value: ""} lambda { BinData::String.new(params) }.must_raise ArgumentError end it ":length and :read_length" do params = {length: 5, read_length: 5} lambda { BinData::String.new(params) }.must_raise ArgumentError end it ":value and :length" do params = {value: "", length: 5} lambda { BinData::String.new(params) }.must_raise ArgumentError end end describe BinData::String, "when assigning" do let(:small) { BinData::String.new(length: 3, pad_byte: "A") } let(:large) { BinData::String.new(length: 5, pad_byte: "B") } it "copies data from small to large" do large.assign(small) large.must_equal "AAABB" end it "copies data from large to small" do small.assign(large) small.must_equal "BBB" end end describe BinData::String do let(:obj) { BinData::String.new("testing") } it "compares with regexp" do (/es/ =~ obj).must_equal 1 end it "compares with regexp" do (obj =~ /es/).must_equal 1 end end describe BinData::String, "with :read_length" do let(:obj) { BinData::String.new(read_length: 5) } specify { obj.num_bytes.must_equal 0 } specify { obj.value.must_equal "" } it "reads :read_length bytes" do obj.read("abcdefghij") obj.must_equal "abcde" end it "remembers :read_length after value is cleared" do obj.assign("abc") obj.num_bytes.must_equal 3 obj.clear obj.read("abcdefghij") obj.must_equal "abcde" end end describe BinData::String, "with :length" do let(:obj) { BinData::String.new(length: 5) } specify { obj.num_bytes.must_equal 5 } specify { obj.value.must_equal "\0\0\0\0\0" } it "retains :length after value is set" do obj.assign("abcdefghij") obj.num_bytes.must_equal 5 end it "reads :length bytes" do obj.read("abcdefghij") obj.must_equal "abcde" end it "pads values less than :length" do obj.assign("abc") obj.must_equal "abc\0\0" end it "accepts values exactly :length" do obj.assign("abcde") obj.must_equal "abcde" end it "truncates values greater than :length" do obj.assign("abcdefghij") obj.must_equal "abcde" end end describe BinData::String, "with :read_length and :initial_value" do let(:obj) { BinData::String.new(read_length: 5, initial_value: "abcdefghij") } specify { obj.num_bytes.must_equal 10 } specify { obj.value.must_equal "abcdefghij" } it "uses :read_length for reading" do io = StringIO.new("ABCDEFGHIJKLMNOPQRST") obj.read(io) io.pos.must_equal 5 end it "forgets :initial_value after reading" do obj.read("ABCDEFGHIJKLMNOPQRST") obj.num_bytes.must_equal 5 obj.must_equal "ABCDE" end end describe BinData::String, "with :read_length and :value" do let(:obj) { BinData::String.new(read_length: 5, value: "abcdefghij") } specify { obj.num_bytes.must_equal 10 } specify { obj.value.must_equal "abcdefghij" } it "uses :read_length for reading" do io = StringIO.new("ABCDEFGHIJKLMNOPQRST") obj.read(io) io.pos.must_equal 5 end describe "after reading" do before(:each) do obj.read("ABCDEFGHIJKLMNOPQRST") end it "is not affected by :read_length after reading" do obj.num_bytes.must_equal 10 obj.must_equal "abcdefghij" end it "returns read value while reading" do obj.stub :reading?, true do obj.must_equal "ABCDE" end end end end describe BinData::String, "with :length and :initial_value" do let(:obj) { BinData::String.new(length: 5, initial_value: "abcdefghij") } specify { obj.num_bytes.must_equal 5 } specify { obj.value.must_equal "abcde" } it "forgets :initial_value after reading" do io = StringIO.new("ABCDEFGHIJKLMNOPQRST") obj.read(io) io.pos.must_equal 5 obj.num_bytes.must_equal 5 obj.must_equal "ABCDE" end end describe BinData::String, "with :pad_byte" do it "accepts a numeric value for :pad_byte" do str = BinData::String.new(length: 5, pad_byte: 6) str.assign("abc") str.must_equal "abc\x06\x06" end it "accepts a character for :pad_byte" do str = BinData::String.new(length: 5, pad_byte: "R") str.assign("abc") str.must_equal "abcRR" end it "does not accept a string for :pad_byte" do params = {length: 5, pad_byte: "RR"} lambda { BinData::String.new(params) }.must_raise ArgumentError end end describe BinData::String, "with :trim_padding" do it "set false is the default" do str1 = BinData::String.new(length: 5) str2 = BinData::String.new(length: 5, trim_padding: false) str1.assign("abc") str2.assign("abc") str1.must_equal "abc\0\0" str2.must_equal "abc\0\0" end describe "trim padding set" do let(:obj) { BinData::String.new(pad_byte: 'R', trim_padding: true) } it "trims the value" do obj.assign("abcRR") obj.must_equal "abc" end it "does not affect num_bytes" do obj.assign("abcRR") obj.num_bytes.must_equal 5 end it "trims if last char is :pad_byte" do obj.assign("abcRR") obj.must_equal "abc" end it "does not trim if value contains :pad_byte not at the end" do obj.assign("abcRRde") obj.must_equal "abcRRde" end end end describe BinData::String, "with :pad_front" do it "set false is the default" do str1 = BinData::String.new(length: 5) str2 = BinData::String.new(length: 5, pad_front: false) str1.assign("abc") str2.assign("abc") str1.must_equal "abc\0\0" str2.must_equal "abc\0\0" end it "pads to the front" do str = BinData::String.new(length: 5, pad_byte: 'R', pad_front: true) str.assign("abc") str.must_equal "RRabc" end it "can alternatively be accesses by :pad_left" do str = BinData::String.new(length: 5, pad_byte: 'R', pad_left: true) str.assign("abc") str.must_equal "RRabc" end describe "and :trim_padding" do let(:obj) { BinData::String.new(length: 5, pad_byte: 'R', pad_front: true, trim_padding: true) } it "assigns" do obj.assign("abc") obj.must_equal "abc" end it "has to_binary_s" do obj.assign("abc") obj.to_binary_s.must_equal_binary "RRabc" end it "reads" do obj.read "RRabc" obj.must_equal "abc" end end end describe BinData::String, "with Ruby 1.9 encodings" do class UTF8String < BinData::String def snapshot super.force_encoding('UTF-8') end end let(:obj) { UTF8String.new } let(:binary_str) { "\xC3\x85\xC3\x84\xC3\x96" } let(:utf8_str) { binary_str.dup.force_encoding('UTF-8') } it "stores assigned values as binary" do obj.assign(utf8_str) obj.to_binary_s.must_equal_binary binary_str end it "stores read values as binary" do obj = UTF8String.new(read_length: binary_str.bytesize) obj.read(binary_str) obj.to_binary_s.must_equal_binary binary_str end it "returns values in correct encoding" do obj.assign(utf8_str) obj.snapshot.must_equal utf8_str end it "has correct num_bytes" do obj.assign(utf8_str) obj.num_bytes.must_equal binary_str.bytesize end end describe BinData::String, "warnings" do it "warns if has :asserted_value but no :length" do obj = BinData::String.new(asserted_value: "ABC") obj.must_warn "obj does not have a :read_length parameter - returning empty string" do lambda { obj.read("abcde") }.must_raise BinData::ValidityError end end it "warns if has :value but no :read_length" do obj = BinData::String.new(value: "ABC") obj.must_warn "obj does not have a :read_length parameter - returning empty string" do obj.read("abcde") end end end bindata-2.3.5/test/delayed_io_test.rb0000755000004100000410000001252513042501567017627 0ustar www-datawww-data#!/usr/bin/env ruby require File.expand_path(File.join(File.dirname(__FILE__), "test_helper")) describe BinData::DelayedIO, "when instantiating" do describe "with no mandatory parameters supplied" do it "raises an error" do args = {} lambda { BinData::DelayedIO.new(args) }.must_raise ArgumentError end end describe "with some but not all mandatory parameters supplied" do it "raises an error" do args = {read_abs_offset: 3} lambda { BinData::DelayedIO.new(args) }.must_raise ArgumentError end end it "fails if a given type is unknown" do args = {type: :does_not_exist, length: 3} lambda { BinData::DelayedIO.new(args) }.must_raise BinData::UnRegisteredTypeError end it "accepts BinData::Base as :type" do obj = BinData::Int8.new(initial_value: 5) array = BinData::DelayedIO.new(type: obj, read_abs_offset: 3) array.must_equal 5 end end describe BinData::DelayedIO, "subclassed with a single type" do class IntDelayedIO < BinData::DelayedIO endian :big default_parameter read_abs_offset: 5 uint16 end it "behaves as type" do obj = IntDelayedIO.new(3) obj.must_equal 3 end it "does not read" do obj = IntDelayedIO.read "\001\002\003\004\005\006\007" assert obj.clear? end it "does not do_num_bytes" do obj = IntDelayedIO.new(3) obj.do_num_bytes.must_equal 0 end it "does num_bytes" do obj = IntDelayedIO.new(3) obj.num_bytes.must_equal 2 end it "does not write" do io = StringIO.new obj = IntDelayedIO.new(3) obj.write(io) io.value.must_equal "" end it "uses read_abs_offset" do obj = IntDelayedIO.new(3) obj.abs_offset.must_equal 5 obj.rel_offset.must_equal 5 end it "uses abs_offset if set" do obj = IntDelayedIO.new(3) obj.abs_offset = 10 obj.abs_offset.must_equal 10 obj.rel_offset.must_equal 10 end it "must call #read before #read_now!" do obj = IntDelayedIO.new(3) lambda { obj.read_now! }.must_raise IOError end it "reads explicitly" do obj = IntDelayedIO.read "\001\002\003\004\005\006\007" obj.read_now! obj.must_equal 0x0607 end it "must call #write before #write_now!" do obj = IntDelayedIO.new(3) lambda { obj.write_now! }.must_raise IOError end it "writes explicitly" do io = StringIO.new "\001\002\003\004\005\006\007\010\011" obj = IntDelayedIO.new(3) obj.write(io) obj.write_now! io.value.must_equal "\001\002\003\004\005\000\003\010\011" end it "writes explicitly after setting abs_offset" do io = StringIO.new "\001\002\003\004\005\006\007\010\011" obj = IntDelayedIO.new(7) obj.write(io) obj.abs_offset = 1 obj.write_now! io.value.must_equal "\001\000\007\004\005\006\007\010\011" end end describe BinData::DelayedIO, "subclassed with multiple types" do class StringDelayedIO < BinData::DelayedIO endian :big default_parameter read_abs_offset: 5 uint16 :len, value: -> { str.length } string :str, read_length: :len end it "behaves as type" do obj = StringDelayedIO.new(str: "hello") obj.snapshot.must_equal({len: 5, str: "hello"}) end it "reads explicitly" do obj = StringDelayedIO.read "\001\002\003\004\005\000\003abc\013" obj.read_now! obj.snapshot.must_equal({len: 3, str: "abc"}) end it "writes explicitly" do io = StringIO.new "\001\002\003\004\005\006\007\010\011\012\013\014\015" obj = StringDelayedIO.new(str: "hello") obj.write(io) obj.write_now! io.value.must_equal "\001\002\003\004\005\000\005hello\015" end end describe BinData::DelayedIO, "inside a Record" do class DelayedIORecord < BinData::Record endian :little uint16 :str_length, value: -> { str.length } delayed_io :str, read_abs_offset: 4 do string read_length: :str_length end delayed_io :my_int, read_abs_offset: 2 do uint16 initial_value: 7 end end it "reads" do obj = DelayedIORecord.read "\x05\x00\x03\x0012345" obj.num_bytes.must_equal 2 obj.snapshot.must_equal({str_length: 0, str: "", my_int: 7}) end it "reads explicitly" do obj = DelayedIORecord.read "\x05\x00\x03\x0012345" obj.str.read_now! obj.my_int.read_now! obj.num_bytes.must_equal 2 obj.snapshot.must_equal({str_length: 5, str: "12345", my_int: 3}) end it "writes" do obj = DelayedIORecord.new(str: "abc", my_int: 2) io = StringIO.new obj.write(io) obj.str.write_now! obj.my_int.write_now! io.value.must_equal "\x03\x00\x02\x00abc" end end describe BinData::DelayedIO, "with auto_call" do class AutoCallDelayedIORecord < BinData::Record auto_call_delayed_io uint8 :a delayed_io :b, read_abs_offset: 1 do uint8 end end it "class reads" do obj = AutoCallDelayedIORecord.read "\x01\x02" obj.snapshot.must_equal({a: 1, b: 2}) end it "reads" do obj = AutoCallDelayedIORecord.new obj.read "\x01\x02" obj.snapshot.must_equal({a: 1, b: 2}) end it "writes" do obj = AutoCallDelayedIORecord.new(a: 1, b: 2) io = StringIO.new obj.write(io) io.value.must_equal "\x01\x02" end it "to_binary_s" do obj = AutoCallDelayedIORecord.new(a: 1, b: 2) obj.to_binary_s.must_equal_binary "\x01\x02" end it "num_bytes" do obj = AutoCallDelayedIORecord.new(a: 1, b: 2) obj.num_bytes.must_equal 2 end end bindata-2.3.5/.gitignore0000644000004100000410000000002213042501567015140 0ustar www-datawww-dataGemfile.lock wiki bindata-2.3.5/BSDL0000644000004100000410000000237313042501567013632 0ustar www-datawww-dataCopyright (C) 2007-2012 Dion Mendel. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. bindata-2.3.5/README.md0000644000004100000410000000365313042501567014444 0ustar www-datawww-data# What is BinData? [![Version ](http://img.shields.io/gem/v/bindata.svg) ](https://rubygems.org/gems/bindata) [![Travis CI ](http://img.shields.io/travis/dmendel/bindata/master.svg) ](https://travis-ci.org/dmendel/bindata) [![Quality ](http://img.shields.io/codeclimate/github/dmendel/bindata.svg)](https://codeclimate.com/github/dmendel/bindata) [![Coverage ](http://img.shields.io/coveralls/dmendel/bindata.svg) ](https://coveralls.io/r/dmendel/bindata) Do you ever find yourself writing code like this? ```ruby io = File.open(...) len = io.read(2).unpack("v") name = io.read(len) width, height = io.read(8).unpack("VV") puts "Rectangle #{name} is #{width} x #{height}" ``` It’s ugly, violates DRY and feels like you’re writing Perl, not Ruby. There is a better way. Here’s how you’d write the above using BinData. ```ruby class Rectangle < BinData::Record endian :little uint16 :len string :name, :read_length => :len uint32 :width uint32 :height end io = File.open(...) r = Rectangle.read(io) puts "Rectangle #{r.name} is #{r.width} x #{r.height}" ``` BinData provides a _declarative_ way to read and write structured binary data. This means the programmer specifies *what* the format of the binary data is, and BinData works out *how* to read and write data in this format. It is an easier (and more readable) alternative to ruby's `#pack` and `#unpack` methods. BinData makes it easy to create new data types. It supports all the common primitive datatypes that are found in structured binary data formats. Support for dependent and variable length fields is built in. # Installation $ gem install bindata or if running ruby 1.8 $ gem install bindata -v '~> 1.8.0' # Documentation [Read the wiki](http://github.com/dmendel/bindata/wiki). # Contact If you have any queries / bug reports / suggestions, please contact me (Dion Mendel) via email at bindata@dm9.info bindata-2.3.5/COPYING0000644000004100000410000000437513042501567014222 0ustar www-datawww-dataBinData is copyrighted free software by Dion Mendel . You can redistribute it and/or modify it under either the terms of the 2-clause BSDL (see the file BSDL), or the conditions below: 1. You may make and give away verbatim copies of the source form of the software without restriction, provided that you duplicate all of the original copyright notices and associated disclaimers. 2. You may modify your copy of the software in any way, provided that you do at least ONE of the following: a) place your modifications in the Public Domain or otherwise make them Freely Available, such as by posting said modifications to Usenet or an equivalent medium, or by allowing the author to include your modifications in the software. b) use the modified software only within your corporation or organization. c) give non-standard binaries non-standard names, with instructions on where to get the original software distribution. d) make other distribution arrangements with the author. 3. You may distribute the software in object code or binary form, provided that you do at least ONE of the following: a) distribute the binaries and library files of the software, together with instructions (in the manual page or equivalent) on where to get the original distribution. b) accompany the distribution with the machine-readable source of the software. c) give non-standard binaries non-standard names, with instructions on where to get the original software distribution. d) make other distribution arrangements with the author. 4. You may modify and include the part of the software into any other software (possibly commercial). 5. The scripts and library files supplied as input to or produced as output from the software do not automatically fall under the copyright of the software, but belong to whomever generated them, and may be sold commercially, and may be aggregated with this software. 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.