Skip to content

About

Library for calling j2735 c code generated with asn1c from Java, implemented using Java 25 FFM/jextract. Available in Maven Central.

Resources

Stars

0 stars

Watchers

1 watching

Forks

Latest commit

 

History

105 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

J2735-ffm-java

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

Using the Library

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.

Usage

Java Library JAR

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 are asnapplication.dll on Windows, libasnapplication.so on Linux x86_64, and libasnapplication-arm64.so on 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.

MessageFrameCodec Methods include:

byte[] xerToUper(String xer)

Converts an XER encoded MessageFrame to UPER

  • xer - The XER encoded MessageFrame
  • returns Byte array with the UPER encoding

String uperToXer(byte[] uper)

Convert an UPER encoded MessageFrame to XER

  • uper - The UPER encoded MessageFrame
  • Returns the XER encoded result

byte[] jerToUper(String jer)

Converts a JER encoded MessageFrame to UPER

  • jer - The JER encoded MessageFrame
  • returns Byte array with the UPER encoding

String uperToJer(byte[] uper)

Convert an UPER encoded MessageFrame to JER

  • uper - The UPER encoded MessageFrame
  • returns The JER encoded result

Ieee1609Dot2DataCodec methods include:

byte[] xerToOer(String xer)

Convert an XER encoded Ieee1609Dot2Data to OER

  • xer - The XER encoded Ieee1609Dot2Data
  • returns Byte array with the OER encoding

String oerToXer(byte[] oer)

Convert an OER encoded Ieee1609Dot2Data to XER

  • oer - The OER encoded Ieee1609Dot2Data
  • returns XER encoded result

byte[] jerToOer(String jer)

Convert a JER encoded Ieee1609Dot2Data to OER

  • jer - The JER encoded Ieee1609Dot2Data
  • returns Byte array with the OER encoding

String oerToJer(byte[] oer)

Convert an OER encoded Ieee1609Dot2Data to JER

  • oer - The OER encoded Ieee1609Dot2Data
  • returns JER encoded result

GeneralCodec methods include:

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, or OER
  • toEncoding - Output encoding, one of XER, JER, UPER, or OER
  • 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, or UPER
  • toEncoding - The output encoding: XER, JER, OER, or UPER
  • 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, or UPER
  • toEncoding - The output encoding: XER, JER, OER, or UPER
  • 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.

byte[] xerToUper(String pdu, String xer)

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

String uperToXer(String pdu, byte[] uper)

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

byte[] jerToUper(String pdu, String jer)

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

String uperToJer(String pdu, byte[] uper)

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

byte[] xerToOer(String pdu, String xer)

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

String oerToXer(String pdu, byte[] oer)

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

byte[] jerToOer(String pdu, String jer)

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

String oerToJer(String pdu, byte[] oer)

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

Usage example

// 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);

Batch conversion example

// 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));
}

Command Line Tool (convert-v2x)

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), or jer (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 0x prefix. 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 0 if every line converted, 1 if any line failed, and 64 if 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.jsonl

Windows 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.xml

The tool is built by CMake together with the native library (see Rebuilding the libraries), and has black-box tests (see Unit Tests).

How it works

Native library

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.

Java library

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.

Development

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.git

Rebuilding the libraries and Java bindings

The 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.

Building for a Single Architecture

For linux/amd64 (x86_64):

docker compose -f docker-compose-build.yml up --build -d

For linux/arm64 (aarch64):

docker compose -f docker-compose-build-arm64.yml up --build -d

The 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 -d

The 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 -d

The 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

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-arm64

Unit Tests

The unit tests can be run in either Linux or Windows.

To test the library:

cd j2735-2024-ffm-lib
./gradlew clean build test

The 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-failure

Demo API and Test Scripts

Requires:

  • Docker

Run docker-compose from the root directory:

docker compose -f docker-compose-api.yml up --build -d

Use 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

Batch conversion

The /batch/{from}/{to}/{pdu} method converts a line-delimited file of messages, one message per line.

  • from - The input encoding: xer, jer, uper, or oer
  • to - The output encoding: xer, jer, uper, or oer
  • 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.txt

See /j2735-2024-api/http-tests/batch.http for more examples.

Demo API Open API Documentation

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

Notes

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'

About

Library for calling j2735 c code generated with asn1c from Java, implemented using Java 25 FFM/jextract. Available in Maven Central.

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages