[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[or-cvs] r10572: initial import (in topf/trunk: . lib lib/fuzz-struct stuff)
Author: benedikt
Date: 2007-06-12 05:27:41 -0400 (Tue, 12 Jun 2007)
New Revision: 10572
Added:
topf/trunk/config.yml
topf/trunk/lib/
topf/trunk/lib/dir.rb
topf/trunk/lib/fuzz-generic.rb
topf/trunk/lib/fuzz-struct.rb
topf/trunk/lib/fuzz-struct/
topf/trunk/lib/fuzz-struct/char-field.rb
topf/trunk/lib/fuzz-struct/float-field.rb
topf/trunk/lib/fuzz-struct/fuzz-struct.rb
topf/trunk/lib/fuzz-struct/hex-octet-field.rb
topf/trunk/lib/fuzz-struct/nested-field.rb
topf/trunk/lib/fuzz-struct/octet-field.rb
topf/trunk/lib/fuzz-struct/pad-field.rb
topf/trunk/lib/fuzz-struct/signed-field.rb
topf/trunk/lib/fuzz-struct/text-field.rb
topf/trunk/lib/fuzz-struct/unsigned-field.rb
topf/trunk/lib/fuzz-struct/yaml.rb
topf/trunk/lib/fuzz.rb
topf/trunk/lib/pkcs1.rb
topf/trunk/lib/topf.rb
topf/trunk/stuff/
topf/trunk/stuff/fuzz-private.pem
topf/trunk/stuff/fuzz-public.pem
topf/trunk/stuff/output-dir.rb
topf/trunk/tor-dir-fuzz.rb
Log:
initial import
Added: topf/trunk/config.yml
===================================================================
--- topf/trunk/config.yml (rev 0)
+++ topf/trunk/config.yml 2007-06-12 09:27:41 UTC (rev 10572)
@@ -0,0 +1,3 @@
+PORT: "2324"
+HOST: 127.0.0.1
+KEYFILE: stuff/fuzz-private.pem
Added: topf/trunk/lib/dir.rb
===================================================================
--- topf/trunk/lib/dir.rb (rev 0)
+++ topf/trunk/lib/dir.rb 2007-06-12 09:27:41 UTC (rev 10572)
@@ -0,0 +1,188 @@
+module TOPF
+ module Base16
+ def Base16.encode(string)
+ temp = []
+ string.each_byte do |i|
+ temp.push i.to_s(16)
+ end
+ temp.join
+ end
+ end
+
+ module Dir
+ DefaultNicknameLength = 6
+ DefaultOrPort = 80
+ DefaultDirPort = 80
+
+ PublicCertificate =
+%{\n-----BEGIN RSA PUBLIC KEY-----
+MIGJAoGBAKo3Ijan5HOp6zzV1VJmveh2uvJv0A5qLiJtNVrebMTAM8kte14Fsylb
+DE5UmDryqbOmNi9gIcZKfjuqEKcuhsEPDObLeabNkdP+YGURVQ72IhQT8XRiGWg3
+2i8F9xhp5KPGKhjkXuAxnt2FS9lkOvIDnm4jnOtzfX0/RJRRrBCvAgMBAAE=
+-----END RSA PUBLIC KEY-----}
+
+ class RouterItem < BitStruct
+ char :item, 6*8, :fuzzable => false
+ char :nickname, TOP::Dir::DefaultNicknameLength*8
+ octets :address, 32
+ signed :OrPort, 32
+ signed :SocksPort, 32
+ signed :DirPort, 32
+
+ initial_value.item = "router"
+ initial_value.nickname = "foobar"
+ initial_value.address = "127.0.0.1"
+ initial_value.SocksPort = 0
+ initial_value.OrPort = TOP::Dir::DefaultOrPort
+ initial_value.DirPort = TOP::Dir::DefaultDirPort
+ end
+
+ class PublishedItem < BitStruct
+ char :item, 9*8
+ char :time, 19*8
+
+ initial_value.item = "published"
+ initial_value.time = "#{Date.today.to_s} #{ Time.now.strftime "%H:%M:%S"}"
+ end
+
+ class OnionKeyItem < BitStruct
+ char :item, 9*8
+ rest :publicKey # key in PEM Format
+
+ initial_value.item = "onion-key"
+ initial_value.publicKey = TOP::Dir::PublicCertificate
+ end
+
+ class SigningKeyItem < BitStruct
+ char :item, 11*8
+ rest :publicKey # key in PEM Format
+
+ initial_value.item = "signing-key"
+ initial_value.publicKey = TOP::Dir::PublicCertificate
+ end
+
+ class BandwidthItem < BitStruct
+ char :item, 9*8
+ signed :avg, 128
+ signed :burst, 128
+ signed :observed, 128
+
+ initial_value.item = "bandwidth"
+ initial_value.avg = 1
+ initial_value.burst = 1
+ initial_value.observed = 1
+ end
+
+ class PlatformItem < BitStruct
+ char :item, 8*8
+ rest :string
+
+ initial_value.item = "platform"
+ end
+
+ class FingerprintItem < BitStruct
+ # FORMAT ??
+ end
+
+ class HibernateItem < BitStruct
+ char :item, 11*8
+ unsigned :state, 1
+
+ initial_value.item = "hibernating"
+ initial_value.state = 0
+ end
+
+ class UptimeItem < BitStruct
+ char :item, 6*8
+ # FORMAT ??
+
+ initial_value.item = "uptime"
+ end
+
+ class ContactItem < BitStruct
+ char :item, 7*8
+ rest :info
+
+ initial_value.item = "contact"
+ end
+
+ class FamilyItem < BitStruct
+ char :item, 6*8
+ rest :names
+
+ initial_value.item = "family"
+ end
+
+ class ReadHistoryItem < BitStruct
+ char :item, 12*8
+ char :time, 19*8
+
+ initial_value.item = "read-history"
+ initial_value.time = "#{Date.today.to_s} #{ Time.now.strftime "%H:%M:%S"}"
+ end
+
+ class WriteHistoryItem < BitStruct
+ char :item, 12*8
+ char :time, 19*8
+
+ initial_value.item = "write-history"
+ initial_value.time = "#{Date.today.to_s} #{ Time.now.strftime "%H:%M:%S"}"
+ end
+
+ class EventDnsItem < BitStruct
+ char :item, 8*8
+ unsigned :bool, 1
+
+ initial_value.item = "eventdns"
+ initial_value.bool = 0
+ end
+
+ class RouterDescriptor
+ def initialize(publicKey, host = "127.0.0.1", port = 80)
+ @osslkey = OpenSSL::PKey::RSA.new(File.read(publicKey) )
+ @pkcs1 = PKCS1::SignatureScheme::RSASSAPKCS1v1_5.new(Digest::SHA1)
+
+ @key = PKCS1::Key::RSA.new(@osslkey.n.to_i , @osslkey.e.to_i, @osslkey.d.to_i)
+
+ @items = [ RouterItem.new,
+ PublishedItem.new,
+ OnionKeyItem.new,
+ SigningKeyItem.new,
+ BandwidthItem.new,
+ ]
+
+ @host = host
+ @port = port
+
+ connect!
+ end
+
+ def connect!
+ end
+
+ def add_item( item )
+ @items.push item
+ end
+
+ def fuzz!
+ @items.each do |item|
+ item.fuzz!
+ end
+ end
+
+ def to_s
+ resultString = ""
+ @items.each do |item|
+ resultString << item.format << "\n"
+ end
+ resultString << "router-signature\n"
+ sig = @pkcs1.sign(@key, resultString)
+
+ resultString << "-----BEGIN SIGNATURE-----\n"
+ resultString << sig
+ resultString << "\n-----END SIGNATURE-----" << "\n"*2
+ end
+ end
+ end
+end
+
Added: topf/trunk/lib/fuzz-generic.rb
===================================================================
--- topf/trunk/lib/fuzz-generic.rb (rev 0)
+++ topf/trunk/lib/fuzz-generic.rb 2007-06-12 09:27:41 UTC (rev 10572)
@@ -0,0 +1,6 @@
+dir = __FILE__
+$:.unshift(File.dirname(dir)) unless
+$:.include?(File.dirname(dir)) || $:.include?(File.expand_path(File.dirname(dir)))
+
+require "fuzz-struct"
+require "fuzz"
Added: topf/trunk/lib/fuzz-struct/char-field.rb
===================================================================
--- topf/trunk/lib/fuzz-struct/char-field.rb (rev 0)
+++ topf/trunk/lib/fuzz-struct/char-field.rb 2007-06-12 09:27:41 UTC (rev 10572)
@@ -0,0 +1,68 @@
+require 'fuzz-struct/fuzz-struct'
+
+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
+
+ 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
+ end
+end
Added: topf/trunk/lib/fuzz-struct/float-field.rb
===================================================================
--- topf/trunk/lib/fuzz-struct/float-field.rb (rev 0)
+++ topf/trunk/lib/fuzz-struct/float-field.rb 2007-06-12 09:27:41 UTC (rev 10572)
@@ -0,0 +1,82 @@
+require 'fuzz-struct/fuzz-struct'
+
+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
+
+ class << self
+ # 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
+ end
+end
Added: topf/trunk/lib/fuzz-struct/fuzz-struct.rb
===================================================================
--- topf/trunk/lib/fuzz-struct/fuzz-struct.rb (rev 0)
+++ topf/trunk/lib/fuzz-struct/fuzz-struct.rb 2007-06-12 09:27:41 UTC (rev 10572)
@@ -0,0 +1,572 @@
+# 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
+ 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
+
+ # Field fuzzable ?
+ attr_reader :fuzzable
+
+ # 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
+
+ @class = opts[:field_class] || opts["field_class"]
+ @display_name = opts[:display_name] || opts["display_name"]
+ @default = opts[:default] || opts["default"] || self.class.default
+ @format = opts[:format] || opts["format"]
+
+ if opts.has_key?(:fuzzable) || opts.has_key?("fuzzable")
+ @fuzzable = opts[:fuzzable] || opts["fuzzable"]
+ else
+ @fuzzable = true
+ end
+ 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
+
+ def fuzz(obj, opts)
+ if @fuzzable and rand(2).to_i == 1
+ val = obj.send(name)
+ str =
+ begin
+ val.inspect(opts)
+ rescue ArgumentError # assume: "wrong number of arguments (1 for 0)"
+ val.inspect
+ end
+ obj.send "#{name}=", ((f=@format) ? (f % Fuzz::fuzzField(@class, @length, str) ) : Fuzz::fuzzField(@class, @length, str))
+ end
+ end
+
+ # Normally, all fields show up in inspect, but some, such as padding,
+ # should not.
+ def inspectable?; true; end
+
+ def to_s(obj, opts)
+ val = obj.send(name)
+ 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 = {}
+
+ class << self
+ # ------------------------
+ # :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 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 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. 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
+ 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
+ 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
+ }
+
+ DETAILED_INSPECT_OPTS = {
+ :format => "%s:\n%s",
+ :field_format => "%30s = %s",
+ :separator => "\n",
+ :field_name_meth => :display_name,
+ :include_rest => true
+ }
+
+ # 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])
+
+ opts[:format] % [self.class, body]
+ end
+
+ def fuzz!(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
+
+ fields_for_inspect.map do |field|
+ field.fuzz(self, opts)
+ end
+ end
+
+ def format(opts = DEFAULT_INSPECT_OPTS)
+ fields_for_output = fields.select {|field| field.inspectable?}
+
+ if opts[:include_rest] and (rest_field = self.class.rest_field)
+ fields_for_output << rest_field
+ end
+
+ fields_for_output.map { |field|
+ field.to_s(self, opts)
+ }.join " "
+ 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.to_s
+ 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
Added: topf/trunk/lib/fuzz-struct/hex-octet-field.rb
===================================================================
--- topf/trunk/lib/fuzz-struct/hex-octet-field.rb (rev 0)
+++ topf/trunk/lib/fuzz-struct/hex-octet-field.rb 2007-06-12 09:27:41 UTC (rev 10572)
@@ -0,0 +1,36 @@
+require 'fuzz-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
+
+ class << self
+ # 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
+ end
+end
Added: topf/trunk/lib/fuzz-struct/nested-field.rb
===================================================================
--- topf/trunk/lib/fuzz-struct/nested-field.rb (rev 0)
+++ topf/trunk/lib/fuzz-struct/nested-field.rb 2007-06-12 09:27:41 UTC (rev 10572)
@@ -0,0 +1,127 @@
+require 'fuzz-struct/fuzz-struct'
+
+class BitStruct
+ # Class for nesting a BitStruct as a field within another BitStruct.
+ # Declared with BitStruct.nest.
+ class NestedField < Field
+ attr_reader :nested_class
+
+ 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
+ val_byte_range = 0..length_byte-1
+
+ 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[val_byte_range]
+ end
+ end
+ end
+ end
+
+ class << self
+ # Define a nested field in the current subclass of BitStruct,
+ # with the given _name_ and _nested_class_. Length is determined from
+ # _nested_class_.
+ #
+ # In _rest_:
+ #
+ # 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.
+ #
+ # 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.
+ #
+ # 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>>
+ #
+ # # 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, nested_class, *rest)
+ opts = parse_options(rest, name, NestedField)
+ 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
+ end
+end
Added: topf/trunk/lib/fuzz-struct/octet-field.rb
===================================================================
--- topf/trunk/lib/fuzz-struct/octet-field.rb (rev 0)
+++ topf/trunk/lib/fuzz-struct/octet-field.rb 2007-06-12 09:27:41 UTC (rev 10572)
@@ -0,0 +1,61 @@
+require 'fuzz-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
+
+ class << self
+ # 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
+ end
+end
Added: topf/trunk/lib/fuzz-struct/pad-field.rb
===================================================================
--- topf/trunk/lib/fuzz-struct/pad-field.rb (rev 0)
+++ topf/trunk/lib/fuzz-struct/pad-field.rb 2007-06-12 09:27:41 UTC (rev 10572)
@@ -0,0 +1,32 @@
+require 'fuzz-struct/fuzz-struct'
+
+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
+
+ class << self
+ # 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
+ end
+end
Added: topf/trunk/lib/fuzz-struct/signed-field.rb
===================================================================
--- topf/trunk/lib/fuzz-struct/signed-field.rb (rev 0)
+++ topf/trunk/lib/fuzz-struct/signed-field.rb 2007-06-12 09:27:41 UTC (rev 10572)
@@ -0,0 +1,238 @@
+require 'fuzz-struct/fuzz-struct'
+
+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 <= 16 ? "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 <= 16 ? "v" : "V"
+ when "network", "big", ""
+ ctl = length <= 16 ? "n" : "N"
+ else
+ raise ArgumentError,
+ "Unrecognized endian option: #{endian.inspect}"
+ end
+
+ data_is_big_endian =
+ ([1234].pack(ctl) == [1234].pack(length <= 16 ? "n" : "N"))
+
+ if length_byte == 1
+ rest = 8 - length_bit
+ mask = ["0"*offset_bit + "1"*length + "0"*rest].pack("B8")[0]
+ mask2 = ["1"*offset_bit + "0"*length + "1"*rest].pack("B8")[0]
+
+ 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
+
+ else
+ raise "unsupported: #{inspect}"
+ end
+ end
+ end
+
+ class << self
+ # 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
+ end
+end
Added: topf/trunk/lib/fuzz-struct/text-field.rb
===================================================================
--- topf/trunk/lib/fuzz-struct/text-field.rb (rev 0)
+++ topf/trunk/lib/fuzz-struct/text-field.rb 2007-06-12 09:27:41 UTC (rev 10572)
@@ -0,0 +1,63 @@
+require 'fuzz-struct/fuzz-struct'
+
+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
+
+ class << self
+ # 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
+ end
+end
Added: topf/trunk/lib/fuzz-struct/unsigned-field.rb
===================================================================
--- topf/trunk/lib/fuzz-struct/unsigned-field.rb (rev 0)
+++ topf/trunk/lib/fuzz-struct/unsigned-field.rb 2007-06-12 09:27:41 UTC (rev 10572)
@@ -0,0 +1,228 @@
+require 'fuzz-struct/fuzz-struct'
+
+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 <= 16 ? "S" : "L"
+ when "little"
+ ctl = length <= 16 ? "v" : "V"
+ when "network", "big", ""
+ ctl = length <= 16 ? "n" : "N"
+ else
+ raise ArgumentError,
+ "Unrecognized endian option: #{endian.inspect}"
+ end
+
+ data_is_big_endian =
+ ([1234].pack(ctl) == [1234].pack(length <= 16 ? "n" : "N"))
+
+ if length_byte == 1
+ rest = 8 - length_bit
+ mask = ["0"*offset_bit + "1"*length + "0"*rest].pack("B8")[0]
+ mask2 = ["1"*offset_bit + "0"*length + "1"*rest].pack("B8")[0]
+
+ 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
+
+ else
+ raise "unsupported: #{inspect}"
+ end
+ end
+ end
+
+ class << self
+ # 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
+ end
+end
Added: topf/trunk/lib/fuzz-struct/yaml.rb
===================================================================
--- topf/trunk/lib/fuzz-struct/yaml.rb (rev 0)
+++ topf/trunk/lib/fuzz-struct/yaml.rb 2007-06-12 09:27:41 UTC (rev 10572)
@@ -0,0 +1,70 @@
+require 'fuzz-struct/fuzz-struct'
+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
Added: topf/trunk/lib/fuzz-struct.rb
===================================================================
--- topf/trunk/lib/fuzz-struct.rb (rev 0)
+++ topf/trunk/lib/fuzz-struct.rb 2007-06-12 09:27:41 UTC (rev 10572)
@@ -0,0 +1,16 @@
+# A Convenience to load all field classes and yaml handling.
+
+dir = __FILE__
+$:.unshift(File.dirname(dir)) unless
+$:.include?(File.dirname(dir)) || $:.include?(File.expand_path(File.dirname(dir)))
+
+require 'fuzz-struct/unsigned-field'
+require 'fuzz-struct/signed-field'
+require 'fuzz-struct/octet-field'
+require 'fuzz-struct/hex-octet-field'
+require 'fuzz-struct/char-field'
+require 'fuzz-struct/text-field'
+require 'fuzz-struct/nested-field'
+require 'fuzz-struct/float-field'
+require 'fuzz-struct/pad-field'
+require 'fuzz-struct/yaml'
Added: topf/trunk/lib/fuzz.rb
===================================================================
--- topf/trunk/lib/fuzz.rb (rev 0)
+++ topf/trunk/lib/fuzz.rb 2007-06-12 09:27:41 UTC (rev 10572)
@@ -0,0 +1,96 @@
+module Fuzz
+ MAX_RAND = 1000
+ MAX_STRING_PERMUTATIONS = 3
+
+ def Fuzz.fuzzField(type, length, value)
+ case type.to_s
+ when "BitStruct::CharField"
+ return Fuzz::fuzzString(value, length )
+ when "BitStruct::UnsignedField"
+ return Fuzz::fuzzUnsignedNumber(length.to_i)
+ when "BitStruct::SignedField"
+ return Fuzz::fuzzSignedNumber(length.to_i)
+ when "BitStruct::OctetField"
+ return value.split(".").collect{ |octet| Fuzz::fuzzString(octet, 2) }.join( "." )
+ else
+ if value.class.to_s == "String"
+ return Fuzz::fuzzString(value, length/8 )
+ else
+ return value
+ end
+ end
+ end
+
+ def Fuzz.fuzzString(string, length)
+ if Fuzz::MAX_STRING_PERMUTATIONS > 0
+ rand( Fuzz::MAX_STRING_PERMUTATIONS ).times do |i|
+ case rand(5)
+ when 0
+ string.insert( rand(string.size), "A"*rand( length ))
+ when 1
+ string.insert( rand(string.size), "%n"*rand( length ))
+ when 2
+ string.insert( rand(string.size), Fuzz::randCharacter( string[rand(string.size)] )*rand( length ))
+ when 3
+ rand(5) == 0 ? string = "" : nil
+ end
+ end
+ end
+ string.gsub(/\\+/, "\\")
+ end
+
+ def Fuzz.randCharacter(default)
+ case rand(9)
+ when 0
+ return '\0'
+ when 1
+ return 0x0.chr
+ when 2
+ return ']'
+ when 3
+ return ')'
+ when 4
+ return '}'
+ when 5
+ return '>'
+ when 6
+ return '\n'
+ when 7
+ return '\''
+ when 8
+ return '\"'
+ else
+ return default
+ end
+ end
+
+ def Fuzz.fuzzSignedNumber(bits)
+ case rand(3)
+ when 0
+ rand(5).to_i # small numbers
+ when 1
+ return -1.to_i
+ when 2
+ return (2.power!(bits)-1)/2 #biggest 2s complement number
+ when 3
+ return -(2.power!(bits))/2 #smallest 2s complement number
+ end
+ end
+
+ def Fuzz.fuzzUnsignedNumber(bits)
+ case rand(1)
+ when 0
+ rand(5).to_i # small numbers
+ when 1
+ return 2.power!(bits)-1 #biggest number
+ end
+ end
+
+ def Fuzz.coreObserver(file)
+ if Dir.new(File.expand_path(File.dirname(file)) ).entries.select{|x| x=~/\.core/}.size != 0
+ return true
+ else
+ return false
+ end
+ end
+end
Added: topf/trunk/lib/pkcs1.rb
===================================================================
--- topf/trunk/lib/pkcs1.rb (rev 0)
+++ topf/trunk/lib/pkcs1.rb 2007-06-12 09:27:41 UTC (rev 10572)
@@ -0,0 +1,823 @@
+# Copyright 2004, 2005 NAKAMURA, Hiroshi <nakahiro@xxxxxxxxxxxx>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+require 'openssl'
+require 'digest/md5'
+require 'digest/sha1'
+require 'digest/sha2'
+
+
+module PKCS1
+
+
+# 1 Introduction
+
+
+# 2 Notation
+
+module Util
+module_function
+
+ def nbits(num)
+ idx = num.size * 8 - 1
+ while idx >= 0
+ if num[idx].nonzero?
+ return idx + 1
+ end
+ idx -= 1
+ end
+ 0
+ end
+
+ def divceil(a, b)
+ (a + b - 1) / b
+ end
+
+ def xor(a, b)
+ if a.size != b.size
+ raise ArgumentError
+ end
+ ary = []
+ a.size.times do |idx|
+ ary << (a[idx] ^ b[idx])
+ end
+ ary.pack("c*")
+ end
+end
+
+
+# 3 Key types
+module Key
+ class RSAPublicKey
+ attr_reader :n
+ attr_reader :e
+
+ def initialize(n, e)
+ @n, @e = n, e
+ @n_bn = OpenSSL::BN.new(@n.to_s)
+ @e_bn = OpenSSL::BN.new(@e.to_s)
+ end
+
+ def encrypt(m)
+ calc(m)
+ end
+
+ # M = S ^ e (mod n)
+ def verify(s)
+ if s < 0 or s >= @n
+ raise ArgumentError, "signature representative out of range"
+ end
+ calc(s)
+ end
+
+ private
+
+ # c = m ^ e (mod n)
+ def calc(input)
+ input_bn = OpenSSL::BN.new(input.to_s)
+ (input_bn.mod_exp(@e_bn, @n_bn)).to_i
+ end
+ end
+
+
+ class RSAPrivateKey
+ include Util
+
+ attr_reader :public_key
+ attr_reader :version
+ attr_reader :n
+ attr_reader :d
+
+ def initialize(n, e, d)
+ @version = 0
+ @n, @d = n, d
+ @n_bn = OpenSSL::BN.new(@n.to_s)
+ @d_bn = OpenSSL::BN.new(@d.to_s)
+ @public_key = RSAPublicKey.new(n, e)
+ end
+
+ def modbytes
+ divceil(modbits, 8)
+ end
+
+ def modbits
+ nbits(@n)
+ end
+
+ def encrypt(m)
+ @public_key.encrypt(m)
+ end
+
+ def decrypt(c)
+ calc(c)
+ end
+
+ def sign(m)
+ if m < 0 or m >= @n
+ raise ArgumentError, "message representative out of range"
+ end
+ calc(m)
+ end
+
+ def verify(s)
+ @public_key.verify(s)
+ end
+
+ private
+
+ # s = m ^ d (mod n)
+ def calc(input)
+ input_bn = OpenSSL::BN.new(input.to_s)
+ (input_bn.mod_exp(@d_bn, @n_bn)).to_i
+ end
+ end
+
+
+ class RSACRTPrivateKey
+ include Util
+
+ attr_reader :public_key
+ attr_reader :version
+ attr_reader :n
+ attr_reader :p
+ attr_reader :q
+ attr_reader :dp
+ attr_reader :dq
+ attr_reader :qinv
+
+ def initialize(n, e, p, q, dp, dq, qinv)
+ @version = 0
+ @n = n
+ @n_bn = OpenSSL::BN.new(@n.to_s)
+ @p, @q, @dp, @dq, @qinv = p, q, dp, dq, qinv
+ @p_bn = OpenSSL::BN.new(@p.to_s)
+ @q_bn = OpenSSL::BN.new(@q.to_s)
+ @dp_bn = OpenSSL::BN.new(@dp.to_s)
+ @dq_bn = OpenSSL::BN.new(@dq.to_s)
+ @qinv_bn = OpenSSL::BN.new(@qinv.to_s)
+ @public_key = RSAPublicKey.new(n, e)
+ end
+
+ def modbytes
+ divceil(modbits, 8)
+ end
+
+ def modbits
+ nbits(@n)
+ end
+
+ def encrypt(m)
+ @public_key.encrypt(m)
+ end
+
+ def decrypt(c)
+ calc(c)
+ end
+
+ def sign(m)
+ if m < 0 or m >= @n
+ raise ArgumentError, "message representative out of range"
+ end
+ calc(m)
+ end
+
+ def verify(s)
+ @public_key.verify(s)
+ end
+
+ private
+
+ # s1 = m ^ dp (mod p)
+ # s2 = m ^ dq (mod q)
+ # h = (s1 - s2) * qinv (mod p)
+ # s = s2 + q * h
+ def calc(input)
+ input_bn = OpenSSL::BN.new(input.to_s)
+ s1 = input_bn.mod_exp(@dp_bn, @p_bn)
+ s2 = input_bn.mod_exp(@dq_bn, @q_bn)
+ h = @qinv_bn.mod_mul(s1 - s2, @p_bn)
+ (s2 + q * h).to_i
+ end
+ end
+
+
+ RSA = RSAPrivateKey
+ RSACRT = RSACRTPrivateKey
+end
+
+
+# 4 Data conversion primitives
+module DataConversion
+module_function
+
+ # Integer to Octet String primitive
+ def i2osp(x, len)
+ if x >= 256 ** len
+ raise ArgumentError, "integer too large"
+ end
+ os = to_bytes(x).sub(/^\x00+/, '')
+ "\x00" * (len - os.size) + os
+ end
+
+ # Octet String to Integer primitive
+ def os2ip(x)
+ from_bytes(x)
+ end
+
+ def to_bytes(num)
+ bits = num.size * 8
+ pos = value = 0
+ str = ""
+ for idx in 0..(bits - 1)
+ if num[idx].nonzero?
+ value |= (num[idx] << pos)
+ end
+ pos += 1
+ if pos == 32
+ str = [value].pack("N") + str
+ pos = value = 0
+ end
+ end
+ str
+ end
+
+ def from_bytes(bytes)
+ num = 0
+ bytes.each_byte do |c|
+ num <<= 8
+ num |= c
+ end
+ num
+ end
+end
+
+
+# 5 Cryptographic primitives
+module CryptographicPrimitive
+module_function
+
+ def rsaep(key, msg)
+ key.encrypt(msg)
+ end
+
+ def rsadp(key, cipher)
+ key.decrypt(cipher)
+ end
+
+ def rsasp1(key, msg)
+ key.sign(msg)
+ end
+
+ def rsavp1(key, sig)
+ key.verify(sig)
+ end
+end
+
+
+# 6 Overview of schemes
+
+
+# 7 Encryption schemes
+module EncryptionScheme
+module_function
+
+ def rsaes_oaep_encrypt(key, msg, seed = nil, label = '')
+ RSAESOAEP.new.encrypt(key, msg, seed, label)
+ end
+
+ def rsaes_oaep_decrypt(key, cipher, seed = nil, label = '')
+ RSAESOAEP.new.decrypt(key, cipher, seed, label)
+ end
+
+
+ class RSAESOAEP
+ include DataConversion
+ include CryptographicPrimitive
+
+ def initialize(digest = Digest::SHA1)
+ @hlen = Hash.size(digest)
+ @encryption_encoder = EncryptionEncoding::EMEOAEP.new(digest)
+ end
+
+ def encrypt(key, msg, seed = nil, label = '')
+ k = key.modbytes
+ if msg.size > k - 2 * @hlen - 2
+ raise ArgumentError, "message too long"
+ end
+ em = @encryption_encoder.encode(msg, k, seed, label)
+ m = os2ip(em)
+ c = rsaep(key, m)
+ i2osp(c, k)
+ end
+
+ def decrypt(key, cipher, label = '')
+ k = key.modbytes
+ if cipher.size != k
+ raise ArgumentError, "decryption error"
+ end
+ if k < 2 * @hlen + 2
+ raise ArgumentError, "decryption error"
+ end
+ c = os2ip(cipher)
+ m = rsadp(key, c)
+ em = i2osp(m, k)
+ @encryption_encoder.decode(em, k, label)
+ end
+ end
+end
+
+
+# 7.1.1 1.b and 7.1.2 3 Encoding methods for encryption
+module EncryptionEncoding
+module_function
+
+ def eme_oaep_encode(msg, embits, seed = nil)
+ EMEOAEP.new.encode(msg, embits, seed)
+ end
+
+ def eme_oaep_decode(msg, em, embits)
+ EMEOAEP.new.decode(msg, em, embits)
+ end
+
+
+ class EMEOAEP
+ include Util
+
+ def initialize(digest = Digest::SHA1, mgf = nil)
+ @digest = digest
+ @hlen = Hash.size(@digest)
+ @mgf = mgf || MaskGeneration::MGF1.new(@digest)
+ end
+
+ def encode(msg, embytes, seed = nil, label = '')
+ lhash = dohash(label)
+ ps = "\x00" * (embytes - msg.size - 2 * @hlen - 2)
+ db = lhash + ps + "\x01" + msg
+ seed ||= OpenSSL::Random.random_bytes(@hlen)
+ dbmask = @mgf.generate(seed, embytes - @hlen - 1)
+ maskeddb = xor(db, dbmask)
+ seedmask = @mgf.generate(maskeddb, @hlen)
+ maskedseed = xor(seed, seedmask)
+ "\x00" + maskedseed + maskeddb
+ end
+
+ def decode(em, embytes, label = '')
+ lhash = dohash(label)
+ y = em[0]
+ maskedseed = em[1, @hlen]
+ maskeddb = em[1 + @hlen, embytes - @hlen - 1]
+ seedmask = @mgf.generate(maskeddb, @hlen)
+ seed = xor(maskedseed, seedmask)
+ dbmask = @mgf.generate(seed, embytes - @hlen - 1)
+ db = xor(maskeddb, dbmask)
+ lhashdash = db[0, @hlen]
+ if lhashdash != lhash
+ raise ArgumentError, "decryption error"
+ end
+ psm = db[@hlen, embytes - 2 * @hlen - 1]
+ if /\A(\x00*)\x01([\x00-\xff]*)\z/ =~ psm
+ ps, m = $1, $2
+ else
+ raise ArgumentError, "decryption error"
+ end
+ m
+ end
+
+ private
+
+ def dohash(msg)
+ @digest.digest(msg)
+ end
+ end
+end
+
+
+# 8 Signature schemes
+module SignatureScheme
+module_function
+
+ def rsassa_pss_sign(key, msg)
+ RSASSAPSS.new.sign(key, msg)
+ end
+
+ def rsassa_pss_verify(key, msg, sig)
+ RSASSAPSS.new.verify(key, msg, sig)
+ end
+
+ def rsassa_pss_sign_hash(key, hash)
+ RSASSAPSS.new.sign_hash(key, hash)
+ end
+
+ def rsassa_pss_verify_hash(key, hash, sig)
+ RSASSAPSS.new.verify_hash(key, hash, sig)
+ end
+
+ def rsassa_pkcs1v1_5_sign(key, msg)
+ RSASSAPKCS1v1_5.new.sign(key, msg)
+ end
+
+ def rsassa_pkcs1v1_5_verify(key, msg, sig)
+ RSASSAPKCS1v1_5.new.verify(key, msg, sig)
+ end
+
+ def rsassa_pkcs1v1_5_sign_hash(key, hash)
+ RSASSAPKCS1v1_5.new.sign_hash(key, hash)
+ end
+
+ def rsassa_pkcs1v1_5_verify_hash(key, hash, sig)
+ RSASSAPKCS1v1_5.new.verify_hash(key, hash, sig)
+ end
+
+
+ class RSASSAPSS
+ include Util
+ include DataConversion
+ include CryptographicPrimitive
+
+ def initialize(digest = Digest::SHA1, slen = 20, mgf = nil)
+ @signature_encoder = SignatureEncoding::EMSAPSS.new(digest, slen, mgf)
+ end
+
+ def sign(key, msg, salt = nil)
+ modbits = key.modbits
+ em = @signature_encoder.encode(msg, modbits - 1, salt)
+ sign_em(key, em, modbits)
+ end
+
+ def sign_hash(key, hash, salt = nil)
+ modbits = key.modbits
+ em = @signature_encoder.encode_hash(hash, modbits - 1, salt)
+ sign_em(key, em, modbits)
+ end
+
+ def verify(key, msg, sig)
+ modbits = key.modbits
+ em = decode_em(key, sig, modbits)
+ @signature_encoder.verify(msg, em, modbits - 1)
+ end
+
+ def verify_hash(key, hash, sig)
+ modbits = key.modbits
+ em = decode_em(key, sig, modbits)
+ @signature_encoder.verify_hash(hash, em, modbits - 1)
+ end
+
+ private
+
+ def sign_em(key, em, modbits)
+ m = os2ip(em)
+ s = rsasp1(key, m)
+ s = i2osp(s, divceil(modbits, 8))
+ s
+ end
+
+ def decode_em(key, sig, modbits)
+ if sig.size != divceil(modbits - 1, 8)
+ raise ArgumentError, "invalid signature"
+ end
+ s = os2ip(sig)
+ m = rsavp1(key, s)
+ emlen = divceil(modbits - 1, 8)
+ i2osp(m, emlen)
+ end
+ end
+
+
+ class RSASSAPKCS1v1_5
+ include DataConversion
+ include CryptographicPrimitive
+
+ def initialize(digest = Digest::SHA1)
+ @signature_encoder = SignatureEncoding::EMSAPKCS1v1_5.new(digest)
+ end
+
+ def sign(key, msg)
+ k = key.modbytes
+ em = @signature_encoder.encode(msg, k)
+ sign_em(key, em, k)
+ end
+
+ def sign_hash(key, hash)
+ k = key.modbytes
+ em = @signature_encoder.encode_hash(hash, k)
+ sign_em(key, em, k)
+ end
+
+ def verify(key, msg, sig)
+ k = key.modbytes
+ em = decode_em(key, sig, k)
+ emdash = @signature_encoder.encode(msg, k)
+ unless em == emdash
+ raise ArgumentError, "invalid signature"
+ end
+ true
+ end
+
+ def verify_hash(key, hash, sig)
+ k = key.modbytes
+ em = decode_em(key, sig, k)
+ emdash = @signature_encoder.encode_hash(hash, k)
+ unless em == emdash
+ raise ArgumentError, "invalid signature"
+ end
+ true
+ end
+
+ private
+
+ def sign_em(key, em, k)
+ m = os2ip(em)
+ s = rsasp1(key, m)
+ i2osp(s, k)
+ end
+
+ def decode_em(key, sig, k)
+ if sig.size != k
+ raise ArgumentError, "invalid signature"
+ end
+ s = os2ip(sig)
+ m = rsavp1(key, s)
+ i2osp(m, k)
+ end
+ end
+end
+
+
+# 9 Encoding methods for signatures with appendix
+module SignatureEncoding
+module_function
+
+ def emsa_pss_encode(msg, embits, salt = nil)
+ EMSAPSS.new.encode(msg, embits, salt)
+ end
+
+ def emsa_pss_verify(msg, em, embits)
+ EMSAPSS.new.verify(msg, em, embits)
+ end
+
+ def emsa_pkcs1_v1_5_encode(msg, emlen)
+ EMSAPKCS1v1_5.new.encode(msg, emlen)
+ end
+
+
+ class EMSAPSS
+ include Util
+
+ def initialize(digest = Digest::SHA1, slen = 20, mgf = nil)
+ @digest = digest
+ @slen = slen
+ @hlen = Hash.size(@digest)
+ @mgf = mgf || MaskGeneration::MGF1.new(@digest)
+ end
+
+ def encode(msg, embits, salt = nil)
+ mhash = dohash(msg)
+ encode_hash(mhash, embits, salt)
+ end
+
+ def encode_hash(mhash, embits, salt = nil)
+ emlen = divceil(embits, 8)
+ if emlen < @hlen + @slen + 2
+ raise ArgumentError, "encoding error"
+ end
+ salt ||= OpenSSL::Random.random_bytes(@slen)
+ mdash = "\x00" * 8 + mhash + salt
+ h = dohash(mdash)
+ ps = "\x00" * (emlen - @slen - @hlen - 2)
+ db = ps + "\x01" + salt
+ dbmask = @mgf.generate(h, emlen - @hlen - 1)
+ maskeddb = xor(db, dbmask)
+ maskeddb[0] &= (0xff >> (8 * emlen - embits))
+ p [emlen, embits, (0xff >> (8 * emlen - embits))]
+ em = maskeddb + h + "\xbc"
+ em
+ end
+
+ def verify(msg, em, embits)
+ mhash = dohash(msg)
+ verify_hash(mhash, em, embits)
+ end
+
+ def verify_hash(mhash, em, embits)
+ emlen = divceil(embits, 8)
+ if emlen < @hlen + @slen + 2
+ raise ArgumentError, "inconsistent"
+ end
+ if em[-1] != 0xbc
+ raise ArgumentError, "inconsistent"
+ end
+ maskeddb = em[0, emlen - @hlen - 1]
+ h = em[emlen - @hlen - 1, @hlen]
+ if maskeddb[0] & (0xff >> (8 * emlen - embits)) != maskeddb[0]
+ raise ArgumentError, "inconsistent"
+ end
+ dbmask = @mgf.generate(h, emlen - @hlen - 1)
+ db = xor(maskeddb, dbmask)
+ db[0] &= (0xff >> (8 * emlen - embits))
+ if /\A\x00+\z/ !~ db[0, emlen - @hlen - @slen - 2]
+ raise ArgumentError, "inconsistent"
+ end
+ if db[emlen - @hlen - @slen - 2] != 0x01
+ raise ArgumentError, "inconsistent"
+ end
+ salt = db[db.size - @slen, @slen]
+ mdash = "\x00" * 8 + mhash + salt
+ hdash = dohash(mdash)
+ if h != hdash
+ raise ArgumentError, "inconsistent"
+ end
+ true
+ end
+
+ private
+
+ def dohash(msg)
+ @digest.digest(msg)
+ end
+ end
+
+
+ class EMSAPKCS1v1_5
+ def initialize(digest = Digest::SHA1)
+ @digest = digest
+ end
+
+ def encode(msg, emlen)
+ h = dohash(msg)
+ encode_hash(h, emlen)
+ end
+
+ def encode_hash(h, emlen)
+ t = Hash.asnoid(@digest) + h
+ if emlen < t.size + 11
+ raise ArgumentError, "intended encoded message length too short"
+ end
+ ps = "\xff" * (emlen - t.size - 3)
+ "\x00\x01" + ps + "\x00" + t
+ end
+
+ private
+
+ def dohash(msg)
+ @digest.digest(msg)
+ end
+ end
+end
+
+
+# B.1 Hash functions
+module Hash
+ ALGORITHMS = {
+ # MD5: 1.2.840.113549.2.5
+ Digest::MD5 => [16, [0x30, 0x20, 0x30, 0x0C, 0x06, 0x08, 0x2A, 0x86, 0x48,
+ 0x86, 0xF7, 0x0D, 0x02, 0x05, 0x00, 0x04, 0x10]],
+ # SHA-1: 1.3.14.3.2.26
+ Digest::SHA1 => [20, [0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0E, 0x03,
+ 0x02, 0x1A, 0x05, 0x00, 0x04, 0x14]],
+ # SHA256: 2.16.840.1.101.3.4.2.1
+ Digest::SHA256 => [32, [0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
+ 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20]],
+ # SHA384: 2.16.840.1.101.3.4.2.2
+ Digest::SHA384 => [48, [0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
+ 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, 0x00, 0x04, 0x30]],
+ # SHA512: 2.16.840.1.101.3.4.2.3
+ Digest::SHA512 => [64, [0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
+ 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, 0x40]],
+ }
+
+module_function
+
+ def asnoid(digest)
+ unless ALGORITHMS.key?(digest)
+ raise ArgumentError, "unknown digest: #{digest}"
+ end
+ ALGORITHMS[digest][1].pack("c*")
+ end
+
+ def size(digest)
+ unless ALGORITHMS.key?(digest)
+ raise ArgumentError, "unknown digest: #{digest}"
+ end
+ ALGORITHMS[digest][0]
+ end
+end
+
+
+# B.2 Mask generation functions
+module MaskGeneration
+module_function
+
+ def mgf1(seed, masklen)
+ MGF1.new.generate(seed, masklen)
+ end
+
+
+ class MGF1
+ include Util
+ include DataConversion
+
+ def initialize(digest = Digest::SHA1)
+ @digest = digest
+ @hlen = Hash.size(@digest)
+ end
+
+ def generate(seed, masklen)
+ if masklen > (2 << 31) * @hlen
+ raise ArgumentError, "mask too long"
+ end
+ t = ""
+ divceil(masklen, @hlen).times do |counter|
+ t += dohash(seed + i2osp(counter, 4))
+ end
+ t[0, masklen]
+ end
+
+ private
+
+ def dohash(msg)
+ @digest.digest(msg)
+ end
+ end
+end
+
+
+end
+
+
+if $0 == __FILE__
+ pkeyfile = ARGV.shift or raise "pkey file not given"
+ osslkey = OpenSSL::PKey::RSA.new(File.read(pkeyfile))
+ n = osslkey.n.to_i
+ e = osslkey.e.to_i
+ d = osslkey.d.to_i
+ p = osslkey.p.to_i
+ q = osslkey.q.to_i
+ dp = osslkey.dmp1.to_i
+ dq = osslkey.dmq1.to_i
+ qinv = osslkey.iqmp.to_i
+
+ require 'pgp/hexdump'
+
+ rsapss = PKCS1::SignatureScheme::RSASSAPSS.new(Digest::SHA256, 0, PKCS1::MaskGeneration::MGF1.new(Digest::SHA1))
+ key = PKCS1::Key::RSA.new(n, e, d)
+ puts PGP::HexDump.encode(rsapss.sign(key, "hello world"))
+ exit
+
+ puts
+
+ key2 = PKCS1::Key::RSACRTPrivateKey.new(n, d, p, q, dp, dq, qinv)
+ puts PGP::HexDump.encode(rsapss.sign_hash(key2, "hello world", "\0"*8))
+
+ p PKCS1::DataConversion.i2osp(65537, 3)
+ p PKCS1::DataConversion.os2ip(PKCS1::DataConversion.i2osp(65537, 3))
+
+ key = PKCS1::Key::RSA.new(osslkey.n.to_i, osslkey.e.to_i, osslkey.d.to_i)
+ msg = "hello"
+ p PKCS1::DataConversion.i2osp(PKCS1::CryptographicPrimitive.rsadp(key, PKCS1::CryptographicPrimitive.rsaep(key, PKCS1::DataConversion.os2ip(msg))), msg.size)
+ p PKCS1::DataConversion.i2osp(PKCS1::CryptographicPrimitive.rsavp1(key, PKCS1::CryptographicPrimitive.rsasp1(key, PKCS1::DataConversion.os2ip(msg))), msg.size)
+
+ p PKCS1::MaskGeneration.mgf1("abc", 20)
+ p PKCS1::MaskGeneration.mgf1("abcd", 20)
+
+ pss = PKCS1::SignatureEncoding::EMSAPSS.new(Digest::SHA1, 8)
+ p pss.encode("hello", 1023)
+ p pss.verify("hello", pss.encode("hello", 1023), 1023)
+
+ rsapss = PKCS1::SignatureScheme::RSASSAPSS.new(Digest::SHA1, 0)
+ p rsapss.sign(key, "hello")
+ p rsapss.verify(key, "hello", rsapss.sign(key, "hello"))
+
+ msg = "foo\nbar" * 1024
+ hash = Digest::SHA1.digest(msg)
+ p rsapss.verify_hash(key, hash, rsapss.sign_hash(key, hash))
+ p rsapss.verify(key, msg, rsapss.sign_hash(key, hash))
+ p rsapss.verify_hash(key, hash, rsapss.sign(key, msg))
+
+ exit
+
+ p PKCS1::SignatureEncoding.emsa_pkcs1_v1_5_encode("foo", 128)
+ rsapkcs1 = PKCS1::SignatureScheme::RSASSAPKCS1v1_5.new(Digest::SHA1)
+ p rsapkcs1.sign(key, "hello")
+ p rsapkcs1.verify(key, "hello", rsapkcs1.sign(key, "hello"))
+ p osslkey.sign(OpenSSL::Digest::SHA1.new, "hello")
+ p osslkey.verify(OpenSSL::Digest::SHA1.new, rsapkcs1.sign(key, "hello"), "hello")
+
+ rsaoaep = PKCS1::EncryptionScheme::RSAESOAEP.new(Digest::SHA1)
+ msg = "hello"
+ p rsaoaep.encrypt(key, msg)
+ p osslkey.public_encrypt(msg, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING)
+ p osslkey.private_decrypt(rsaoaep.encrypt(key, msg), OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING) == msg
+ p rsaoaep.decrypt(key, rsaoaep.encrypt(key, msg)) == msg
+end
Added: topf/trunk/lib/topf.rb
===================================================================
--- topf/trunk/lib/topf.rb (rev 0)
+++ topf/trunk/lib/topf.rb 2007-06-12 09:27:41 UTC (rev 10572)
@@ -0,0 +1,9 @@
+dir = __FILE__
+$:.unshift(File.dirname(dir)) unless
+$:.include?(File.dirname(dir)) || $:.include?(File.expand_path(File.dirname(dir)))
+
+require "fuzz-struct"
+require "fuzz"
+require "pkcs1"
+require "dir"
+require "control"
Added: topf/trunk/stuff/fuzz-private.pem
===================================================================
--- topf/trunk/stuff/fuzz-private.pem (rev 0)
+++ topf/trunk/stuff/fuzz-private.pem 2007-06-12 09:27:41 UTC (rev 10572)
@@ -0,0 +1,30 @@
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: DES-EDE3-CBC,CAB4E89285F21271
+
+l5DJpslp/MUrcQN4nMfv6D1kivwtUYKsu6dhH7v6/CDz/BX4onqoaHVxYnk5uiJA
+O05hnq9zGtinFa/lKt+Cj3ibPLkDbMxvmHP7FvP9UF5Xe5oyM02aIvoqpVplNA49
+NGcpTUpGXwvMYZC8YChG6T02vqCtMGrGImJ8LFnoypqMM2dgng/hCRkbxphOHf1j
+JfnPFFTHhh7728gwszJaOrH8egPCm8w9yWtkbB2ph+VSS3qUvwyhTHsddfgmHMni
+ROwMKN8KLIeSJGf5XQRmaOFCm70PgYZN7Cx2WKrZP26uIkbvejyywB5+5BNSPZci
+vBjULFZ1bf4wo/wzTNAQ4AyNu7ckNzeOuBhFZ0VDRi+Zywy6/pRoLNZlq7TZHjk2
+bvy+X2pk090ZzVkrT/7enWATDnRdetPL/4HTe0lQTa3eo2SD+awN9U+Liat+5X32
+lcuu9vU4yl56ExJe5VGTa88D0j7CIivJ+AbraOPLwLIRLwbTamJtOHkMMKr7LKfd
+PUVJwqJz1sIjiTaJ6SKOIZ/NtmXfh0SpzVRCuZ3AafL+Jq3kJeAea2pDEfp1u5Zz
+O23h0cu2eG35Ryc5+tLbSxuz2ukBaJp5hDOaqEiZ7KYuKyoBTysL2RKNK0pUzg5B
+2SHRsuGSy4yMcyn845W6llKMb6dQOfPx3V1NkvjOfcsfU7GAYAqOc+a22Jfz2jjX
+OUYtjuEP/IwBu4HnjlfWUQfhhxmgsiS3DvGqMykjr+6pScr7PIGR57axlSj7GFDP
+/kVvUXwPV1hQgjcN/II2RDe8oBJYDoBqi30nlqkDqR6qH7/HSCuzBR3SvLtU+hy7
+WfaICWE5U2XgxY27l+NqiOFrwJic/OzeOY0YEU0PqPnnLeGLoFzgw2GYf4GlqOMZ
+njyAbD7GLR61xMyQxwN0bspb+3uoq47ptJLmcdsB1ybJJsONx+Z/ZH1IQU1navGT
+uNYzN0JNAzKbiflcoykVdBfzLOMv0IjfVCLphg+akljvahf0QUC1LEYSrpHhpaxc
+eMnJxSf+k3AzIQLUIgVJoR5aRcMINlbZVc64HH3jccy8otP77FyB/AwP2FLU9D7U
+R5dWAUodLpDOWgrZLjJV1tB6zSqWsHQ/wWnAd4t+Q/nRxW+UOWd+/OWI/LwFFBra
+Vu8rdIk6MntZ3HDzuW2W5v7U8O5Xhwejp5TPdjs2gdwEoJeNi4HA6gSOuYwVGKng
+j5Eari7tfJe6f6LxSPcLto4jGNP1Q2RNN4ERGt+YPqPPWUsqOz3qFwNBfBtykP5s
+mKb4Hi3ZbPLyuHrIEX12xhRk+s3PEQAKYY/dpllv6ooGf3gaXT5PPjx0U2HGZJec
+My+N0D3/lrYTTBVH3nGufqssIYdC1sCtDyBWyX68UtEeDimjTqfdWTqzfVSV9F0U
+h8CwfTQKJZv0F4q0VBNWNz5BSeXSLAX1jq8TsebuQjL591z1rzlzTudsFzqiqmWk
+YD2+uUW9GxTRlhFl8pr6yWtV1vwQ/moyKFRRRRv8RjKqJnTSsUv1aQdlC6d7fud+
+GEoBhrozDF1Bfnaud2nS8AHN3XBIQxgQTv04szFXoZe8vhpOPMLehElBRcw79s+R
+-----END RSA PRIVATE KEY-----
Added: topf/trunk/stuff/fuzz-public.pem
===================================================================
--- topf/trunk/stuff/fuzz-public.pem (rev 0)
+++ topf/trunk/stuff/fuzz-public.pem 2007-06-12 09:27:41 UTC (rev 10572)
@@ -0,0 +1,16 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIICmDCCAYACAQAwUzELMAkGA1UEBhMCREUxHTAbBgNVBAoTFEZ1enppbmcgT3Jn
+YW5pc2F0aW9uMQ0wCwYDVQQLEwRGdXp6MRYwFAYDVQQDEw1GUUROLUZ1enpob3N0
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlBb6Byx7MKX3cPh9cD/v
+/7FamBnCfrO3+dCNrEzZRuaS9BMbF0FbxKDeWdbEpytPw7h1Khse4wGCg3JafL3k
+z/Lt/A87MfNMbeBYkfXUA436zdp4ambgeV55AGWxHbLCDCqLc4l7o0wVvwXdNV08
+lZj+Co517ZD2jiKeJOfyd6D1Vt1i/kx0CcLYuY2aQCwXXiCvKaA3j4pWN33eRIFv
+CO8yDB1DyxmhQJdzt8xRQLohV5CVWYgi9krk/pcZd5beczSYLy3iokMvjN42y5hY
+oytd/kOXw7R5QW1+0gqU7RRpkDQ8QhBLuawg6JpnCXXSULylgw5u03bszXHCFSvX
+KQIDAQABoAAwDQYJKoZIhvcNAQEFBQADggEBAGqziC9+GwZFPO0Oij8BMuP9B7yN
+vi5+04dVQ3865hVAhI0eFhaa3vkR1zXe8dDEi0IlVPgKIjBht3pjCTlwPrAM0sAv
+zeEzZUlpng+9QDL47yumS6QZZ1PceORio0hwaOHsgBVfx86O1N0WXniuSTAsawkJ
+5DKsH4EHHmIuQgTUXwVT5Aby2edD7h9Tqv0gvMF63dXqtsACUNPKpJT+HSiaph5P
+cQuap2OB1QpGTqo5V9cX2sml5KMRhUiJXGwSuhSsY/LgS8b/c4M7UyPDpfVb9u6D
+QQ4XS9MqEXjCtuMTC+uaomzbHtdzRTkBvZgNUL4RzlkJ/d3aoal3fl81sCQ=
+-----END CERTIFICATE REQUEST-----
Added: topf/trunk/stuff/output-dir.rb
===================================================================
--- topf/trunk/stuff/output-dir.rb (rev 0)
+++ topf/trunk/stuff/output-dir.rb 2007-06-12 09:27:41 UTC (rev 10572)
@@ -0,0 +1,6 @@
+require "zlib"
+
+begin
+ puts Zlib::Inflate.new.inflate( Net::HTTP.get(URI.parse( 'http://127.0.0.1:2323/tor/server/all.z') ) )
+end
+
Added: topf/trunk/tor-dir-fuzz.rb
===================================================================
--- topf/trunk/tor-dir-fuzz.rb (rev 0)
+++ topf/trunk/tor-dir-fuzz.rb 2007-06-12 09:27:41 UTC (rev 10572)
@@ -0,0 +1,40 @@
+require "lib/topf"
+
+require "net/http"
+require "base64"
+require "yaml"
+
+begin
+ config = YAML::load_file "config.yml"
+
+ host = config["HOST"]
+ port = config["PORT"]
+ keyFile = config["KEYFILE"]
+
+ raise "option missing" if !host or !port or !keyFile
+
+ reset = 0
+ rd = TOP::Dir::RouterDescriptor.new( keyFile, host, port )
+ dirServer = Net::HTTP.new(host, port)
+
+ begin
+ puts "lets stress #{host} a bit..."
+ while true
+ response, body = dirServer.post "/tor/", rd.to_s
+ rd.fuzz!
+ if (reset += 1) == 10
+ rd = TOP::Dir::RouterDescriptor.new
+ reset = 0
+ end
+ end
+ rescue
+ # something happened.. show the error-message and routerdescriptor that caused this mess..
+ puts $!
+
+ puts ">"*50
+ puts rd.to_s
+ puts ">"*50
+
+ dirServer.finish
+ end
+end