### Ruby pack/unpack Example Source: https://github.com/dmendel/bindata/wiki/Home This is an example of how to read structured binary data using Ruby's built-in pack and unpack methods. It is considered less readable and more verbose than using BinData. ```ruby io = File.open(...) len = io.read(2).unpack("v")[0] name = io.read(len) width, height = io.read(8).unpack("VV") puts "Rectangle #{name} is #{width} x #{height}" ``` -------------------------------- ### Install BinData Gem Source: https://github.com/dmendel/bindata/wiki/Installation Use this command to install the BinData gem via RubyGems. ```bash gem install bindata ``` -------------------------------- ### Define a Basic Bindata Record Source: https://github.com/dmendel/bindata/wiki/QuickStart Start by subclassing BinData::Record to define a new binary data structure. ```ruby class MyFancyFormat < BinData::Record end ``` -------------------------------- ### Bit-based Integers Packing - Example A Source: https://github.com/dmendel/bindata/wiki/PrimitiveTypes Demonstrates how bit-based integers are packed into bytes when interspersed with byte-aligned fields. Adjacent bitfields are packed contiguously. ```ruby class A < BinData::Record bit4 :a uint8 :b bit4 :c end Data is stored as: AAAA0000 BBBBBBBB CCCC0000 ``` -------------------------------- ### Bit-based Integers Packing - Example B Source: https://github.com/dmendel/bindata/wiki/PrimitiveTypes Shows how bit-based integers are packed when they are adjacent, resulting in more compact storage compared to when byte-aligned fields are present. ```ruby class B < BinData::Record bit4 :a bit8 :b bit4 :c end Data is stored as: AAAABBBB BBBBCCCC ``` -------------------------------- ### Implementing GZIP Format with BinData Source: https://context7.com/dmendel/bindata/llms.txt This example demonstrates implementing the GZIP format (RFC 1952) using BinData, combining various features like custom primitives, bit flags, and conditional fields. ```ruby require 'bindata' require 'zlib' class Gzip < BinData::Record class Mtime < BinData::Primitive uint32le :time def set(v) = self.time = v.to_i def get = Time.at(time) end endian :little uint16 :ident, asserted_value: 0x8b1f uint8 :compression_method, initial_value: 8 bit3 :freserved, asserted_value: 0 bit1 :fcomment, value: -> { comment.length > 0 ? 1 : 0 } bit1 :ffilename, value: -> { file_name.length > 0 ? 1 : 0 } bit1 :fextra, value: -> { extra.len > 0 ? 1 : 0 } bit1 :fcrc16, value: 0 bit1 :ftext mtime :mtime uint8 :extra_flags uint8 :os, initial_value: 255 struct :extra, onlyif: -> { fextra.nonzero? } do uint16 :len, value: -> { data.length } string :data, read_length: :len end stringz :file_name, onlyif: -> { ffilename.nonzero? } stringz :comment, onlyif: -> { fcomment.nonzero? } count_bytes_remaining :bytes_remaining string :compressed_data, read_length: -> { bytes_remaining - footer.num_bytes } struct :footer do uint32 :crc32 uint32 :uncompressed_size end end # Write a gzip file g = Gzip.new g.file_name = "hello.txt" g.mtime = Time.now content = "Hello, BinData!" g.compressed_data = Zlib::Deflate.deflate(content)[2..-5] g.footer.crc32 = Zlib::crc32(content) g.footer.uncompressed_size = content.bytesize File.open("hello.gz", "wb") { |f| g.write(f) } # Read it back g2 = Gzip.read(File.binread("hello.gz")) g2.file_name #=> "hello.txt" g2.footer.uncompressed_size #=> 15 g2.comment? #=> false ``` -------------------------------- ### Pascal String Implementation with BinData Source: https://github.com/dmendel/bindata/wiki/Records Implements a Pascal-style string where the length is stored in a preceding byte. This example shows how to define it for both reading and writing. ```ruby class PascalString < BinData::Record uint8 :len, value: -> { data.length } string :data, read_length: :len end ``` -------------------------------- ### BinData Declarative Binary Data Parsing Source: https://github.com/dmendel/bindata/blob/master/README.md This example shows how to define a binary data structure using BinData's declarative syntax. It simplifies reading and writing structured binary data compared to manual unpack operations. ```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}" ``` -------------------------------- ### Traditional Ruby Binary Data Parsing Source: https://github.com/dmendel/bindata/blob/master/README.md This example demonstrates the traditional Ruby approach to reading structured binary data using File.open, unpack, and manual length calculations. It is verbose and less readable. ```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}" ``` -------------------------------- ### BinData 1.8.x vs 2.0.0 String Conversion Source: https://github.com/dmendel/bindata/blob/master/NEWS.rdoc Illustrates the change in string-to-symbol conversion for method names between BinData versions 1.8.x and 2.0.0 and later. The example shows how reading binary data into a Record class results in a Hash with symbols as keys in newer versions. ```ruby 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} ``` -------------------------------- ### Create and use custom transforms with BinData sections Source: https://context7.com/dmendel/bindata/llms.txt Define custom IO transforms by inheriting from `BinData::IO::Transform` and implementing `read` and `write` methods. This example shows a simple XOR transform. ```ruby class XorTransform < BinData::IO::Transform def initialize(key) = super().tap { @key = key } def read(n) = chain_read(n).bytes.map { |b| (b ^ @key).chr }.join def write(d) = chain_write(d.bytes.map { |b| (b ^ @key).chr }.join) end enc = BinData::Section.new(transform: -> { XorTransform.new(0xff) }, type: [:string, { read_length: 5 }]) enc.read("\x97\x9A\x93\x93\x90") #=> "hello" ``` -------------------------------- ### Renamed Method: to_binary_s Source: https://github.com/dmendel/bindata/blob/master/NEWS.rdoc The method for getting the binary string representation has been renamed from #to_s to #to_binary_s. Update your code to use the new method. ```ruby # Old: obj.to_s # New: obj.to_binary_s ``` -------------------------------- ### BinData Common Parameters: :initial_value Source: https://context7.com/dmendel/bindata/llms.txt The :initial_value parameter sets a default value for a field that can be overwritten. This example shows a string field with an initial value. ```ruby # :initial_value — default that can be overwritten greeting = BinData::String.new(initial_value: "hello ") greeting + "world" #=> "hello world" greeting.assign("hi ") greeting + "world" #=> "hi world" ``` -------------------------------- ### Define IP Protocol Header with Bit and Byte Integers Source: https://context7.com/dmendel/bindata/llms.txt Example of defining a complex binary structure like an IP header using a mix of bit-based and byte-based integers. Supports various endianness and bitfield definitions. ```ruby # Internet Protocol header using mixed byte- and bit-based integers class IP_PDU < BinData::Record endian :big bit4 :version, value: 4 bit4 :header_length uint8 :tos uint16 :total_length uint16 :ident bit3 :flags bit13 :frag_offset uint8 :ttl uint8 :protocol uint16 :checksum uint32 :src_addr uint32 :dest_addr string :options, read_length: -> { header_length_in_bytes - 20 } string :data, read_length: -> { total_length - header_length_in_bytes } def header_length_in_bytes header_length * 4 end end pkt = IP_PDU.read(raw_bytes) pkt.version #=> 4 pkt.src_addr #=> integer representation of source IP pkt.data #=> payload string ``` -------------------------------- ### Virtual Fields: :assert Parameter Source: https://context7.com/dmendel/bindata/llms.txt Introduces virtual fields, which have no binary representation but enable cross-field assertions and named validity checks. This example defines a virtual field 'valid' to check if a UnitVector is normalized. ```ruby class UnitVector < BinData::Record endian :little double :x double :y virtual :valid, assert: -> { (x**2 + y**2 - 1.0).abs < 0.000001 } end vec = UnitVector.new(x: 1.0, y: 0.0) vec.valid.assert! # passes vec2 = UnitVector.new(x: 0.3, y: 0.2) vec2.valid.assert! #=> raises BinData::ValidityError: assertion failed for obj.valid ``` -------------------------------- ### Initialize and Use Int16be and FloatBe Source: https://github.com/dmendel/bindata/wiki/PrimitiveTypes Demonstrates initializing an Int16be with a value and converting it to binary. Also shows reading a FloatBe from binary data and performing arithmetic operations. ```ruby int16 = BinData::Int16be.new(941) int16.to_binary_s #=> "\003\255" fl = BinData::FloatBe.read("\100\055\370\124") #=> 2.71828174591064 fl.num_bytes #=> 4 fl * int16 #=> 2557.90320057996 ``` -------------------------------- ### Instantiation with Value and Parameters Source: https://github.com/dmendel/bindata/wiki/CommonOperations A shortcut to instantiate and assign a value in one step. ```APIDOC ## ::new(value, parameters) ### Description Instantiates a BinData object and assigns an initial value using provided parameters. ### Method Class method ### Parameters * **value** (any) - The initial value to assign. * **parameters** (Hash) - A hash of parameters to configure the BinData object. ### Request Example ```ruby obj = BinData::Array.new([1, 2, 3], type: :int8) ``` This is equivalent to: ```ruby obj = BinData::Array.new(type: :int8) obj.assign([1, 2, 3]) ``` ``` -------------------------------- ### Instantiation with Parameters Source: https://github.com/dmendel/bindata/wiki/CommonOperations Creates a BinData object with specified parameters. ```APIDOC ## ::new(parameters) ### Description Creates a BinData object with specified parameters. ### Method Class method ### Parameters * **parameters** (Hash) - A hash of parameters to configure the BinData object. ### Request Example ```ruby obj = BinData::Array.new(type: :int8, initial_length: 6) ``` ``` -------------------------------- ### Replaced Offset Calculation Source: https://github.com/dmendel/bindata/blob/master/NEWS.rdoc struct.offset_of("field") should be replaced with struct.field.offset. This provides a more direct way to get the offset. ```ruby # Old: struct.offset_of("field") # New: struct.field.offset ``` -------------------------------- ### Choice Initialization with Hash and Array Source: https://github.com/dmendel/bindata/wiki/CompoundTypes Demonstrates creating a BinData::Choice instance using both a hash and an array for the ':choices' parameter, showing how to specify types and parameters. ```ruby type1 = [:string, {value: "Type1"}] type2 = [:string, {value: "Type2"}] choices = {5 => type1, 17 => type2} obj = BinData::Choice.new(choices: choices, selection: 5) obj # => "Type1" choices = [ type1, type2 ] obj = BinData::Choice.new(choices: choices, selection: 1) obj # => "Type2" ``` -------------------------------- ### Initialize String with :initial_value Source: https://github.com/dmendel/bindata/wiki/PrimitiveTypes Shows how to initialize a BinData::String with an initial value and how to append to it. ```ruby obj = BinData::String.new(initial_value: "hello ") obj + "world" #=> "hello world" obj.assign("good-bye " ) obj + "world" #=> "good-bye world" ``` -------------------------------- ### Instantiate BinData::Array with initial value and parameters Source: https://github.com/dmendel/bindata/wiki/CommonOperations A shortcut to instantiate and assign a value in one step for BinData objects. ```ruby obj = BinData::Array.new([1, 2, 3], type: :int8) ``` -------------------------------- ### User-Defined Primitive Types Source: https://context7.com/dmendel/bindata/llms.txt Inherit from `BinData::Primitive` and implement `#get` / `#set` to expose a custom Ruby interface over an internal binary layout. ```APIDOC ## User-Defined Primitive Types — `BinData::Primitive` Inherit from `BinData::Primitive` and implement `#get` / `#set` to expose a custom Ruby interface over an internal binary layout. This allows the type to accept `:initial_value`, `:value`, `:assert`, etc. ```ruby 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 class Favourites < BinData::Record pascal_string :language, initial_value: "ruby" pascal_string :os, initial_value: "linux" end f = Favourites.new f.os = "freebsd" f.to_binary_s #=> "\x04ruby\x07freebsd" ``` ``` -------------------------------- ### Get number of bytes for BinData::Array element Source: https://github.com/dmendel/bindata/wiki/CommonOperations Returns the number of bytes required for the binary data representation of a BinData object or its elements. ```ruby arr = BinData::Array.new(type: :uint16be, initial_length: 5) arr[0].num_bytes #=> 2 arr.num_bytes #=> 10 ``` -------------------------------- ### Instantiate and Populate a Bindata Record Source: https://github.com/dmendel/bindata/wiki/QuickStart After defining a Bindata record, you can instantiate it, set its fields, and serialize it to binary. ```ruby class MyFancyFormat < BinData::Record stringz :comment uint8 :len, value: -> { data.length } array :data, type: :int32be, initial_length: :len end obj = MyFancyFormat.new obj.comment = "this is my comment" obj.data = [2, 4, 6] obj.to_binary_s #=> "this is my comment\x00\x03\x00\x00\x00\x02\x00\x00\x00\x04\x00\x00\x00\x06" ``` -------------------------------- ### Accessing Underlying Primitive Value Source: https://github.com/dmendel/bindata/blob/master/NEWS.rdoc When a BinData object is returned from struct/array access, use the #value method to get the underlying primitive type. ```ruby obj.value ``` -------------------------------- ### Instantiate BinData::Stringz with initial value Source: https://github.com/dmendel/bindata/wiki/CommonOperations Create a BinData object and assign an initial value during instantiation. ```ruby obj = BinData::Stringz.new("hello") ``` -------------------------------- ### BinData Common Parameters: :assert Source: https://context7.com/dmendel/bindata/llms.txt The :assert parameter raises a BinData::ValidityError if a condition is false. This example validates a string against expected magic bytes. ```ruby # :assert — raises BinData::ValidityError if condition is false magic = BinData::String.new(assert: "BM") # validates BMP magic bytes magic.read("BM") #=> "BM" magic.read("XX") #=> raises BinData::ValidityError ``` -------------------------------- ### Instantiation with Value Source: https://github.com/dmendel/bindata/wiki/CommonOperations Creates a BinData object and initializes it with a given value. ```APIDOC ## ::new(value) ### Description Creates a BinData object and assigns an initial value. ### Method Class method ### Parameters * **value** (any) - The initial value to assign to the BinData object. ### Request Example ```ruby obj = BinData::Stringz.new("hello") ``` This is equivalent to: ```ruby obj = BinData::Stringz.new obj.assign("hello") ``` ``` -------------------------------- ### Get snapshot of BinData::Uint8 Source: https://github.com/dmendel/bindata/wiki/CommonOperations Returns the value of a BinData object as primitive Ruby objects. The output can be used for serialization or as a reduced memory representation. ```ruby obj = BinData::Uint8.new(2) obj.class #=> BinData::Uint8 obj + 3 #=> 5 obj.snapshot #=> 2 obj.snapshot.class #=> Fixnum ``` -------------------------------- ### Instantiate BinData::Array with initial length Source: https://github.com/dmendel/bindata/wiki/CommonOperations Use ::new with parameters to create a BinData object with a specified type and initial length. ```ruby obj = BinData::Array.new(type: :int8, initial_length: 6) ``` -------------------------------- ### BinData Common Parameters: :value Source: https://context7.com/dmendel/bindata/llms.txt The :value parameter defines an immutable computed field. This example uses a lambda to set the length of an array based on its data. ```ruby # :value — immutable computed field class IntList < BinData::Record uint8 :len, value: -> { data.length } array :data, type: :uint32be end list = IntList.new list.data.push(10, 20, 30) list.len #=> 3 ``` -------------------------------- ### Add a String Field to a Bindata Record Source: https://github.com/dmendel/bindata/wiki/QuickStart Add fields to your BinData::Record subclass to represent attributes of the file format. This example adds a null-terminated string. ```ruby class MyFancyFormat < BinData::Record stringz :comment end ``` -------------------------------- ### Instantiate and assign BinData::Stringz Source: https://github.com/dmendel/bindata/wiki/CommonOperations This is an alternative way to instantiate and assign a value to a BinData object. ```ruby obj = BinData::Stringz.new obj.assign("hello") ``` -------------------------------- ### Replaced Field Num Bytes Calculation Source: https://github.com/dmendel/bindata/blob/master/NEWS.rdoc struct.num_bytes("field") should be replaced with struct.field.num_bytes. This provides a direct way to get the number of bytes for a field. ```ruby # Old: struct.num_bytes("field") # New: struct.field.num_bytes ``` -------------------------------- ### BinData Common Parameters: :asserted_value Source: https://context7.com/dmendel/bindata/llms.txt The :asserted_value parameter is a shortcut for combining :assert and :value for constant fields. This example creates an unsigned 32-bit integer with a specific asserted value. ```ruby # :asserted_value — assert + value combined (shortcut for constants) obj = BinData::Uint32be.new(asserted_value: 0xCAFEBABE) ``` -------------------------------- ### Array Initialization with :initial_length Source: https://github.com/dmendel/bindata/wiki/CompoundTypes Creates a BinData::Array with a specified initial length and reads data into it. The snapshot method shows the current state of the array after reading, truncating excess data. ```ruby obj = BinData::Array.new(type: :int8, initial_length: 4) obj.read("\002\003\004\005\006\007") obj.snapshot #=> [2, 3, 4, 5] ``` -------------------------------- ### Replaced Array Element Num Bytes Calculation Source: https://github.com/dmendel/bindata/blob/master/NEWS.rdoc array.num_bytes(n) should be replaced with array[n].num_bytes. This provides a direct way to get the number of bytes for an array element. ```ruby # Old: array.num_bytes(n) # New: array[n].num_bytes ``` -------------------------------- ### Get relative offset of BinData::Int8 in a Record Source: https://github.com/dmendel/bindata/wiki/CommonOperations Returns the offset of a BinData object relative to its parent structure. Useful for understanding data layout within complex structures. ```ruby class Tuple < BinData::Record string :name, length: 10 int8 :a int8 :b end t = Tuple.new t.b.rel_offset #=> 11 ``` -------------------------------- ### User Defined Primitive: PascalString Source: https://github.com/dmendel/bindata/wiki/PrimitiveTypes Defines a custom string type that stores its length as a preceding uint8. This example shows how to create a user-defined type that behaves like a BinData::Primitive. ```ruby class PascalString < BinData::Record uint8 :len, value: -> { data.length } string :data, read_length: :len end ``` ```ruby class Favourites < BinData::Record pascal_string :language, initial_value: "ruby" pascal_string :os, initial_value: "linux" end f = Favourites.new f.os = "freebsd" f.to_binary_s #=> "\004ruby\007freebsd" ``` ```ruby 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 ``` -------------------------------- ### Instantiate and assign BinData::Array Source: https://github.com/dmendel/bindata/wiki/CommonOperations This is an alternative way to instantiate and assign a value to a BinData::Array object. ```ruby obj = BinData::Array.new(type: :int8) obj.assign([1, 2, 3]) ``` -------------------------------- ### Custom Primitive Type: PascalString Source: https://context7.com/dmendel/bindata/llms.txt Define a custom primitive type by inheriting from `BinData::Primitive` and implementing `#get` and `#set`. This allows a custom Ruby interface over a specific binary layout. ```ruby 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 ``` -------------------------------- ### Choice with copy_on_change and Binary Output Source: https://github.com/dmendel/bindata/wiki/CompoundTypes Illustrates the use of ':copy_on_change' within a choice that selects between little-endian and big-endian integers. It shows how changing the selection affects the binary output. ```ruby class MyNumber < BinData::Record int8 :is_big_endian choice :data, selection: -> { is_big_endian != 0 }, copy_on_change: true do int32le false int32be true end end obj = MyNumber.new obj.is_big_endian = 1 obj.data = 5 obj.to_binary_s #=> "\001\000\000\000\005" obj.is_big_endian = 0 obj.to_binary_s #=> "\000\005\000\000\000" ``` -------------------------------- ### User Defined Primitive: LispBool Source: https://github.com/dmendel/bindata/wiki/PrimitiveTypes Implements a boolean type where true is represented by an int8 value of 1 and false by -1. Demonstrates custom get and set methods for external representation. ```ruby class LispBool < BinData::Primitive int8 :val def get case self.val when 1 :t when -1 nil else nil # unknown value, default to false end end def set(v) case v when :t self.val = 1 when nil self.val = -1 else self.val = -1 # unknown value, default to false end end end b = LispBool.new b.assign(:t) b.to_binary_s #=> "\001" b.read("\xff") b.snapshot #=> nil ``` -------------------------------- ### BinData Record Definition and Usage Source: https://github.com/dmendel/bindata/wiki/Home This snippet demonstrates defining a binary data structure using BinData and then reading data from an IO stream according to that structure. It shows the declarative and readable nature of 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}" ``` -------------------------------- ### Get absolute offset of BinData::Int8 in nested Records Source: https://github.com/dmendel/bindata/wiki/CommonOperations Returns the offset of a BinData object with respect to the most distant ancestor structure. Useful for arrays and records to determine absolute positions. ```ruby class Tuple < BinData::Record int8 :a int8 :b end class MyRecord < BinData::Record tuple :t1 tuple :t2 end obj = MyRecord.new obj.t2.b.abs_offset #=> 3 obj.t2.b.rel_offset #=> 1 ``` -------------------------------- ### Determine Stream Length with count_bytes_remaining Source: https://github.com/dmendel/bindata/wiki/AdvancedIO Use `count_bytes_remaining` to get the number of bytes left in a stream. This is useful for formats where lengths are implied, but requires seekable streams and may not stream well. ```ruby class StringWithChecksum < BinData::Record count_bytes_remaining :bytes_remaining string :the_string, read_length: -> { bytes_remaining - 2 } int16le :checksum end ``` -------------------------------- ### Clone BinData Repository Source: https://github.com/dmendel/bindata/wiki/Installation Use this command to clone the BinData source code from its GitHub repository. ```bash git clone https://github.com/dmendel/bindata.git ``` -------------------------------- ### General Choice Syntax with Block Form Source: https://github.com/dmendel/bindata/wiki/CompoundTypes Presents the general structure for defining a choice within a BinData::Record using the block form, specifying types and parameters for each option. ```ruby class MyRecord < BinData::Record choice :name, selection: -> { ... } do type key, param1: "foo", param2: "bar" ... # option 1 type key, param1: "foo", param2: "bar" ... # option 2 end end ``` -------------------------------- ### Using Custom Primitive Types in Records Source: https://context7.com/dmendel/bindata/llms.txt Custom primitive types can be used like any other BinData type within a `BinData::Record`. The example shows a `Favourites` record using the `PascalString` type. ```ruby class Favourites < BinData::Record pascal_string :language, initial_value: "ruby" pascal_string :os, initial_value: "linux" end f = Favourites.new f.os = "freebsd" f.to_binary_s #=> "\x04ruby\x07freebsd" ``` -------------------------------- ### Choice with Default Selection Source: https://github.com/dmendel/bindata/wiki/CompoundTypes Demonstrates how to use a ':default' key within a choice definition to specify a fallback type when the selection value does not match any other key. This simplifies handling of default cases. ```ruby class MyNumber < BinData::Record int8 :is_big_endian choice :data, selection: :is_big_endian, copy_on_change: true do int32le 0 # zero is little endian int32be :default # anything else is big endian end end ``` -------------------------------- ### Read from IO (Instance Method) Source: https://github.com/dmendel/bindata/wiki/CommonOperations Reads and assigns binary data from an IO stream to an existing BinData object. ```APIDOC ## #read(io) ### Description Reads and assigns binary data from `io` to the object. ### Method Instance method ### Parameters * **io** (IO or String) - The source to read binary data from. ### Request Example ```ruby obj = BinData::Stringz.new obj.read("string 1\0string 2\0") obj #=> "string 1" ``` ``` -------------------------------- ### Dynamic Type Creation Source: https://context7.com/dmendel/bindata/llms.txt When the binary format is not known until runtime, use `BinData::Struct` to construct types on the fly and optionally register them by name. ```APIDOC ## Dynamic Type Creation — `BinData::Struct` When the binary format is not known until runtime, use `BinData::Struct` to construct types on the fly and optionally register them by name. ```ruby # Dynamically create and name a type BinData::Struct.new(name: :point3d, fields: [[:float_le, :x], [:float_le, :y], [:float_le, :z]]) ``` -------------------------------- ### Array with Anonymous Struct for Polygon Points Source: https://github.com/dmendel/bindata/wiki/CompoundTypes Defines a Polygon structure where an array of points is represented as an anonymous struct. This example demonstrates dynamic array length based on a preceding field and how to manipulate array elements. ```ruby class Polygon < BinData::Record endian :little uint8 :num_points, value: -> { points.length } array :points, initial_length: :num_points do double :x double :y end end triangle = Polygon.new triangle.points[0].assign(x: 1, y: 2) triangle.points[1].x = 3 triangle.points[1].y = 4 triangle.points << {x: 5, y: 6} ``` -------------------------------- ### Assigning Values During Instantiation in BinData Source: https://github.com/dmendel/bindata/blob/master/NEWS.rdoc Illustrates the updated syntax for assigning values to BinData objects upon instantiation, introduced in version 1.3.0. The new syntax allows passing the value as the first argument. ```ruby obj = BinData::Stringz.new("abc", :max_length => 10) ``` -------------------------------- ### Nested Records: Inline Anonymous Structs Source: https://context7.com/dmendel/bindata/llms.txt Shows how to use inline anonymous struct blocks for grouping related fields within a record. This example defines a Polygon with an array of points, where each point is a struct with x and y coordinates. ```ruby class Polygon < BinData::Record endian :little uint8 :num_points, value: -> { points.length } array :points, initial_length: :num_points do double :x double :y end end triangle = Polygon.new triangle.points[0].assign(x: 0.0, y: 0.0) triangle.points[1].assign(x: 1.0, y: 0.0) triangle.points << { x: 0.5, y: 1.0 } triangle.num_points #=> 3 triangle.to_binary_s #=> packed binary of 1 byte + 3×(double,double) ``` -------------------------------- ### Traditional Binary Parsing vs. Bindata Source: https://github.com/dmendel/bindata/wiki/QuickStart Compares a manual Ruby binary parsing method with the equivalent Bindata record definition. Use Bindata for more readable and maintainable binary data handling. ```ruby def read_fancy_format(io) comment, len, rest = io.read.unpack("Z*Ca*") data = rest.unpack("N#{len}") {:comment => comment, :len => len, :data => *data} end ``` ```ruby class MyFancyFormat < BinData::Record stringz :comment uint8 :len array :data, type: :int32be, initial_length: :len end ``` -------------------------------- ### Endianness: :big_and_little Keyword Source: https://context7.com/dmendel/bindata/llms.txt Demonstrates using the :big_and_little endianness keyword to automatically generate both big-endian and little-endian variants of a record. It also shows how endianness cascades into nested types. ```ruby # Automatic endian resolution for custom types class Coord < BinData::Record endian :big_and_little # creates CoordBe and CoordLe int16 :x int16 :y end c_big = Coord.new(endian: :big, x: 1, y: 2) c_little = Coord.new(endian: :little, x: 1, y: 2) c_big.to_binary_s #=> "\x00\x01\x00\x02" c_little.to_binary_s #=> "\x01\x00\x02\x00" # In a parent record, CoordLe is chosen automatically class Rectangle < BinData::Record endian :little coord :upper_left # resolves to CoordLe coord :lower_right end ``` -------------------------------- ### Choice Syntax: Parameter vs. Block Form Source: https://github.com/dmendel/bindata/wiki/CompoundTypes Illustrates two ways to define a choice type: using the ':choices' parameter with a hash, and using the block form. The block form is generally preferred for clarity. ```ruby class MyInt16 < BinData::Record uint8 :e, assert: -> { value == 0 || value == 1 } choice :int, selection: :e, choices: {0 => :int16be, 1 => :int16le} end -vs- class MyInt16 < BinData::Record uint8 :e, assert: -> { value == 0 || value == 1 } choice :int, selection: :e do int16be 0 int16le 1 end end ``` -------------------------------- ### BinData::Array: Fixed-Length Array Source: https://context7.com/dmendel/bindata/llms.txt Demonstrates creating a fixed-length array of elements. The ':initial_length' parameter specifies the exact number of elements to read. ```ruby # Fixed-length array obj = BinData::Array.new(type: :int8, initial_length: 4) obj.read("\x01\x02\x03\x04\x05\x06") obj.snapshot #=> [1, 2, 3, 4] ``` -------------------------------- ### Array Syntax: :type parameter with parameters vs. block form Source: https://github.com/dmendel/bindata/wiki/CompoundTypes Illustrates declaring an array with parameterized types using both the :type parameter and the block form. The block form is more readable when array elements have specific configurations. ```ruby class A < BinData::Record array :numbers, type: [:uint8, {initial_value: :index}], initial_length: 3 end -vs- class A < BinData::Record array :numbers, initial_length: 3 do uint8 initial_value: :index end end ``` -------------------------------- ### Inspect BinData object Source: https://github.com/dmendel/bindata/wiki/CommonOperations Returns a human-readable representation of a BinData object, which is a shortcut to #snapshot.inspect. ```ruby obj = BinData::Uint8.new(2) obj.inspect ``` -------------------------------- ### To Binary String Source: https://github.com/dmendel/bindata/wiki/CommonOperations Returns the binary data representation of the object as a string. ```APIDOC ## #to_binary_s ### Description Returns the binary data representation of this object as a string. ### Method Instance method ### Request Example ```ruby obj = BinData::Uint16be.new(4660) obj.to_binary_s #=> "\022\064" ``` ``` -------------------------------- ### Slow Initialization in BinData Source: https://github.com/dmendel/bindata/wiki/FAQ Avoid slow initialization in BinData for imperative use cases by reusing objects with #clear or employing the prototype pattern. For optimal performance, prefer a declarative approach. ```ruby 999.times do |i| foo = Foo.new(:bar => "baz") ... end ``` ```ruby foo = Foo.new(:bar => "baz") 999.times do foo.clear ... end ``` ```ruby prototype = Foo.new(:bar => "baz") 999.times do foo = prototype.new ... end ``` ```ruby class FooList < BinData::Array default_parameter :initial_length => 999 foo :bar => "baz" end array = FooList.new array.each { ... } ``` -------------------------------- ### Sized String with Fixed Length - Padding Source: https://github.com/dmendel/bindata/wiki/PrimitiveTypes Illustrates how a sized string is padded with null bytes when the assigned value is shorter than its specified fixed length. ```ruby obj = BinData::String.new(length: 6) obj.assign("abcd") obj #=> "abcd\000\000" ``` -------------------------------- ### Use :asserted_value as Shortcut Source: https://github.com/dmendel/bindata/wiki/PrimitiveTypes Demonstrates the :asserted_value parameter as a shorthand for when both :assert and :value parameters have the same value. ```ruby obj = BinData::Uint32be.new(assert: 42, value: 42) obj = BinData::Uint32be.new(asserted_value: 42) ``` -------------------------------- ### Custom BinData Type Initialization Source: https://github.com/dmendel/bindata/blob/master/NEWS.rdoc Demonstrates the required change for custom BinData types subclassing BinData::Base or BinData::BasePrimitive, as of version 1.3.0. Instance variables must now be initialized in #initialize_instance instead of #initialize. ```ruby class MyType < BinData::Base register_self def initialize_instance @var1 = ... @var2 = ... end ... end ``` -------------------------------- ### Handle Unused Bitfields with resume_byte_alignment Source: https://github.com/dmendel/bindata/wiki/AdvancedTopics Use resume_byte_alignment to automatically skip to the next byte boundary after a sequence of bitfields. This avoids manual counting of unused bits. ```ruby class RecordWithBitfield < BinData::Record bit1 :foo bit1 :bar bit1 :baz resume_byte_alignment stringz :qux end ``` -------------------------------- ### Dynamic Bitfield Definition Source: https://github.com/dmendel/bindata/wiki/PrimitiveTypes Illustrates how to define bitfields with a dynamic number of bits using the ':nbits' parameter, referencing another field for the bit count. ```ruby class Rectangle < BinData::Record bit5 :bit_length sbit :xmin, nbits: :bit_length sbit :xmax, nbits: :bit_length sbit :ymin, nbits: :bit_length sbit :ymax, nbits: :bit_length end ``` -------------------------------- ### Array Syntax: :type parameter vs. block form Source: https://github.com/dmendel/bindata/wiki/CompoundTypes Demonstrates two ways to declare an array's element type: using the :type parameter or a block form. The :type parameter is clearer for simple types, while the block form is better for types with parameters. ```ruby class A < BinData::Record array :numbers, type: :uint8, initial_length: 3 end -vs- class A < BinData::Record array :numbers, initial_length: 3 do uint8 end end ``` -------------------------------- ### Access buffer content and padding in BinData Source: https://context7.com/dmendel/bindata/llms.txt After reading a buffer, you can access its parsed contents and calculate the amount of padding using `num_bytes` and `raw_num_bytes`. ```ruby blk = ConfigBlock.read(raw_data) blk.data.version #=> parsed version # Padding amount: blk.data.num_bytes - blk.data.raw_num_bytes ``` -------------------------------- ### Read from IO (Class Method) Source: https://github.com/dmendel/bindata/wiki/CommonOperations Creates a BinData object and reads its value from a given IO stream or string. ```APIDOC ## ::read(io) ### Description Creates a BinData object and reads its value from the given string or `IO`. The newly created object is returned. ### Method Class method ### Parameters * **io** (IO or String) - The source to read binary data from. ### Request Example ```ruby obj = BinData::Int8.read("\xff") obj.snapshot #=> -1 ``` ``` -------------------------------- ### Dynamic Type Creation with BinData::Struct Source: https://context7.com/dmendel/bindata/llms.txt Use `BinData::Struct` to construct types dynamically at runtime when the binary format is not known beforehand. Types can also be registered by name. ```ruby # Dynamically create and name a type BinData::Struct.new(name: :point3d, fields: [[:float_le, :x], [:float_le, :y], [:float_le, :z]]) ``` -------------------------------- ### Snapshot Source: https://github.com/dmendel/bindata/wiki/CommonOperations Returns the value of the object as primitive Ruby objects. ```APIDOC ## #snapshot ### Description Returns the value of this object as primitive Ruby objects (numerics, strings, arrays, and hashes). The output of `#snapshot` may be useful for serialization or as a reduced memory usage representation. ### Method Instance method ### Response * **any** - The value of the object as primitive Ruby types. ### Request Example ```ruby obj = BinData::Uint8.new(2) obj.snapshot #=> 2 obj.snapshot.class #=> Fixnum ``` ``` -------------------------------- ### Inspect Object Source: https://github.com/dmendel/bindata/wiki/CommonOperations Returns a human-readable representation of the object. ```APIDOC ## #inspect ### Description Returns a human readable representation of this object. This is a shortcut to `#snapshot.inspect`. ### Method Instance method ### Response * **String** - A human-readable representation of the object. ### Request Example ```ruby obj = BinData::Uint8.new(2) obj.inspect #=> "#" ``` ``` -------------------------------- ### Trace Reading with BinData Source: https://github.com/dmendel/bindata/blob/master/NEWS.rdoc Use this to trace values when reading data. Debugging output is written to the specified IO stream. ```ruby BinData::trace_reading(STDERR) do obj_not_quite_working_correctly.read(io) end ``` -------------------------------- ### Renamed Class: BinData::Record Source: https://github.com/dmendel/bindata/blob/master/NEWS.rdoc BinData::MultiValue has been renamed to BinData::Record. Update any references to the old class name. ```ruby # Old: BinData::MultiValue # New: BinData::Record ``` -------------------------------- ### Use :value Parameter for Constants Source: https://github.com/dmendel/bindata/wiki/PrimitiveTypes Demonstrates using the :value parameter to set a constant value for a primitive. Assigning a new value to a primitive with a :value parameter does not change its effective value. ```ruby pi = BinData::FloatLe.new(value: Math::PI) pi.assign(3) puts pi #=> 3.14159265358979 ``` -------------------------------- ### To Hexadecimal String Source: https://github.com/dmendel/bindata/wiki/CommonOperations Returns the binary data representation of the object as an ASCII hexadecimal string. ```APIDOC ## #to_hex ### Description Returns the binary data representation of this object as an ASCII hexadecimal string. ### Method Instance method ### Request Example ```ruby obj = BinData::Uint16be.new(4660) obj.to_hex #=> "1234" ``` ``` -------------------------------- ### Convert BinData::Uint16be to binary string Source: https://github.com/dmendel/bindata/wiki/CommonOperations Returns the binary data representation of a BinData object as a string. ```ruby obj = BinData::Uint16be.new(4660) obj.to_binary_s #=> "\022\064" ``` -------------------------------- ### Read binary data into BinData::Stringz Source: https://github.com/dmendel/bindata/wiki/CommonOperations Reads and assigns binary data from an IO object into an existing BinData object. ```ruby obj = BinData::Stringz.new obj.read("string 1\0string 2\0") obj #=> "string 1" ``` -------------------------------- ### Common Operations Source: https://context7.com/dmendel/bindata/llms.txt All BinData types share a uniform API for instantiation, I/O, and inspection, including reading, writing, serialization, and state management. ```APIDOC ## Common Operations — Reading, Writing, and Inspecting All BinData types share a uniform API for instantiation, I/O, and inspection. ```ruby # Instantiation obj = BinData::Array.new([1, 2, 3], type: :int8) # value + params shortcut # Class-level read (factory) rec = MyRecord.read(File.binread("file.bin")) rec = MyRecord.read(io_object) # Instance read / write obj.read("\x01\x02\x03") File.open("out.bin", "wb") { |f| obj.write(f) } # Binary serialization obj.to_binary_s #=> binary String obj.to_hex #=> "010203" (hex ASCII) # State obj.num_bytes #=> byte size of serialized form obj.snapshot #=> plain Ruby hash/array/primitive (no BinData wrappers) obj.clear #=> reset to initial state obj.clear? #=> true if in initial state # Offsets (useful for debugging struct layouts) rec.some_field.rel_offset #=> offset within immediate parent rec.some_field.abs_offset #=> offset from outermost ancestor # Assign arr = BinData::Array.new(type: :uint8) arr.assign([10, 20, 30]) arr.snapshot #=> [10, 20, 30] ``` ``` -------------------------------- ### BinData Common Operations: Instantiation and Reading Source: https://context7.com/dmendel/bindata/llms.txt BinData types share a uniform API for instantiation and reading from binary data. Class-level read methods can parse directly from files or I/O objects. ```ruby # Instantiation obj = BinData::Array.new([1, 2, 3], type: :int8) # value + params shortcut # Class-level read (factory) rec = MyRecord.read(File.binread("file.bin")) rec = MyRecord.read(io_object) ``` -------------------------------- ### Handle Fixed-Length and Null-Terminated Strings Source: https://context7.com/dmendel/bindata/llms.txt BinData supports fixed-length strings with padding/trimming and null-terminated C-style strings. Custom padding bytes and trimming behavior can be specified. ```ruby # Fixed-length string with custom padding obj = BinData::String.new(length: 8, pad_byte: 'X', trim_padding: true) obj.assign("hi") obj.snapshot #=> "hi" (trimmed on read) obj.to_binary_s #=> "hiXXXXXX" (padded on write) # Null-terminated string ts = BinData::Stringz.new ts.read("hello\x00world") ts #=> "hello" ts.num_bytes #=> 6 (includes the null terminator) # String with encoding override class UTF8String < BinData::String def snapshot super.force_encoding('UTF-8') end end s = UTF8String.new("\xC3\x85\xC3\x84\xC3\x96") s #=> "ÅÄÖ" ``` -------------------------------- ### Dynamically Create a BinData Type Source: https://github.com/dmendel/bindata/wiki/AdvancedTopics Use BinData::Struct.new to dynamically create a new type with specified fields. This is useful when the record format is not known until runtime. ```ruby # Dynamically create my_new_type BinData::Struct.new(name: :my_new_type, fields: [ [:int8, :a], [:int8, :b] ]) # Create an array of these types array = BinData::Array.new(type: :my_new_type) ``` -------------------------------- ### BinData::Array: Read Until Sentinel Source: https://context7.com/dmendel/bindata/llms.txt Shows how to create an array that reads elements until a specific sentinel value or condition is met. The ':read_until' parameter takes a lambda that evaluates to true when the sentinel is encountered. ```ruby # Read until sentinel obj2 = BinData::Array.new(type: :int8, read_until: -> { element == 0 }) obj2.read("\x05\x03\x07\x00\x09") obj2.snapshot #=> [5, 3, 7, 0] ``` -------------------------------- ### Parameterizing User-Defined Types: Default Parameters Source: https://context7.com/dmendel/bindata/llms.txt Use `default_parameter` to define optional configuration parameters with default values. These can be overridden during instantiation. ```ruby # Default parameters (overridable) class IntArray < BinData::Array default_parameters type: :uint16be, initial_length: 5 end IntArray.new.size #=> 5 IntArray.new(initial_length: 10).size #=> 10 ``` -------------------------------- ### Replaced Registry Instance Access Source: https://github.com/dmendel/bindata/blob/master/NEWS.rdoc Registry.instance should be replaced with RegisteredClasses. Update your code to use the new access method. ```ruby # Old: Registry.instance # New: RegisteredClasses ```