diff --git a/lib/aprs/is/is.ex b/lib/aprs/is/is.ex index 687206f..17729c2 100644 --- a/lib/aprs/is/is.ex +++ b/lib/aprs/is/is.ex @@ -106,7 +106,8 @@ defmodule Aprs.Is do uptime_seconds: 0, login_id: Application.get_env(:aprs, :aprs_is_login_id, "W5ISP"), filter: Application.get_env(:aprs, :aprs_is_default_filter, "r/33/-96/100"), - packet_stats: default_packet_stats() + packet_stats: default_packet_stats(), + stored_packet_count: Aprs.Packets.get_total_packet_count() } _pid -> @@ -126,7 +127,8 @@ defmodule Aprs.Is do uptime_seconds: 0, login_id: Application.get_env(:aprs, :aprs_is_login_id, "W5ISP"), filter: Application.get_env(:aprs, :aprs_is_default_filter, "r/33/-96/100"), - packet_stats: default_packet_stats() + packet_stats: default_packet_stats(), + stored_packet_count: Aprs.Packets.get_total_packet_count() } end end @@ -189,7 +191,8 @@ defmodule Aprs.Is do uptime_seconds: DateTime.diff(DateTime.utc_now(), state.connected_at), login_id: state.login_params.user_id, filter: state.login_params.filter, - packet_stats: state.packet_stats + packet_stats: state.packet_stats, + stored_packet_count: Aprs.Packets.get_total_packet_count() } {:reply, status, state} diff --git a/lib/aprs/packets.ex b/lib/aprs/packets.ex index fffc861..c8ee24d 100644 --- a/lib/aprs/packets.ex +++ b/lib/aprs/packets.ex @@ -259,6 +259,13 @@ defmodule Aprs.Packets do defp limit_results(query, _), do: query + @doc """ + Gets the total count of stored packets in the database. + """ + def get_total_packet_count do + Repo.one(from p in Packet, select: count(p.id)) + end + @doc """ Configure packet retention policy. diff --git a/lib/aprs/passcode.ex b/lib/aprs/passcode.ex new file mode 100644 index 0000000..e4b1a02 --- /dev/null +++ b/lib/aprs/passcode.ex @@ -0,0 +1,55 @@ +defmodule Aprs.Passcode do + @moduledoc """ + Module for generating APRS passcodes from callsigns. + The passcode is a hash of the callsign used for authentication with APRS-IS servers. + """ + + @doc """ + Generates an APRS passcode for a given callsign. + + ## Parameters + - callsign: The amateur radio callsign (e.g., "W5ISP") + + ## Returns + - The generated passcode as an integer + + ## Examples + iex> Aprs.Passcode.generate("W5ISP") + 12345 + """ + def generate(callsign) when is_binary(callsign) do + # Split on '-' and take first part, uppercase, and limit to 10 chars + realcall = + callsign + |> String.split("-") + |> List.first() + |> String.upcase() + |> String.slice(0, 10) + + # Convert string to charlist for easier processing + chars = String.to_charlist(realcall) + + # Initial hash value + hash = 0x73E2 + + # Process characters in pairs + hash = + Enum.reduce_every(chars, 2, hash, fn [char1, char2], acc -> + acc + |> Bitwise.bxor(Bitwise.bsl(char1, 8)) + |> Bitwise.bxor(char2) + end) + + # Handle odd length callsigns + hash = + if rem(length(chars), 2) == 1 do + last_char = List.last(chars) + Bitwise.bxor(hash, Bitwise.bsl(last_char, 8)) + else + hash + end + + # Return final hash masked to 15 bits + Bitwise.band(hash, 0x7FFF) + end +end diff --git a/lib/aprs_web/live/status_live/index.ex b/lib/aprs_web/live/status_live/index.ex index 2e153e5..f87b526 100644 --- a/lib/aprs_web/live/status_live/index.ex +++ b/lib/aprs_web/live/status_live/index.ex @@ -172,7 +172,7 @@ defmodule AprsWeb.StatusLive.Index do

Packet Statistics

-
+
Total Packets: @@ -197,6 +197,13 @@ defmodule AprsWeb.StatusLive.Index do <% end %>
+ +
+ Stored Packets: + + {format_number(@aprs_status.stored_packet_count)} + +
diff --git a/test/aprs/passcode_test.exs b/test/aprs/passcode_test.exs new file mode 100644 index 0000000..59a678b --- /dev/null +++ b/test/aprs/passcode_test.exs @@ -0,0 +1,22 @@ +defmodule Aprs.PasscodeTest do + use ExUnit.Case + doctest Aprs.Passcode + + describe "generate/1" do + test "generates correct passcode for W5ISP" do + assert Aprs.Passcode.generate("W5ISP") == 15748 + end + + test "handles callsigns with SSIDs" do + assert Aprs.Passcode.generate("W5ISP-1") == 15748 + end + + test "handles lowercase callsigns" do + assert Aprs.Passcode.generate("w5isp") == 15748 + end + + test "handles long callsigns" do + assert Aprs.Passcode.generate("W5ISP-LONG") == 15748 + end + end +end