Start repo, claude & kimi still vibing tho
This commit is contained in:
171
lib/odinsea/net/hex.ex
Normal file
171
lib/odinsea/net/hex.ex
Normal file
@@ -0,0 +1,171 @@
|
||||
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
|
||||
Reference in New Issue
Block a user