Flexible ingress
Receive plain UDP over IPv4 or IPv6, or authenticated encrypted UDPSEC station traffic.
AIS stream processing and routing platform
Normalize · Deduplicate · Tag · Route · Forward
Receive AIS feeds, turn receiver traffic into one controlled logical stream, and deliver it to the UDP egress targets that need it.
AIS stream processing
AISMixer keeps transport, NMEA assembly, metadata, deduplication, and delivery in one near-real-time processing path.
Receive plain UDP over IPv4 or IPv6, or authenticated encrypted UDPSEC station traffic.
Extract both !AIVDM and !AIVDO from realistic receiver and application output.
Assemble fully out-of-order fragments into complete logical messages and emit them in ordinal order.
Make one group-atomic decision for each complete multipart message, globally in broadcast mode or separately for each routing target.
Control NMEA TAG s, c, and g across the multipart lifecycle while keeping metadata separate from routing identity.
Forward accepted sentences to every configured UDP output in broadcast mode or to the named targets selected by logical routing.
Campaign D–E data-plane foundation
Immutable ingress frames move through explicit single-process stages. Each accepted frame receives one ProcessingSnapshot, and PythonDataPlaneProcessor—the sole production and reference processor—returns one ordered OutputBatch. Each ProcessorOutput carries exact immutable bytes and explicit numeric target IDs for one unified egress path. Exact lifecycle rules and edge cases remain in the normative behavioural contract.
Built-in UDP and UDPSEC producers create immutable IngressFrame objects. Valid legacy IngressEvent objects pass through one compatibility adapter; invalid or unsupported items are rejected before routing-snapshot acquisition and processor invocation.
Each accepted frame receives exactly one immutable ProcessingSnapshot. Configured external target names remain the operator interface, while production matching uses a precompiled numeric target-only plan and preserves target order.
DataPlaneProcessor returns OutputBatch, and PythonDataPlaneProcessor remains the sole production and reference implementation. Every ProcessorOutput carries explicit ordered numeric target IDs.
Each emitted NMEA sentence becomes one exact immutable bytes payload and is encoded as UTF-8 once. Multipart fragments remain separate payloads; a group is never concatenated into one output.
Complete frame processing precedes the first egress dispatch. A non-empty OutputBatch prevents later-frame processing until sequential dispatch returns without error; this is not a delivery acknowledgement or transaction and does not roll back completed processor effects.
Essential asyncio stages share one supervised lifecycle within the current process. Failure or unexpected completion cancels and awaits sibling tasks; there is no automatic restart, retry, rollback, delivery acknowledgement, or replay.
Need the exact edge-case semantics? Read the behavioural contract.
Explicit lifecycle ownership
The reference implementation makes retained state, lifetime progress, capacity admission, cleanup, and observation explicit instead of relying on incidental container behaviour.
Identity is the exact sentence or ordered multipart tuple, in a global or per-target scope. TTL is deterministic, duplicates do not refresh retention, and expired entries are removed before the oldest live entry is evicted.
The Python object accepts optional max_entries; current service wiring leaves it at None, so this dimension is unbounded in the current service.
Fragments occupy ordinal slots. Unique progress refreshes group lifetime; an exact duplicate does not. Conflict, expiry, capacity removal, completion, and reset remain distinct lifecycle outcomes and clean related TAG context.
The Python object accepts optional max_fragments_per_group and max_pending_groups; current service wiring leaves both at None.
Handshake replay records, pending sessions, active sessions, and the private nonce state owned by each pending or active session are hard-bounded. Monotonic local time governs TTL, and expired state is removed before deterministic live-state eviction.
Installing a same-address pending candidate preserves the current active session and replaces only an older pending candidate. The active session changes only after authenticated encrypted confirmation and pending-to-active promotion; a failed confirmation, or pending expiry, replacement, or capacity eviction, leaves it intact. All secure state is in-memory, process-local, and lost at restart.
Deduplication, assembly, and secure state expose immutable point-in-time statistics for inspection and regression testing. Reading a snapshot does not run cleanup or alter an earlier snapshot.
This is a basis for future differential conformance, not a complete monitoring or Prometheus export system.
Local by design. This state is non-durable and is not shared across processes. The current runtime supervises essential asyncio tasks within one process; coordinator/worker processes, IPC, and cross-process state synchronization do not exist yet.
Reference semantics and staged evolution
Campaign C established immutable bytes-native ingress. Campaign D added the synchronous processor contract and explicit process-local runtime stages. Campaign E completed numeric target identity, compiled target-only routing, exact immutable output bytes, OutputBatch, and unified numeric egress. PythonDataPlaneProcessor remains the sole production and reference implementation.
Immutable IngressFrame values, bytes-native scan spans, and ParsedSentence metadata form the ingress representation. Each accepted frame is paired with one ProcessingSnapshot; when routing is enabled, its targets come from the compiled numeric target-only plan. PythonDataPlaneProcessor produces exact immutable output bytes in an ordered OutputBatch with explicit numeric target IDs on every ProcessorOutput. Production egress is unified through send_to_ids(), and stage supervision remains process-local.
Campaign F prepares future worker boundaries through bounded queues and backpressure policy, processor-instance ownership, worker lifecycle preparation, and a metrics boundary. It does not itself introduce coordinator processes, IPC, a multiprocess runtime, or native bindings.
Later work covers a coordinator, dedicated ingress and egress workers, cross-process supervision, IPC and routing-snapshot distribution, restart and recovery policy, and a native processor with bindings behind the established contracts. No native processor exists today, no native API or ABI has been selected, and no performance claim is made.
Routing and logical zones
Static logical routing connects named ingress sources to named UDP egress targets through reusable source sets and ordered routes. Names remain the configuration and control interface, while production matching uses a precompiled numeric target-only plan.
Define zones with include, union, intersection, and difference.
Zones are sets of internal source identities—not map areas, MMSI filters, vessel filters, or payload rules.
The compiled plan preserves declared target order. Routing mode scopes deduplication to each logical target, so one dispatch path does not suppress another.
Names stay operator-facing. Process-local numeric target IDs are not configuration values. source_id remains separate from the NMEA TAG s value emitted downstream.
Runtime routing control
An optional local Unix-domain control plane exposes concise routing operations through the globally installed aismixerctl command.
See the active generation, enabled state, logical zones, ordered routes, and target identities.
Validate a complete candidate before swapping the immutable routing snapshot, or return to legacy broadcast mode.
Optional generation checks prevent an operator from overwriting a newer routing change unintentionally.
Deliberately local and process-scoped. Runtime routes are not persisted after restart, configuration files are not rewritten, and ingress or egress adapters are not created dynamically. Unix socket permissions are the current authorization boundary; there is no application-level control token.
Network endpoint controls
Small application-level controls make IPv4 and IPv6 endpoints easier to place inside an operator’s wider firewall and routing policy.
Restrict UDP and UDPSEC listeners to literal IP addresses or CIDR networks, with explicit deny-all behavior available.
Bind an egress socket to a literal IPv4 or IPv6 source address and constrain destination resolution to the same family.
These policies complement operating-system firewall and routing rules; they do not replace them.
source_ip is source-address binding—not interface selection, routing-table selection, socket marking, or SDN.
UDPSEC lifecycle
The current nmea_sproxy and AISMixer UDPSEC path uses authenticated ephemeral ECDHE and independent directional keys, with encrypted sequence-zero confirmation before the client treats a new session as established.
For every session handshake, each endpoint creates a fresh ephemeral P-256 ECDHE keypair. Long-term P-256 identity keys only authenticate canonical, role-separated SHA-256 transcript digests with ECDSA; they are not ECDHE key-agreement inputs.
HKDF-SHA256 derives independent C2S and S2C keys from the shared secret and authenticated transcript. AES-256-GCM protects encrypted data and control traffic.
The client sends an encrypted sequence-zero ping for the pending session. The server promotes only after authenticating it and returns an encrypted sequence-zero pong under the promoted S2C key; the client treats the session as established only after authenticating that pong.
Unrelated, malformed, stale-key, wrong-sequence, and wrong-source datagrams neither confirm the candidate nor extend its fixed confirmation deadline.
Both endpoints must be updated together; there is no legacy fallback, mixed mode, negotiation switch, certificate system, or algorithm negotiation.
Allowed traffic drives expiry cleanup rather than a background timer. Replay, pending-session, active-session, and nonce state is in-memory, process-local, non-durable, and lost when the service restarts.
Forward-secrecy boundary: Later compromise of a long-term identity key does not by itself reconstruct keys for completed sessions when old ephemeral private keys and raw shared secrets are gone and neither endpoint was compromised while they were in memory. An identity key compromised in the present enables future impersonation. This implemented property is supported by engineering validation, not formal cryptographic verification.
Transport boundary: UDPSEC authenticates and encrypts data and control packets within its scope, but does not prove AIS truthfulness or detect spoofing, guarantee UDP delivery or ordering, protect compromised endpoints, or provide complete denial-of-service resistance. It does not hide peer IP addresses, packet timing or lengths, or the cleartext ClientHello station identifier; NOSESSION hints are unauthenticated.
Read the security policy, behavioural contract, nmea_sproxy guide, and Wiki for the detailed boundaries.
nmea_sproxy and physical AIS receivers
nmea_sproxy is the station-side proxy that connects one local AIS source to one configured AISMixer destination.
One local input maps to one configured output. nmea_sproxy does not mix, fan out, route, deduplicate, assemble multipart AIS, or rewrite TAG metadata.
A networked receiver can feed local UDP into nmea_sproxy or send UDP directly to AISMixer, depending on the deployment. The main AISMixer process does not read serial ports directly. Trusted plain UDP does not provide UDPSEC encryption, authentication, replay protection, or liveness.
Deployment and operations
The repository-managed deployment keeps runtime files, operator state, systemd integration, and local routing control aligned.
The service provisions /run/aismixer while AISMixer is running and supports the optional local control socket.
Privilege-aware lifecycle scripts handle root or sudo operation and maintain the deployed runtime and service units.
Normal install, update, and uninstall flows preserve operator configuration and cryptographic keys.
The installed workflow places aismixerctl in /usr/local/bin for local routing operations.
Documentation and project status
The Wiki is the detailed guide to architecture, configuration, routing, security, and operations. The Roadmap tracks future work without promising dates.
Project community
Choose the public channel that fits the conversation. Discussions, Ideas, and Q&A help other users too; reproducible defects belong in Issues. Private contact remains available in the maintainer profile below.
Explore public project conversations, announcements, deployment experience, field testing, and other community topics.
Browse GitHub DiscussionsDiscussions is suitable for architecture proposals, integration ideas, deployment experience, AIS field testing, and academic or research collaboration that can be discussed publicly.
Open IdeasUse GitHub Issues for reproducible defects and concrete repository work, not general private correspondence.
Open GitHub IssuesUse Q&A for focused questions about installation, configuration, usage, architecture, and operation.
Open Q&AFounder and maintainer
Founder, originator, and maintainer of AISMixer
Backend and network systems developer and researcher with interests in AIS/NMEA processing, maritime and regional security, transport, and tourism.
AI-assisted development: ChatGPT has supported architectural reasoning, planning, documentation, and review. OpenAI Codex has supported repository analysis, implementation, testing, and validation. Both tools are used under the founder’s direction; project decisions and final responsibility remain human.