# frozen_string_literal: true
#
# Copyright (c) 2006-2024 Hal Brodigan (postmodern.mod3 at gmail.com)
#
# 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 'ronin/support/binary/ctypes/mixin'
require 'ronin/support/binary/array'
module Ronin
module Support
module Binary
#
# Provides a translation layer between C-types and Ruby `Array#pack`
# codes.
#
# ## Supported Types
#
# * `:uint8` - unsigned 8-bit integer.
# * `:uint16` - unsigned 16-bit integer.
# * `:uint32` - unsigned 32-bit integer.
# * `:uint64` - unsigned 64-bit integer.
# * `:int8` - signed 8-bit integer.
# * `:int16` - signed 16-bit integer.
# * `:int32` - signed 32-bit integer.
# * `:int64` - signed 64-bit integer.
# * `:uchar` - unsigned character.
# * `:ushort` - unsigned short integer, native endian.
# * `:uint` - unsigned integer, native endian.
# * `:ulong` - unsigned long integer, native endian.
# * `:ulong_long` - unsigned quad integer, native endian.
# * `:char` - signed character.
# * `:short` - signed short integer, native endian.
# * `:int` - signed integer, native endian.
# * `:long` - signed long integer, native endian.
# * `:long_long` - signed quad integer, native endian.
# * `:float` - single-precision float, native format.
# * `:double` - double-precision float, native format.
# * `:float_le` - single-precision float, little endian.
# * `:double_le` - double-precision float, little endian.
# * `:float_be` - single-precision float, big endian.
# * `:double_be` - double-precision float, big endian.
# * `:byte` - signed byte.
# * `:string` - binary String, `\0` terminated.
# * `:uint16_le` - unsigned 16-bit integer, little endian.
# * `:uint32_le` - unsigned 32-bit integer, little endian.
# * `:uint64_le` - unsigned 64-bit integer, little endian.
# * `:int16_le` - signed 16-bit integer, little endian.
# * `:int32_le` - signed 32-bit integer, little endian.
# * `:int64_le` - signed 64-bit integer, little endian.
# * `:uint16_be` - unsigned 16-bit integer, big endian.
# * `:uint32_be` - unsigned 32-bit integer, big endian.
# * `:uint64_be` - unsigned 64-bit integer, big endian.
# * `:int16_be` - signed 16-bit integer, big endian.
# * `:int32_be` - signed 32-bit integer, big endian.
# * `:int64_be` - signed 64-bit integer, big endian.
# * `:ushort_le` - unsigned short integer, little endian.
# * `:uint_le` - unsigned integer, little endian.
# * `:ulong_le` - unsigned long integer, little endian.
# * `:ulong_long_le` - unsigned quad integer, little endian.
# * `:short_le` - signed short integer, little endian.
# * `:int_le` - signed integer, little endian.
# * `:long_le` - signed long integer, little endian.
# * `:long_long_le` - signed quad integer, little endian.
# * `:ushort_be` - unsigned short integer, little endian.
# * `:uint_be` - unsigned integer, little endian.
# * `:ulong_be` - unsigned long integer, little endian.
# * `:ulong_long_be` - unsigned quad integer, little endian.
# * `:short_be` - signed short integer, little endian.
# * `:int_be` - signed integer, little endian.
# * `:long_be` - signed long integer, little endian.
# * `:long_long_be` - signed quad integer, little endian.
#
# @see https://rubydoc.info/stdlib/core/Array:pack
#
# @api pbulic
#
class Template
include CTypes::Mixin
# The fields of the binary template.
#
# @return [::Array]
attr_reader :fields
# The field types of the binary template.
#
# @return [::Array]
#
# @since 1.0.0
attr_reader :types
# The `Array#pack` string for the binary template.
#
# @return [String]
#
# @since 1.0.0
attr_reader :pack_string
#
# Creates a new Binary template.
#
# @param [::Array] fields
# The C-types which the packer will use.
#
# @param [Hash{Symbol => Object}] kwargs
# Additional keyword arguments.
#
# @option kwargs [:little, :big, :net, nil] :endian
# The desired endianness of the values within the template.
#
# @option kwargs [:x86, :x86_64,
# :ppc, :ppc64,
# :mips, :mips_le, :mips_be,
# :mips64, :mips64_le, :mips64_be,
# :arm, :arm_le, :arm_be,
# :arm64, :arm64_le, :arm64_be] :arch
# The desired architecture for the values within the template.
#
# @option kwargs [:linux, :macos, :windows,
# :android, :apple_ios, :bsd,
# :freebsd, :openbsd, :netbsd] :os
# The Operating System (OS) to use.
#
# @raise [ArgumentError]
# A given type is not known.
#
# @example
# template = Template.new([:uint32, [:char, 10]])
# template.pack(0x123456, ['A', 'B', 'C'])
# # => "CBA\x00XYZ\x00\x00\x00\x00\x00\x00\x00"
# template.unpack("CBA\x00XYZ\x00\x00\x00\x00\x00\x00\x00")
# # => [4276803, #]
#
def initialize(fields, **kwargs)
initialize_type_system(**kwargs)
@fields = []
@types = []
@pack_string = String.new
fields.each do |field|
type = @type_resolver.resolve(field)
@fields << field
@types << type
if @pack_string
if type.pack_string then @pack_string << type.pack_string
else @pack_string = nil
end
end
end
end
#
# Alias for `Template.new`.
#
# @param [::Array] fields
# The C-types which the packer will use.
#
# @param [Hash{Symbol => Object}] kwargs
# Additional keyword arguments.
#
# @option kwargs [:little, :big, :net, nil] :endian
# The desired endianness of the values within the template.
#
# @option kwargs [:x86, :x86_64,
# :ppc, :ppc64,
# :mips, :mips_le, :mips_be,
# :mips64, :mips64_le, :mips64_be,
# :arm, :arm_le, :arm_be,
# :arm64, :arm64_le, :arm64_be] :arch
# The desired architecture for the values within the template.
#
# @option kwargs [:linux, :macos, :windows,
# :android, :apple_ios, :bsd,
# :freebsd, :openbsd, :netbsd] :os
# The Operating System (OS) to use.
#
# @return [Template]
# The new template object.
#
# @raise [ArgumentError]
# A given type is not known.
#
# @example
# template = Template[:uint32, [:char, 10]]
# template.pack(0x123456, ['A', 'B', 'C'])
# # => "CBA\x00XYZ\x00\x00\x00\x00\x00\x00\x00"
# template.unpack("CBA\x00XYZ\x00\x00\x00\x00\x00\x00\x00")
# # => [4276803, #]
#
# @see #initialize
#
# @since 1.0.0
#
def self.[](*fields,**kwargs)
new(fields,**kwargs)
end
#
# Packs the data.
#
# @param [::Array] arguments
# The values to pack.
#
# @return [String]
# The packed data.
#
# @example
# template = Template.new[:uint32, [:char, 10]]
# template.pack(0x123456, ['A', 'B', 'C'])
# # => "CBA\x00XYZ\x00\x00\x00\x00\x00\x00\x00"
#
def pack(*arguments)
if @pack_string
values = []
@types.each do |type|
# shift off the next value(s) and enqueue them
type.enqueue_value(values,arguments.shift)
end
values.pack(@pack_string)
else
buffer = String.new
@types.each do |type|
# shift off the next value and pack it
buffer << type.pack(arguments.shift)
end
return buffer
end
end
#
# Unpacks the string.
#
# @param [String] data
# The raw String to unpack.
#
# @return [::Array]
# The unpacked data.
#
# @example
# template = Template.new[:uint32, [:char, 10]]
# template.unpack("CBA\x00XYZ\x00\x00\x00\x00\x00\x00\x00")
# # => [4276803, #]
#
def unpack(data)
if @pack_string
values = data.unpack(@pack_string)
@types.map do |type|
type.dequeue_value(values)
end
else
array = []
offset = 0
@types.each do |type|
slice = if type.size == Float::INFINITY
data.byteslice(offset..)
else
data.byteslice(offset,type.size)
end
array << type.unpack(slice)
offset += type.size
end
return array
end
end
#
# Converts the template to a `Array#pack` template String.
#
# @return [String]
# The template String.
#
# @example
# template = Template.new[:uint32, [:char, 10]]
# template.to_s
# # => "La10"
#
# @see https://rubydoc.info/stdlib/core/Array:pack
#
def to_s
@pack_string
end
alias to_str to_s
end
end
end
end