#
# Copyright (c) 2006-2012 Hal Brodigan (postmodern.mod3 at gmail.com)
#
# This file is part of Ronin Support.
#
# Ronin Support is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ronin Support is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Ronin Support. If not, see .
#
require 'set'
module Ronin
module Binary
#
# Provides a translation layer between C types and Ruby `Array#pack`
# codes.
#
# ## Types
#
# * `:uint8` (`C`) - unsigned 8-bit integer.
# * `:uint16` (`S`) - unsigned 16-bit integer.
# * `:uint32` (`L`) - unsigned 32-bit integer.
# * `:uint64` (`Q`) - unsigned 64-bit integer.
# * `:int8` (`c`) - signed 8-bit integer.
# * `:int16` (`s`) - signed 16-bit integer.
# * `:int32` (`l`) - signed 32-bit integer.
# * `:int64` (`q`) - signed 64-bit integer.
# * `:uint16_le` (`v`) - unsigned 16-bit integer, little endian.
# * `:uint32_le` (`V`) - unsigned 32-bit integer, little endian.
# * `:uint16_be` (`n`) - unsigned 16-bit integer, big endian.
# * `:uint32_be` (`N`) - unsigned 32-bit integer, big endian.
# * `:uchar` (`Z`) - unsigned character.
# * `:ushort` (`S!`) - unsigned short integer, native endian.
# * `:uint` (`I!`) - unsigned integer, native endian.
# * `:ulong` (`L!`) - unsigned long integer, native endian.
# * `:ulong_long` (`Q`) - unsigned quad integer, native endian.
# * `:char` (`Z`) - signed character.
# * `:short` (`s!`) - signed short integer, native endian.
# * `:int` (`i!`) - signed integer, native endian.
# * `:long` (`l!`) - signed long integer, native endian.
# * `:long_long` (`q`) - signed quad integer, native endian.
# * `:utf8` (`U`) - UTF8 character.
# * `:float` (`F`) - single-precision float, native format.
# * `:double` (`D`) - double-precision float, native format.
# * `:float_le` (`e`) - single-precision float, little endian.
# * `:double_le` (`E`) - double-precision float, little endian.
# * `:float_be` (`g`) - single-precision float, big endian.
# * `:double_be` (`G`) - double-precision float, big endian.
# * `:ubyte` (`C`) - unsigned byte.
# * `:byte` (`c`) - signed byte.
# * `:string` (`Z*`) - binary String, `\0` terminated.
#
# ### Ruby 1.9 specific types
#
# * `:uint16_le` (`S<`) - unsigned 16-bit integer, little endian.
# * `:uint32_le` (`L<`) - unsigned 32-bit integer, little endian.
# * `:uint64_le` (`Q<`) - unsigned 64-bit integer, little endian.
# * `:int16_le` (`s<`) - signed 16-bit integer, little endian.
# * `:int32_le` (`l<`) - signed 32-bit integer, little endian.
# * `:int64_le` (`q<`) - signed 64-bit integer, little endian.
# * `:uint16_be` (`S>`) - unsigned 16-bit integer, big endian.
# * `:uint32_be` (`L>`) - unsigned 32-bit integer, big endian.
# * `:uint64_be` (`Q>`) - unsigned 64-bit integer, big endian.
# * `:int16_be` (`s>`) - signed 16-bit integer, big endian.
# * `:int32_be` (`l>`) - signed 32-bit integer, big endian.
# * `:int64_be` (`q>`) - signed 64-bit integer, big endian.
# * `:ushort_le` (`S<`) - unsigned short integer, little endian.
# * `:uint_le` (`I<`) - unsigned integer, little endian.
# * `:ulong_le` (`L<`) - unsigned long integer, little endian.
# * `:ulong_long_le` (`Q<`) - unsigned quad integer, little endian.
# * `:short_le` (`s<`) - signed short integer, little endian.
# * `:int_le` (`i<`) - signed integer, little endian.
# * `:long_le` (`l<`) - signed long integer, little endian.
# * `:long_long_le` (`q<`) - signed quad integer, little endian.
# * `:ushort_be` (`S>`) - unsigned short integer, little endian.
# * `:uint_be` (`I>`) - unsigned integer, little endian.
# * `:ulong_be` (`L>`) - unsigned long integer, little endian.
# * `:ulong_long_be` (`Q>`) - unsigned quad integer, little endian.
# * `:short_be` (`s>`) - signed short integer, little endian.
# * `:int_be` (`i>`) - signed integer, little endian.
# * `:long_be` (`l>`) - signed long integer, little endian.
# * `:long_long_be` (`q>`) - signed quad integer, little endian.
#
# @see http://rubydoc.info/stdlib/core/Array:pack
#
# @api semipbulic
#
# @since 0.5.0
#
class Template
# Supported types and corresponding `Array#pack` codes.
TYPES = {
:uint8 => 'C',
:uint16 => 'S',
:uint32 => 'L',
:uint64 => 'Q',
:int8 => 'c',
:int16 => 's',
:int32 => 'l',
:int64 => 'q',
:uint16_le => 'v',
:uint32_le => 'V',
:uint16_be => 'n',
:uint32_be => 'N',
:uchar => 'Z',
:ushort => 'S!',
:uint => 'I!',
:ulong => 'L!',
:ulong_long => 'Q',
:char => 'Z',
:short => 's!',
:int => 'i!',
:long => 'l!',
:long_long => 'q',
:utf8 => 'U',
:float => 'F',
:double => 'D',
:float_le => 'e',
:double_le => 'E',
:float_be => 'g',
:double_be => 'G',
:ubyte => 'C',
:byte => 'c',
:string => 'Z*'
}
# Additional types, not available on Ruby 1.8:
if RUBY_VERSION > '1.9.'
TYPES.merge!(
:uint16_le => 'S<',
:uint32_le => 'L<',
:uint64_le => 'Q<',
:int16_le => 's<',
:int32_le => 'l<',
:int64_le => 'q<',
:uint16_be => 'S>',
:uint32_be => 'L>',
:uint64_be => 'Q>',
:int16_be => 's>',
:int32_be => 'l>',
:int64_be => 'q>',
:ushort_le => 'S!<',
:uint_le => 'I!<',
:ulong_le => 'L!<',
:ulong_long_le => 'Q<',
:short_le => 's!<',
:int_le => 'i!<',
:long_le => 'l!<',
:long_long_le => 'q<',
:ushort_be => 'S!>',
:uint_be => 'I!>',
:ulong_be => 'L!>',
:ulong_long_be => 'Q>',
:short_be => 's!>',
:int_be => 'i!>',
:long_be => 'l!>',
:long_long_be => 'q>'
)
end
# Integer types
INT_TYPES = Set[
:uint8,
:uint16,
:uint32,
:uint64,
:int8,
:int16,
:int32,
:int64,
:ubyte,
:ushort,
:uint,
:ulong,
:ulong_long,
:byte,
:short,
:int,
:long,
:long_long,
:uint16_le,
:uint32_le,
:uint64_le,
:int16_le,
:int32_le,
:int64_le,
:ushort_le,
:uint_le,
:ulong_le,
:ulong_long_le,
:short_le,
:int_le,
:long_le,
:long_long_le,
:uint16_be,
:uint32_be,
:uint64_be,
:int16_be,
:int32_be,
:int64_be,
:ushort_be,
:uint_be,
:ulong_be,
:ulong_long_be,
:short_be,
:int_be,
:long_be,
:long_long_be
]
# Float types
FLOAT_TYPES = Set[
:float, :double,
:float_le, :double_le,
:float_be, :double_be
]
# Character types
CHAR_TYPES = Set[:uchar, :char]
# String types
STRING_TYPES = CHAR_TYPES + Set[:string]
# Types which have little and big endian forms
ENDIAN_TYPES = Set[
:uint16, :uint32, :uint64,
:int16, :int32, :int64,
:ushort, :uint, :ulong, :ulong_long,
:short, :int, :long, :long_long,
:float, :double
]
# The fields of the template
attr_reader :fields
#
# Creates a new Binary Template.
#
# @param [Array] fields
# The types which the packer will use.
#
# @param [Hash] options
# Template options.
#
# @option options [:little, :big, :network] :endian
# The endianness to apply to types.
#
# @raise [ArgumentError]
# A given type is not known.
#
# @note
# The following types are **not supported** on Ruby 1.8:
#
# * `:uint16_le`
# * `:uint32_le`
# * `:uint64_le`
# * `:int16_le`
# * `:int32_le`
# * `:int64_le`
# * `:uint16_be`
# * `:uint32_be`
# * `:uint64_be`
# * `:int16_be`
# * `:int32_be`
# * `:int64_be`
# * `:ushort_le`
# * `:uint_le`
# * `:ulong_le`
# * `:ulong_long_le`
# * `:short_le`
# * `:int_le`
# * `:long_le`
# * `:long_long_le`
# * `:ushort_be`
# * `:uint_be`
# * `:ulong_be`
# * `:ulong_long_be`
# * `:short_be`
# * `:int_be`
# * `:long_be`
# * `:long_long_be`
#
# @example
# Template.new(:uint32, [:char, 100])
#
def initialize(fields,options={})
@fields = fields
@template = self.class.compile(@fields,options)
end
#
# @see #initialize
#
def self.[](*fields)
new(fields)
end
#
# Translates the type of the field.
#
# @param [Symbol] type
# The type to translate.
#
# @param [Hash] options
# Translation options.
#
# @option options [:little, :big, :network] :endian
# The endianness to apply to types.
#
# @return [Symbol]
# The translated type.
#
# @raise [ArgumentError]
# The value of `:endian` is unknown.
#
def self.translate(type,options={})
if (options[:endian] && ENDIAN_TYPES.include?(type))
type = case options[:endian]
when :little
:"#{type}_le"
when :big, :network
:"#{type}_be"
else
raise(ArgumentError,"unknown endianness: #{type}")
end
end
return type
end
#
# Compiles binary types into an `Array#pack` / `String#unpack`
# template.
#
# @param [Array] types
# The types which the packer will use.
#
# @param [Hash] options
# Type options.
#
# @option options [:little, :big, :network] :endian
# The endianness to apply to types.
#
# @return [String]
# The `Array#pack` / `String#unpack` template.
#
# @raise [ArgumentError]
# A given type is not known.
#
def self.compile(types,options={})
string = ''
types.each do |(type,length)|
type = translate(type,options)
unless (code = TYPES[type])
raise(ArgumentError,"#{type.inspect} not supported")
end
string << code << length.to_s
end
return string
end
#
# Packs the data.
#
# @param [Array] data
# The data to pack.
#
# @return [String]
# The packed data.
#
def pack(*data)
data.pack(@template)
end
#
# Unpacks the string.
#
# @param [String] string
# The raw String to unpack.
#
# @return [Array]
# The unpacked data.
#
def unpack(string)
string.unpack(@template)
end
#
# Converts the template to a `Array#pack` template String.
#
# @return [String]
# The template String.
#
# @see http://rubydoc.info/stdlib/core/Array:pack
#
def to_s
@template
end
end
end
end