# 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 an Array of binary types that can be read from and written
# to.
#
# @note This class provides lazy memory mapped access to an underlying
# buffer. This means values are decoded/encoded each time they are read
# or written to.
#
# ## Examples
#
# Creating an array of `int32`s:
#
# array = Binary::Binary::Array.new(:int32, 4)
# # => #
# array[0] = 0x11111111
# array[1] = 0x22222222
# array[2] = 0x33333333
# array[3] = -1
# array.to_s
# # => "\x11\x11\x11\x11\"\"\"\"3333\xFF\xFF\xFF\xFF"
#
# Creating an array from an existing String:
#
# array = Binary::Array.new(:uint32_le, "\x41\x00\x00\x00\x42\x00\x00\x00")
# # => #
# array[0]
# # => 65
# array[1]
# # => 66
#
# @api public
#
# @since 1.0.0
#
class Array < Memory
include CTypes::Mixin
include Enumerable
# The underlying type of the data within the array buffer.
#
# @return [CTypes::Type]
attr_reader :type
# The number of elements in the array buffer.
#
# @return [Integer]
attr_reader :length
#
# Initializes the array buffer.
#
# @param [Symbol] type
# The type of each element in the array buffer.
#
# @param [Integer, String, ByteSlice] length_or_string
# The length 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 array 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 array buffer.
#
# @raise [ArgumentError]
# Either the `length_or_string` argument was not an Integer or a
# String.
#
# @example Creating a new array buffer:
# array = Binary::Array.new(:uint32_le, 10)
# # => #
#
# @example Creating a new array buffer from a String:
# array = Binary::Array.new(:uint32_le, "\x41\x00\x00\x00\x42\x00\x00\x00")
# # => #
#
def initialize(type, length_or_string, **kwargs)
initialize_type_system(**kwargs)
@type = @type_resolver.resolve(type)
@cache = []
case length_or_string
when String, ByteSlice
super(length_or_string)
@length = size / @type.size
when Integer
@length = length_or_string
super(@type.size * @length)
else
raise(ArgumentError,"first argument must be either a length (Integer) or a buffer (String): #{length_or_string.inspect}")
end
end
#
# Reads the struct from the IO stream.
#
# @param [IO] io
# The IO object to read from.
#
# @param [Symbol] type
# The desired type of each element in the array.
#
# @param [Integer] length
# The desired length of the array.
#
# @return [Binary::Struct]
# The read array.
#
# @example
# file = File.new('binary.dat','b')
# array = Binary::Array.read_from(file,:int32,10)
#
# @see #read_from
#
def self.read_from(io,type,length)
new(type,length).read_from(io)
end
#
# Reads a value from the array at the given index.
#
# @param [Integer] index
# The index to read from.
#
# @return [Integer, Float, String, Binary::Array, Binary::Struct]
# The integer, float, or character read from the given index.
#
# @example
# array = Binary::Array.new(:uint32_le, "\x41\x00\x00\x00\x42\x00\x00\x00")
# array[0]
# # => 65
# array[1]
# # => 66
#
def [](index)
offset = index * @type.size
if (index < 0) || ((offset + @type.size) > size)
raise(IndexError,"index #{index} is out of bounds: 0...#{@length}")
end
case @type
when CTypes::ObjectType
@cache[index] ||= @type.unpack(byteslice(offset,@type.size))
else
data = super(offset,@type.size)
@type.unpack(data)
end
end
#
# Writes a value to the array at the given index.
#
# @param [Integer] index
# The array index to write the value to.
#
# @param [Integer, Float, String, Binary::Array, Binary::Struct] value
# The integer, float, or character value to write to the array.
#
# @return [Integer, Float, String, Binary::Array, Binary::Struct]
# The integer, float, or character value that was written.
#
# @example
# array = Binary::Array.new(:int32, 4)
# array[0]
# # => 0
# array[0] = 0x11111111
# array[0]
# # => 286331153
#
def []=(index,value)
offset = index * @type.size
if (index < 0) || ((offset + @type.size) > size)
raise(IndexError,"index #{index} is out of bounds: 0...#{@length}")
end
data = @type.pack(value)
return super(offset,@type.size,data)
end
#
# Enumerates over every value within the array buffer.
#
# @yield [value]
# The given block will be passed each value at each index within the
# array buffer.
#
# @yieldparam [Integer, Float, String] value
# The integer, float, or character read from the array buffer.
#
# @return [Enumerator]
# If no block was given, an Enumerator will be returned.
#
def each
return enum_for(__method__) unless block_given?
(0...@length).each do |index|
yield self[index]
end
end
#
# Inspects the array buffer.
#
# @return [String]
#
def inspect
"#<#{self.class}: #{@string.inspect}>"
end
end
end
end
end