# 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/memory'
require 'ronin/support/binary/byte_slice'
require 'ronin/support/binary/ctypes/mixin'
module Ronin
module Support
module Binary
#
# Represents a binary buffer of data.
#
# ## Examples
#
# Writing bytes into an empty buffer:
#
# buffer = Binary::Buffer.new(10)
# # => #
# buffer[0] = 0x41
# buffer[1] = 0x42
# buffer[2] = 0x43
# buffer.to_s
# # => "ABC\x00\x00\x00\x00\x00\x00\x00"
#
# Writing different types of data to a buffer:
#
# buffer = Binary::Buffer.new(16)
# # => #
# buffer.put_uint32(0,0x11223344)
# buffer.put_int32(4,-1)
# buffer.put_string(8,"ABC")
# buffer.put_float32(12,0.5)
# buffer.to_s
# # => "D3\"\x11\xFF\xFF\xFF\xFFABC\x00\x00\x00\x00?"
#
# Creating a buffer from an existing String:
#
# buffer = Binary::Buffer.new("\x41\x00\x00\x00\x42\x00\x00\x00")
# # => #
# buffer.get_uint32(0)
# # => 65
# buffer.get_uint32(4)
# # => 66
#
# @api public
#
# @since 1.0.0
#
class Buffer < Memory
include CTypes::Mixin
#
# Initializes the buffer.
#
# @param [Integer, String, ByteSlice] length_or_string
# The size of the buffer or an existing String which will be used
# as the underlying buffer.
#
# @param [Hash{Symbol => Object}] kwargs
# Additional keyword arguments.
#
# @option kwargs [:little, :big, :net, nil] :endian
# The desired endianness of the values within the buffer.
#
# @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 buffer.
#
# @raise [ArgumentError]
# Either the `length_or_string` argument was not an Integer or a
# String.
#
def initialize(length_or_string, **kwargs)
initialize_type_system(**kwargs)
super(length_or_string)
end
#
# Reads from the IO stream and returns a buffer.
#
# @param [IO] io
# The IO object to read from.
#
# @param [Integer] size
# The size of the buffer to read.
#
# @return [Buffer]
# The read buffer.
#
# @see #read_from
#
# @api public
#
def self.read_from(io,size)
new(io.read(size))
end
alias length size
#
# Writes a value to the buffer at the given index.
#
# @param [Integer, Range] index_or_range
# The index within the string to write to.
#
# @param [Integer, nil] length
# Optional additional length argument.
#
# @param [String] value
# The integer, float, or character value to write to the buffer.
#
# @return [String]
# The string written into the buffer.
#
# @example Writing a single byte:
# buffer[0] = 0x41
#
# @example Writing a single char:
# buffer[0] = 'A'
#
# @example Writing an Array of bytes to the given range of indexes:
# buffer[0..3] = [0x41, 0x42, 0x43]
#
# @example Writing an Array of chars to the given range of indexes:
# buffer[0..3] = ['A', 'B', 'C']
#
# @example Writing an Array of bytes to the given index and length:
# buffer[0,3] = [0x41, 0x42, 0x43]
#
# @example Writing an Array of bytes to the given index and length:
# buffer[0,3] = ['A', 'B', 'C']
#
def []=(index_or_range,length=nil,value)
value = case value
when Integer
value.chr(Encoding::ASCII_8BIT)
when ::Array
value.map { |char_or_byte|
case char_or_byte
when Integer
char_or_byte.chr(Encoding::ASCII_8BIT)
else
char_or_byte
end
}.join
else
value
end
super(index_or_range,length,value)
end
#
# Converts the buffer to a String.
#
# @return [String]
# The raw binary buffer.
#
def to_s
@string.to_s
end
alias to_str to_s
#
# @group Reader Methods
#
#
# Reads a value of the given type at the given offset.
#
# @param [Symbol] type
# The type of the value to read.
#
# @param [Integer] offset
# The offset within the buffer to read.
#
# @return [Integer, Float, String]
# The decoded value.
#
def get(type,offset)
type = @type_system[type]
if (offset < 0) || ((offset + type.size) > size)
raise(IndexError,"offset #{offset} is out of bounds: 0...#{size - type.size}")
end
data = @string[offset,type.size]
return type.unpack(data)
end
#
# Alias for `get(:byte,offset)`.
#
# @param [Integer] offset
# The offset of the `byte` within the buffer.
#
# @return [Integer]
# The read `byte`.
#
# @see #get
#
def get_byte(offset)
get(:byte,offset)
end
#
# Alias for `get(:char,offset)`.
#
# @param [Integer] offset
# The offset of the `char` within the buffer.
#
# @return [String]
# The read `char`.
#
# @see #get
#
def get_char(offset)
get(:char,offset)
end
#
# Alias for `get(:uchar,offset)`.
#
# @param [Integer] offset
# The offset of the `uchar` within the buffer.
#
# @return [String]
# The read `uchar`.
#
# @see #get
#
def get_uchar(offset)
get(:uchar,offset)
end
#
# Reads a null-byte terminated C string from the buffer, at the given
# offset.
#
# @param [Integer] offset
# The offset of the `string` within the buffer.
#
# @param [Integer, nil] length
# The optional maximum desired length of the string.
#
# @return [String]
# The read C string, without the null-byte.
#
def get_string(offset,length=nil)
if (offset < 0) || (offset >= size)
raise(IndexError,"offset #{offset} is out of bounds: 0...#{size - 1}")
elsif (length && (offset + length) > size)
raise(IndexError,"offset #{offset} or length #{length} is out of bounds: 0...#{size - 1}")
end
if length
substring = @string[offset,length]
if (null_byte_index = substring.index("\0"))
substring[0...null_byte_index]
else
substring
end
else
if (null_byte_index = @string.index("\0",offset))
@string[offset...null_byte_index]
else
@string[offset..]
end
end
end
#
# Alias for `get(:int8,offset)`.
#
# @param [Integer] offset
# The offset of the `int8` within the buffer.
#
# @return [Integer]
# The read `int8`.
#
# @see #get
#
def get_int8(offset)
get(:int8,offset)
end
#
# Alias for `get(:int16,offset)`.
#
# @param [Integer] offset
# The offset of the `int16` within the buffer.
#
# @return [Integer]
# The read `int16`.
#
# @see #get
#
def get_int16(offset)
get(:int16,offset)
end
#
# Alias for `get(:int32,offset)`.
#
# @param [Integer] offset
# The offset of the `int32` within the buffer.
#
# @return [Integer]
# The read `int32`.
#
# @see #get
#
def get_int32(offset)
get(:int32,offset)
end
#
# Alias for `get(:int64,offset)`.
#
# @param [Integer] offset
# The offset of the `int64` within the buffer.
#
# @return [Integer]
# The read `int64`.
#
# @see #get
#
def get_int64(offset)
get(:int64,offset)
end
#
# Alias for `get(:uint8,offset)`.
#
# @param [Integer] offset
# The offset of the `uint8` within the buffer.
#
# @return [Integer]
# The read `uint8`.
#
# @see #get
#
def get_uint8(offset)
get(:uint8,offset)
end
#
# Alias for `get(:uint16,offset)`.
#
# @param [Integer] offset
# The offset of the `uint16` within the buffer.
#
# @return [Integer]
# The read `uint16`.
#
# @see #get
#
def get_uint16(offset)
get(:uint16,offset)
end
#
# Alias for `get(:uint32,offset)`.
#
# @param [Integer] offset
# The offset of the `uint32` within the buffer.
#
# @return [Integer]
# The read `uint32`.
#
# @see #get
#
def get_uint32(offset)
get(:uint32,offset)
end
#
# Alias for `get(:uint64,offset)`.
#
# @param [Integer] offset
# The offset of the `uint64` within the buffer.
#
# @return [Integer]
# The read `uint64`.
#
# @see #get
#
def get_uint64(offset)
get(:uint64,offset)
end
#
# Alias for `get(:short,offset)`.
#
# @param [Integer] offset
# The offset of the `short` within the buffer.
#
# @return [Integer]
# The read `short`.
#
# @see #get
#
def get_short(offset)
get(:short,offset)
end
#
# Alias for `get(:int,offset)`.
#
# @param [Integer] offset
# The offset of the `int` within the buffer.
#
# @return [Integer]
# The read `int`.
#
# @see #get
#
def get_int(offset)
get(:int,offset)
end
#
# Alias for `get(:long,offset)`.
#
# @param [Integer] offset
# The offset of the `long` within the buffer.
#
# @return [Integer]
# The read `long`.
#
# @see #get
#
def get_long(offset)
get(:long,offset)
end
#
# Alias for `get(:long_long,offset)`.
#
# @param [Integer] offset
# The offset of the `long_long` within the buffer.
#
# @return [Integer]
# The read `long_long`.
#
# @see #get
#
def get_long_long(offset)
get(:long_long,offset)
end
#
# Alias for `get(:ushort,offset)`.
#
# @param [Integer] offset
# The offset of the `ushort` within the buffer.
#
# @return [Integer]
# The read `ushort`.
#
# @see #get
#
def get_ushort(offset)
get(:ushort,offset)
end
#
# Alias for `get(:uint,offset)`.
#
# @param [Integer] offset
# The offset of the `uint` within the buffer.
#
# @return [Integer]
# The read `uint`.
#
# @see #get
#
def get_uint(offset)
get(:uint,offset)
end
#
# Alias for `get(:ulong,offset)`.
#
# @param [Integer] offset
# The offset of the `ulong` within the buffer.
#
# @return [Integer]
# The read `ulong`.
#
# @see #get
#
def get_ulong(offset)
get(:ulong,offset)
end
#
# Alias for `get(:ulong_long,offset)`.
#
# @param [Integer] offset
# The offset of the `ulong_long` within the buffer.
#
# @return [Integer]
# The read `ulong_long`.
#
# @see #get
#
def get_ulong_long(offset)
get(:ulong_long,offset)
end
#
# Alias for `get(:float32,offset)`.
#
# @param [Integer] offset
# The offset of the `float32` within the buffer.
#
# @return [Float]
# The read `float32`.
#
# @see #get
#
def get_float32(offset)
get(:float32,offset)
end
#
# Alias for `get(:float64,offset)`.
#
# @param [Integer] offset
# The offset of the `float64` within the buffer.
#
# @return [Float]
# The read `float64`.
#
# @see #get
#
def get_float64(offset)
get(:float64,offset)
end
#
# Alias for `get(:float,offset)`.
#
# @param [Integer] offset
# The offset of the `float` within the buffer.
#
# @return [Float]
# The read `float`.
#
# @see #get
#
def get_float(offset)
get(:float,offset)
end
#
# Alias for `get(:double,offset)`.
#
# @param [Integer] offset
# The offset of the `double` within the buffer.
#
# @return [Float]
# The read `double`.
#
# @see #get
#
def get_double(offset)
get(:double,offset)
end
#
# Returns the buffer starting at the given offset and with the given
# size.
#
# @param [Integer] offset
# The offset within the buffer.
#
# @param [Integer] size
# The number of bytes for the buffer.
#
# @return [Buffer]
# The new buffer.
#
# @example
# subbuffer = buffer.buffer_at(10,40)
#
def buffer_at(offset,size)
Buffer.new(byteslice(offset,size))
end
#
# Returns the array starting at the given offset, with the given
# length, containing the given type.
#
# @param [Integer] offset
# The offset within the buffer that the array starts at.
#
# @param [Symbol] type
# The type name.
#
# @param [Integer] length
# The number of elements in the array.
#
# @return [Array]
# The new array.
#
# @example
# array = buffer.array_at(0,:uint32,10)
#
def array_at(offset,type,length)
type = @type_system[type]
size = type.size * length
return Array.new(type,byteslice(offset,size))
end
#
# Returns a new {Struct} instance starting at the given offset.
#
# @param [Integer] offset
# The offset within the buffer that the struct starts at.
#
# @param [Class] struct_class
# The struct class.
#
# @return [Binary::Struct]
# The new struct instance.
#
# @example
# struct = buffer.struct_at(10,MyStruct)
# # => #
#
def struct_at(offset,struct_class)
unless struct_class < Struct
raise(ArgumentError,"the given class must be a #{Struct} subclass: #{struct_class.inspect}")
end
return struct_class.new(byteslice(offset,struct_class.size))
end
#
# Returns a new {Union} instance starting at the given offset.
#
# @param [Integer] offset
# The offset within the buffer that the union starts at.
#
# @param [Class] union_class
# The union class.
#
# @return [Union]
# The new union instance.
#
# @example
# union = buffer.union_at(10,MyUnion)
# # => #
#
def union_at(offset,union_class)
unless union_class < Union
raise(ArgumentError,"the given class must be a #{Union} subclass: #{union_class.inspect}")
end
return union_class.new(byteslice(offset,union_class.size))
end
#
# Reads an array of the given type, starting at the given offset, with
# the given length.
#
# @param [Symbol] type
# The type of the value to read.
#
# @param [Integer] offset
# The offset that the array starts at within the buffer.
#
# @param [Integer] count
# The number of desired elements within the array.
#
# @return [::Array