1
mirror of https://github.com/rapid7/metasploit-framework synced 2024-10-29 18:07:27 +01:00

use the bit-struct gem

removed vendored copy of bit-struct and use the gem
instead

MS-1699
This commit is contained in:
David Maloney 2016-06-28 15:58:47 -05:00
parent 0a83b34a85
commit dcddd2d671
No known key found for this signature in database
GPG Key ID: DEDBA9DC3A913DB2
18 changed files with 4 additions and 2224 deletions

View File

@ -6,6 +6,7 @@ PATH
activerecord (~> 4.2.6)
activesupport (~> 4.2.6)
bcrypt
bit-struct
filesize
jsobfu
json
@ -82,6 +83,7 @@ GEM
rspec-expectations (>= 2.99)
thor (~> 0.19)
bcrypt (3.1.11)
bit-struct (0.15.0)
builder (3.2.2)
capybara (2.7.1)
addressable

View File

@ -1,15 +0,0 @@
# -*- coding: binary -*-
# A Convenience to load all field classes and yaml handling.
# XXX: Pretty certian this monkeypatch isn't required in Metasploit.
if "a"[0].kind_of? Fixnum
unless Fixnum.methods.include? :ord
class Fixnum
def ord; self; end
end
end
end
require 'bit-struct/bit-struct'
require 'bit-struct/fields'
require 'bit-struct/yaml'

View File

@ -1,187 +0,0 @@
= BitStruct
Class for packed binary data stored in ruby Strings. BitStruct accessors, generated from user declared fields, use pack/unpack to treat substrings as fields with a specified portable format.
Field types include:
* signed and unsigned integer (1..16 bits, or 24, 32, 40, 48... bits)
* numeric fields (signed, unsigned, float) can be designated as any of the following endians: little, big, native, network (default)
* fixed point, with arbitrary scale factor
* fixed length character array
* null-terminated character array for printable text
* octets (hex and decimal representation options; useful for IP and MAC addrs)
* float
* nested BitStruct
* vectors of embedded BitStructs
* free-form "rest" field (e.g., for the variable-size payload of a packet)
Field options (specifiable as :foo => val or "foo" => val) include:
* *display_name*: used in BitStruct#inspect_detailed and BitStruct#describe outputs.
* *default*: default field value
* *format*: alternate format string for inspect
* *endian*: for byte ordering of numeric fields (unsigned, signed, float): little, big, native, network (default)
* *fixed*: float stored as fixed-point integer, with specified scale factor
== Installation
For .gem:
gem install bit-struct
For .tgz, unpack and then:
ruby install.rb config
ruby install.rb setup
ruby install.rb install
== Uses
BitStruct is useful for defining packets used in network protocols. This is especially useful for raw IP--see examples/ping-recv.rb. All multibyte numeric fields are stored by default in network order.
BitStruct is most efficient when your data is primarily treated as a binary string, and only secondarily treated as a data structure. (For instance, you are routing packets from one socket to another, possibly looking at one or two fields as it passes through or munging some headers.) If accessor operations are a bottleneck, a better approach is to define a class that wraps an array and uses pack/unpack when the object needs to behave like a binary string.
== Features
* Extensible with user-defined field classes.
* Fields are fully introspectable and can be defined programmatically.
* BitStruct.describe prints out documentation of all the fields of a BitStruct subclass, based on declarations. This is useful for communicating with developers who are not using ruby, but need to talk the same protocols. See Example, below.
* Fields are inherited by subclasses. (The free-form "rest" field does not inherit, because it usually represents a payload whose structure is defined in subclasses using the fixed-size fields.)
* BitStruct#inspect and BitStruct#inspect_detailed can be used for prettified display of contents. (More generally, BitStruct#inspect takes some options that control formatting and detail level.) See Example, below.
* BitStruct inherits from String, so all the usual methods are available, and string-sharing (copy-on-write) is in effect.
* Easy access to a "prototype" instance of each BitStruct subclass, from which all instances of that subclass are initialized as a copy (in the absence of other initialization parameters, such as a hash, a string, or a block). See BitStruct.initial_value, and BitStruct#initialize. See Example, below.
* Easy conversion to and from hashes, using BitStruct#to_h and BitStruct.new.
* BitStructs can persist using Marshal (a BitStruct is after all just a string) or using YAML (with human readable representation of the fields).
* Includes tests, examples, and rdoc API documentation.
== Limitations
* Fields that are not aligned on byte boundaries may cross no more than two bytes boundaries. (See examples/byte-bdy.rb.)
* No variable length fields (except the #rest field).
== Future plans
* Currently, the library is written in pure ruby. The implementation uses Array#pack and String#unpack calls, as well as shifting and masking in pure ruby. Future versions will optionally generate a customized C extension for better efficiency.
* A debug mode in which a class identifier is prepended to every BitStruct, so that protocol errors can be detected. (This feature has been implemented in an app that uses BitStruct, but needs to be refactored into the BitStruct library itself.)
* Remove field size and alignment limitations.
== Example
An IP packet can be defined and used like this:
require 'bit-struct'
class IP < BitStruct
unsigned :ip_v, 4, "Version"
unsigned :ip_hl, 4, "Header length"
unsigned :ip_tos, 8, "TOS"
unsigned :ip_len, 16, "Length"
unsigned :ip_id, 16, "ID"
unsigned :ip_off, 16, "Frag offset"
unsigned :ip_ttl, 8, "TTL"
unsigned :ip_p, 8, "Protocol"
unsigned :ip_sum, 16, "Checksum"
octets :ip_src, 32, "Source addr"
octets :ip_dst, 32, "Dest addr"
rest :body, "Body of message"
note " rest is application defined message body"
initial_value.ip_v = 4
initial_value.ip_hl = 5
end
ip = IP.new
ip.ip_tos = 0
ip.ip_len = 0
ip.ip_id = 0
ip.ip_off = 0
ip.ip_ttl = 255
ip.ip_p = 255
ip.ip_sum = 0
ip.ip_src = "192.168.1.4"
ip.ip_dst = "192.168.1.255"
ip.body = "This is the payload text."
ip.ip_len = ip.length
puts ip.inspect
puts "-"*50
puts ip.inspect_detailed
puts "-"*50
puts IP.describe
(Note that you can also construct an IP packet by passing a string to new, or by passing a hash of <tt>field,value</tt> pairs, or by providing a block that is yielded the new BitStruct.)
The output of this fragment is:
#<IP ip_v=4, ip_hl=5, ip_tos=0, ip_len=45, ip_id=0, ip_off=0, ip_ttl=255, ip_p=255, ip_sum=0, ip_src="192.168.1.4", ip_dst="192.168.1.255", body="This is the payload text.">
--------------------------------------------------
IP:
Version = 4
Header length = 5
TOS = 0
Length = 45
ID = 0
Frag offset = 0
TTL = 255
Protocol = 255
Checksum = 0
Source addr = "192.168.1.4"
Dest addr = "192.168.1.255"
Body of message = "This is the payload text."
--------------------------------------------------
Description of IP Packet:
byte: type name [size] description
----------------------------------------------------------------------
@0: unsigned ip_v [ 4b] Version
@0: unsigned ip_hl [ 4b] Header length
@1: unsigned ip_tos [ 8b] TOS
@2: unsigned ip_len [ 16b] Length
@4: unsigned ip_id [ 16b] ID
@6: unsigned ip_off [ 16b] Frag offset
@8: unsigned ip_ttl [ 8b] TTL
@9: unsigned ip_p [ 8b] Protocol
@10: unsigned ip_sum [ 16b] Checksum
@12: octets ip_src [ 32b] Source addr
@16: octets ip_dst [ 32b] Dest addr
rest is application defined message body
== Web site
The current version of this software can be found at http://redshift.sourceforge.net/bit-struct.
== License
This software is distributed under the Ruby license. See http://www.ruby-lang.org.
== Author
Joel VanderWerf, mailto:vjoel@users.sourceforge.net
Copyright (c) 2005-2009, Joel VanderWerf.

View File

@ -1,575 +0,0 @@
# -*- coding: binary -*-
# Class for packed binary data, with defined bitfields and accessors for them.
# See {intro.txt}[link:../doc/files/intro_txt.html] for an overview.
#
# Data after the end of the defined fields is accessible using the +rest+
# declaration. See examples/ip.rb. Nested fields can be declared using +nest+.
# See examples/nest.rb.
#
# Note that all string methods are still available: length, grep, etc.
# The String#replace method is useful.
#
class BitStruct < String
VERSION = "0.13.6"
class Field
# Offset of field in bits.
attr_reader :offset
# Length of field in bits.
attr_reader :length
alias size length
# Name of field (used for its accessors).
attr_reader :name
# Options, such as :default (varies for each field subclass).
# In general, options can be provided as strings or as symbols.
attr_reader :options
# Display name of field (used for printing).
attr_reader :display_name
# Default value.
attr_reader :default
# Format for printed value of field.
attr_reader :format
# Subclasses can override this to define a default for all fields of this
# class, not just the one currently being added to a BitStruct class, a
# "default default" if you will. The global default, if #default returns
# nil, is to fill the field with zero. Most field classes just let this
# default stand. The default can be overridden per-field when a BitStruct
# class is defined.
def self.default; nil; end
# Used in describe.
def self.class_name
@class_name ||= name[/\w+$/]
end
# Used in describe. Can be overridden per-subclass, as in NestedField.
def class_name
self.class.class_name
end
# Yield the description of this field, as an array of 5 strings: byte
# offset, type, name, size, and description. The opts hash may have:
#
# :expand :: if the value is true, expand complex fields
#
# (Subclass implementations may yield more than once for complex fields.)
#
def describe opts
bits = size
if bits > 32 and bits % 8 == 0
len_str = "%dB" % (bits/8)
else
len_str = "%db" % bits
end
byte_offset = offset / 8 + (opts[:byte_offset] || 0)
yield ["@%d" % byte_offset, class_name, name, len_str, display_name]
end
# Options are _display_name_, _default_, and _format_ (subclasses of Field
# may add other options).
def initialize(offset, length, name, opts = {})
@offset, @length, @name, @options =
offset, length, name, opts
@display_name = opts[:display_name] || opts["display_name"]
@default = opts[:default] || opts["default"] || self.class.default
@format = opts[:format] || opts["format"]
end
# Inspect the value of this field in the specified _obj_.
def inspect_in_object(obj, opts)
val = obj.send(name)
str =
begin
val.inspect(opts)
rescue ArgumentError # assume: "wrong number of arguments (1 for 0)"
val.inspect
end
(f=@format) ? (f % str) : str
end
# Normally, all fields show up in inspect, but some, such as padding,
# should not.
def inspectable?; true; end
end
NULL_FIELD = Field.new(0, 0, :null, :display_name => "null field")
# Raised when a field is added after an instance has been created. Fields
# cannot be added after this point.
class ClosedClassError < StandardError; end
# Raised if the chosen field name is not allowed, either because another
# field by that name exists, or because a method by that name exists.
class FieldNameError < StandardError; end
@default_options = {}
@initial_value = nil
@closed = nil
@rest_field = nil
@note = nil
class << self
def inherited cl
cl.instance_eval do
@initial_value = nil
@closed = nil
@rest_field = nil
@note = nil
end
end
# ------------------------
# :section: field access methods
#
# For introspection and metaprogramming.
#
# ------------------------
# Return the list of fields for this class.
def fields
@fields ||= self == BitStruct ? [] : superclass.fields.dup
end
# Return the list of fields defined by this class, not inherited
# from the superclass.
def own_fields
@own_fields ||= []
end
# Add a field to the BitStruct (usually, this is only used internally).
def add_field(name, length, opts = {})
round_byte_length ## just to make sure this has been calculated
## before adding anything
name = name.to_sym
if @closed
raise ClosedClassError, "Cannot add field #{name}: " +
"The definition of the #{self.inspect} BitStruct class is closed."
end
if fields.find {|f|f.name == name}
raise FieldNameError, "Field #{name} is already defined as a field."
end
if instance_methods(true).find {|m| m == name}
if opts[:allow_method_conflict] || opts["allow_method_conflict"]
warn "Field #{name} is already defined as a method."
else
raise FieldNameError,"Field #{name} is already defined as a method."
end
end
field_class = opts[:field_class]
prev = fields[-1] || NULL_FIELD
offset = prev.offset + prev.length
field = field_class.new(offset, length, name, opts)
field.add_accessors_to(self)
fields << field
own_fields << field
@bit_length += field.length
@round_byte_length = (bit_length/8.0).ceil
if @initial_value
diff = @round_byte_length - @initial_value.length
if diff > 0
@initial_value << "\0" * diff
end
end
field
end
def parse_options(ary, default_name, default_field_class) # :nodoc:
opts = ary.grep(Hash).first || {}
opts = default_options.merge(opts)
opts[:display_name] = ary.grep(String).first || default_name
opts[:field_class] = ary.grep(Class).first || default_field_class
opts
end
# Get or set the hash of default options for the class, which apply to all
# fields. Changes take effect immediately, so can be used alternatingly with
# blocks of field declarations. If +h+ is provided, update the default
# options with that hash. Default options are inherited.
#
# This is especially useful with the <tt>:endian => val</tt> option.
def default_options h = nil
@default_options ||= superclass.default_options.dup
if h
@default_options.merge! h
end
@default_options
end
# Length, in bits, of this object.
def bit_length
@bit_length ||= fields.inject(0) {|a, f| a + f.length}
end
# Length, in bytes (rounded up), of this object.
def round_byte_length
@round_byte_length ||= (bit_length/8.0).ceil
end
def closed! # :nodoc:
@closed = true
end
def field_by_name name
@field_by_name ||= {}
field = @field_by_name[name]
unless field
field = fields.find {|f| f.name == name}
@field_by_name[name] = field if field
end
field
end
end
# Return the list of fields for this class.
def fields
self.class.fields
end
# Return the rest field for this class.
def rest_field
self.class.rest_field
end
# Return the field with the given name.
def field_by_name name
self.class.field_by_name name
end
# ------------------------
# :section: metadata inspection methods
#
# Methods to textually describe the format of a BitStruct subclass.
#
# ------------------------
class << self
# Default format for describe. Fields are byte, type, name, size,
# and description.
DESCRIBE_FORMAT = "%8s: %-12s %-14s[%4s] %s"
# Can be overridden to use a different format.
def describe_format
DESCRIBE_FORMAT
end
# Textually describe the fields of this class of BitStructs.
# Returns a printable table (array of line strings), based on +fmt+,
# which defaults to #describe_format, which defaults to +DESCRIBE_FORMAT+.
def describe(fmt = nil, opts = {})
if fmt.kind_of? Hash
opts = fmt; fmt = nil
end
if block_given?
fields.each do |field|
field.describe(opts) do |desc|
yield desc
end
end
nil
else
fmt ||= describe_format
result = []
unless opts[:omit_header]
result << fmt % ["byte", "type", "name", "size", "description"]
result << "-"*70
end
fields.each do |field|
field.describe(opts) do |desc|
result << fmt % desc
end
end
unless opts[:omit_footer]
result << @note if @note
end
result
end
end
# Subclasses can use this to append a string (or several) to the #describe
# output. Notes are not cumulative with inheritance. When used with no
# arguments simply returns the note string
def note(*str)
@note = str unless str.empty?
@note
end
end
# ------------------------
# :section: initialization and conversion methods
#
# ------------------------
# Initialize the string with the given string or bitstruct, or with a hash of
# field=>value pairs, or with the defaults for the BitStruct subclass, or
# with an IO or other object with a #read method. Fields can be strings or
# symbols. Finally, if a block is given, yield the instance for modification
# using accessors.
def initialize(value = nil) # :yields: instance
self << self.class.initial_value
case value
when Hash
value.each do |k, v|
send "#{k}=", v
end
when nil
else
if value.respond_to?(:read)
value = value.read(self.class.round_byte_length)
end
self[0, value.length] = value
end
self.class.closed!
yield self if block_given?
end
DEFAULT_TO_H_OPTS = {
:convert_keys => :to_sym,
:include_rest => true
}
# Returns a hash of {name=>value,...} for each field. By default, include
# the rest field.
# Keys are symbols derived from field names using +to_sym+, unless
# <tt>opts[:convert_keys]<\tt> is set to some other method name.
def to_h(opts = DEFAULT_TO_H_OPTS)
converter = opts[:convert_keys] || :to_sym
fields_for_to_h = fields
if opts[:include_rest] and (rest_field = self.class.rest_field)
fields_for_to_h += [rest_field]
end
fields_for_to_h.inject({}) do |h,f|
h[f.name.send(converter)] = send(f.name)
h
end
end
# Returns an array of values of the fields of the BitStruct. By default,
# include the rest field.
def to_a(include_rest = true)
ary =
fields.map do |f|
send(f.name)
end
if include_rest and (rest_field = self.class.rest_field)
ary << send(rest_field.name)
end
ary
end
## temporary hack for 1.9
if "a"[0].kind_of? String
def [](*args)
if args.size == 1 and args[0].kind_of?(Fixnum)
super.ord
else
super
end
end
def []=(*args)
if args.size == 2 and (i=args[0]).kind_of?(Fixnum)
super(i, args[1].chr)
else
super
end
end
end
class << self
# The unique "prototype" object from which new instances are copied.
# The fields of this instance can be modified in the class definition
# to set default values for the fields in that class. (Otherwise, defaults
# defined by the fields themselves are used.) A copy of this object is
# inherited in subclasses, which they may override using defaults and
# by writing to the initial_value object itself.
#
# If called with a block, yield the initial value object before returning
# it. Useful for customization within a class definition.
#
def initial_value # :yields: the initial value
unless @initial_value
iv = defined?(superclass.initial_value) ?
superclass.initial_value.dup : ""
if iv.length < round_byte_length
iv << "\0" * (round_byte_length - iv.length)
end
@initial_value = "" # Serves as initval while the real initval is inited
@initial_value = new(iv)
@closed = false # only creating the first _real_ instance closes.
fields.each do |field|
@initial_value.send("#{field.name}=", field.default) if field.default
end
end
yield @initial_value if block_given?
@initial_value
end
# Take +data+ (a string or BitStruct) and parse it into instances of
# the +classes+, returning them in an array. The classes can be given
# as an array or a separate arguments. (For parsing a string into a _single_
# BitStruct instance, just use the #new method with the string as an arg.)
def parse(data, *classes)
classes.flatten.map do |c|
c.new(data.slice!(0...c.round_byte_length))
end
end
# Join the given structs (array or multiple args) as a string.
# Actually, the inherited String#+ instance method is the same, as is using
# Array#join.
def join(*structs)
structs.flatten.map {|struct| struct.to_s}.join("")
end
end
# ------------------------
# :section: inspection methods
#
# ------------------------
DEFAULT_INSPECT_OPTS = {
:format => "#<%s %s>",
:field_format => "%s=%s",
:separator => ", ",
:field_name_meth => :name,
:include_rest => true,
:brackets => ["[", "]"],
:include_class => true,
:simple_format => "<%s>"
}
DETAILED_INSPECT_OPTS = {
:format => "%s:\n%s",
:field_format => "%30s = %s",
:separator => "\n",
:field_name_meth => :display_name,
:include_rest => true,
:brackets => [nil, "\n"],
:include_class => true,
:simple_format => "\n%s"
}
# A standard inspect method which does not add newlines.
def inspect(opts = DEFAULT_INSPECT_OPTS)
field_format = opts[:field_format]
field_name_meth = opts[:field_name_meth]
fields_for_inspect = fields.select {|field| field.inspectable?}
if opts[:include_rest] and (rest_field = self.class.rest_field)
fields_for_inspect << rest_field
end
ary = fields_for_inspect.map do |field|
field_format %
[field.send(field_name_meth),
field.inspect_in_object(self, opts)]
end
body = ary.join(opts[:separator])
if opts[:include_class]
opts[:format] % [self.class, body]
else
opts[:simple_format] % body
end
end
# A more visually appealing inspect method that puts each field/value on
# a separate line. Very useful when output is scrolling by on a screen.
#
# (This is actually a convenience method to call #inspect with the
# DETAILED_INSPECT_OPTS opts.)
def inspect_detailed
inspect(DETAILED_INSPECT_OPTS)
end
# ------------------------
# :section: field declaration methods
#
# ------------------------
# Define accessors for a variable length substring from the end of
# the defined fields to the end of the BitStruct. The _rest_ may behave as
# a String or as some other String or BitStruct subclass.
#
# This does not add a field, which is useful because a superclass can have
# a rest method which accesses subclass data. In particular, #rest does
# not affect the #round_byte_length class method. Of course, any data
# in rest does add to the #length of the BitStruct, calculated as a string.
# Also, _rest_ is not inherited.
#
# The +ary+ argument(s) work as follows:
#
# If a class is provided, use it for the Field class (String by default).
# If a string is provided, use it for the display_name (+name+ by default).
# If a hash is provided, use it for options.
#
# *Warning*: the rest reader method returns a copy of the field, so
# accessors on that returned value do not affect the original rest field.
#
def self.rest(name, *ary)
if @rest_field
raise ArgumentError, "Duplicate rest field: #{name.inspect}."
end
opts = parse_options(ary, name, String)
offset = round_byte_length
byte_range = offset..-1
class_eval do
field_class = opts[:field_class]
define_method name do ||
field_class.new(self[byte_range])
end
define_method "#{name}=" do |val|
self[byte_range] = val
end
@rest_field = Field.new(offset, -1, name, {
:display_name => opts[:display_name],
:rest_class => field_class
})
end
end
# Not included with the other fields, but accessible separately.
def self.rest_field; @rest_field; end
end

View File

@ -1,49 +0,0 @@
# -*- coding: binary -*-
class BitStruct
# Class for fixed length binary strings of characters.
# Declared with BitStruct.char.
class CharField < Field
#def self.default
# don't define this, since it must specify N nulls and we don't know N
#end
# Used in describe.
def self.class_name
@class_name ||= "char"
end
def add_accessors_to(cl, attr = name) # :nodoc:
unless offset % 8 == 0
raise ArgumentError,
"Bad offset, #{offset}, for #{self.class} #{name}." +
" Must be multiple of 8."
end
unless length % 8 == 0
raise ArgumentError,
"Bad length, #{length}, for #{self.class} #{name}." +
" Must be multiple of 8."
end
offset_byte = offset / 8
length_byte = length / 8
last_byte = offset_byte + length_byte - 1
byte_range = offset_byte..last_byte
val_byte_range = 0..length_byte-1
cl.class_eval do
define_method attr do ||
self[byte_range].to_s
end
define_method "#{attr}=" do |val|
val = val.to_s
if val.length < length_byte
val += "\0" * (length_byte - val.length)
end
self[byte_range] = val[val_byte_range]
end
end
end
end
end

View File

@ -1,301 +0,0 @@
# -*- coding: binary -*-
class BitStruct
class << self
# Define a char string field in the current subclass of BitStruct,
# with the given _name_ and _length_ (in bits). Trailing nulls _are_
# considered part of the string.
#
# If a class is provided, use it for the Field class.
# If a string is provided, use it for the display_name.
# If a hash is provided, use it for options.
#
# Note that the accessors have COPY semantics, not reference.
#
def char(name, length, *rest)
opts = parse_options(rest, name, CharField)
add_field(name, length, opts)
end
alias string char
BitStruct.autoload :CharField, "bit-struct/char-field"
# Define a floating point field in the current subclass of BitStruct,
# with the given _name_ and _length_ (in bits).
#
# If a class is provided, use it for the Field class.
# If a string is provided, use it for the display_name.
# If a hash is provided, use it for options.
#
# The <tt>:endian => :native</tt> option overrides the default of
# <tt>:network</tt> byte ordering, in favor of native byte ordering. Also
# permitted are <tt>:big</tt> (same as <tt>:network</tt>) and
# <tt>:little</tt>.
#
def float name, length, *rest
opts = parse_options(rest, name, FloatField)
add_field(name, length, opts)
end
BitStruct.autoload :FloatField, "bit-struct/float-field"
# Define an octet string field in the current subclass of BitStruct,
# with the given _name_ and _length_ (in bits). Trailing nulls are
# not considered part of the string. The field is accessed using
# period-separated hex digits.
#
# If a class is provided, use it for the Field class.
# If a string is provided, use it for the display_name.
# If a hash is provided, use it for options.
#
def hex_octets(name, length, *rest)
opts = parse_options(rest, name, HexOctetField)
add_field(name, length, opts)
end
BitStruct.autoload :HexOctetField, "bit-struct/hex-octet-field"
# Define a nested field in the current subclass of BitStruct,
# with the given _name_ and _nested_class_. Length is determined from
# _nested_class_.
#
# If a class is provided, use it for the Field class (i.e. <=NestedField).
# If a string is provided, use it for the display_name.
# If a hash is provided, use it for options.
#
# For example:
#
# class Sub < BitStruct
# unsigned :x, 8
# end
#
# class A < BitStruct
# nest :n, Sub
# end
#
# a = A.new
#
# p a # ==> #<A n=#<Sub x=0>>
#
# If a block is given, use it to define the nested fields. For example, the
# following is equivalent to the above example:
#
# class A < BitStruct
# nest :n do
# unsigned :x, 8
# end
# end
#
# WARNING: the accessors have COPY semantics, not reference. When you call a
# reader method to get the nested structure, you get a *copy* of that data.
# Expressed in terms of the examples above:
#
# # This fails to set x in a.
# a.n.x = 3
# p a # ==> #<A n=#<Sub x=0>>
#
# # This works
# n = a.n
# n.x = 3
# a.n = n
# p a # ==> #<A n=#<Sub x=3>>
#
def nest(name, *rest, &block)
nested_class = rest.grep(Class).find {|cl| cl <= BitStruct}
rest.delete nested_class
opts = parse_options(rest, name, NestedField)
nested_class = opts[:nested_class] ||= nested_class
unless (block and not nested_class) or (nested_class and not block)
raise ArgumentError,
"nested field must have either a nested_class option or a block," +
" but not both"
end
unless nested_class
nested_class = Class.new(BitStruct)
nested_class.class_eval(&block)
end
opts[:default] ||= nested_class.initial_value.dup
opts[:nested_class] = nested_class
field = add_field(name, nested_class.bit_length, opts)
field
end
alias struct nest
BitStruct.autoload :NestedField, "bit-struct/nested-field"
# Define an octet string field in the current subclass of BitStruct,
# with the given _name_ and _length_ (in bits). Trailing nulls are
# not considered part of the string. The field is accessed using
# period-separated decimal digits.
#
# If a class is provided, use it for the Field class.
# If a string is provided, use it for the display_name.
# If a hash is provided, use it for options.
#
def octets(name, length, *rest)
opts = parse_options(rest, name, OctetField)
add_field(name, length, opts)
end
BitStruct.autoload :OctetField, "bit-struct/octet-field"
# Define a padding field in the current subclass of BitStruct,
# with the given _name_ and _length_ (in bits).
#
# If a class is provided, use it for the Field class.
# If a string is provided, use it for the display_name.
# If a hash is provided, use it for options.
#
def pad(name, length, *rest)
opts = parse_options(rest, name, PadField)
add_field(name, length, opts)
end
alias padding pad
BitStruct.autoload :PadField, "bit-struct/pad-field"
# Define a signed integer field in the current subclass of BitStruct,
# with the given _name_ and _length_ (in bits).
#
# If a class is provided, use it for the Field class.
# If a string is provided, use it for the display_name.
# If a hash is provided, use it for options.
#
# SignedField adds the <tt>:fixed => divisor</tt> option, which specifies
# that the internally stored value is interpreted as a fixed point real
# number with the specified +divisor+.
#
# The <tt>:endian => :native</tt> option overrides the default of
# <tt>:network</tt> byte ordering, in favor of native byte ordering. Also
# permitted are <tt>:big</tt> (same as <tt>:network</tt>) and
# <tt>:little</tt>.
#
def signed name, length, *rest
opts = parse_options(rest, name, SignedField)
add_field(name, length, opts)
end
BitStruct.autoload :SignedField, "bit-struct/signed-field"
# Define a printable text string field in the current subclass of BitStruct,
# with the given _name_ and _length_ (in bits). Trailing nulls are
# _not_ considered part of the string.
#
# If a class is provided, use it for the Field class.
# If a string is provided, use it for the display_name.
# If a hash is provided, use it for options.
#
# Note that the accessors have COPY semantics, not reference.
#
def text(name, length, *rest)
opts = parse_options(rest, name, TextField)
add_field(name, length, opts)
end
BitStruct.autoload :TextField, "bit-struct/text-field"
# Define a unsigned integer field in the current subclass of BitStruct,
# with the given _name_ and _length_ (in bits).
#
# If a class is provided, use it for the Field class.
# If a string is provided, use it for the display_name.
# If a hash is provided, use it for options.
#
# UnsignedField adds the <tt>:fixed => divisor</tt> option, which specifies
# that the internally stored value is interpreted as a fixed point real
# number with the specified +divisor+.
#
# The <tt>:endian => :native</tt> option overrides the default of
# <tt>:network</tt> byte ordering, in favor of native byte ordering. Also
# permitted are <tt>:big</tt> (same as <tt>:network</tt>) and
# <tt>:little</tt>.
#
def unsigned name, length, *rest
opts = parse_options(rest, name, UnsignedField)
add_field(name, length, opts)
end
BitStruct.autoload :UnsignedField, "bit-struct/unsigned-field"
# Define a vector field in the current subclass of BitStruct,
# with the given _name_.
#
# If a class is provided, use it for the Vector class, otherwise
# the block must define the entry fields. The two forms looks like
# this:
#
# class Vec < BitStruct::Vector
# # these declarations apply to *each* entry in the vector:
# unsigned :x, 16
# signed :y, 32
# end
#
# class Packet < BitStruct
# # Using the Vec class defined above
# vector :v, Vec, "a vector", :length => 5
#
# # equivalently, using an anonymous subclass of BitStruct::Vector
# vector :v2, "a vector", :length => 5 do
# unsigned :x, 16
# signed :y, 32
# end
# end
#
# If a string is provided, use it for the display_name.
# If a hash is provided, use it for options.
# If a number is provided, use it for length (equivalent to using the
# :length option).
#
# WARNING: the accessors have COPY semantics, not reference. When you call a
# reader method to get the vector structure, you get a *copy* of that data.
#
# For example, to modify the numeric fields in a Packet as defined above:
#
# pkt = Packet.new
# vec = pkt.v
# entry = vec[2]
# entry.x = 123
# entry.y = -456
# vec[2] = entry
# pkt.v = vec
#
def vector(name, *rest, &block)
opts = parse_options(rest, name, nil)
cl = opts[:field_class]
opts[:field_class] = VectorField
unless (block and not cl) or (cl and not block)
raise ArgumentError,
"vector must have either a class or a block, but not both"
end
case
when cl == nil
vector_class = Class.new(BitStruct::Vector)
vector_class.class_eval(&block)
when cl < BitStruct
vector_class = Class.new(BitStruct::Vector)
vector_class.struct_class cl
when cl < BitStruct::Vector
vector_class = cl
else raise ArgumentError, "Bad vector class: #{cl.inspect}"
end
vector_class.default_options default_options
length = opts[:length] || rest.grep(Integer).first
## what about :length => :lenfield
unless length
raise ArgumentError,
"Must provide length as argument N or as option :length => N"
end
opts[:default] ||= vector_class.new(length) ## nil if variable length
opts[:vector_class] = vector_class
bit_length = vector_class.struct_class.round_byte_length * 8 * length
field = add_field(name, bit_length, opts)
field
end
BitStruct.autoload :VectorField, "bit-struct/vector-field"
end
autoload :Vector, "bit-struct/vector"
end

View File

@ -1,62 +0,0 @@
# -*- coding: binary -*-
class BitStruct
# Class for floats (single and double precision) in network order.
# Declared with BitStruct.float.
class FloatField < Field
# Used in describe.
def self.class_name
@class_name ||= "float"
end
def add_accessors_to(cl, attr = name) # :nodoc:
unless offset % 8 == 0
raise ArgumentError,
"Bad offset, #{offset}, for #{self.class} #{name}." +
" Must be multiple of 8."
end
unless length == 32 or length == 64
raise ArgumentError,
"Bad length, #{length}, for #{self.class} #{name}." +
" Must be 32 or 64."
end
offset_byte = offset / 8
length_byte = length / 8
last_byte = offset_byte + length_byte - 1
byte_range = offset_byte..last_byte
endian = (options[:endian] || options["endian"]).to_s
case endian
when "native"
ctl = case length
when 32; "f"
when 64; "d"
end
when "little"
ctl = case length
when 32; "e"
when 64; "E"
end
when "network", "big", ""
ctl = case length
when 32; "g"
when 64; "G"
end
else
raise ArgumentError,
"Unrecognized endian option: #{endian.inspect}"
end
cl.class_eval do
define_method attr do ||
self[byte_range].unpack(ctl).first
end
define_method "#{attr}=" do |val|
self[byte_range] = [val].pack(ctl)
end
end
end
end
end

View File

@ -1,21 +0,0 @@
# -*- coding: binary -*-
require 'bit-struct/char-field'
class BitStruct
# Class for char fields that can be accessed with values like
# "xx:xx:xx:xx", where each xx is up to 2 hex digits representing a
# single octet. The original string-based accessors are still available with
# the <tt>_chars</tt> suffix.
#
# Declared with BitStruct.hex_octets.
class HexOctetField < BitStruct::OctetField
# Used in describe.
def self.class_name
@class_name ||= "hex_octets"
end
SEPARATOR = ":"
FORMAT = "%02x"
BASE = 16
end
end

View File

@ -1,77 +0,0 @@
# -*- coding: binary -*-
require 'bit-struct/bit-struct'
class BitStruct
# Class for nesting a BitStruct as a field within another BitStruct.
# Declared with BitStruct.nest.
class NestedField < Field
def initialize(*args)
super
end
# Used in describe.
def self.class_name
@class_name ||= "nest"
end
def class_name
@class_name ||= nested_class.name[/\w+$/]
end
def nested_class
@nested_class ||= options[:nested_class] || options["nested_class"]
end
def describe opts
if opts[:expand]
opts = opts.dup
opts[:byte_offset] = offset / 8
opts[:omit_header] = opts[:omit_footer] = true
nested_class.describe(nil, opts) {|desc| yield desc}
else
super
end
end
def add_accessors_to(cl, attr = name) # :nodoc:
unless offset % 8 == 0
raise ArgumentError,
"Bad offset, #{offset}, for nested field #{name}." +
" Must be multiple of 8."
end
unless length % 8 == 0
raise ArgumentError,
"Bad length, #{length}, for nested field #{name}." +
" Must be multiple of 8."
end
offset_byte = offset / 8
length_byte = length / 8
last_byte = offset_byte + length_byte - 1
byte_range = offset_byte..last_byte
nc = nested_class
cl.class_eval do
define_method attr do ||
nc.new(self[byte_range])
end
define_method "#{attr}=" do |val|
if val.length != length_byte
raise ArgumentError, "Size mismatch in nested struct assignment " +
"to #{attr} with value #{val.inspect}"
end
if val.class != nc
warn "Type mismatch in nested struct assignment " +
"to #{attr} with value #{val.inspect}"
end
self[byte_range] = val
end
end
end
end
end

View File

@ -1,46 +0,0 @@
# -*- coding: binary -*-
require 'bit-struct/char-field'
class BitStruct
# Class for char fields that can be accessed with values like
# "xxx.xxx.xxx.xxx", where each xxx is up to 3 decimal digits representing a
# single octet. The original string-based accessors are still available with
# the <tt>_chars</tt> suffix.
#
# Declared with BitStruct.octets.
class OctetField < BitStruct::CharField
# Used in describe.
def self.class_name
@class_name ||= "octets"
end
SEPARATOR = "."
FORMAT = "%d"
BASE = 10
def add_accessors_to(cl, attr = name) # :nodoc:
attr_chars = "#{attr}_chars"
super(cl, attr_chars)
sep = self.class::SEPARATOR
base = self.class::BASE
fmt = self.class::FORMAT
cl.class_eval do
define_method attr do ||
ary = []
send(attr_chars).each_byte do |c|
ary << fmt % c
end
ary.join(sep)
end
old_writer = "#{attr_chars}="
define_method "#{attr}=" do |val|
data = val.split(sep).map{|s|s.to_i(base)}.pack("C*")
send(old_writer, data)
end
end
end
end
end

View File

@ -1,16 +0,0 @@
# -*- coding: binary -*-
class BitStruct
# Class for fixed length padding.
class PadField < Field
# Used in describe.
def self.class_name
@class_name ||= "padding"
end
def add_accessors_to(cl, attr = name) # :nodoc:
# No accessors for padding.
end
def inspectable?; false; end
end
end

View File

@ -1,259 +0,0 @@
# -*- coding: binary -*-
class BitStruct
# Class for signed integers in network order, 1-16 bits, or 8n bits.
# Declared with BitStruct.signed.
class SignedField < Field
# Used in describe.
def self.class_name
@class_name ||= "signed"
end
def add_accessors_to(cl, attr = name) # :nodoc:
offset_byte = offset / 8
offset_bit = offset % 8
length_bit = offset_bit + length
length_byte = (length_bit/8.0).ceil
last_byte = offset_byte + length_byte - 1
max = 2**length-1
mid = 2**(length-1)
max_unsigned = 2**length
to_signed = proc {|n| (n>=mid) ? n - max_unsigned : n}
# to_signed = proc {|n| (n>=mid) ? -((n ^ max) + 1) : n}
divisor = options[:fixed] || options["fixed"]
divisor_f = divisor && divisor.to_f
# if divisor and not divisor.is_a? Fixnum
# raise ArgumentError, "fixed-point divisor must be a fixnum"
# end
endian = (options[:endian] || options["endian"]).to_s
case endian
when "native"
ctl = length_byte <= 2 ? "s" : "l"
if length == 16 or length == 32
to_signed = proc {|n| n}
# with pack support, to_signed can be replaced with no-op
end
when "little"
ctl = length_byte <= 2 ? "v" : "V"
when "network", "big", ""
ctl = length_byte <= 2 ? "n" : "N"
else
raise ArgumentError,
"Unrecognized endian option: #{endian.inspect}"
end
data_is_big_endian =
([1234].pack(ctl) == [1234].pack(length_byte <= 2 ? "n" : "N"))
if length_byte == 1
rest = 8 - length_bit
mask = ["0"*offset_bit + "1"*length + "0"*rest].pack("B8")[0].ord
mask2 = ["1"*offset_bit + "0"*length + "1"*rest].pack("B8")[0].ord
cl.class_eval do
if divisor
define_method attr do ||
to_signed[(self[offset_byte] & mask) >> rest] / divisor_f
end
define_method "#{attr}=" do |val|
val = (val * divisor).round
self[offset_byte] =
(self[offset_byte] & mask2) | ((val<<rest) & mask)
end
else
define_method attr do ||
to_signed[(self[offset_byte] & mask) >> rest]
end
define_method "#{attr}=" do |val|
self[offset_byte] =
(self[offset_byte] & mask2) | ((val<<rest) & mask)
end
end
end
elsif offset_bit == 0 and length % 8 == 0
field_length = length
byte_range = offset_byte..last_byte
cl.class_eval do
case field_length
when 8
if divisor
define_method attr do ||
to_signed[self[offset_byte]] / divisor_f
end
define_method "#{attr}=" do |val|
val = (val * divisor).round
self[offset_byte] = val
end
else
define_method attr do ||
to_signed[self[offset_byte]]
end
define_method "#{attr}=" do |val|
self[offset_byte] = val
end
end
when 16, 32
if divisor
define_method attr do ||
to_signed[self[byte_range].unpack(ctl).first] / divisor_f
end
define_method "#{attr}=" do |val|
val = (val * divisor).round
self[byte_range] = [val].pack(ctl)
end
else
define_method attr do ||
to_signed[self[byte_range].unpack(ctl).first]
end
define_method "#{attr}=" do |val|
self[byte_range] = [val].pack(ctl)
end
end
else
reader_helper = proc do |substr|
bytes = substr.unpack("C*")
bytes.reverse! unless data_is_big_endian
bytes.inject do |sum, byte|
(sum << 8) + byte
end
end
writer_helper = proc do |val|
bytes = []
val += max_unsigned if val < 0
while val > 0
bytes.push val % 256
val = val >> 8
end
if bytes.length < length_byte
bytes.concat [0] * (length_byte - bytes.length)
end
bytes.reverse! if data_is_big_endian
bytes.pack("C*")
end
if divisor
define_method attr do ||
to_signed[reader_helper[self[byte_range]] / divisor_f]
end
define_method "#{attr}=" do |val|
self[byte_range] = writer_helper[(val * divisor).round]
end
else
define_method attr do ||
to_signed[reader_helper[self[byte_range]]]
end
define_method "#{attr}=" do |val|
self[byte_range] = writer_helper[val]
end
end
end
end
elsif length_byte == 2 # unaligned field that fits within two whole bytes
byte_range = offset_byte..last_byte
rest = 16 - length_bit
mask = ["0"*offset_bit + "1"*length + "0"*rest]
mask = mask.pack("B16").unpack(ctl).first
mask2 = ["1"*offset_bit + "0"*length + "1"*rest]
mask2 = mask2.pack("B16").unpack(ctl).first
cl.class_eval do
if divisor
define_method attr do ||
to_signed[(self[byte_range].unpack(ctl).first & mask) >> rest] /
divisor_f
end
define_method "#{attr}=" do |val|
val = (val * divisor).round
x = (self[byte_range].unpack(ctl).first & mask2) |
((val<<rest) & mask)
self[byte_range] = [x].pack(ctl)
end
else
define_method attr do ||
to_signed[(self[byte_range].unpack(ctl).first & mask) >> rest]
end
define_method "#{attr}=" do |val|
x = (self[byte_range].unpack(ctl).first & mask2) |
((val<<rest) & mask)
self[byte_range] = [x].pack(ctl)
end
end
end
elsif length_byte == 3 # unaligned field that fits within 3 whole bytes
byte_range = offset_byte..last_byte
rest = 32 - length_bit
mask = ["0"*offset_bit + "1"*length + "0"*rest]
mask = mask.pack("B32").unpack(ctl).first
mask2 = ["1"*offset_bit + "0"*length + "1"*rest]
mask2 = mask2.pack("B32").unpack(ctl).first
cl.class_eval do
if divisor
define_method attr do ||
bytes = self[byte_range]
bytes << 0
to_signed[((bytes.unpack(ctl).first & mask) >> rest)] /
divisor_f
end
define_method "#{attr}=" do |val|
val = (val * divisor).round
bytes = self[byte_range]
bytes << 0
x = (bytes.unpack(ctl).first & mask2) |
((val<<rest) & mask)
self[byte_range] = [x].pack(ctl)[0..2]
end
else
define_method attr do ||
bytes = self[byte_range]
bytes << 0
to_signed[(bytes.unpack(ctl).first & mask) >> rest]
end
define_method "#{attr}=" do |val|
bytes = self[byte_range]
bytes << 0
x = (bytes.unpack(ctl).first & mask2) |
((val<<rest) & mask)
self[byte_range] = [x].pack(ctl)[0..2]
end
end
end
else
raise "unsupported: #{inspect}"
end
end
end
end

View File

@ -1,45 +0,0 @@
# -*- coding: binary -*-
class BitStruct
# Class for null-terminated printable text strings.
# Declared with BitStruct.text.
class TextField < Field
# Used in describe.
def self.class_name
@class_name ||= "text"
end
def add_accessors_to(cl, attr = name) # :nodoc:
unless offset % 8 == 0
raise ArgumentError,
"Bad offset, #{offset}, for #{self.class} #{name}." +
" Must be multiple of 8."
end
unless length % 8 == 0
raise ArgumentError,
"Bad length, #{length}, for #{self.class} #{name}." +
" Must be multiple of 8."
end
offset_byte = offset / 8
length_byte = length / 8
last_byte = offset_byte + length_byte - 1
byte_range = offset_byte..last_byte
val_byte_range = 0..length_byte-1
cl.class_eval do
define_method attr do ||
self[byte_range].sub(/\0*$/, "").to_s
end
define_method "#{attr}=" do |val|
val = val.to_s
if val.length < length_byte
val += "\0" * (length_byte - val.length)
end
self[byte_range] = val[val_byte_range]
end
end
end
end
end

View File

@ -1,249 +0,0 @@
# -*- coding: binary -*-
class BitStruct
# Class for unsigned integers in network order, 1-16 bits, or 8n bits.
# Declared with BitStruct.unsigned.
class UnsignedField < Field
# Used in describe.
def self.class_name
@class_name ||= "unsigned"
end
def add_accessors_to(cl, attr = name) # :nodoc:
offset_byte = offset / 8
offset_bit = offset % 8
length_bit = offset_bit + length
length_byte = (length_bit/8.0).ceil
last_byte = offset_byte + length_byte - 1
divisor = options[:fixed] || options["fixed"]
divisor_f = divisor && divisor.to_f
# if divisor and not divisor.is_a? Fixnum
# raise ArgumentError, "fixed-point divisor must be a fixnum"
# end
endian = (options[:endian] || options["endian"]).to_s
case endian
when "native"
ctl = length_byte <= 2 ? "S" : "L"
when "little"
ctl = length_byte <= 2 ? "v" : "V"
when "network", "big", ""
ctl = length_byte <= 2 ? "n" : "N"
else
raise ArgumentError,
"Unrecognized endian option: #{endian.inspect}"
end
data_is_big_endian =
([1234].pack(ctl) == [1234].pack(length_byte <= 2 ? "n" : "N"))
if length_byte == 1
rest = 8 - length_bit
mask = ["0"*offset_bit + "1"*length + "0"*rest].pack("B8")[0].ord
mask2 = ["1"*offset_bit + "0"*length + "1"*rest].pack("B8")[0].ord
cl.class_eval do
if divisor
define_method attr do ||
((self[offset_byte] & mask) >> rest) / divisor_f
end
define_method "#{attr}=" do |val|
val = (val * divisor).round
self[offset_byte] =
(self[offset_byte] & mask2) | ((val<<rest) & mask)
end
else
define_method attr do ||
(self[offset_byte] & mask) >> rest
end
define_method "#{attr}=" do |val|
self[offset_byte] =
(self[offset_byte] & mask2) | ((val<<rest) & mask)
end
end
end
elsif offset_bit == 0 and length % 8 == 0
field_length = length
byte_range = offset_byte..last_byte
cl.class_eval do
case field_length
when 8
if divisor
define_method attr do ||
self[offset_byte] / divisor_f
end
define_method "#{attr}=" do |val|
val = (val * divisor).round
self[offset_byte] = val
end
else
define_method attr do ||
self[offset_byte]
end
define_method "#{attr}=" do |val|
self[offset_byte] = val
end
end
when 16, 32
if divisor
define_method attr do ||
self[byte_range].unpack(ctl).first / divisor_f
end
define_method "#{attr}=" do |val|
val = (val * divisor).round
self[byte_range] = [val].pack(ctl)
end
else
define_method attr do ||
self[byte_range].unpack(ctl).first
end
define_method "#{attr}=" do |val|
self[byte_range] = [val].pack(ctl)
end
end
else
reader_helper = proc do |substr|
bytes = substr.unpack("C*")
bytes.reverse! unless data_is_big_endian
bytes.inject do |sum, byte|
(sum << 8) + byte
end
end
writer_helper = proc do |val|
bytes = []
while val > 0
bytes.push val % 256
val = val >> 8
end
if bytes.length < length_byte
bytes.concat [0] * (length_byte - bytes.length)
end
bytes.reverse! if data_is_big_endian
bytes.pack("C*")
end
if divisor
define_method attr do ||
reader_helper[self[byte_range]] / divisor_f
end
define_method "#{attr}=" do |val|
self[byte_range] = writer_helper[(val * divisor).round]
end
else
define_method attr do ||
reader_helper[self[byte_range]]
end
define_method "#{attr}=" do |val|
self[byte_range] = writer_helper[val]
end
end
end
end
elsif length_byte == 2 # unaligned field that fits within two whole bytes
byte_range = offset_byte..last_byte
rest = 16 - length_bit
mask = ["0"*offset_bit + "1"*length + "0"*rest]
mask = mask.pack("B16").unpack(ctl).first
mask2 = ["1"*offset_bit + "0"*length + "1"*rest]
mask2 = mask2.pack("B16").unpack(ctl).first
cl.class_eval do
if divisor
define_method attr do ||
((self[byte_range].unpack(ctl).first & mask) >> rest) /
divisor_f
end
define_method "#{attr}=" do |val|
val = (val * divisor).round
x = (self[byte_range].unpack(ctl).first & mask2) |
((val<<rest) & mask)
self[byte_range] = [x].pack(ctl)
end
else
define_method attr do ||
(self[byte_range].unpack(ctl).first & mask) >> rest
end
define_method "#{attr}=" do |val|
x = (self[byte_range].unpack(ctl).first & mask2) |
((val<<rest) & mask)
self[byte_range] = [x].pack(ctl)
end
end
end
elsif length_byte == 3 # unaligned field that fits within 3 whole bytes
byte_range = offset_byte..last_byte
rest = 32 - length_bit
mask = ["0"*offset_bit + "1"*length + "0"*rest]
mask = mask.pack("B32").unpack(ctl).first
mask2 = ["1"*offset_bit + "0"*length + "1"*rest]
mask2 = mask2.pack("B32").unpack(ctl).first
cl.class_eval do
if divisor
define_method attr do ||
bytes = self[byte_range]
bytes << 0
((bytes.unpack(ctl).first & mask) >> rest) /
divisor_f
end
define_method "#{attr}=" do |val|
val = (val * divisor).round
bytes = self[byte_range]
bytes << 0
x = (bytes.unpack(ctl).first & mask2) |
((val<<rest) & mask)
self[byte_range] = [x].pack(ctl)[0..2]
end
else
define_method attr do ||
bytes = self[byte_range]
bytes << 0
(bytes.unpack(ctl).first & mask) >> rest
end
define_method "#{attr}=" do |val|
bytes = self[byte_range]
bytes << 0
x = (bytes.unpack(ctl).first & mask2) |
((val<<rest) & mask)
self[byte_range] = [x].pack(ctl)[0..2]
end
end
end
else
raise "unsupported: #{inspect}"
end
end
end
end

View File

@ -1,78 +0,0 @@
# -*- coding: binary -*-
require 'bit-struct/vector'
class BitStruct
# Class for embedding a BitStruct::Vector as a field within a BitStruct.
# Declared with BitStruct.vector.
class VectorField < Field
# Used in describe.
def self.class_name
@class_name ||= "vector"
end
# Used in describe.
def class_name
@class_name ||= vector_class.name[/\w+$/]
end
# Returns the subclass of Vector that is used to manage the value of this
# field. If the class was specified in the BitStruct.vector declaration,
# #vector_class will return it, otherwise it will be an anonymous class
# (which you can assign to a constant to make nonymous ;).
def vector_class
@vector_class ||= options[:vector_class] || options["vector_class"]
end
def describe opts # :nodoc:
if opts[:expand]
opts = opts.dup
opts[:byte_offset] = offset / 8
opts[:omit_header] = opts[:omit_footer] = true
vector_class.describe(nil, opts) {|desc| yield desc}
else
super
end
end
def add_accessors_to(cl, attr = name) # :nodoc:
unless offset % 8 == 0
raise ArgumentError,
"Bad offset, #{offset}, for vector field #{name}." +
" Must be multiple of 8."
end
unless length % 8 == 0
raise ArgumentError,
"Bad length, #{length}, for vector field #{name}." +
" Must be multiple of 8."
end
offset_byte = offset / 8
length_byte = length / 8
last_byte = offset_byte + length_byte - 1
byte_range = offset_byte..last_byte
vc = vector_class
cl.class_eval do
define_method attr do ||
vc.new(self[byte_range])
end
define_method "#{attr}=" do |val|
if val.length != length_byte
raise ArgumentError, "Size mismatch in vector field assignment " +
"to #{attr} with value #{val.inspect}"
end
if val.class != vc
warn "Type mismatch in vector field assignment " +
"to #{attr} with value #{val.inspect}"
end
self[byte_range] = val
end
end
end
end
end

View File

@ -1,174 +0,0 @@
# -*- coding: binary -*-
# A Vector is, like a BitStruct, a String. It retains all of the String
# methods, except for #[], #[]=, and #each. These methods operate on entries
# instead of chars. Other methods, including #length and #slice, are unchanged.
# Hence a Vector can be used directly with sockets, binary files, etc.
#
# Note that Vector is not a subclass of BitStruct. It cannot be used in
# a #nest declaration in a BitStruct. Instead, use the #vector declaration.
# See BitStruct::VectorField.
#
# Different instances of the same Vector class may have different lengths, and
# a single instance can change its length. The length should always be a
# multiple of the struct size.
class BitStruct::Vector < String
include Enumerable
@default_options = {}
@struct_class = nil
class << self
def inherited cl
cl.instance_eval do
@struct_class = nil
end
end
# Called as a class method with a single argument in a user-defined
# subclass to specify a particular BitStruct class to use for each entry,
# instead of generating an anonymous class. Called without arguments to
# access the struct class, generating an anonymous one if needed.
# The struct_class inherits from the struct_class of the parent Vector
# class.
def struct_class cl = nil
if cl
if @struct_class
warn "changing struct_class in #{self} to #{cl}"
end
@struct_class = cl
@struct_class.default_options default_options
else
unless @struct_class
@struct_class = self == BitStruct::Vector ? BitStruct :
Class.new(superclass.struct_class)
@struct_class.default_options default_options
end
end
@struct_class
end
def method_missing(*a, &block) # :nodoc:
struct_class.send(*a, &block)
end
alias :orig_respond_to? :respond_to?
def respond_to?(*m) # :nodoc:
orig_respond_to?(*m) || struct_class.respond_to?(*m)
end
# Get or set the hash of default options for the class, which apply to all
# fields in the entries. If +h+ is provided, update the default options
# with that hash. Default options are inherited.
#
# This is especially useful with the <tt>:endian => val</tt> option.
def default_options h = nil
@default_options ||= superclass.default_options.dup
if h
@default_options.merge! h
if @struct_class
@struct_class.default_options h
end
end
@default_options
end
def describe(*args)
fmt = args[0] || BitStruct.describe_format
if block_given?
struct_class.describe(*args){|desc| yield desc}
yield ["..."]*5
else
struct_class.describe(*args) + [fmt % (["..."]*5)]
end
end
end
# Convenience method for instances. Returns the BitStruct class that
# describes each entry.
def struct_class
self.class.struct_class
end
# Convenience method for instances. Returns the string length in bytes of
# each entry in the vector.
def struct_class_length
self.class.struct_class.round_byte_length
end
# +arg+ can be an integer (number of entries) or a string
# (binary data, such as another Vector of the same size).
def initialize arg # :yields: instance
case arg
when Integer
super(struct_class.initial_value * arg)
else
begin
super arg
rescue NameError
raise ArgumentError, "must be string or integer: #{arg.inspect}"
end
end
yield self if block_given?
end
# Get the +i+-th entry. Returns a *copy* of the entry. If you want to
# use this copy to modify the entry, you must modify the copy and then
# use #[]= to replace the entry with the copy.
def [](i)
sc = self.class.struct_class
entry_length = sc.round_byte_length
unless (0...(length / entry_length)).include? i
raise ArgumentError, "index out of range: #{i}"
end
sc.new slice(entry_length * i, entry_length)
end
alias _old_replace_substr []=
# Set the +i+-th entry to +val+.
def []=(i,val)
entry_length = struct_class_length
unless (0...(length / entry_length)).include? i
raise ArgumentError, "index out of range: #{i}"
end
unless val.length == entry_length
raise ArgumentError, "wrong entry length: #{val.length} != #{entry_length}"
end
_old_replace_substr(entry_length * i, entry_length, val)
end
## TODO: [i..j] etc.
# Iterate over entries.
def each
entry_length = struct_class_length
(length / entry_length).times do |i|
yield self[i]
end
end
def inspect(opts = BitStruct::DEFAULT_INSPECT_OPTS)
if opts[:include_class]
opts = opts.dup
opts[:include_class] = false
s = self.class.inspect + ": "
else
s = ""
end
s << entries.map{|entry| entry.inspect(opts)}.join(opts[:separator])
lb, rb = opts[:brackets]
[lb, s, rb].join
end
def inspect_detailed
inspect(BitStruct::DETAILED_INSPECT_OPTS)
end
end

View File

@ -1,70 +0,0 @@
# -*- coding: binary -*-
require 'yaml'
class BitStruct
if RUBY_VERSION == "1.8.2"
def is_complex_yaml? # :nodoc:
true
end
YAML.add_ruby_type(/^bitstruct/) do |type, val|
subtype, subclass = YAML.read_type_class(type, Object)
subclass.new(val)
end
def to_yaml_type # :nodoc:
"!ruby/bitstruct:#{self.class}"
end
def to_yaml( opts = {} ) # :nodoc:
opts[:DocType] = self.class if Hash === opts
YAML.quick_emit(self.object_id, opts) do |out|
out.map(to_yaml_type) do |map|
fields.each do |field|
fn = field.name
map.add(fn, send(fn))
end
end
end
end
else
yaml_as "tag:path.berkeley.edu,2006:bitstruct"
def to_yaml_properties # :nodoc:
yaml_fields = fields.select {|field| field.inspectable?}
props = yaml_fields.map {|f| f.name.to_s}
if (rest_field = self.class.rest_field)
props << rest_field.name.to_s
end
props
end
# Return YAML representation of the BitStruct.
def to_yaml( opts = {} )
YAML::quick_emit( object_id, opts ) do |out|
out.map( taguri, to_yaml_style ) do |map|
to_yaml_properties.each do |m|
map.add( m, send( m ) )
end
end
end
end
def self.yaml_new( klass, tag, val ) # :nodoc:
unless Hash === val
raise YAML::TypeError, "Invalid BitStruct: " + val.inspect
end
bitstruct_name, bitstruct_type = YAML.read_type_class( tag, BitStruct )
st = bitstruct_type.new
val.each do |k,v|
st.send( "#{k}=", v )
end
st
end
end
end

View File

@ -98,6 +98,8 @@ Gem::Specification.new do |spec|
spec.add_runtime_dependency 'patch_finder'
# TimeZone info
spec.add_runtime_dependency 'tzinfo-data'
# BitStruct Library used for handling certain Protocol Header/Packet construction
spec.add_runtime_dependency 'bit-struct'
#
# REX Libraries