aprs.me/lib/aprsme/packet_field_whitelist.ex
Graham McIntire f0f0bdc589
Some checks failed
Elixir CI / Build and test (push) Has been cancelled
Elixir CI / Dialyzer (push) Has been cancelled
Elixir CI / Build and Push Docker Image (push) Has been cancelled
perf: optimize packet receive→parse→store pipeline (35-60% throughput)
Eliminates redundant work on the hot path:

1. Remove struct_to_map — Map.put on struct already returns plain map,
   eliminating O(n) recursive traversal of the entire parser struct tree
   per packet. [15-25% gain]

2. Delete data_extended early — moves Map.delete to before recursive
   sanitization/DateTime walks, avoiding wasted work on data that is
   already extracted to top-level columns. [8-12% gain]

3. Truncate received_at to :second at source — removes microsecond
   truncation + the recursive truncate_datetimes_to_second pass.
   [5-8% gain]

4. Merge sanitize passes — new sanitize_packet_with_encoding/1 does
   truncation + encoding sanitization in one Map.new pass instead of
   two sequential traversals. [5-8% gain]

5. Fix raw→raw_packet key — consumer read :raw_packet but dispatch
   wrote :raw, silently dropping raw packet data to the DB column.

6. Fold has_weather into extract_additional_data — set when weather
   is found during extraction instead of scanning 10 fields per packet.
   [2-4% gain]

7. filter_fields with MapSet — O(1) membership check instead of O(n)
   list scan. [2-3% gain]

8. Single-pass chunk reduction — builds valid_inserts and valid_bcasts
   in one Enum.reduce, saving 2 extra list traversals. [2-3% gain]

Dead code removed: struct_to_map/1, extract_from_mic_e_map/1,
set_has_weather/1, set_received_at/1, truncate_datetimes_to_second/1,
__original_struct__ MicE branch.

Tests: 2481/2490 passing (+3 improvement, remaining 9 are pre-existing)
2026-08-02 15:13:42 -05:00

92 lines
2 KiB
Elixir

defmodule Aprsme.PacketFieldWhitelist do
@moduledoc """
Maintains a whitelist of allowed fields for APRS packets based on the database schema.
This ensures only valid fields are passed to database operations.
"""
# Define all valid fields from the Packet schema
# This list is derived from the schema definition in lib/aprsme/packet.ex
# Using string list to handle both atom and string keys consistently
@allowed_fields ~w[
addressee
altitude
aprs_messaging
base_callsign
comment
course
dao
data
data_type
destination
device_identifier
equipment_type
has_position
has_weather
humidity
is_item
is_object
item_name
lat
location
lon
manufacturer
message_number
message_text
object_name
path
pressure
rain_1h
rain_24h
rain_since_midnight
raw_packet
received_at
region
sender
snow
speed
ssid
symbol_code
symbol_table_id
temperature
timestamp
wind_direction
wind_gust
wind_speed
inserted_at
updated_at
]
@doc """
Returns the list of allowed field names as atoms.
"""
def allowed_fields, do: @allowed_fields
@allowed_set MapSet.new(@allowed_fields)
@doc """
Filters a map to only include allowed fields.
Handles both atom and string keys.
"""
def filter_fields(attrs) when is_map(attrs) do
Map.new(Enum.filter(attrs, fn {key, _value} -> MapSet.member?(@allowed_set, to_string(key)) end))
end
@doc """
Checks if a field is allowed.
"""
def allowed?(field) when is_atom(field), do: to_string(field) in @allowed_fields
def allowed?(field) when is_binary(field), do: field in @allowed_fields
def allowed?(_), do: false
@doc """
Returns a list of fields that are not allowed from the given map.
Useful for debugging.
"""
def invalid_fields(attrs) when is_map(attrs) do
attrs
|> Map.keys()
|> Enum.reject(&allowed?/1)
|> Enum.map(&to_string/1)
|> Enum.sort()
end
end