# MAVLink 2 Signing Guide

Primary references:

- MAVLink message signing: https://mavlink.io/en/guide/message_signing.html
- MAVLink packet serialization: https://mavlink.io/en/guide/serialization.html

## Overview

XMAVLink parses the MAVLink 2 signing incompatibility flag (`0x01`) as a known
frame-shape feature and extracts the 13-byte signing trailer into
`XMAVLink.Frame.Signature`.

`XMAVLink.Frame.sign_frame/4` can sign an already packed MAVLink 2 frame when
given a 32-byte key, link id, and timestamp. Router forwarding uses it through
`XMAVLink.Signing.sign_outbound/2` when a signing-enabled connection sends an
unsigned MAVLink 2 frame.

`XMAVLink.Frame.validate_signature/2` verifies the 48-bit signature for a
parsed signed frame. `XMAVLink.Signing.validate_inbound/2` adds the policy
state needed for replay protection by tracking the last accepted timestamp per
`{source_system, source_component, link_id}` stream and rejecting first-seen
streams more than 6,000,000 ticks behind the local signing timestamp.

Routers accept a `:signing` configuration with `:secret_key`, `:link_id`,
`:timestamp`, optional `:accept_unsigned`, optional `:accept_mavlink1`, and
optional timestamp persistence callbacks. Receive paths seed each connection
with that policy. Configured signed MAVLink 2 frames are verified before dialect
unpacking, delivered to subscribers, forwarded through the normal routing logic,
and recorded for replay protection. Unsigned MAVLink 2 inbound frames are
rejected by default while signing is enabled unless `accept_unsigned: true` is
set. MAVLink 1 frames remain accepted under a signing policy unless
`accept_mavlink1: false` is configured. When signing is not configured, signed
MAVLink 2 frames still return `:signed_frame_unsupported`.

Outbound routing signs unsigned MAVLink 2 frames on signing-enabled
connections, updates that connection's local timestamp after each signed send,
and leaves MAVLink 1 frames unsigned. Already signed MAVLink 2 frames are
forwarded with their existing signature rather than being re-signed. If a
timestamp save callback is configured and the local timestamp advances, the
callback must succeed before a signed inbound frame is accepted or an outbound
signed frame is emitted.

Unknown incompatible flags are still rejected. If a frame has both the signing
flag and unsupported incompatible flags, XMAVLink consumes the known 13-byte
signature trailer when present so stream transports keep frame boundaries, but
the packet is not accepted.

`SETUP_SIGNING` frames carry key material. Inbound `SETUP_SIGNING` frames are
delivered to local subscribers but are not forwarded from one MAVLink connection
to another by generic routing. Locally originated `SETUP_SIGNING` frames are
still routed normally so applications can build an intentional provisioning
flow, but XMAVLink does not automate key provisioning or key rotation.

## Signature Frame Shape

The MAVLink 2 signed trailer is 13 bytes:

- `link_id`: 8-bit link identifier.
- `timestamp`: little-endian 48-bit timestamp in 10 microsecond units since
  2015-01-01 00:00:00 GMT.
- `signature`: 48-bit signature value.

The signature is defined as the first 48 bits of SHA-256 over the 32-byte shared
secret key followed by the wire header including the magic byte, payload, CRC,
link id, and timestamp.

## Public Policy Shape

Signing is currently configured per router and copied into each connection:

- `signing: nil` or omitted: current unsigned behavior, but signed frames remain
  rejected until an explicit acceptance policy is configured.
- `signing: [secret_key: <<_::256>>, link_id: 0..255, timestamp: non_neg_integer]`:
  validate inbound signed MAVLink 2 frames, track inbound replay timestamps, and
  sign unsigned outbound MAVLink 2 frames on each connection.
- `accept_unsigned: false | true`: explicit policy for unsigned frames when
  signing is enabled. The policy helper defaults to reject.
- `accept_mavlink1: true | false`: explicit policy for MAVLink 1 frames when
  signing is enabled. The policy helper defaults to accept for mixed-link
  compatibility. Set false for signed-only deployments that should reject
  MAVLink 1 inbound and outbound traffic on signing-enabled connections.
- `timestamp_load: fun | {module, function, args}`: optional zero-arity callback
  that returns a previously saved timestamp, `{:ok, timestamp}`, or `nil` when
  no timestamp has been saved yet. Loaded timestamps are combined with the
  configured or current timestamp by taking the maximum value. `:error`,
  `{:error, reason}`, invalid timestamps, or callback failures reject the
  signing configuration.
- `timestamp_save: fun | {module, function, args}`: optional one-arity callback
  that receives each advanced local timestamp and returns `:ok` or
  `{:ok, result}`. MFA callbacks receive the timestamp appended to `args`.
  Save failures return `:timestamp_save_failed` to the caller and prevent that
  state-advancing frame from being accepted or emitted.

## Operational Notes

Applications are responsible for storing shared keys and persisted timestamps
securely. Timestamp persistence callbacks should write to durable storage before
returning `:ok`; otherwise XMAVLink will treat the save as failed and reject
the state-advancing frame.
