Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
0bd2895
sort javadocs
mbuckton Jan 3, 2026
ece0418
fix read me
mbuckton Jan 3, 2026
1997aae
simply unpack the framing
mbuckton Jan 4, 2026
dc98e97
clean code
mbuckton Jan 4, 2026
ba5eb2b
fix issue in cleanup
mbuckton Jan 4, 2026
e1b44a7
align the flip() with maps
mbuckton Jan 4, 2026
8f8aae3
fix crc
mbuckton Jan 4, 2026
5e81a26
compute the extra crc
mbuckton Jan 4, 2026
cacd506
compute the extra crc
mbuckton Jan 4, 2026
e994a6c
windows fails to load from the jar
mbuckton Jan 4, 2026
010a590
parse V1 correctly
mbuckton Jan 5, 2026
8e9b320
generate json schemas
mbuckton Jan 5, 2026
0f984a2
update copyright
mbuckton Jan 5, 2026
f6b117d
fix(parsing): truncated packets
mbuckton Jan 6, 2026
cba2208
fix(framing): Add V2 signature validation and signing
mbuckton Jan 16, 2026
a5ccd3b
add system id context and simple frame based error detection
mbuckton Jan 16, 2026
282deea
Add initial context and spoofing detection for the stream
mbuckton Jan 17, 2026
b9bb3e2
fix tests
mbuckton Jan 17, 2026
0cb46d0
add message name
mbuckton Jan 17, 2026
20a928b
add message name
mbuckton Jan 17, 2026
d24f64b
cleanup and update copytight
mbuckton Jan 17, 2026
6d5890a
additional tests
mbuckton Jan 18, 2026
8dd4944
additional tests
mbuckton Jan 18, 2026
585ac2d
additional tests
mbuckton Jan 31, 2026
a38d3be
feat(parsing): add support for loading dialects from file paths
mbuckton Feb 3, 2026
b7b01e7
Merge remote-tracking branch 'origin/development' into development
mbuckton Feb 3, 2026
503f0d8
fix(codec): improve value handling in `CharFieldCodec` and enhance XM…
mbuckton Apr 25, 2026
9bd4165
test(mavlink): add unit tests for dialect loading and validation
mbuckton Apr 26, 2026
f0e3524
feat(mavlink): add minimal ArduPilot dialect XML for MAVLink integration
mbuckton Apr 26, 2026
bfaaf52
test(mavlink): remove legacy `MavlinkFrameRoundTripAllMessagesTest` a…
mbuckton Apr 26, 2026
085c639
Bump org.apache.maven.plugins:maven-source-plugin from 3.3.1 to 3.4.0
dependabot[bot] May 19, 2026
7ee1d2f
Bump org.apache.maven.plugins:maven-surefire-plugin from 3.5.4 to 3.5.5
dependabot[bot] May 19, 2026
096b777
Bump io.swagger.core.v3:swagger-annotations from 2.2.41 to 2.2.50
dependabot[bot] May 19, 2026
b804832
Bump org.mockito:mockito-junit-jupiter from 5.21.0 to 5.23.0
dependabot[bot] May 19, 2026
e60e7be
Bump org.mockito:mockito-core from 5.21.0 to 5.23.0
dependabot[bot] May 19, 2026
5f652fc
Merge pull request #6 from Maps-Messaging/dependabot/maven/developmen…
mbuckton May 19, 2026
80362e1
Merge pull request #5 from Maps-Messaging/dependabot/maven/developmen…
mbuckton May 19, 2026
037b026
Merge pull request #4 from Maps-Messaging/dependabot/maven/developmen…
mbuckton May 19, 2026
5031033
Merge pull request #3 from Maps-Messaging/dependabot/maven/developmen…
mbuckton May 19, 2026
56352ab
Merge pull request #2 from Maps-Messaging/dependabot/maven/developmen…
mbuckton May 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 54 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,34 +1,41 @@
# MAVLink Payload Codec (Java)

This repository provides a lightweight, schema-driven MAVLink payload
This repository provides a lightweight, schema-driven MAVLink **payload**
encoder and decoder for Java.

It parses MAVLink XML dialects (including `<include>` handling), compiles
message definitions, and provides fast, thread-safe encoding and decoding
of MAVLink message **payloads**.
It parses MAVLink XML dialects (including `{@code <include>}` handling),
compiles message definitions, and provides fast, thread-safe encoding and
decoding of MAVLink message **payloads only**.

This library is intentionally limited in scope.
This library is intentionally limited in scope and designed to be composed
into larger MAVLink-capable systems.

---

## Scope (Important)
## Scope (Read This First)

This library operates **only on MAVLink message payloads**.

It does **not**:
It **does**:

- Parse or generate MAVLink v1/v2 frames
- Handle headers, CRCs, signing, or message framing
- Manage system IDs, component IDs, or sequence numbers
- Implement any transport (UART, UDP, TCP, etc.)
- Parse MAVLink XML dialects
- Compile message definitions into an immutable registry
- Encode Java field maps into MAVLink payload bytes
- Decode MAVLink payload bytes into typed field maps
- Provides ability to sign and validate V2 messages
- Correctly handle MAVLink field ordering, arrays, enums, and extensions
- Creates JsonSchemas to match the fields that it parses to and from

If you need a full MAVLink stack, this library is not it.
It **does not**:

This library is designed to sit **under** a framing and transport layer.
- Manage system IDs, component IDs, or sequence numbers
- Implement any transport (UART, UDP, TCP, etc.)

---

## Features

- MAVLink XML dialect parsing with `<include>` support
- MAVLink XML dialect parsing with `{@code <include>}` support
- Canonical dialect compilation (e.g. `common.xml`)
- Immutable, thread-safe compiled registries
- Correct MAVLink field ordering and packing rules
Expand All @@ -38,6 +45,7 @@ This library is designed to sit **under** a framing and transport layer.
- Safe failure on invalid or truncated payloads
- No shared mutable state at runtime

---

## Getting Started

Expand All @@ -51,6 +59,9 @@ MavlinkCodec codec =
loader.getDialectOrThrow("common");
```

The built-in `common` dialect is loaded eagerly and is always available.

---

## Encoding a Payload

Expand All @@ -70,6 +81,7 @@ Map<String, Object> values = Map.of(
byte[] payload = codec.encodePayload(1, values);
```

---

## Decoding a Payload

Expand All @@ -80,6 +92,10 @@ Map<String, Object> decoded =
int load = (int) decoded.get("load");
```

Field values are returned using standard Java types based on the MAVLink
message definition.

---

## Loading Custom Dialects

Expand All @@ -92,8 +108,11 @@ try (InputStream xml = Files.newInputStream(Path.of("custom.xml"))) {
}
```

Dialects are compiled once and cached by name.
- Dialects are compiled once
- Compiled codecs are cached by name
- Subsequent lookups are lock-free

---

## Thread Safety

Expand All @@ -107,16 +126,20 @@ A single codec instance may be safely:
- Cached globally
- Used in high-throughput systems

---

## Error Handling

- Unknown message IDs result in `IOException`
- Invalid field types result in `IOException`
- Invalid enum values result in `IOException`
- Truncated or malformed payloads result in `IOException`
All parsing and encoding errors are reported explicitly:

No unchecked exceptions escape parsing paths.
- Unknown message IDs → `IOException`
- Invalid field types → `IOException`
- Invalid enum values → `IOException`
- Truncated or malformed payloads → `IOException`

No unchecked exceptions escape payload encode/decode paths.

---

## Design Philosophy

Expand All @@ -126,13 +149,7 @@ No unchecked exceptions escape parsing paths.
- Explicit errors over silent failure
- Practical MAVLink usage, not theoretical completeness


## License

Apache License 2.0

See the `LICENSE` file for details.

---

## Intended Users

Expand All @@ -143,5 +160,14 @@ This library is intended for:
- Telemetry ingestion pipelines
- MAVLink-aware tooling that does not want transport coupling

If you want a full MAVLink stack, use something else.
If you want a full MAVLink stack, use something else.
If you want a clean, deterministic payload codec, this is it.

---

## License

Apache License 2.0

See the `LICENSE` file for details.

34 changes: 31 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>io.mapsmessaging</groupId>
<artifactId>mavlink</artifactId>
<version>1.0.0</version>
<version>1.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>Mavlink parser, message formatter and schema</name>
Expand Down Expand Up @@ -229,7 +229,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.3.1</version>
<version>3.4.0</version>
<executions>
<execution>
<id>attach-sources</id>
Expand Down Expand Up @@ -281,7 +281,7 @@
</includes>
</configuration>
<groupId>org.apache.maven.plugins</groupId>
<version>3.5.4</version>
<version>3.5.5</version>
</plugin>

<!-- sBOM generator -->
Expand Down Expand Up @@ -370,6 +370,14 @@
<version>2.13.2</version>
</dependency>

<!-- Source: https://mvnrepository.com/artifact/io.swagger.core.v3/swagger-annotations -->
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-annotations</artifactId>
<version>2.2.50</version>
<scope>compile</scope>
</dependency>

<!-- Junit5 -->
<dependency>
<artifactId>junit-jupiter-engine</artifactId>
Expand All @@ -378,6 +386,26 @@
<version>6.0.1</version>
</dependency>

<dependency>
<groupId>com.networknt</groupId>
<artifactId>json-schema-validator</artifactId>
<version>1.5.1</version>
<scope>test</scope>
</dependency>
<!-- Source: https://mvnrepository.com/artifact/org.mockito/mockito-junit-jupiter -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>5.23.0</version>
<scope>test</scope>
</dependency>
<!-- Source: https://mvnrepository.com/artifact/org.mockito/mockito-core -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.23.0</version>
<scope>test</scope>
</dependency>
</dependencies>

</project>
102 changes: 102 additions & 0 deletions src/main/java/io/mapsmessaging/mavlink/MavlinkEventFactory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* Copyright [ 2020 - 2024 ] Matthew Buckton
* Copyright [ 2024 - 2026 ] MapsMessaging B.V.
*
* Licensed under the Apache License, Version 2.0 with the Commons Clause
* (the "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
* https://commonsclause.com/
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*
*/

package io.mapsmessaging.mavlink;

import io.mapsmessaging.mavlink.codec.MavlinkCodec;
import io.mapsmessaging.mavlink.codec.MavlinkFrameCodec;
import io.mapsmessaging.mavlink.context.Detection;
import io.mapsmessaging.mavlink.context.FrameFailureReason;
import io.mapsmessaging.mavlink.message.CompiledMessage;
import io.mapsmessaging.mavlink.message.Frame;
import org.xml.sax.SAXException;

import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.Optional;

public class MavlinkEventFactory {

private MavlinkFrameCodec frameCodec;
private SystemContextManager systemContextManager;

public MavlinkEventFactory() throws IOException {
this("common");
}

public MavlinkEventFactory(String dialectName) throws IOException {
Optional<MavlinkCodec> codec = MavlinkMessageFormatLoader.getInstance().getDialect(dialectName);
if (codec.isPresent()) {
MavlinkCodec codecInstance = codec.get();
frameCodec = new MavlinkFrameCodec(codecInstance);
systemContextManager = new SystemContextManager();
}
else{
throw new IOException("Mavlink "+dialectName+" codec not found");
}
}


public MavlinkEventFactory(Path dialectPath) throws IOException, ParserConfigurationException, SAXException {
MavlinkCodec codec = MavlinkMessageFormatLoader.getInstance().loadDialect(dialectPath);
frameCodec = new MavlinkFrameCodec(codec);
systemContextManager = new SystemContextManager();
}

public MavlinkEventFactory(MavlinkFrameCodec frameCodec, SystemContextManager systemContextManager){
this.frameCodec = frameCodec;
this.systemContextManager = systemContextManager;
}

public Optional<ProcessedFrame> unpack(String streamName, ByteBuffer payload) throws IOException {
long timestamp = System.nanoTime();

Optional<Frame> frameOptional = frameCodec.tryUnpackFrame(payload);
if (frameOptional.isEmpty()) {
return Optional.empty();
}
Frame frame = frameOptional.get();
FrameFailureReason failureReason = frame.getValidated();
Map<String, Object> fields = frameCodec.parsePayload(frame);
String name = "";
CompiledMessage message = frameCodec.getRegistry().getCompiledMessagesById().get(frame.getMessageId());
if(message != null){
name = message.getName();
}
if (failureReason == FrameFailureReason.OK || failureReason == FrameFailureReason.UNSIGNED) {
List<Detection> detectionList = systemContextManager.onValidatedFrame(frame, streamName, timestamp);
return Optional.of(new ProcessedFrame(name, frame, fields, true, detectionList));
}
List<Detection> detectionList = systemContextManager.onInvalidFrame(
frame.getSystemId(),
streamName,
timestamp,
failureReason
);
return Optional.of(new ProcessedFrame(name, frame, Map.of(), false, detectionList));
}



}
40 changes: 40 additions & 0 deletions src/main/java/io/mapsmessaging/mavlink/MavlinkFrameEnvelope.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright [ 2020 - 2024 ] Matthew Buckton
* Copyright [ 2024 - 2026 ] MapsMessaging B.V.
*
* Licensed under the Apache License, Version 2.0 with the Commons Clause
* (the "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
* https://commonsclause.com/
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*
*/

package io.mapsmessaging.mavlink;

import io.mapsmessaging.mavlink.message.Version;
import lombok.Value;

@Value
public class MavlinkFrameEnvelope {

Version version;
int messageId;

int systemId;
int componentId;
int sequence;

int payloadLength;
byte[] payload;

boolean signed;
}
Loading