metasploit-framework/lib/bindata/single_value.rb

194 lines
5.3 KiB
Ruby

require 'bindata/single'
require 'bindata/struct'
module BinData
# A SingleValue is a declarative way to define a new BinData data type.
# The data type must contain a single value only. For new data types
# that contain multiple values see BinData::MultiValue.
#
# To define a new data type, set fields as if for MultiValue and add a
# #get and #set method to extract / convert the data between the fields
# and the #value of the object.
#
# require 'bindata'
#
# class PascalString < BinData::SingleValue
# uint8 :len, :value => lambda { data.length }
# string :data, :read_length => :len
#
# def get
# self.data
# end
#
# def set(v)
# self.data = v
# end
# end
#
# ps = PascalString.new(:initial_value => "hello")
# ps.to_s #=> "\005hello"
# ps.read("\003abcde")
# ps.value #=> "abc"
#
# # Unsigned 24 bit big endian integer
# class Uint24be < BinData::SingleValue
# uint8 :byte1
# uint8 :byte2
# uint8 :byte3
#
# def get
# (self.byte1 << 16) | (self.byte2 << 8) | self.byte3
# end
#
# def set(v)
# v = 0 if v < 0
# v = 0xffffff if v > 0xffffff
#
# self.byte1 = (v >> 16) & 0xff
# self.byte2 = (v >> 8) & 0xff
# self.byte3 = v & 0xff
# end
# end
#
# u24 = Uint24be.new
# u24.read("\x12\x34\x56")
# "0x%x" % u24.value #=> 0x123456
#
# == Parameters
#
# SingleValue objects accept all the parameters that BinData::Single do.
#
class SingleValue < Single
class << self
# Register the names of all subclasses of this class.
def inherited(subclass) #:nodoc:
register(subclass.name, subclass)
end
# Returns or sets the endianess of numerics used in this stucture.
# Endianess is applied to the fields of this structure.
# Valid values are :little and :big.
def endian(endian = nil)
@endian ||= nil
if [:little, :big].include?(endian)
@endian = endian
elsif endian != nil
raise ArgumentError, "unknown value for endian '#{endian}'"
end
@endian
end
# Returns all stored fields. Should only be called by
# #sanitize_parameters
def fields
@fields || []
end
# Used to define fields for the internal structure.
def method_missing(symbol, *args)
name, params = args
type = symbol
name = (name.nil? or name == "") ? nil : name.to_s
params ||= {}
# note that fields are stored in an instance variable not a class var
@fields ||= []
# check that type is known
unless Sanitizer.type_exists?(type, endian)
raise TypeError, "unknown type '#{type}' for #{self}", caller
end
# check that name is okay
if name != nil
# check for duplicate names
@fields.each do |t, n, p|
if n == name
raise SyntaxError, "duplicate field '#{name}' in #{self}", caller
end
end
# check that name doesn't shadow an existing method
if self.instance_methods.include?(name)
raise NameError.new("", name),
"field '#{name}' shadows an existing method", caller
end
end
# remember this field. These fields will be recalled upon creating
# an instance of this class
@fields.push([type, name, params])
end
# Ensures that +params+ is of the form expected by #initialize.
def sanitize_parameters!(sanitizer, params)
struct_params = {}
struct_params[:fields] = self.fields
struct_params[:endian] = self.endian unless self.endian.nil?
params[:struct_params] = struct_params
super(sanitizer, params)
end
end
# These are the parameters used by this class.
mandatory_parameter :struct_params
def initialize(params = {}, env = nil)
super(params, env)
@struct = BinData::Struct.new(param(:struct_params), create_env)
end
# Forward method calls to the internal struct.
def method_missing(symbol, *args, &block)
if @struct.respond_to?(symbol)
@struct.__send__(symbol, *args, &block)
else
super
end
end
#---------------
private
# Retrieve a sensible default from the internal struct.
def sensible_default
get
end
# Read data into the fields of the internal struct then return the value.
def read_val(io)
@struct.read(io)
get
end
# Sets +val+ into the fields of the internal struct then returns the
# string representation.
def val_to_str(val)
set(val)
@struct.to_s
end
###########################################################################
# To be implemented by subclasses
# Extracts the value for this data object from the fields of the
# internal struct.
def get
raise NotImplementedError
end
# Sets the fields of the internal struct to represent +v+.
def set(v)
raise NotImplementedError
end
# To be implemented by subclasses
###########################################################################
end
end