This is a Java library and API for invoking J2735 (2024) encode/decode functions on C code generated by asn1c using the Foreign Function and Memory API in Java 25, and a Java wrapper generated by the Jextract tool.
It includes the same complete implementation of J2735 (2024) as the USDOT asn1_codec C++ project, now easily accessible from Java on both Linux and Windows.
It enables converting between these ASN.1 encodings:
- XER - XML Encoding Rules
- JER - JSON Encoding Rules
- UPER - Unaligned Packed Encoding Rules
- OER - Octet Encoding Rules
The library is available in Maven Central. The repository includes both the Java JAR and native libraries for Linux (x86_64 and aarch64) and Windows (x86_64). Consuming the native libraries from Maven Central requires some extra configuration for clients. To use the library from a Gradle project, add a configurations section to build.gradle:
configurations {
nativeLibraryArtifact_windows_x86_64
nativeLibraryArtifact_linux_x86_64
nativeLibraryArtifact_linux_aarch64
}Add the Java dependency to the dependencies section in the normal way, and also include the native library dependencies as shown:
dependencies {
...
implementation 'com.neaeraconsulting:j2735-2024-ffm-lib:3.0.0-beta6'
nativeLibraryArtifact_windows_x86_64 (
group: 'com.neaeraconsulting',
name: 'j2735-2024-ffm-lib',
version: '3.0.0-beta6',
classifier: 'windows-x86_64',
ext: 'dll',
transitive: false
)
nativeLibraryArtifact_linux_x86_64 (
group: 'com.neaeraconsulting',
name: 'j2735-2024-ffm-lib',
version: '3.0.0-beta6',
classifier: 'linux-x86_64',
ext: 'so',
transitive: false
)
nativeLibraryArtifact_linux_aarch64 (
group: 'com.neaeraconsulting',
name: 'j2735-2024-ffm-lib',
version: '3.0.0-beta6',
classifier: 'linux-aarch64',
ext: 'so',
transitive: false
)
}Note the "classifier", "ext", and "transitive: false" tags.
Register tasks to copy the native libraries to the build/libs directory of the client:
def buildDir = project.layout.buildDirectory.get()
// Copy the native library to the build directory - Windows
tasks.register('copyNativeLibrary_windows_x86_64', Copy) {
from configurations.nativeLibraryArtifact_windows_x86_64
into "$buildDir/libs"
rename { libFileName -> 'asnapplication.dll' }
}
// Copy the native library to the build directory - Linux
tasks.register('copyNativeLibrary_linux_x86_64', Copy) {
from configurations.nativeLibraryArtifact_linux_x86_64
into "$buildDir/libs"
rename { libFileName -> 'libasnapplication.so' }
}
// Copy the native library to the build directory - Linux ARM64
tasks.register('copyNativeLibrary_linux_aarch64', Copy) {
from configurations.nativeLibraryArtifact_linux_aarch64
into "$buildDir/libs"
rename { libFileName -> 'libasnapplication-arm64.so' }
}
tasks.register('copyDependencies', Copy) {
from configurations.runtimeClasspath
into "$buildDir/libs"
}Add the copy tasks to the build task:
build {
dependsOn copyNativeLibrary_linux_x86_64
dependsOn copyNativeLibrary_linux_aarch64
dependsOn copyNativeLibrary_windows_x86_64
finalizedBy copyDependencies
}Finally, add the native access flag to the client JAR's MANIFEST:
jar {
manifest {
attributes (
'Main-Class': 'org.example.Main',
'Class-Path': configurations.runtimeClasspath.collect { it.getName() }.join(' '),
'Enable-Native-Access': 'ALL-UNNAMED'
)
}
}to prevent native access warnings when running the client.
Instructions for consuming from a Maven POM are similar, writeup TBD.
The j2735-2024-ffm-lib project is a Java library that exposes three codec classes:
GeneralCodec- Methods to interconvert any PDU within the J2735, IEEE 1609.2, or SEMI specifications between XER, JER, UPER, and OER, including batch conversion of lists of messages.MessageFrameCodec- Convenience methods for converting J2735 Message Frames between UPER and XER or JER.Ieee1609Dot2DataCodec- Convenience methods for converting IEEE 1609.2 Data between OER and XER or JER.
Each codec has two constructors:
(textBufferSize, binaryBufferSize, errorBufferSize, libraryPath)- Loads the native library from the given path.(textBufferSize, binaryBufferSize, errorBufferSize)- Locates the native library for the current OS and architecture relative to the working directory, checking./<os>-<arch>/(e.g../linux-arm64/) and then./. The expected file names areasnapplication.dllon Windows,libasnapplication.soon Linux x86_64, andlibasnapplication-arm64.soon Linux aarch64.
// Auto-detect the native library in the working directory
GeneralCodec codec = new GeneralCodec(262144L, 8192L, 512L);The LibraryDetector class exposes the same lookup (findLibrary, findLibraryFromResource, getLibraryFilename) for callers who want to resolve the path themselves.
Converts an XER encoded MessageFrame to UPER
- xer - The XER encoded MessageFrame
- returns Byte array with the UPER encoding
Convert an UPER encoded MessageFrame to XER
- uper - The UPER encoded MessageFrame
- Returns the XER encoded result
Converts a JER encoded MessageFrame to UPER
- jer - The JER encoded MessageFrame
- returns Byte array with the UPER encoding
Convert an UPER encoded MessageFrame to JER
- uper - The UPER encoded MessageFrame
- returns The JER encoded result
Convert an XER encoded Ieee1609Dot2Data to OER
- xer - The XER encoded Ieee1609Dot2Data
- returns Byte array with the OER encoding
Convert an OER encoded Ieee1609Dot2Data to XER
- oer - The OER encoded Ieee1609Dot2Data
- returns XER encoded result
Convert a JER encoded Ieee1609Dot2Data to OER
- jer - The JER encoded Ieee1609Dot2Data
- returns Byte array with the OER encoding
Convert an OER encoded Ieee1609Dot2Data to JER
- oer - The OER encoded Ieee1609Dot2Data
- returns JER encoded result
byte[] convertGeneral(byte[] inputBytes, String pdu, AsnEncoding fromEncoding, AsnEncoding toEncoding)
General purpose conversion function that can convert any PDU to or from any encoding
- inputBytes - Input byte array: XER or JER text as UTF-8 bytes, or UPER or OER binary
- pdu - The Protocol Data Unit, e.g. "MessageFrame", "Ieee1609Dot2Data", "VehicleEventFlags", etc.
- fromEncoding - Input encoding, one of
XER,JER,UPER, orOER - toEncoding - Output encoding, one of
XER,JER,UPER, orOER - returns The encoded message as bytes (XER and JER results should be converted to a UTF-8 string by the caller)
List<byte[]> convertBatch(List<byte[]> inputBytesList, String pdu, AsnEncoding fromEncoding, AsnEncoding toEncoding)
Batch conversion of a list of messages of the same PDU and encodings, reusing the input and output buffers for efficiency. Includes constraint check.
- inputBytesList - List of encoded messages
- pdu - The PDU to convert
- fromEncoding - The input encoding:
XER,JER,OER, orUPER - toEncoding - The output encoding:
XER,JER,OER, orUPER - returns List of converted messages. Any input message that fails to convert is logged and omitted from the result rather than aborting the batch.
List<byte[]> convertBatch(List<byte[]> inputBytesList, String pdu, AsnEncoding fromEncoding, AsnEncoding toEncoding, boolean checkConstraints)
Batch conversion as above, with the option to skip the constraint check
- inputBytesList - List of encoded messages
- pdu - The PDU to convert
- fromEncoding - The input encoding:
XER,JER,OER, orUPER - toEncoding - The output encoding:
XER,JER,OER, orUPER - checkConstraints - Whether to check constraints
- returns List of converted messages. Any input message that fails to convert is logged and omitted from the result rather than aborting the batch.
Converts an XER encoded PDU to UPER
- pdu - The Protocol Data Unit, e.g. "MessageFrame"
- xer - The XER encoded PDU
- returns Byte array with the UPER encoding
Convert a UPER encoded PDU to XER
- pdu - The Protocol Data Unit, e.g. "MessageFrame"
- uper - The UPER encoded PDU
- returns The XER encoded result
Converts a JER encoded PDU to UPER
- pdu - The Protocol Data Unit, e.g. "MessageFrame"
- jer - The JER encoded PDU
- returns Byte array with the UPER encoding
Convert a UPER encoded PDU to JER
- pdu - The Protocol Data Unit, e.g. "MessageFrame"
- uper - The UPER encoded PDU
- returns The JER encoded result
Convert an XER encoded PDU to OER
- pdu - The Protocol Data Unit, e.g. "Ieee1609Dot2Data"
- xer - The XER encoded PDU
- returns Byte array with the OER encoding
Convert an OER encoded PDU to XER
- pdu - The Protocol Data Unit, e.g. "Ieee1609Dot2Data"
- oer - The OER encoded PDU
- returns The XER encoded result
Convert a JER encoded PDU to OER
- pdu - The Protocol Data Unit, e.g. "Ieee1609Dot2Data"
- jer - The JER encoded PDU
- returns Byte array with the OER encoding
Convert an OER encoded PDU to JER
- pdu - The Protocol Data Unit, e.g. "Ieee1609Dot2Data"
- oer - The OER encoded PDU
- returns The JER encoded result
// Initialize the library
MessageFrameCodec codec;
String libResource = System.getProperty("os.name").toLowerCase().contains("win")
? "C:/Users/spongebob/app/asnapplication.dll" // Use windows library
: "/home/app/libasnapplication.so"; // Use Linux library
Path libPath = Paths.get(libResource);
codec = new MessageFrameCodec(262144L, 8192L, 512L, libPath);
// Convert XER to UPER
byte[] uper = codec.xerToUper("<MessageFrame><messageId>19</messageId><value><SPAT><intersections><IntersectionState><id><id>12111</id></id><revision>0</revision><status>0000000000000000</status><timeStamp>35176</timeStamp><states><MovementState><signalGroup>2</signalGroup><state-time-speed><MovementEvent><eventState><protected-Movement-Allowed/></eventState><timing><minEndTime>22120</minEndTime><maxEndTime>22121</maxEndTime></timing></MovementEvent></state-time-speed></MovementState><MovementState><signalGroup>4</signalGroup><state-time-speed><MovementEvent><eventState><stop-And-Remain/></eventState><timing><minEndTime>22181</minEndTime><maxEndTime>22181</maxEndTime></timing></MovementEvent></state-time-speed></MovementState><MovementState><signalGroup>6</signalGroup><state-time-speed><MovementEvent><eventState><protected-Movement-Allowed/></eventState><timing><minEndTime>22120</minEndTime><maxEndTime>22121</maxEndTime></timing></MovementEvent></state-time-speed></MovementState><MovementState><signalGroup>8</signalGroup><state-time-speed><MovementEvent><eventState><stop-And-Remain/></eventState><timing><minEndTime>21852</minEndTime><maxEndTime>21852</maxEndTime></timing></MovementEvent></state-time-speed></MovementState><MovementState><signalGroup>1</signalGroup><state-time-speed><MovementEvent><eventState><stop-And-Remain/></eventState><timing><minEndTime>21852</minEndTime><maxEndTime>21852</maxEndTime></timing></MovementEvent></state-time-speed></MovementState><MovementState><signalGroup>5</signalGroup><state-time-speed><MovementEvent><eventState><stop-And-Remain/></eventState><timing><minEndTime>21852</minEndTime><maxEndTime>21852</maxEndTime></timing></MovementEvent></state-time-speed></MovementState></states></IntersectionState></intersections></SPAT></value></MessageFrame>");
// Convert UPER to XER
String xer = codec.uperToXer(HexFormat.of().parseHex("001338000817a780000089680500204642b342b34802021a15a955a940181190acd0acd20100868555c555c00104342aae2aae002821a155715570"));
// Convert UPER to JER
String jer = codec.uperToJer(HexFormat.of().parseHex("001338000817a780000089680500204642b342b34802021a15a955a940181190acd0acd20100868555c555c00104342aae2aae002821a155715570"));
// Convert JER to UPER
byte[] uperFromJer = codec.jerToUper(jer);// Initialize the general codec
GeneralCodec generalCodec = new GeneralCodec(262144L, 8192L, 512L, libPath);
// Convert a list of UPER encoded MessageFrames to JER, reusing the native buffers
List<byte[]> uperList = List.of(
HexFormat.of().parseHex("001338000817a780000089680500204642b342b34802021a15a955a940181190acd0acd20100868555c555c00104342aae2aae002821a155715570"),
uperFromJer);
List<byte[]> jerList = generalCodec.convertBatch(uperList, "MessageFrame", AsnEncoding.UPER, AsnEncoding.JER);
// JER results are UTF-8 text
for (byte[] jerBytes : jerList) {
System.out.println(new String(jerBytes, StandardCharsets.UTF_8));
}convert-v2x is a native command line tool that calls the same C API as the Java library. It is useful for bulk conversion of files and for testing and debugging the native code without Java. Prebuilt binaries are in the lib folder (and also in j2735-2024-ffm-lib/lib):
| Platform | Executable | Native library it needs in the same folder |
|---|---|---|
| Linux x86_64 | convert-v2x |
libasnapplication.so |
| Linux aarch64 | convert-v2x-arm64 |
libasnapplication.so (a copy or rename of libasnapplication-arm64.so) |
| Windows x86_64 | convert-v2x.exe |
asnapplication.dll |
Usage:
convert-v2x <from-encoding> <to-encoding> <PDU>
- from-encoding, to-encoding - one of
uper,oer,xer(canonical XER), orjer(minified JER) - PDU - The Protocol Data Unit, e.g.
MessageFrame,SPAT,Ieee1609Dot2Data
Behavior:
- Reads one record per line from stdin and writes one converted line per input line to stdout, in order. Trailing CR/LF is stripped, so Windows line endings are fine.
- UPER and OER input and output are hex strings: an even number of hex digits with no whitespace or
0xprefix. XER and JER records must each be on a single line. - Constraints are always checked.
- If a line fails to convert, it prints an empty output line and logs the reason to stderr. The remaining lines are still processed.
- Input lines and output records are limited to 1 MiB each. Longer lines fail.
- Exit code is
0if every line converted,1if any line failed, and64if arguments are missing (the usage text is printed).
Linux examples:
# Convert a file of hex encoded UPER MessageFrames (one per line) to JER
cat data.hex | ./convert-v2x uper jer MessageFrame > data.jsonl
# Convert a file of SPATs with no MessageFrame (one per line) from canonical XER to JER
cat data.xml | ./convert-v2x xer jer SPAT > data.jsonlWindows PowerShell examples:
# Convert 1609.2 Data from XER to OER hex
Get-Content example.xml | .\convert-v2x.exe xer oer Ieee1609Dot2Data > example.hex
# Convert 1609.2 Data from OER hex to XER
Get-Content example.hex | .\convert-v2x.exe oer xer Ieee1609Dot2Data > example.xmlThe tool is built by CMake together with the native library (see Rebuilding the libraries), and has black-box tests (see Unit Tests).
A native, dynamic library is generated from asn1c generated C code: libasnapplication.so for Linux x86_64, libasnapplication-arm64.so for Linux aarch64, and asnapplication.dll for Windows x86_64. The library is installed on the same image as the Java API.
The Java library, j2735-2024-ffm-lib, includes code generated by the Jextract tool.
Native method calls and native data structures are handled inside a Java thread-scoped "Arena".
Arenas are FFM's way of limiting the scope and lifetime of memory used by native methods, to prevent memory leaks and security issues.
See: docs.oracle.com: Memory Segments and Arenas
Java FFM bindings are generated by running the Jextract tool on the src/convert.h header file.
Prerequisites:
- Java 25 is required to build and use version 3.x of the library.
- The build scripts require Docker.
- Windows Pro 11 with Docker Desktop is required to run the Windows build scripts.
To get started check out the repository with submodules
git clone --recurse-submodules https://github.com/neaeraconsulting/j2735-ffm-java.gitThe native library for Linux and the jextract bindings can be regenerated using Docker Compose. The build process pulls in the generated C files from asn1_codec, compiles them with the C API in the src folder, and generates the Java bindings.
For linux/amd64 (x86_64):
docker compose -f docker-compose-build.yml up --build -dFor linux/arm64 (aarch64):
docker compose -f docker-compose-build-arm64.yml up --build -dThe ARM64 Compose file sets platform: linux/arm64, so it works on an ARM64 host and on an x86 host when Docker Desktop or Docker Engine has ARM64 emulation available. It builds and runs the ARM64 container and writes the artifacts to the host folders shown in the Compose file.
The unzipped C files are copied to the generated-files folder. They are not persisted to the repo since they are identical to the files from asn1_codec, but it can be useful to have the unzipped files for debugging the src/convert.h API in an IDE.
The compiled shared library and the convert-v2x CLI are copied to the lib folder (libasnapplication.so and convert-v2x for x86_64, libasnapplication-arm64.so and convert-v2x-arm64 for ARM64).
The Java source code for Linux from jextract is copied to the generated-jextract folder.
The Windows images can only be built on a Windows machine. To rebuild the Windows DLL, switch Docker Desktop to Windows Containers, and run:
docker compose -f docker-compose-build-windows.yml up --build -dThe build is an optimized Release build by default. For an unoptimized build with PDB debug symbols, suitable for debugging in WinDbg or Visual Studio, set BUILD_TYPE first:
$env:BUILD_TYPE="Debug"
docker compose -f docker-compose-build-windows.yml up --build -dThe DLL and convert-v2x.exe (plus .pdb files for a Debug build) will be copied to the /lib folder, and Windows-specific jextract Java code is copied to /generated-jextract-windows. Copy the generated code to:
- folder:
/j2735-2024-ffm-lib/src/main/java/generated- linux code in subfolder:
/linux - windows code in subfolder:
/windows
- linux code in subfolder:
Edit both copies of convert_h.java. Replace this generated code:
static final SymbolLookup SYMBOL_LOOKUP = SymbolLookup.libraryLookup(System.mapLibraryName("asnapplication"), LIBRARY_ARENA)
.or(SymbolLookup.loaderLookup())
.or(Linker.nativeLinker().defaultLookup());with this:
// Manual project customization: MessageFrameCodec supplies the lookup for
// the caller-selected native library path before invoking a downcall.
public static SymbolLookup SYMBOL_LOOKUP;to facilitate the method this library uses for loading the library, instead of the default lookup code.
After regenerating the native libraries to the lib folder, also be sure to copy them to j2735-2024-ffm-lib/lib and to the j2735-2024-ffm-lib/src/test/resources/j2735ffm folder since they are required for publication and the unit tests in that Java project via:
cd lib
cp libasnapplication.so ../j2735-2024-ffm-lib/lib/
cp libasnapplication-arm64.so ../j2735-2024-ffm-lib/lib/
cp asnapplication.dll ../j2735-2024-ffm-lib/lib/
cp libasnapplication.so ../j2735-2024-ffm-lib/src/test/resources/j2735ffm/
cp libasnapplication-arm64.so ../j2735-2024-ffm-lib/src/test/resources/j2735ffm/
cp asnapplication.dll ../j2735-2024-ffm-lib/src/test/resources/j2735ffm/Also copy the CLI executables to j2735-2024-ffm-lib/lib:
cp convert-v2x ../j2735-2024-ffm-lib/lib/
cp convert-v2x-arm64 ../j2735-2024-ffm-lib/lib/
cp convert-v2x.exe ../j2735-2024-ffm-lib/lib/and make the linux tools executable in git:
cd ../j2735-2024-ffm-lib/lib
git update-index --chmod=+x convert-v2x
git update-index --chmod=+x convert-v2x-arm64The unit tests can be run in either Linux or Windows.
To test the library:
cd j2735-2024-ffm-lib
./gradlew clean build testThe convert-v2x CLI has black-box tests in test/cli that pipe the data files through the binary and check stdout, stderr and the exit code. Both Docker builds run them with ctest right after compiling, so a failing test fails the build. To run them in a local CMake build directory:
cmake . && cmake --build . && ctest --output-on-failureRequires:
- Docker
Run docker-compose from the root directory:
docker compose -f docker-compose-api.yml up --build -dUse the *.http scripts in the /j2735-2024-api/http-tests directory to test the API. These scripts work with
IntelliJ IDE, or in VSCode with the REST Client extension.
The following translation methods are available at base URL http://localhost:4000. All methods are POSTs.
| Method | Description |
|---|---|
| /uper/bin/xer | UPER binary to XER |
| /uper/hex/xer | UPER hex string to XER |
| /xer/uper/bin | XER to UPER binary |
| /xer/uper/hex | XER to UPER hex string |
| /xer/oer/hex | XER to OER hex string |
| /oer/hex/xer | OER hex string to XER |
| /jer/uper/hex | JER to UPER hex string |
| /uper/hex/jer | UPER hex string to JER |
| /jer/oer/hex | JER to OER hex string |
| /oer/hex/jer | OER hex string to JER |
| /jer/uper/hex/{pdu} | JER to UPER hex string for any PDU |
| /uper/hex/jer/{pdu} | UPER hex string to JER for any PDU |
| /batch/{from}/{to}/{pdu} | Batch convert line-delimited messages of any PDU |
Content types are:
| Encoding | Content Type |
|---|---|
| UPER binary | application/octet-stream |
| UPER hex | text/plain |
| XER | application/xml |
| JER | application/json |
| OER hex | text/plain |
| Batch | text/plain |
The /batch/{from}/{to}/{pdu} method converts a line-delimited file of messages, one message per line.
- from - The input encoding:
xer,jer,uper, oroer - to - The output encoding:
xer,jer,uper, oroer - pdu - The Protocol Data Unit, e.g. "MessageFrame" or "Ieee1609Dot2Data"
UPER and OER messages are hex strings. XER and JER messages must each be on a single line. Blank input lines are skipped. Each output line corresponds to one input line. If a message fails to convert, the output contains an empty line in its place. Every output line, including the last, ends with \n. An unsupported encoding returns HTTP 400.
Example:
POST http://localhost:4000/batch/uper/jer/MessageFrame
Content-Type: text/plain
< ./messages_uper.txtSee /j2735-2024-api/http-tests/batch.http for more examples.
Swagger UI documentation for the Demo REST API is available at:
http://localhost:4000/swagger-ui.html
The OpenAPI specification is available in JSON format at:
http://localhost:4000/api-docs
or in YAML format at:
http://localhost:4000/api-docs.yaml
Testing the native exe with WinDbg (requires a BUILD_TYPE=Debug Windows build so the .pdb files are present in lib). Replace <repo> with the path to your checkout:
cmd /c 'cd /d <repo>\lib && windbgx -y "<repo>\lib" -srcpath "<repo>\src;<repo>\generated-files\2024" convert-v2x.exe xer oer Ieee1609Dot2Data < example.xml'