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)
92 lines
2 KiB
Elixir
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
|