require'bindata/io'require'bindata/lazy'require'bindata/params'require'bindata/registry'require'bindata/sanitize'require'stringio'moduleBinData# Error raised when unexpected results occur when reading data from IO.classValidityError<StandardError;end# This is the abstract base class for all data objects.## == Parameters## Parameters may be provided at initialisation to control the behaviour of# an object. These params are:## [<tt>:readwrite</tt>] Deprecated. An alias for :onlyif.# [<tt>:onlyif</tt>] Used to indicate a data object is optional.# if false, calls to #read or #write will not# perform any I/O, #num_bytes will return 0 and# #snapshot will return nil. Default is true.# [<tt>:check_offset</tt>] Raise an error if the current IO offset doesn't# meet this criteria. A boolean return indicates# success or failure. Any other return is compared# to the current offset. The variable +offset+# is made available to any lambda assigned to# this parameter. This parameter is only checked# before reading.# [<tt>:adjust_offset</tt>] Ensures that the current IO offset is at this# position before reading. This is like# <tt>:check_offset</tt>, except that it will# adjust the IO offset instead of raising an error.classBaseclass<<selfextendParameters# Define methods for:# bindata_mandatory_parameters# bindata_optional_parameters# bindata_default_parameters# bindata_mutually_exclusive_parametersdefine_x_parameters(:bindata_mandatory,[])do|array,args|args.each{|arg|array<<arg.to_sym}array.uniq!enddefine_x_parameters(:bindata_optional,[])do|array,args|args.each{|arg|array<<arg.to_sym}array.uniq!enddefine_x_parameters(:bindata_default,{})do|hash,args|params=args.length>0?args[0]:{}hash.merge!(params)enddefine_x_parameters(:bindata_mutually_exclusive,[])do|array,args|array<<[args[0].to_sym,args[1].to_sym]end# Returns a list of internal parameters that are accepted by this objectdefinternal_parameters(bindata_mandatory_parameters+bindata_optional_parameters+bindata_default_parameters.keys).uniqend# Ensures that +params+ is of the form expected by #initialize.defsanitize_parameters!(sanitizer,params)# replace :readwrite with :onlyififparams.has_key?(:readwrite)warn":readwrite is deprecated. Replacing with :onlyif"params[:onlyif]=params.delete(:readwrite)end# add default parametersbindata_default_parameters.eachdo|k,v|params[k]=vunlessparams.has_key?(k)end# ensure mandatory parameters existbindata_mandatory_parameters.eachdo|prm|ifnotparams.has_key?(prm)raiseArgumentError,"parameter ':#{prm}' must be specified "+"in #{self}"endend# ensure mutual exclusionbindata_mutually_exclusive_parameters.eachdo|param1,param2|ifparams.has_key?(param1)andparams.has_key?(param2)raiseArgumentError,"params #{param1} and #{param2} "+"are mutually exclusive"endendend# Can this data object self reference itself?defrecursive?falseend# Instantiates this class and reads from +io+. For single value objects# just the value is returned, otherwise the newly created data object is# returned.defread(io)data=self.newdata.read(io)data.single_value??data.value:dataend# Registers the mapping of +name+ to +klass+.defregister(name,klass)Registry.instance.register(name,klass)endprivate:registerend# Define the parameters we use in this class.bindata_optional_parameters:check_offset,:adjust_offsetbindata_default_parameters:onlyif=>truebindata_mutually_exclusive_parameters:check_offset,:adjust_offset# Creates a new data object.## +params+ is a hash containing symbol keys. Some params may# reference callable objects (methods or procs). +parent+ is the# parent data object (e.g. struct, array, choice) this object resides# under.definitialize(params={},parent=nil)@params=Sanitizer.sanitize(self.class,params)@parent=parentend# The parent data object.attr_accessor:parent# Returns all the custom parameters supplied to this data object.defparameters@params.extra_parametersend# Reads data into this data object by calling #do_read then #done_read.defread(io)io=BinData::IO.new(io)unlessBinData::IO===iodo_read(io)done_readselfend# Reads the value for this data from +io+.defdo_read(io)raiseArgumentError,"io must be a BinData::IO"unlessBinData::IO===ioclearcheck_offset(io)ifeval_param(:onlyif)_do_read(io)endend# Writes the value for this data to +io+ by calling #do_write.defwrite(io)io=BinData::IO.new(io)unlessBinData::IO===iodo_write(io)io.flushselfend# Writes the value for this data to +io+.defdo_write(io)raiseArgumentError,"io must be a BinData::IO"unlessBinData::IO===ioifeval_param(:onlyif)_do_write(io)endend# Returns the number of bytes it will take to write this data by calling# #do_num_bytes.defnum_bytes(what=nil)num=do_num_bytes(what)num.ceilend# Returns the number of bytes it will take to write this data.defdo_num_bytes(what=nil)ifeval_param(:onlyif)_do_num_bytes(what)else0endend# Returns a snapshot of this data object.# Returns nil if :onlyif is falsedefsnapshotifeval_param(:onlyif)_snapshotelsenilendend# Returns the string representation of this data object.defto_sio=StringIO.newwrite(io)io.rewindio.readend# Return a human readable representation of this object.definspectsnapshot.inspectend# Returns the object this object represents.defobjselfend#---------------private# Returns the value of the evaluated parameter. +key+ references a# parameter from the +params+ hash used when creating the data object.# +values+ contains data that may be accessed when evaluating +key+.# Returns nil if +key+ does not refer to any parameter.defeval_param(key,values=nil)LazyEvaluator.eval(no_eval_param(key),self,values)end# Returns the parameter from the +params+ hash referenced by +key+.# Use this method if you are sure the parameter is not to be evaluated.# You most likely want #eval_param.defno_eval_param(key)@params.internal_parameters[key]end# Returns whether +key+ exists in the +params+ hash used when creating# this data object.defhas_param?(key)@params.internal_parameters.has_key?(key)end# Checks that the current offset of +io+ is as expected. This should# be called from #do_read before performing the reading.defcheck_offset(io)ifhas_param?(:check_offset)actual_offset=io.offsetexpected=eval_param(:check_offset,:offset=>actual_offset)ifnotexpectedraiseValidityError,"offset not as expected"elsifactual_offset!=expectedandexpected!=trueraiseValidityError,"offset is '#{actual_offset}' but "+"expected '#{expected}'"endelsifhas_param?(:adjust_offset)actual_offset=io.offsetexpected=eval_param(:adjust_offset)ifactual_offset!=expectedbeginseek=expected-actual_offsetio.seekbytes(seek)warn"adjusting stream position by #{seek} bytes"if$VERBOSErescue# could not seek so raise an errorraiseValidityError,"offset is '#{actual_offset}' but "+"couldn't seek to expected '#{expected}'"endendendend############################################################################ To be implemented by subclasses# Resets the internal state to that of a newly created object.defclearraiseNotImplementedErrorend# Returns true if the object has not been changed since creation.defclear?(*args)raiseNotImplementedErrorend# Returns whether this data object contains a single value. Single# value data objects respond to <tt>#value</tt> and <tt>#value=</tt>.defsingle_value?raiseNotImplementedErrorend# To be called after calling #do_read.defdone_readraiseNotImplementedErrorend# Reads the data for this data object from +io+.def_do_read(io)raiseNotImplementedErrorend# Writes the value for this data to +io+.def_do_write(io)raiseNotImplementedErrorend# Returns the number of bytes it will take to write this data.def_do_num_bytesraiseNotImplementedErrorend# Returns a snapshot of this data object.def_snapshotraiseNotImplementedErrorend# Set visibility requirements of methods to implementpublic:clear,:clear?,:single_value?,:done_readprivate:_do_read,:_do_write,:_do_num_bytes,:_snapshot# End To be implemented by subclasses###########################################################################endend