towerops/lib/towerops/proto/decode.ex
Graham McIntire 7c1aea50ec fix: M19, L16 — protobuf validation on 6 decode paths, vault ETS docs
- M19: Add validate_mikrotik_device, validate_mikrotik_command, validate_check,
  validate_snmp_query, validate_sensor, and validate_interface functions with
  string length and list size validation, wired into all call sites
- L16: Document that Cloak Vault ETS table name collision risk is mitigated by
  ETS tables being node-local (only a concern on same-BEAM-node, not a supported
  production configuration)
2026-05-12 15:46:13 -05:00

2018 lines
64 KiB
Elixir

# credo:disable-for-this-file Credo.Check.Refactor.CyclomaticComplexity
# credo:disable-for-this-file Credo.Check.Refactor.Nesting
defmodule Towerops.Proto.Decode do
@moduledoc """
Protobuf decode functions with integrated validation.
Each decode function parses wire format bytes and validates business rules
(string lengths, UUID formats, numeric ranges) in a single pass.
"""
alias Towerops.Proto.Types
alias Towerops.Proto.Wire
# Validation constants
@max_string_length 10_000
@max_short_string_length 255
@max_error_message_length 1_000
@max_metrics_per_batch 10_000
@max_oid_values 50_000
@max_mikrotik_sentences 1_000
@max_uptime_seconds 4_294_967_295
@max_response_time_ms 60_000
@max_timestamp 2_147_483_647
@max_neighbors 1_000
# Public decode functions
@spec decode_agent_heartbeat(binary()) ::
{:ok, Types.AgentHeartbeat.t()} | {:error, Types.decode_error()}
def decode_agent_heartbeat(data) when is_binary(data) do
hb = %Types.AgentHeartbeat{
version: "",
hostname: "",
uptime_seconds: 0,
ip_address: "",
arch: ""
}
with {:ok, result} <- decode_heartbeat_fields(data, hb) do
validate_heartbeat(result)
end
end
@spec decode_heartbeat_metadata(binary()) ::
{:ok, Types.HeartbeatMetadata.t()} | {:error, Types.decode_error()}
def decode_heartbeat_metadata(data) when is_binary(data) do
hm = %Types.HeartbeatMetadata{
version: "",
hostname: "",
uptime_seconds: 0
}
decode_heartbeat_metadata_fields(data, hm)
end
@spec decode_heartbeat_response(binary()) ::
{:ok, Types.HeartbeatResponse.t()} | {:error, Types.decode_error()}
def decode_heartbeat_response(data) when is_binary(data) do
hr = %Types.HeartbeatResponse{status: ""}
decode_heartbeat_response_fields(data, hr)
end
@spec decode_metric_batch(binary()) ::
{:ok, Types.MetricBatch.t()} | {:error, Types.decode_error()}
def decode_metric_batch(data) when is_binary(data) do
with {:ok, metrics} <- decode_metric_batch_fields(data, []) do
reversed = Enum.reverse(metrics)
if length(reversed) > @max_metrics_per_batch do
{:error, {:batch_too_large, "Batch exceeds #{@max_metrics_per_batch} metrics"}}
else
{:ok, %Types.MetricBatch{metrics: reversed}}
end
end
end
@spec decode_snmp_result(binary()) ::
{:ok, Types.SnmpResult.t()} | {:error, Types.decode_error()}
def decode_snmp_result(data) when is_binary(data) do
r = %Types.SnmpResult{
device_id: "",
job_type: :discover,
oid_values: %{},
timestamp: 0,
job_id: ""
}
with {:ok, result} <- decode_snmp_result_fields(data, r) do
validate_snmp_result(result)
end
end
@spec decode_agent_error(binary()) ::
{:ok, Types.AgentError.t()} | {:error, Types.decode_error()}
def decode_agent_error(data) when is_binary(data) do
e = %Types.AgentError{
device_id: "",
job_id: "",
message: "",
timestamp: 0
}
with {:ok, result} <- decode_agent_error_fields(data, e) do
validate_agent_error(result)
end
end
@spec decode_credential_test_result(binary()) ::
{:ok, Types.CredentialTestResult.t()} | {:error, Types.decode_error()}
def decode_credential_test_result(data) when is_binary(data) do
r = %Types.CredentialTestResult{
test_id: "",
success: false,
error_message: "",
system_description: "",
timestamp: 0
}
with {:ok, result} <- decode_credential_test_result_fields(data, r) do
validate_credential_test_result(result)
end
end
@spec decode_mikrotik_result(binary()) ::
{:ok, Types.MikrotikResult.t()} | {:error, Types.decode_error()}
def decode_mikrotik_result(data) when is_binary(data) do
r = %Types.MikrotikResult{
device_id: "",
job_id: "",
sentences: [],
error: "",
timestamp: 0
}
with {:ok, result} <- decode_mikrotik_result_fields(data, r) do
validate_mikrotik_result(result)
end
end
@spec decode_monitoring_check(binary()) ::
{:ok, Types.MonitoringCheck.t()} | {:error, Types.decode_error()}
def decode_monitoring_check(data) when is_binary(data) do
c = %Types.MonitoringCheck{
device_id: "",
status: "",
response_time_ms: 0.0,
timestamp: 0
}
with {:ok, result} <- decode_monitoring_check_fields(data, c) do
validate_monitoring_check(result)
end
end
@spec decode_lldp_topology_result(binary()) ::
{:ok, Types.LldpTopologyResult.t()} | {:error, Types.decode_error()}
def decode_lldp_topology_result(data) when is_binary(data) do
r = %Types.LldpTopologyResult{
device_id: "",
job_id: "",
local_system_name: "",
neighbors: [],
timestamp: 0
}
with {:ok, result} <- decode_lldp_topology_fields(data, r) do
validate_lldp_topology_result(result)
end
end
@spec decode_check_result(binary()) ::
{:ok, Types.CheckResult.t()} | {:error, Types.decode_error()}
def decode_check_result(data) when is_binary(data) do
r = %Types.CheckResult{
check_id: "",
status: 0,
output: "",
response_time_ms: 0.0,
timestamp: 0
}
with {:ok, result} <- decode_check_result_fields(data, r) do
validate_check_result(result)
end
end
@spec decode_sensor(binary()) ::
{:ok, Types.Sensor.t()} | {:error, Types.decode_error()}
def decode_sensor(data) when is_binary(data) do
s = %Types.Sensor{
id: "",
sensor_type: "",
oid: "",
divisor: 0.0,
unit: "",
metadata: %{}
}
decode_sensor_fields(data, s)
end
@spec decode_agent_job(binary()) ::
{:ok, Types.AgentJob.t()} | {:error, Types.decode_error()}
def decode_agent_job(data) when is_binary(data) do
j = %Types.AgentJob{
job_id: "",
job_type: :discover,
device_id: "",
snmp_device: nil,
queries: [],
mikrotik_device: nil,
mikrotik_commands: []
}
decode_agent_job_fields(data, j)
end
# Server->agent message decoders (no validation needed)
@spec decode_agent_config(binary()) ::
{:ok, Types.AgentConfig.t()} | {:error, Types.decode_error()}
def decode_agent_config(data) when is_binary(data) do
c = %Types.AgentConfig{
version: "",
poll_interval_seconds: 0,
devices: [],
checks: []
}
decode_agent_config_fields(data, c)
end
@spec decode_agent_job_list(binary()) ::
{:ok, Types.AgentJobList.t()} | {:error, Types.decode_error()}
def decode_agent_job_list(data) when is_binary(data) do
decode_agent_job_list_fields(data, [])
end
@spec decode_check_list(binary()) ::
{:ok, Types.CheckList.t()} | {:error, Types.decode_error()}
def decode_check_list(data) when is_binary(data) do
decode_check_list_fields(data, [])
end
# Field decoder functions
defp decode_heartbeat_fields(<<>>, acc), do: {:ok, acc}
defp decode_heartbeat_fields(data, acc) do
with {:ok, {field_num, wire_type, rest}} <- Wire.decode_tag(data) do
case field_num do
1 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_heartbeat_fields(rest2, %{acc | version: value})
end
2 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_heartbeat_fields(rest2, %{acc | hostname: value})
end
3 ->
with {:ok, {value, rest2}} <- decode_uint_field(rest, wire_type) do
decode_heartbeat_fields(rest2, %{acc | uptime_seconds: value})
end
4 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_heartbeat_fields(rest2, %{acc | ip_address: value})
end
5 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_heartbeat_fields(rest2, %{acc | arch: value})
end
_ ->
with {:ok, rest2} <- Wire.skip_field(wire_type, rest) do
decode_heartbeat_fields(rest2, acc)
end
end
end
end
defp decode_heartbeat_metadata_fields(<<>>, acc), do: {:ok, acc}
defp decode_heartbeat_metadata_fields(data, acc) do
with {:ok, {field_num, wire_type, rest}} <- Wire.decode_tag(data) do
case field_num do
1 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_heartbeat_metadata_fields(rest2, %{acc | version: value})
end
2 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_heartbeat_metadata_fields(rest2, %{acc | hostname: value})
end
3 ->
with {:ok, {value, rest2}} <- decode_uint_field(rest, wire_type) do
decode_heartbeat_metadata_fields(rest2, %{acc | uptime_seconds: value})
end
_ ->
with {:ok, rest2} <- Wire.skip_field(wire_type, rest) do
decode_heartbeat_metadata_fields(rest2, acc)
end
end
end
end
defp decode_heartbeat_response_fields(<<>>, acc), do: {:ok, acc}
defp decode_heartbeat_response_fields(data, acc) do
with {:ok, {field_num, wire_type, rest}} <- Wire.decode_tag(data) do
case field_num do
1 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_heartbeat_response_fields(rest2, %{acc | status: value})
end
_ ->
with {:ok, rest2} <- Wire.skip_field(wire_type, rest) do
decode_heartbeat_response_fields(rest2, acc)
end
end
end
end
defp decode_metric_batch_fields(<<>>, acc), do: {:ok, acc}
defp decode_metric_batch_fields(data, acc) do
with {:ok, {field_num, wire_type, rest}} <- Wire.decode_tag(data) do
case field_num do
1 ->
with {:ok, {metric_data, rest2}} <- Wire.decode_bytes(rest),
{:ok, metric} <- decode_metric(metric_data) do
decode_metric_batch_fields(rest2, [metric | acc])
end
_ ->
with {:ok, rest2} <- Wire.skip_field(wire_type, rest) do
decode_metric_batch_fields(rest2, acc)
end
end
end
end
defp decode_metric(data) do
with {:ok, {field_num, _wire_type, rest}} <- Wire.decode_tag(data) do
case field_num do
1 ->
with {:ok, {msg_data, _rest}} <- Wire.decode_bytes(rest),
{:ok, sr} <- decode_sensor_reading(msg_data) do
{:ok, {:sensor_reading, sr}}
end
2 ->
with {:ok, {msg_data, _rest}} <- Wire.decode_bytes(rest),
{:ok, is} <- decode_interface_stat(msg_data) do
{:ok, {:interface_stat, is}}
end
3 ->
with {:ok, {msg_data, _rest}} <- Wire.decode_bytes(rest),
{:ok, nd} <- decode_neighbor_discovery(msg_data) do
{:ok, {:neighbor_discovery, nd}}
end
4 ->
with {:ok, {msg_data, _rest}} <- Wire.decode_bytes(rest),
{:ok, mc} <- decode_monitoring_check(msg_data) do
{:ok, {:monitoring_check, mc}}
end
5 ->
with {:ok, {msg_data, _rest}} <- Wire.decode_bytes(rest),
{:ok, cr} <- decode_check_result(msg_data) do
{:ok, {:check_result, cr}}
end
_ ->
{:error, {:invalid_metric, "Unknown metric type: #{field_num}"}}
end
end
end
defp decode_sensor_reading(data) do
sr = %Types.SensorReading{
sensor_id: "",
value: 0.0,
status: "",
timestamp: 0
}
decode_sensor_reading_fields(data, sr)
end
defp decode_sensor_reading_fields(<<>>, acc), do: {:ok, acc}
defp decode_sensor_reading_fields(data, acc) do
with {:ok, {field_num, wire_type, rest}} <- Wire.decode_tag(data) do
case field_num do
1 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_sensor_reading_fields(rest2, %{acc | sensor_id: value})
end
2 ->
with {:ok, {value, rest2}} <- decode_double_field(rest, wire_type) do
decode_sensor_reading_fields(rest2, %{acc | value: value})
end
3 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_sensor_reading_fields(rest2, %{acc | status: value})
end
4 ->
with {:ok, {value, rest2}} <- decode_int64_field(rest, wire_type) do
decode_sensor_reading_fields(rest2, %{acc | timestamp: value})
end
_ ->
with {:ok, rest2} <- Wire.skip_field(wire_type, rest) do
decode_sensor_reading_fields(rest2, acc)
end
end
end
end
defp decode_interface_stat(data) do
is = %Types.InterfaceStat{
interface_id: "",
if_in_octets: 0,
if_out_octets: 0,
if_in_errors: 0,
if_out_errors: 0,
if_in_discards: 0,
if_out_discards: 0,
timestamp: 0
}
decode_interface_stat_fields(data, is)
end
defp decode_interface_stat_fields(<<>>, acc), do: {:ok, acc}
defp decode_interface_stat_fields(data, acc) do
with {:ok, {field_num, wire_type, rest}} <- Wire.decode_tag(data) do
case field_num do
1 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_interface_stat_fields(rest2, %{acc | interface_id: value})
end
2 ->
with {:ok, {value, rest2}} <- decode_int64_field(rest, wire_type) do
decode_interface_stat_fields(rest2, %{acc | if_in_octets: value})
end
3 ->
with {:ok, {value, rest2}} <- decode_int64_field(rest, wire_type) do
decode_interface_stat_fields(rest2, %{acc | if_out_octets: value})
end
4 ->
with {:ok, {value, rest2}} <- decode_int64_field(rest, wire_type) do
decode_interface_stat_fields(rest2, %{acc | if_in_errors: value})
end
5 ->
with {:ok, {value, rest2}} <- decode_int64_field(rest, wire_type) do
decode_interface_stat_fields(rest2, %{acc | if_out_errors: value})
end
6 ->
with {:ok, {value, rest2}} <- decode_int64_field(rest, wire_type) do
decode_interface_stat_fields(rest2, %{acc | if_in_discards: value})
end
7 ->
with {:ok, {value, rest2}} <- decode_int64_field(rest, wire_type) do
decode_interface_stat_fields(rest2, %{acc | if_out_discards: value})
end
8 ->
with {:ok, {value, rest2}} <- decode_int64_field(rest, wire_type) do
decode_interface_stat_fields(rest2, %{acc | timestamp: value})
end
_ ->
with {:ok, rest2} <- Wire.skip_field(wire_type, rest) do
decode_interface_stat_fields(rest2, acc)
end
end
end
end
defp decode_neighbor_discovery(data) do
nd = %Types.NeighborDiscovery{
interface_id: "",
protocol: "",
remote_chassis_id: "",
remote_system_name: "",
remote_system_description: "",
remote_platform: "",
remote_port_id: "",
remote_port_description: "",
remote_address: "",
remote_capabilities: [],
timestamp: 0
}
decode_neighbor_discovery_fields(data, nd)
end
defp decode_neighbor_discovery_fields(<<>>, acc), do: {:ok, acc}
defp decode_neighbor_discovery_fields(data, acc) do
with {:ok, {field_num, wire_type, rest}} <- Wire.decode_tag(data) do
case field_num do
1 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_neighbor_discovery_fields(rest2, %{acc | interface_id: value})
end
2 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_neighbor_discovery_fields(rest2, %{acc | protocol: value})
end
3 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_neighbor_discovery_fields(rest2, %{acc | remote_chassis_id: value})
end
4 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_neighbor_discovery_fields(rest2, %{acc | remote_system_name: value})
end
5 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_neighbor_discovery_fields(rest2, %{acc | remote_system_description: value})
end
6 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_neighbor_discovery_fields(rest2, %{acc | remote_platform: value})
end
7 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_neighbor_discovery_fields(rest2, %{acc | remote_port_id: value})
end
8 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_neighbor_discovery_fields(rest2, %{acc | remote_port_description: value})
end
9 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_neighbor_discovery_fields(rest2, %{acc | remote_address: value})
end
10 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
caps = [value | acc.remote_capabilities]
decode_neighbor_discovery_fields(rest2, %{acc | remote_capabilities: caps})
end
11 ->
with {:ok, {value, rest2}} <- decode_int64_field(rest, wire_type) do
decode_neighbor_discovery_fields(rest2, %{acc | timestamp: value})
end
_ ->
with {:ok, rest2} <- Wire.skip_field(wire_type, rest) do
decode_neighbor_discovery_fields(rest2, acc)
end
end
end
end
defp decode_snmp_result_fields(<<>>, acc), do: {:ok, %{acc | oid_values: Map.new(acc.oid_values)}}
defp decode_snmp_result_fields(data, acc) do
with {:ok, {field_num, wire_type, rest}} <- Wire.decode_tag(data) do
case field_num do
1 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_snmp_result_fields(rest2, %{acc | device_id: value})
end
2 ->
with {:ok, {value, rest2}} <- decode_enum_field(rest, wire_type),
{:ok, job_type} <- Types.job_type_from_int(value) do
decode_snmp_result_fields(rest2, %{acc | job_type: job_type})
end
3 ->
with {:ok, {entry_data, rest2}} <- Wire.decode_bytes(rest),
{:ok, {key, val}} <- decode_map_entry(entry_data) do
new_map = Map.put(acc.oid_values, key, val)
decode_snmp_result_fields(rest2, %{acc | oid_values: new_map})
end
4 ->
with {:ok, {value, rest2}} <- decode_int64_field(rest, wire_type) do
decode_snmp_result_fields(rest2, %{acc | timestamp: value})
end
5 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_snmp_result_fields(rest2, %{acc | job_id: value})
end
_ ->
with {:ok, rest2} <- Wire.skip_field(wire_type, rest) do
decode_snmp_result_fields(rest2, acc)
end
end
end
end
defp decode_agent_error_fields(<<>>, acc), do: {:ok, acc}
defp decode_agent_error_fields(data, acc) do
with {:ok, {field_num, wire_type, rest}} <- Wire.decode_tag(data) do
case field_num do
1 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_agent_error_fields(rest2, %{acc | device_id: value})
end
2 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_agent_error_fields(rest2, %{acc | job_id: value})
end
3 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_agent_error_fields(rest2, %{acc | message: value})
end
4 ->
with {:ok, {value, rest2}} <- decode_int64_field(rest, wire_type) do
decode_agent_error_fields(rest2, %{acc | timestamp: value})
end
_ ->
with {:ok, rest2} <- Wire.skip_field(wire_type, rest) do
decode_agent_error_fields(rest2, acc)
end
end
end
end
defp decode_credential_test_result_fields(<<>>, acc), do: {:ok, acc}
defp decode_credential_test_result_fields(data, acc) do
with {:ok, {field_num, wire_type, rest}} <- Wire.decode_tag(data) do
case field_num do
1 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_credential_test_result_fields(rest2, %{acc | test_id: value})
end
2 ->
with {:ok, {value, rest2}} <- decode_bool_field(rest, wire_type) do
decode_credential_test_result_fields(rest2, %{acc | success: value})
end
3 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_credential_test_result_fields(rest2, %{acc | error_message: value})
end
4 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_credential_test_result_fields(rest2, %{acc | system_description: value})
end
5 ->
with {:ok, {value, rest2}} <- decode_int64_field(rest, wire_type) do
decode_credential_test_result_fields(rest2, %{acc | timestamp: value})
end
_ ->
with {:ok, rest2} <- Wire.skip_field(wire_type, rest) do
decode_credential_test_result_fields(rest2, acc)
end
end
end
end
defp decode_mikrotik_result_fields(<<>>, acc), do: {:ok, %{acc | sentences: Enum.reverse(acc.sentences)}}
defp decode_mikrotik_result_fields(data, acc) do
with {:ok, {field_num, wire_type, rest}} <- Wire.decode_tag(data) do
case field_num do
1 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_mikrotik_result_fields(rest2, %{acc | device_id: value})
end
2 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_mikrotik_result_fields(rest2, %{acc | job_id: value})
end
3 ->
with {:ok, {msg_data, rest2}} <- Wire.decode_bytes(rest),
{:ok, sentence} <- decode_mikrotik_sentence(msg_data) do
decode_mikrotik_result_fields(rest2, %{acc | sentences: [sentence | acc.sentences]})
end
4 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_mikrotik_result_fields(rest2, %{acc | error: value})
end
5 ->
with {:ok, {value, rest2}} <- decode_int64_field(rest, wire_type) do
decode_mikrotik_result_fields(rest2, %{acc | timestamp: value})
end
_ ->
with {:ok, rest2} <- Wire.skip_field(wire_type, rest) do
decode_mikrotik_result_fields(rest2, acc)
end
end
end
end
defp decode_mikrotik_sentence(data) do
decode_mikrotik_sentence_fields(data, %{})
end
defp decode_mikrotik_sentence_fields(<<>>, attributes) do
{:ok, %Types.MikrotikSentence{attributes: attributes}}
end
defp decode_mikrotik_sentence_fields(data, attributes) do
with {:ok, {field_num, wire_type, rest}} <- Wire.decode_tag(data) do
case field_num do
1 ->
with {:ok, {entry_data, rest2}} <- Wire.decode_bytes(rest),
{:ok, {key, val}} <- decode_map_entry(entry_data) do
decode_mikrotik_sentence_fields(rest2, Map.put(attributes, key, val))
end
_ ->
with {:ok, rest2} <- Wire.skip_field(wire_type, rest) do
decode_mikrotik_sentence_fields(rest2, attributes)
end
end
end
end
defp decode_monitoring_check_fields(<<>>, acc), do: {:ok, acc}
defp decode_monitoring_check_fields(data, acc) do
with {:ok, {field_num, wire_type, rest}} <- Wire.decode_tag(data) do
case field_num do
1 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_monitoring_check_fields(rest2, %{acc | device_id: value})
end
2 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_monitoring_check_fields(rest2, %{acc | status: value})
end
3 ->
with {:ok, {value, rest2}} <- decode_double_field(rest, wire_type) do
decode_monitoring_check_fields(rest2, %{acc | response_time_ms: value})
end
4 ->
with {:ok, {value, rest2}} <- decode_int64_field(rest, wire_type) do
decode_monitoring_check_fields(rest2, %{acc | timestamp: value})
end
_ ->
with {:ok, rest2} <- Wire.skip_field(wire_type, rest) do
decode_monitoring_check_fields(rest2, acc)
end
end
end
end
defp decode_lldp_topology_fields(<<>>, acc), do: {:ok, %{acc | neighbors: Enum.reverse(acc.neighbors)}}
defp decode_lldp_topology_fields(data, acc) do
with {:ok, {field_num, wire_type, rest}} <- Wire.decode_tag(data) do
case field_num do
1 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_lldp_topology_fields(rest2, %{acc | device_id: value})
end
2 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_lldp_topology_fields(rest2, %{acc | job_id: value})
end
3 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_lldp_topology_fields(rest2, %{acc | local_system_name: value})
end
4 ->
with {:ok, {msg_data, rest2}} <- Wire.decode_bytes(rest),
{:ok, neighbor} <- decode_lldp_neighbor(msg_data) do
decode_lldp_topology_fields(rest2, %{acc | neighbors: [neighbor | acc.neighbors]})
end
5 ->
with {:ok, {value, rest2}} <- decode_int64_field(rest, wire_type) do
decode_lldp_topology_fields(rest2, %{acc | timestamp: value})
end
_ ->
with {:ok, rest2} <- Wire.skip_field(wire_type, rest) do
decode_lldp_topology_fields(rest2, acc)
end
end
end
end
defp decode_lldp_neighbor(data) do
n = %Types.LldpNeighbor{
neighbor_name: "",
local_port: "",
remote_port: "",
remote_port_id: "",
management_addresses: []
}
decode_lldp_neighbor_fields(data, n)
end
defp decode_lldp_neighbor_fields(<<>>, acc),
do: {:ok, %{acc | management_addresses: Enum.reverse(acc.management_addresses)}}
defp decode_lldp_neighbor_fields(data, acc) do
with {:ok, {field_num, wire_type, rest}} <- Wire.decode_tag(data) do
case field_num do
1 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_lldp_neighbor_fields(rest2, %{acc | neighbor_name: value})
end
2 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_lldp_neighbor_fields(rest2, %{acc | local_port: value})
end
3 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_lldp_neighbor_fields(rest2, %{acc | remote_port: value})
end
4 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_lldp_neighbor_fields(rest2, %{acc | remote_port_id: value})
end
5 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
addrs = [value | acc.management_addresses]
decode_lldp_neighbor_fields(rest2, %{acc | management_addresses: addrs})
end
_ ->
with {:ok, rest2} <- Wire.skip_field(wire_type, rest) do
decode_lldp_neighbor_fields(rest2, acc)
end
end
end
end
defp decode_check_result_fields(<<>>, acc), do: {:ok, acc}
defp decode_check_result_fields(data, acc) do
with {:ok, {field_num, wire_type, rest}} <- Wire.decode_tag(data) do
case field_num do
1 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_check_result_fields(rest2, %{acc | check_id: value})
end
2 ->
with {:ok, {value, rest2}} <- decode_uint_field(rest, wire_type) do
decode_check_result_fields(rest2, %{acc | status: value})
end
3 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_check_result_fields(rest2, %{acc | output: value})
end
4 ->
with {:ok, {value, rest2}} <- decode_double_field(rest, wire_type) do
decode_check_result_fields(rest2, %{acc | response_time_ms: value})
end
5 ->
with {:ok, {value, rest2}} <- decode_int64_field(rest, wire_type) do
decode_check_result_fields(rest2, %{acc | timestamp: value})
end
_ ->
with {:ok, rest2} <- Wire.skip_field(wire_type, rest) do
decode_check_result_fields(rest2, acc)
end
end
end
end
defp decode_agent_config_fields(<<>>, acc),
do: {:ok, %{acc | devices: Enum.reverse(acc.devices), checks: Enum.reverse(acc.checks)}}
defp decode_agent_config_fields(data, acc) do
with {:ok, {field_num, wire_type, rest}} <- Wire.decode_tag(data) do
case field_num do
1 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_agent_config_fields(rest2, %{acc | version: value})
end
2 ->
with {:ok, {value, rest2}} <- decode_uint_field(rest, wire_type) do
decode_agent_config_fields(rest2, %{acc | poll_interval_seconds: value})
end
3 ->
with {:ok, {msg_data, rest2}} <- Wire.decode_bytes(rest),
{:ok, device} <- decode_device(msg_data) do
decode_agent_config_fields(rest2, %{acc | devices: [device | acc.devices]})
end
4 ->
with {:ok, {msg_data, rest2}} <- Wire.decode_bytes(rest),
{:ok, check} <- decode_check(msg_data),
{:ok, check} <- validate_check(check) do
decode_agent_config_fields(rest2, %{acc | checks: [check | acc.checks]})
end
_ ->
with {:ok, rest2} <- Wire.skip_field(wire_type, rest) do
decode_agent_config_fields(rest2, acc)
end
end
end
end
defp decode_device(data) do
d = %Types.Device{
id: "",
name: "",
ip_address: "",
snmp: nil,
poll_interval_seconds: 0,
sensors: [],
interfaces: [],
monitoring_enabled: false,
check_interval_seconds: 0
}
decode_device_fields(data, d)
end
defp decode_device_fields(<<>>, acc),
do: {:ok, %{acc | sensors: Enum.reverse(acc.sensors), interfaces: Enum.reverse(acc.interfaces)}}
defp decode_device_fields(data, acc) do
with {:ok, {field_num, wire_type, rest}} <- Wire.decode_tag(data) do
case field_num do
1 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_device_fields(rest2, %{acc | id: value})
end
2 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_device_fields(rest2, %{acc | name: value})
end
3 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_device_fields(rest2, %{acc | ip_address: value})
end
4 ->
with {:ok, {msg_data, rest2}} <- Wire.decode_bytes(rest),
{:ok, snmp_config} <- decode_snmp_config(msg_data) do
decode_device_fields(rest2, %{acc | snmp: snmp_config})
end
5 ->
with {:ok, {value, rest2}} <- decode_uint_field(rest, wire_type) do
decode_device_fields(rest2, %{acc | poll_interval_seconds: value})
end
6 ->
with {:ok, {msg_data, rest2}} <- Wire.decode_bytes(rest),
{:ok, sensor} <- decode_sensor(msg_data),
{:ok, sensor} <- validate_sensor(sensor) do
decode_device_fields(rest2, %{acc | sensors: [sensor | acc.sensors]})
end
7 ->
with {:ok, {msg_data, rest2}} <- Wire.decode_bytes(rest),
{:ok, interface} <- decode_interface(msg_data),
{:ok, interface} <- validate_interface(interface) do
decode_device_fields(rest2, %{acc | interfaces: [interface | acc.interfaces]})
end
8 ->
with {:ok, {value, rest2}} <- decode_bool_field(rest, wire_type) do
decode_device_fields(rest2, %{acc | monitoring_enabled: value})
end
9 ->
with {:ok, {value, rest2}} <- decode_uint_field(rest, wire_type) do
decode_device_fields(rest2, %{acc | check_interval_seconds: value})
end
_ ->
with {:ok, rest2} <- Wire.skip_field(wire_type, rest) do
decode_device_fields(rest2, acc)
end
end
end
end
defp decode_snmp_config(data) do
s = %Types.SnmpConfig{
enabled: false,
version: "",
community: "",
port: 0,
transport: ""
}
decode_snmp_config_fields(data, s)
end
defp decode_snmp_config_fields(<<>>, acc), do: {:ok, acc}
defp decode_snmp_config_fields(data, acc) do
with {:ok, {field_num, wire_type, rest}} <- Wire.decode_tag(data) do
case field_num do
1 ->
with {:ok, {value, rest2}} <- decode_bool_field(rest, wire_type) do
decode_snmp_config_fields(rest2, %{acc | enabled: value})
end
2 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_snmp_config_fields(rest2, %{acc | version: value})
end
3 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_snmp_config_fields(rest2, %{acc | community: value})
end
4 ->
with {:ok, {value, rest2}} <- decode_uint_field(rest, wire_type) do
decode_snmp_config_fields(rest2, %{acc | port: value})
end
5 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_snmp_config_fields(rest2, %{acc | transport: value})
end
_ ->
with {:ok, rest2} <- Wire.skip_field(wire_type, rest) do
decode_snmp_config_fields(rest2, acc)
end
end
end
end
defp decode_sensor_fields(<<>>, acc), do: {:ok, acc}
defp decode_sensor_fields(data, acc) do
with {:ok, {field_num, wire_type, rest}} <- Wire.decode_tag(data) do
case field_num do
1 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_sensor_fields(rest2, %{acc | id: value})
end
2 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_sensor_fields(rest2, %{acc | sensor_type: value})
end
3 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_sensor_fields(rest2, %{acc | oid: value})
end
4 ->
with {:ok, {value, rest2}} <- decode_double_field(rest, wire_type) do
decode_sensor_fields(rest2, %{acc | divisor: value})
end
5 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_sensor_fields(rest2, %{acc | unit: value})
end
6 ->
with {:ok, {entry_data, rest2}} <- Wire.decode_bytes(rest),
{:ok, {key, val}} <- decode_map_entry(entry_data) do
decode_sensor_fields(rest2, %{acc | metadata: Map.put(acc.metadata, key, val)})
end
_ ->
with {:ok, rest2} <- Wire.skip_field(wire_type, rest) do
decode_sensor_fields(rest2, acc)
end
end
end
end
defp decode_interface(data) do
i = %Types.Interface{
id: "",
if_index: 0,
if_name: ""
}
decode_interface_fields(data, i)
end
defp decode_interface_fields(<<>>, acc), do: {:ok, acc}
defp decode_interface_fields(data, acc) do
with {:ok, {field_num, wire_type, rest}} <- Wire.decode_tag(data) do
case field_num do
1 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_interface_fields(rest2, %{acc | id: value})
end
2 ->
with {:ok, {value, rest2}} <- decode_uint_field(rest, wire_type) do
decode_interface_fields(rest2, %{acc | if_index: value})
end
3 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_interface_fields(rest2, %{acc | if_name: value})
end
_ ->
with {:ok, rest2} <- Wire.skip_field(wire_type, rest) do
decode_interface_fields(rest2, acc)
end
end
end
end
defp decode_check(data) do
c = %Types.Check{
id: "",
check_type: "",
interval_seconds: 0,
timeout_ms: 0,
config: :no_config
}
decode_check_fields(data, c)
end
defp decode_check_fields(<<>>, acc), do: {:ok, acc}
defp decode_check_fields(data, acc) do
with {:ok, {field_num, wire_type, rest}} <- Wire.decode_tag(data) do
case field_num do
1 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_check_fields(rest2, %{acc | id: value})
end
2 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_check_fields(rest2, %{acc | check_type: value})
end
3 ->
with {:ok, {value, rest2}} <- decode_uint_field(rest, wire_type) do
decode_check_fields(rest2, %{acc | interval_seconds: value})
end
4 ->
with {:ok, {value, rest2}} <- decode_uint_field(rest, wire_type) do
decode_check_fields(rest2, %{acc | timeout_ms: value})
end
5 ->
with {:ok, {msg_data, rest2}} <- Wire.decode_bytes(rest),
{:ok, http_config} <- decode_http_check_config(msg_data) do
decode_check_fields(rest2, %{acc | config: {:http, http_config}})
end
6 ->
with {:ok, {msg_data, rest2}} <- Wire.decode_bytes(rest),
{:ok, tcp_config} <- decode_tcp_check_config(msg_data) do
decode_check_fields(rest2, %{acc | config: {:tcp, tcp_config}})
end
7 ->
with {:ok, {msg_data, rest2}} <- Wire.decode_bytes(rest),
{:ok, dns_config} <- decode_dns_check_config(msg_data) do
decode_check_fields(rest2, %{acc | config: {:dns, dns_config}})
end
8 ->
with {:ok, {msg_data, rest2}} <- Wire.decode_bytes(rest),
{:ok, ssl_config} <- decode_ssl_check_config(msg_data) do
decode_check_fields(rest2, %{acc | config: {:ssl, ssl_config}})
end
_ ->
with {:ok, rest2} <- Wire.skip_field(wire_type, rest) do
decode_check_fields(rest2, acc)
end
end
end
end
defp decode_http_check_config(data) do
h = %Types.HttpCheckConfig{
url: "",
method: "",
expected_status: 0,
verify_ssl: false,
headers: %{},
body: "",
regex: "",
follow_redirects: false
}
decode_http_check_config_fields(data, h)
end
defp decode_http_check_config_fields(<<>>, acc), do: {:ok, acc}
defp decode_http_check_config_fields(data, acc) do
with {:ok, {field_num, wire_type, rest}} <- Wire.decode_tag(data) do
case field_num do
1 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_http_check_config_fields(rest2, %{acc | url: value})
end
2 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_http_check_config_fields(rest2, %{acc | method: value})
end
3 ->
with {:ok, {value, rest2}} <- decode_uint_field(rest, wire_type) do
decode_http_check_config_fields(rest2, %{acc | expected_status: value})
end
4 ->
with {:ok, {value, rest2}} <- decode_bool_field(rest, wire_type) do
decode_http_check_config_fields(rest2, %{acc | verify_ssl: value})
end
5 ->
with {:ok, {entry_data, rest2}} <- Wire.decode_bytes(rest),
{:ok, {key, val}} <- decode_map_entry(entry_data) do
decode_http_check_config_fields(rest2, %{acc | headers: Map.put(acc.headers, key, val)})
end
6 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_http_check_config_fields(rest2, %{acc | body: value})
end
7 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_http_check_config_fields(rest2, %{acc | regex: value})
end
8 ->
with {:ok, {value, rest2}} <- decode_bool_field(rest, wire_type) do
decode_http_check_config_fields(rest2, %{acc | follow_redirects: value})
end
_ ->
with {:ok, rest2} <- Wire.skip_field(wire_type, rest) do
decode_http_check_config_fields(rest2, acc)
end
end
end
end
defp decode_tcp_check_config(data) do
t = %Types.TcpCheckConfig{
host: "",
port: 0,
send: "",
expect: ""
}
decode_tcp_check_config_fields(data, t)
end
defp decode_tcp_check_config_fields(<<>>, acc), do: {:ok, acc}
defp decode_tcp_check_config_fields(data, acc) do
with {:ok, {field_num, wire_type, rest}} <- Wire.decode_tag(data) do
case field_num do
1 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_tcp_check_config_fields(rest2, %{acc | host: value})
end
2 ->
with {:ok, {value, rest2}} <- decode_uint_field(rest, wire_type) do
decode_tcp_check_config_fields(rest2, %{acc | port: value})
end
3 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_tcp_check_config_fields(rest2, %{acc | send: value})
end
4 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_tcp_check_config_fields(rest2, %{acc | expect: value})
end
_ ->
with {:ok, rest2} <- Wire.skip_field(wire_type, rest) do
decode_tcp_check_config_fields(rest2, acc)
end
end
end
end
defp decode_dns_check_config(data) do
d = %Types.DnsCheckConfig{
hostname: "",
server: "",
record_type: "",
expected: ""
}
decode_dns_check_config_fields(data, d)
end
defp decode_dns_check_config_fields(<<>>, acc), do: {:ok, acc}
defp decode_dns_check_config_fields(data, acc) do
with {:ok, {field_num, wire_type, rest}} <- Wire.decode_tag(data) do
case field_num do
1 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_dns_check_config_fields(rest2, %{acc | hostname: value})
end
2 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_dns_check_config_fields(rest2, %{acc | server: value})
end
3 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_dns_check_config_fields(rest2, %{acc | record_type: value})
end
4 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_dns_check_config_fields(rest2, %{acc | expected: value})
end
_ ->
with {:ok, rest2} <- Wire.skip_field(wire_type, rest) do
decode_dns_check_config_fields(rest2, acc)
end
end
end
end
defp decode_ssl_check_config(data) do
s = %Types.SslCheckConfig{
host: "",
port: 0,
warning_days: 0
}
decode_ssl_check_config_fields(data, s)
end
defp decode_ssl_check_config_fields(<<>>, acc), do: {:ok, acc}
defp decode_ssl_check_config_fields(data, acc) do
with {:ok, {field_num, wire_type, rest}} <- Wire.decode_tag(data) do
case field_num do
1 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_ssl_check_config_fields(rest2, %{acc | host: value})
end
2 ->
with {:ok, {value, rest2}} <- decode_uint_field(rest, wire_type) do
decode_ssl_check_config_fields(rest2, %{acc | port: value})
end
3 ->
with {:ok, {value, rest2}} <- decode_uint_field(rest, wire_type) do
decode_ssl_check_config_fields(rest2, %{acc | warning_days: value})
end
_ ->
with {:ok, rest2} <- Wire.skip_field(wire_type, rest) do
decode_ssl_check_config_fields(rest2, acc)
end
end
end
end
defp decode_agent_job_list_fields(<<>>, acc), do: {:ok, %Types.AgentJobList{jobs: Enum.reverse(acc)}}
defp decode_agent_job_list_fields(data, acc) do
with {:ok, {field_num, wire_type, rest}} <- Wire.decode_tag(data) do
case field_num do
1 ->
with {:ok, {msg_data, rest2}} <- Wire.decode_bytes(rest),
{:ok, job} <- decode_agent_job(msg_data) do
decode_agent_job_list_fields(rest2, [job | acc])
end
_ ->
with {:ok, rest2} <- Wire.skip_field(wire_type, rest) do
decode_agent_job_list_fields(rest2, acc)
end
end
end
end
defp decode_agent_job_fields(<<>>, acc),
do: {:ok, %{acc | queries: Enum.reverse(acc.queries), mikrotik_commands: Enum.reverse(acc.mikrotik_commands)}}
defp decode_agent_job_fields(data, acc) do
with {:ok, {field_num, wire_type, rest}} <- Wire.decode_tag(data) do
case field_num do
1 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_agent_job_fields(rest2, %{acc | job_id: value})
end
2 ->
with {:ok, {value, rest2}} <- decode_enum_field(rest, wire_type),
{:ok, job_type} <- Types.job_type_from_int(value) do
decode_agent_job_fields(rest2, %{acc | job_type: job_type})
end
3 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_agent_job_fields(rest2, %{acc | device_id: value})
end
4 ->
with {:ok, {msg_data, rest2}} <- Wire.decode_bytes(rest),
{:ok, snmp_device} <- decode_snmp_device(msg_data),
{:ok, snmp_device} <- validate_snmp_device(snmp_device) do
decode_agent_job_fields(rest2, %{acc | snmp_device: snmp_device})
end
5 ->
with {:ok, {msg_data, rest2}} <- Wire.decode_bytes(rest),
{:ok, query} <- decode_snmp_query(msg_data),
{:ok, query} <- validate_snmp_query(query) do
decode_agent_job_fields(rest2, %{acc | queries: [query | acc.queries]})
end
6 ->
with {:ok, {msg_data, rest2}} <- Wire.decode_bytes(rest),
{:ok, mikrotik_device} <- decode_mikrotik_device(msg_data),
{:ok, mikrotik_device} <- validate_mikrotik_device(mikrotik_device) do
decode_agent_job_fields(rest2, %{acc | mikrotik_device: mikrotik_device})
end
7 ->
with {:ok, {msg_data, rest2}} <- Wire.decode_bytes(rest),
{:ok, command} <- decode_mikrotik_command(msg_data),
{:ok, command} <- validate_mikrotik_command(command) do
decode_agent_job_fields(rest2, %{acc | mikrotik_commands: [command | acc.mikrotik_commands]})
end
_ ->
with {:ok, rest2} <- Wire.skip_field(wire_type, rest) do
decode_agent_job_fields(rest2, acc)
end
end
end
end
defp decode_snmp_device(data) do
d = %Types.SnmpDevice{
ip: "",
community: "",
version: "",
port: 0,
v3_security_level: "",
v3_username: "",
v3_auth_protocol: "",
v3_auth_password: "",
v3_priv_protocol: "",
v3_priv_password: "",
transport: ""
}
decode_snmp_device_fields(data, d)
end
defp decode_snmp_device_fields(<<>>, acc), do: {:ok, acc}
defp decode_snmp_device_fields(data, acc) do
with {:ok, {field_num, wire_type, rest}} <- Wire.decode_tag(data) do
case field_num do
1 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_snmp_device_fields(rest2, %{acc | ip: value})
end
2 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_snmp_device_fields(rest2, %{acc | community: value})
end
3 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_snmp_device_fields(rest2, %{acc | version: value})
end
4 ->
with {:ok, {value, rest2}} <- decode_uint_field(rest, wire_type) do
decode_snmp_device_fields(rest2, %{acc | port: value})
end
5 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_snmp_device_fields(rest2, %{acc | v3_security_level: value})
end
6 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_snmp_device_fields(rest2, %{acc | v3_username: value})
end
7 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_snmp_device_fields(rest2, %{acc | v3_auth_protocol: value})
end
8 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_snmp_device_fields(rest2, %{acc | v3_auth_password: value})
end
9 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_snmp_device_fields(rest2, %{acc | v3_priv_protocol: value})
end
10 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_snmp_device_fields(rest2, %{acc | v3_priv_password: value})
end
11 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_snmp_device_fields(rest2, %{acc | transport: value})
end
_ ->
with {:ok, rest2} <- Wire.skip_field(wire_type, rest) do
decode_snmp_device_fields(rest2, acc)
end
end
end
end
defp decode_snmp_query(data) do
q = %Types.SnmpQuery{
query_type: :get,
oids: []
}
decode_snmp_query_fields(data, q)
end
defp decode_snmp_query_fields(<<>>, acc), do: {:ok, %{acc | oids: Enum.reverse(acc.oids)}}
defp decode_snmp_query_fields(data, acc) do
with {:ok, {field_num, wire_type, rest}} <- Wire.decode_tag(data) do
case field_num do
1 ->
with {:ok, {value, rest2}} <- decode_enum_field(rest, wire_type),
{:ok, query_type} <- Types.query_type_from_int(value) do
decode_snmp_query_fields(rest2, %{acc | query_type: query_type})
end
2 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_snmp_query_fields(rest2, %{acc | oids: [value | acc.oids]})
end
_ ->
with {:ok, rest2} <- Wire.skip_field(wire_type, rest) do
decode_snmp_query_fields(rest2, acc)
end
end
end
end
defp decode_mikrotik_device(data) do
d = %Types.MikrotikDevice{
ip: "",
port: 0,
username: "",
password: "",
use_ssl: false,
ssh_port: 0
}
decode_mikrotik_device_fields(data, d)
end
defp decode_mikrotik_device_fields(<<>>, acc), do: {:ok, acc}
defp decode_mikrotik_device_fields(data, acc) do
with {:ok, {field_num, wire_type, rest}} <- Wire.decode_tag(data) do
case field_num do
1 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_mikrotik_device_fields(rest2, %{acc | ip: value})
end
2 ->
with {:ok, {value, rest2}} <- decode_uint_field(rest, wire_type) do
decode_mikrotik_device_fields(rest2, %{acc | port: value})
end
3 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_mikrotik_device_fields(rest2, %{acc | username: value})
end
4 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_mikrotik_device_fields(rest2, %{acc | password: value})
end
5 ->
with {:ok, {value, rest2}} <- decode_bool_field(rest, wire_type) do
decode_mikrotik_device_fields(rest2, %{acc | use_ssl: value})
end
6 ->
with {:ok, {value, rest2}} <- decode_uint_field(rest, wire_type) do
decode_mikrotik_device_fields(rest2, %{acc | ssh_port: value})
end
_ ->
with {:ok, rest2} <- Wire.skip_field(wire_type, rest) do
decode_mikrotik_device_fields(rest2, acc)
end
end
end
end
defp decode_mikrotik_command(data) do
c = %Types.MikrotikCommand{
command: "",
args: %{}
}
decode_mikrotik_command_fields(data, c)
end
defp decode_mikrotik_command_fields(<<>>, acc), do: {:ok, acc}
defp decode_mikrotik_command_fields(data, acc) do
with {:ok, {field_num, wire_type, rest}} <- Wire.decode_tag(data) do
case field_num do
1 ->
with {:ok, {value, rest2}} <- decode_string_field(rest, wire_type) do
decode_mikrotik_command_fields(rest2, %{acc | command: value})
end
2 ->
with {:ok, {entry_data, rest2}} <- Wire.decode_bytes(rest),
{:ok, {key, val}} <- decode_map_entry(entry_data) do
decode_mikrotik_command_fields(rest2, %{acc | args: Map.put(acc.args, key, val)})
end
_ ->
with {:ok, rest2} <- Wire.skip_field(wire_type, rest) do
decode_mikrotik_command_fields(rest2, acc)
end
end
end
end
defp decode_check_list_fields(<<>>, acc), do: {:ok, %Types.CheckList{checks: Enum.reverse(acc)}}
defp decode_check_list_fields(data, acc) do
with {:ok, {field_num, wire_type, rest}} <- Wire.decode_tag(data) do
case field_num do
1 ->
with {:ok, {msg_data, rest2}} <- Wire.decode_bytes(rest),
{:ok, check} <- decode_check(msg_data),
{:ok, check} <- validate_check(check) do
decode_check_list_fields(rest2, [check | acc])
end
_ ->
with {:ok, rest2} <- Wire.skip_field(wire_type, rest) do
decode_check_list_fields(rest2, acc)
end
end
end
end
# Low-level field decoders
defp decode_string_field(data, 2) do
Wire.decode_bytes(data)
end
defp decode_string_field(_data, wire_type) do
{:error, {:wire_error, "Expected length-delimited (2) for string, got #{wire_type}"}}
end
defp decode_uint_field(data, 0) do
Wire.decode_varint(data)
end
defp decode_uint_field(_data, wire_type) do
{:error, {:wire_error, "Expected varint (0) for uint, got #{wire_type}"}}
end
defp decode_int64_field(data, 0) do
with {:ok, {raw, rest}} <- Wire.decode_varint(data) do
{:ok, {Wire.decode_int64_value(raw), rest}}
end
end
defp decode_int64_field(_data, wire_type) do
{:error, {:wire_error, "Expected varint (0) for int64, got #{wire_type}"}}
end
defp decode_double_field(data, 1) do
Wire.decode_double(data)
end
defp decode_double_field(_data, wire_type) do
{:error, {:wire_error, "Expected 64-bit (1) for double, got #{wire_type}"}}
end
defp decode_bool_field(data, 0) do
with {:ok, {value, rest}} <- Wire.decode_varint(data) do
{:ok, {value != 0, rest}}
end
end
defp decode_bool_field(_data, wire_type) do
{:error, {:wire_error, "Expected varint (0) for bool, got #{wire_type}"}}
end
defp decode_enum_field(data, 0) do
Wire.decode_varint(data)
end
defp decode_enum_field(_data, wire_type) do
{:error, {:wire_error, "Expected varint (0) for enum, got #{wire_type}"}}
end
defp decode_map_entry(data) do
decode_map_entry_fields(data, nil, nil)
end
defp decode_map_entry_fields(<<>>, key, value) when key != nil and value != nil do
{:ok, {key, value}}
end
defp decode_map_entry_fields(<<>>, _key, _value) do
{:error, {:wire_error, "Incomplete map entry"}}
end
defp decode_map_entry_fields(data, key, value) do
with {:ok, {field_num, wire_type, rest}} <- Wire.decode_tag(data) do
case field_num do
1 ->
with {:ok, {k, rest2}} <- decode_string_field(rest, wire_type) do
decode_map_entry_fields(rest2, k, value)
end
2 ->
with {:ok, {v, rest2}} <- decode_string_field(rest, wire_type) do
decode_map_entry_fields(rest2, key, v)
end
_ ->
with {:ok, rest2} <- Wire.skip_field(wire_type, rest) do
decode_map_entry_fields(rest2, key, value)
end
end
end
end
# Validation functions
defp validate_heartbeat(hb) do
with :ok <- validate_string("version", hb.version, @max_short_string_length),
:ok <- validate_string("hostname", hb.hostname, @max_short_string_length),
:ok <- validate_range("uptime_seconds", hb.uptime_seconds, 0, @max_uptime_seconds),
:ok <- validate_string("ip_address", hb.ip_address, @max_short_string_length),
:ok <- validate_string("arch", hb.arch, @max_short_string_length) do
{:ok, hb}
end
end
defp validate_snmp_device(d) do
with :ok <- validate_string("community", d.community, @max_short_string_length),
:ok <- validate_string("v3_auth_password", d.v3_auth_password, @max_short_string_length),
:ok <- validate_string("v3_priv_password", d.v3_priv_password, @max_short_string_length) do
{:ok, d}
end
end
defp validate_snmp_result(sr) do
with :ok <- validate_uuid("device_id", sr.device_id),
:ok <- validate_map_size("oid_values", sr.oid_values, @max_oid_values),
:ok <- validate_timestamp("timestamp", sr.timestamp),
:ok <- validate_string("job_id", sr.job_id, @max_short_string_length) do
{:ok, sr}
end
end
defp validate_agent_error(e) do
with :ok <- validate_uuid("device_id", e.device_id),
:ok <- validate_uuid("job_id", e.job_id),
:ok <- validate_string("message", e.message, @max_error_message_length),
:ok <- validate_timestamp("timestamp", e.timestamp) do
{:ok, e}
end
end
defp validate_credential_test_result(r) do
with :ok <- validate_uuid("test_id", r.test_id),
:ok <- validate_string("error_message", r.error_message, @max_error_message_length),
:ok <- validate_string("system_description", r.system_description, @max_string_length),
:ok <- validate_timestamp("timestamp", r.timestamp) do
{:ok, r}
end
end
defp validate_mikrotik_result(r) do
with :ok <- validate_uuid("device_id", r.device_id),
:ok <- validate_uuid("job_id", r.job_id),
:ok <- validate_list_size("sentences", r.sentences, @max_mikrotik_sentences),
:ok <- validate_string("error", r.error, @max_error_message_length),
:ok <- validate_timestamp("timestamp", r.timestamp) do
{:ok, r}
end
end
defp validate_monitoring_check(c) do
with :ok <- validate_uuid("device_id", c.device_id),
:ok <- validate_string("status", c.status, @max_short_string_length),
:ok <- validate_range("response_time_ms", c.response_time_ms, 0, @max_response_time_ms),
:ok <- validate_timestamp("timestamp", c.timestamp) do
{:ok, c}
end
end
defp validate_lldp_topology_result(r) do
with :ok <- validate_uuid("device_id", r.device_id),
:ok <- validate_uuid("job_id", r.job_id),
:ok <- validate_string("local_system_name", r.local_system_name, @max_string_length),
:ok <- validate_list_size("neighbors", r.neighbors, @max_neighbors),
:ok <- validate_timestamp("timestamp", r.timestamp) do
{:ok, r}
end
end
defp validate_check_result(r) do
with :ok <- validate_uuid("check_id", r.check_id),
:ok <- validate_string("output", r.output, @max_string_length),
:ok <- validate_range("response_time_ms", r.response_time_ms, 0, @max_response_time_ms),
:ok <- validate_timestamp("timestamp", r.timestamp) do
{:ok, r}
end
end
defp validate_mikrotik_device(d) do
with :ok <- validate_string("ip", d.ip, @max_short_string_length),
:ok <- validate_string("username", d.username, @max_short_string_length),
:ok <- validate_string("password", d.password, @max_short_string_length) do
{:ok, d}
end
end
defp validate_mikrotik_command(c) do
with :ok <- validate_string("command", c.command, @max_string_length) do
{:ok, c}
end
end
defp validate_check(c) do
with :ok <- validate_string("id", c.id, @max_short_string_length),
:ok <- validate_string("check_type", c.check_type, @max_short_string_length) do
{:ok, c}
end
end
defp validate_snmp_query(q) do
with :ok <- validate_list_size("oids", q.oids, @max_oid_values) do
{:ok, q}
end
end
defp validate_sensor(s) do
with :ok <- validate_string("id", s.id, @max_short_string_length),
:ok <- validate_string("sensor_type", s.sensor_type, @max_short_string_length),
:ok <- validate_string("oid", s.oid, @max_short_string_length),
:ok <- validate_string("unit", s.unit, @max_short_string_length) do
{:ok, s}
end
end
defp validate_interface(i) do
with :ok <- validate_string("id", i.id, @max_short_string_length),
:ok <- validate_string("if_name", i.if_name, @max_short_string_length) do
{:ok, i}
end
end
# Validation helpers
defp validate_uuid(field, value) do
error_atom = field_to_error_atom(field)
if String.length(value) == 0 do
{:error, {error_atom, "#{field} cannot be empty"}}
else
# UUID format: 8-4-4-4-12 hex characters
if String.match?(value, ~r/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/) do
:ok
else
{:error, {error_atom, "#{field} must be a valid UUID"}}
end
end
end
defp field_to_error_atom("device_id"), do: :invalid_device_id
defp field_to_error_atom("job_id"), do: :invalid_job_id
defp field_to_error_atom("test_id"), do: :invalid_test_id
defp field_to_error_atom("check_id"), do: :invalid_check_id
defp field_to_error_atom("sensor_id"), do: :invalid_sensor_id
defp field_to_error_atom("interface_id"), do: :invalid_interface_id
defp field_to_error_atom(_), do: :invalid_device_id
defp validate_string(_field, value, max_len) when byte_size(value) <= max_len, do: :ok
defp validate_string(field, _value, max_len) do
{:error, {:string_too_long, "#{field} exceeds #{max_len} bytes"}}
end
defp validate_range(_field, value, min, max) when value >= min and value <= max, do: :ok
defp validate_range(field, value, min, max) do
error_atom = field_to_range_error_atom(field)
{:error, {error_atom, "#{field} value #{value} not in range #{min}..#{max}"}}
end
defp field_to_range_error_atom("uptime_seconds"), do: :invalid_uptime
defp field_to_range_error_atom("response_time_ms"), do: :invalid_response_time
defp field_to_range_error_atom(_), do: :invalid_counter
defp validate_timestamp(_field, value) when value >= 0 and value <= @max_timestamp, do: :ok
defp validate_timestamp(field, value) do
{:error, {:invalid_timestamp, "#{field} value #{value} not in valid range"}}
end
defp validate_map_size(_field, map, max_size) when map_size(map) <= max_size, do: :ok
defp validate_map_size(field, _map, max_size) do
{:error, {:too_many_oids, "#{field} exceeds #{max_size} entries"}}
end
defp validate_list_size(_field, list, max_size) when length(list) <= max_size, do: :ok
defp validate_list_size(field, _list, max_size) do
{:error, {:too_many_neighbors, "#{field} exceeds #{max_size} items"}}
end
end