172 lines
4.5 KiB
Elixir
172 lines
4.5 KiB
Elixir
defmodule Odinsea.Net.Hex do
|
|
@moduledoc """
|
|
Hex encoding/decoding utilities ported from Java HexTool.
|
|
"""
|
|
|
|
@doc """
|
|
Converts a binary to a hex string (space-separated bytes).
|
|
|
|
## Examples
|
|
iex> Odinsea.Net.Hex.encode(<<0x01, 0xAB, 0xFF>>)
|
|
"01 AB FF"
|
|
"""
|
|
@spec encode(binary()) :: String.t()
|
|
def encode(<<>>), do: ""
|
|
|
|
def encode(binary) when is_binary(binary) do
|
|
binary
|
|
|> :binary.bin_to_list()
|
|
|> Enum.map(&byte_to_hex/1)
|
|
|> Enum.join(" ")
|
|
end
|
|
|
|
@doc """
|
|
Converts a binary to a hex string (no spaces).
|
|
|
|
## Examples
|
|
iex> Odinsea.Net.Hex.to_string_compact(<<0x01, 0xAB, 0xFF>>)
|
|
"01ABFF"
|
|
"""
|
|
@spec to_string_compact(binary()) :: String.t()
|
|
def to_string_compact(<<>>), do: ""
|
|
|
|
def to_string_compact(binary) when is_binary(binary) do
|
|
binary
|
|
|> :binary.bin_to_list()
|
|
|> Enum.map(&byte_to_hex/1)
|
|
|> Enum.join()
|
|
end
|
|
|
|
@doc """
|
|
Converts a binary to a hex string with ASCII representation.
|
|
|
|
## Examples
|
|
iex> Odinsea.Net.Hex.to_pretty_string(<<0x48, 0x65, 0x6C, 0x6C, 0x6F>>)
|
|
"48 65 6C 6C 6F | Hello"
|
|
"""
|
|
@spec to_pretty_string(binary()) :: String.t()
|
|
def to_pretty_string(binary) when is_binary(binary) do
|
|
hex_part = encode(binary)
|
|
ascii_part = to_ascii(binary)
|
|
"#{hex_part} | #{ascii_part}"
|
|
end
|
|
|
|
@doc """
|
|
Converts a hex string to binary.
|
|
|
|
## Examples
|
|
iex> Odinsea.Net.Hex.from_string("01 AB FF")
|
|
<<0x01, 0xAB, 0xFF>>
|
|
"""
|
|
@spec from_string(String.t()) :: binary()
|
|
def from_string(hex_string) when is_binary(hex_string) do
|
|
hex_string
|
|
|> String.replace(~r/\s+/, "")
|
|
|> String.downcase()
|
|
|> do_from_string()
|
|
end
|
|
|
|
defp do_from_string(<<>>), do: <<>>
|
|
|
|
defp do_from_string(<<hex1, hex2, rest::binary>>) do
|
|
byte = hex_to_byte(<<hex1, hex2>>)
|
|
<<byte, do_from_string(rest)::binary>>
|
|
end
|
|
|
|
defp do_from_string(<<_>>), do: raise(ArgumentError, "Invalid hex string length")
|
|
|
|
@doc """
|
|
Converts a byte to its 2-character hex representation.
|
|
"""
|
|
@spec byte_to_hex(byte()) :: String.t()
|
|
def byte_to_hex(byte) when is_integer(byte) and byte >= 0 and byte <= 255 do
|
|
byte
|
|
|> Integer.to_string(16)
|
|
|> String.pad_leading(2, "0")
|
|
|> String.upcase()
|
|
end
|
|
|
|
@doc """
|
|
Converts a 2-character hex string to a byte.
|
|
"""
|
|
@spec hex_to_byte(String.t()) :: byte()
|
|
def hex_to_byte(<<hex1, hex2>>) do
|
|
parse_hex_digit(hex1) * 16 + parse_hex_digit(hex2)
|
|
end
|
|
|
|
defp parse_hex_digit(?0), do: 0
|
|
defp parse_hex_digit(?1), do: 1
|
|
defp parse_hex_digit(?2), do: 2
|
|
defp parse_hex_digit(?3), do: 3
|
|
defp parse_hex_digit(?4), do: 4
|
|
defp parse_hex_digit(?5), do: 5
|
|
defp parse_hex_digit(?6), do: 6
|
|
defp parse_hex_digit(?7), do: 7
|
|
defp parse_hex_digit(?8), do: 8
|
|
defp parse_hex_digit(?9), do: 9
|
|
defp parse_hex_digit(?a), do: 10
|
|
defp parse_hex_digit(?b), do: 11
|
|
defp parse_hex_digit(?c), do: 12
|
|
defp parse_hex_digit(?d), do: 13
|
|
defp parse_hex_digit(?e), do: 14
|
|
defp parse_hex_digit(?f), do: 15
|
|
defp parse_hex_digit(_), do: raise(ArgumentError, "Invalid hex digit")
|
|
|
|
@doc """
|
|
Converts a binary to its ASCII representation (non-printable chars as dots).
|
|
"""
|
|
@spec to_ascii(binary()) :: String.t()
|
|
def to_ascii(<<>>), do: ""
|
|
|
|
def to_ascii(<<byte, rest::binary>>) do
|
|
char = if byte >= 32 and byte <= 126, do: <<byte>>, else: <<".">>
|
|
char <> to_ascii(rest)
|
|
end
|
|
|
|
@doc """
|
|
Converts a short (2 bytes) to a hex string.
|
|
"""
|
|
@spec short_to_hex(integer()) :: String.t()
|
|
def short_to_hex(value) when is_integer(value) do
|
|
<<value::signed-integer-little-16>>
|
|
|> encode()
|
|
end
|
|
|
|
@doc """
|
|
Converts an int (4 bytes) to a hex string.
|
|
"""
|
|
@spec int_to_hex(integer()) :: String.t()
|
|
def int_to_hex(value) when is_integer(value) do
|
|
<<value::signed-integer-little-32>>
|
|
|> encode()
|
|
end
|
|
|
|
@doc """
|
|
Formats a binary as a hex dump with offsets.
|
|
"""
|
|
@spec hex_dump(binary(), non_neg_integer()) :: String.t()
|
|
def hex_dump(binary, offset \\ 0) do
|
|
binary
|
|
|> :binary.bin_to_list()
|
|
|> Enum.chunk_every(16)
|
|
|> Enum.with_index()
|
|
|> Enum.map(fn {chunk, idx} ->
|
|
offset_str = Integer.to_string(offset + idx * 16, 16) |> String.pad_leading(8, "0")
|
|
hex_str = format_chunk(chunk)
|
|
ascii_str = to_ascii(:binary.list_to_bin(chunk))
|
|
"#{offset_str} #{hex_str} |#{ascii_str}|"
|
|
end)
|
|
|> Enum.join("\n")
|
|
end
|
|
|
|
defp format_chunk(chunk) do
|
|
chunk
|
|
|> Enum.map(&byte_to_hex/1)
|
|
|> Enum.map(&String.pad_trailing(&1, 2))
|
|
|> Enum.chunk_every(8)
|
|
|> Enum.map(&Enum.join(&1, " "))
|
|
|> Enum.join(" ")
|
|
|> String.pad_trailing(48)
|
|
end
|
|
end
|