diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a8631dd..93905a3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,6 +8,7 @@ on: branches: - main - develop + - "**" jobs: test: runs-on: ubuntu-latest @@ -22,7 +23,7 @@ jobs: - name: Print env run: env | sort - name: Setup Gradle - uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2 + uses: gradle/actions/setup-gradle@v5 with: gradle-version: '8.13' - name: Test Library diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index dc77d2f..0ea9004 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -18,7 +18,7 @@ jobs: - name: Print env run: env | sort - name: Setup Gradle - uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2 + uses: gradle/actions/setup-gradle@v5 with: gradle-version: '8.13' - name: Publish package diff --git a/.gitignore b/.gitignore index 17fa201..62cdf3b 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ build .idea/ bin/ +**/bin/ out.log diff --git a/Dockerfile-build b/Dockerfile-build index e2dc401..badbbea 100644 --- a/Dockerfile-build +++ b/Dockerfile-build @@ -26,7 +26,6 @@ WORKDIR /build ADD ./asn1_codec/asn1c_combined/generated-files/2024.tar.gz /build ADD ./CMakeLists.txt /build -ADD ./run-lib.sh /build COPY ./src /build/src/ ENV CC=/usr/bin/clang diff --git a/README.md b/README.md index a7cdbfc..001ace3 100644 --- a/README.md +++ b/README.md @@ -61,8 +61,7 @@ A native, dynamic library, `libasnapplication.so`, is generated from asn1c gener The Java library, `j2735-2024-ffm-lib`, includes code generated by the [Jextract tool](https://github.com/openjdk/jextract). 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. +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](https://docs.oracle.com/en/java/javase/22/core/memory-segments-and-arenas.html#GUID-01CE34E8-7BCB-4540-92C4-E127C1F62711) Java FFM bindings are generated by running the Jextract tool on the [src/convert.h](src/convert.h) header file. @@ -99,6 +98,14 @@ The Java source code from jextract is copied to the `generated-jextract` folder. The Windows library doesn't have an automated build process. It can be recreated using Visual Studio 2022 (not VSCode) with the Clang compiler for Windows. Some edits to the generated C files are required to build for Windows. Follow the instructions here: [C codec edits for Windows](generated-files/README.md). And then build via CMake in Visual Studio. The `CMakeSettings.json` file contains the Visual Studio configuration to use CMake with the clang compiler. +After regenerating the native libraries to the `lib` folder, also be sure to copy them to the [j2735-2024-ffm-lib/src/test/resources/j2735ffm](j2735-2024-ffm-lib/src/test/resources/j2735ffm) folder since they are required for the unit tests in that Java project via: + +```bash +cd lib +cp libasnapplication.so ../j2735-2024-ffm-lib/src/test/resources/j2735ffm/ +cp asnapplication.dll ../j2735-2024-ffm-lib/src/test/resources/j2735ffm/ +``` + ## Unit Tests The unit tests can be run in either Linux or Windows. @@ -143,7 +150,19 @@ Content types are: | UPER hex | text/plain | | XER | application/xml | +### 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 diff --git a/generated-files/.gitignore b/generated-files/.gitignore index 8609144..6b17158 100644 --- a/generated-files/.gitignore +++ b/generated-files/.gitignore @@ -1,2 +1,3 @@ *.c -*.h \ No newline at end of file +*.h +2024/* \ No newline at end of file diff --git a/generated-files/README.md b/generated-files/README.md index 83e2b5d..bd42521 100644 --- a/generated-files/README.md +++ b/generated-files/README.md @@ -5,11 +5,15 @@ C files extracted from the zip archive from asn1_codec are extracted here for us Not persisted to version control. To compile for Windows run the script `prepare-for-windows.sh` script to remove files -related to GeneralizedTime, and edit the file `pdu_collection.c` to remove the lines: +related to GeneralizedTime, and edit the file `pdu_collection.c` to comment out the lines: ```c extern struct asn_TYPE_descriptor_s asn_DEF_Period; extern struct asn_TYPE_descriptor_s asn_DEF_AggregatedSingleTariffClassSession; extern struct asn_TYPE_descriptor_s asn_DEF_DetectedChargeObject; + +&asn_DEF_AggregatedSingleTariffClassSession, +&asn_DEF_DetectedChargeObject, +&asn_DEF_Period, ``` diff --git a/j2735-2024-api/build.gradle b/j2735-2024-api/build.gradle index b069618..684bc0d 100644 --- a/j2735-2024-api/build.gradle +++ b/j2735-2024-api/build.gradle @@ -2,7 +2,7 @@ plugins { id 'java' id 'java-library' id "io.freefair.lombok" version "8.10.2" - id 'org.springframework.boot' version '3.3.5' + id 'org.springframework.boot' version '3.5.6' id 'io.spring.dependency-management' version '1.1.6' } @@ -21,6 +21,17 @@ dependencies { testImplementation 'org.junit.jupiter:junit-jupiter:5.10.3' implementation 'commons-io:commons-io:2.18.0' implementation 'org.springframework.boot:spring-boot-starter-web' + + // Springdoc 2.8, requires Spring Boot 3.5 + // Ref: https://springdoc.org/#what-is-the-compatibility-matrix-of-springdoc-openapi-with-spring-boot + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.13' + + // Needed for springdoc to read javadocs + // Ref: https://springdoc.org/#javadoc-support + implementation 'com.github.therapi:therapi-runtime-javadoc:0.15.0' + annotationProcessor 'com.github.therapi:therapi-runtime-javadoc-scribe:0.15.0' + + compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' diff --git a/j2735-2024-api/http-tests/api-docs.http b/j2735-2024-api/http-tests/api-docs.http new file mode 100644 index 0000000..602c67b --- /dev/null +++ b/j2735-2024-api/http-tests/api-docs.http @@ -0,0 +1,8 @@ +### OPEN API Docs in JSON format +GET http://localhost:4000/api-docs + +### OPEN API Docs in YAML format +GET http://localhost:4000/api-docs.yaml + +### Swagger HTML Doce +GET http://localhost:4000/swagger-ui.html \ No newline at end of file diff --git a/j2735-2024-api/src/main/java/j2735api/ApiConfiguration.java b/j2735-2024-api/src/main/java/j2735api/ApiConfiguration.java index eca9491..a0a43e4 100644 --- a/j2735-2024-api/src/main/java/j2735api/ApiConfiguration.java +++ b/j2735-2024-api/src/main/java/j2735api/ApiConfiguration.java @@ -25,6 +25,7 @@ public class ApiConfiguration { long textBufferSize; long uperBufferSize; + long errorBufferSize; String libraryPath; String windowsLibraryPath; } diff --git a/j2735-2024-api/src/main/java/j2735api/ApiController.java b/j2735-2024-api/src/main/java/j2735api/ApiController.java index 7db807e..74c7c4e 100644 --- a/j2735-2024-api/src/main/java/j2735api/ApiController.java +++ b/j2735-2024-api/src/main/java/j2735api/ApiController.java @@ -34,7 +34,7 @@ import static org.springframework.http.MediaType.TEXT_PLAIN_VALUE; /** - * HTTP Methods for converting J2735 MessageFrames between XER, JER and UPER + * HTTP Methods for converting J2735 MessageFrames between XER and UPER * @author Ivan Yourshaw */ @RestController @@ -53,6 +53,12 @@ public String healthCheck() { return "I am in good health, thanks for checking."; } + + /** + * Convert a J2735 MessageFrame in XER format to UPER format + * @param xer The J2735 MessageFrame in XER format + * @return The J2735 MessageFrame in UPER format as a byte array + */ @PostMapping( value = "/xer/uper/bin", consumes = APPLICATION_XML_VALUE, @@ -61,6 +67,13 @@ public byte[] xerToUper(@RequestBody String xer) { return codec.xerToUper(xer); } + + + /** + * Convert a J2735 MessageFrame in XER format to UPER format + * @param xer The J2735 MessageFrame in XER format + * @return The J2735 MessageFrame in UPER format as a Base64 encoded string + */ @PostMapping( value = "/xer/uper/hex", consumes = APPLICATION_XML_VALUE, @@ -74,7 +87,11 @@ public String xerToUperHex(@RequestBody String xer) { - + /** + * Convert a J2735 MessageFrame in UPER format to XER format + * @param request The HttpServletRequest containing the J2735 MessageFrame in UPER format as a byte array + * @return The J2735 MessageFrame in XER format + */ @PostMapping( value = "/uper/bin/xer", consumes = APPLICATION_OCTET_STREAM_VALUE, @@ -90,6 +107,11 @@ public String uperToXer(HttpServletRequest request) { } } + /** + * Convert a J2735 MessageFrame in UPER format to XER format + * @param uperHex The J2735 MessageFrame in UPER format as a Hex encoded string + * @return The J2735 MessageFrame in XER format + */ @PostMapping( value = "/uper/hex/xer", consumes = TEXT_PLAIN_VALUE, diff --git a/j2735-2024-api/src/main/java/j2735api/CodecConfig.java b/j2735-2024-api/src/main/java/j2735api/CodecConfig.java index 8377699..82e5457 100644 --- a/j2735-2024-api/src/main/java/j2735api/CodecConfig.java +++ b/j2735-2024-api/src/main/java/j2735api/CodecConfig.java @@ -39,6 +39,7 @@ public MessageFrameCodec messageFrameCodec() { return new MessageFrameCodec( config.getTextBufferSize(), config.getUperBufferSize(), + config.getErrorBufferSize(), libPath ); } diff --git a/j2735-2024-api/src/main/java/j2735api/J2735ApiApplication.java b/j2735-2024-api/src/main/java/j2735api/J2735ApiApplication.java index 6acd17f..054fe31 100644 --- a/j2735-2024-api/src/main/java/j2735api/J2735ApiApplication.java +++ b/j2735-2024-api/src/main/java/j2735api/J2735ApiApplication.java @@ -15,11 +15,13 @@ */ package j2735api; +import io.swagger.v3.oas.annotations.OpenAPIDefinition; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication +@OpenAPIDefinition public class J2735ApiApplication { public static void main(String[] args) { @@ -27,5 +29,4 @@ public static void main(String[] args) { } - } diff --git a/j2735-2024-api/src/main/resources/application.yaml b/j2735-2024-api/src/main/resources/application.yaml index a4fc00e..e78fd10 100644 --- a/j2735-2024-api/src/main/resources/application.yaml +++ b/j2735-2024-api/src/main/resources/application.yaml @@ -2,8 +2,15 @@ spring.application.name: j2735-2024-api server.port: 4000 spring.main.web-application-type: servlet +springdoc.swagger-ui.path: /swagger-ui.html +springdoc.api-docs.path: /api-docs + j2735.api: + # Use a large default value for text encoding, because the XML encodings for some messages, + # such as MAPs and TIMs, can be quite large text-buffer-size: 262144 + # UPER binary is a more compact encoding, so we can use a smaller buffer size. uper-buffer-size: 8192 + error-buffer-size: 256 library-path: /home/libasnapplication.so windows-library-path: C:/temp/asnapplication.dll diff --git a/j2735-2024-api/src/main/resources/logback.xml b/j2735-2024-api/src/main/resources/logback.xml index 8a0bffb..042a376 100644 --- a/j2735-2024-api/src/main/resources/logback.xml +++ b/j2735-2024-api/src/main/resources/logback.xml @@ -8,7 +8,7 @@ - + diff --git a/j2735-2024-ffm-lib/build.gradle b/j2735-2024-ffm-lib/build.gradle index 572728b..d5069e7 100644 --- a/j2735-2024-ffm-lib/build.gradle +++ b/j2735-2024-ffm-lib/build.gradle @@ -6,7 +6,7 @@ plugins { } group = 'usdot.jpo.ode' -version = '2.0.0' +version = '2.0.1' @@ -16,13 +16,14 @@ repositories { } dependencies { + implementation 'org.slf4j:slf4j-api:2.0.16' + // Use JUnit Jupiter for testing. testImplementation 'org.junit.jupiter:junit-jupiter:5.10.3' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' - implementation 'org.slf4j:slf4j-api:2.0.16' testImplementation 'org.slf4j:slf4j-simple:2.0.16' testImplementation 'org.hamcrest:hamcrest:3.0' - implementation 'commons-io:commons-io:2.20.0' + testImplementation 'commons-io:commons-io:2.20.0' } publishing { diff --git a/j2735-2024-ffm-lib/src/main/java/generated/__fsid_t.java b/j2735-2024-ffm-lib/src/main/java/generated/__fsid_t.java index b706617..f257d14 100644 --- a/j2735-2024-ffm-lib/src/main/java/generated/__fsid_t.java +++ b/j2735-2024-ffm-lib/src/main/java/generated/__fsid_t.java @@ -2,17 +2,15 @@ package generated; -import static java.lang.foreign.MemoryLayout.PathElement.groupElement; -import static java.lang.foreign.MemoryLayout.PathElement.sequenceElement; - -import java.lang.foreign.Arena; -import java.lang.foreign.GroupLayout; -import java.lang.foreign.MemoryLayout; -import java.lang.foreign.MemorySegment; -import java.lang.foreign.SegmentAllocator; -import java.lang.foreign.SequenceLayout; -import java.lang.invoke.VarHandle; -import java.util.function.Consumer; +import java.lang.invoke.*; +import java.lang.foreign.*; +import java.nio.ByteOrder; +import java.util.*; +import java.util.function.*; +import java.util.stream.*; + +import static java.lang.foreign.ValueLayout.*; +import static java.lang.foreign.MemoryLayout.PathElement.*; /** * {@snippet lang=c : diff --git a/j2735-2024-ffm-lib/src/main/java/generated/convert_h.java b/j2735-2024-ffm-lib/src/main/java/generated/convert_h.java index 42ac66f..c7d3273 100644 --- a/j2735-2024-ffm-lib/src/main/java/generated/convert_h.java +++ b/j2735-2024-ffm-lib/src/main/java/generated/convert_h.java @@ -2,31 +2,15 @@ package generated; -import static java.lang.foreign.ValueLayout.JAVA_BYTE; -import static java.lang.foreign.ValueLayout.OfBoolean; -import static java.lang.foreign.ValueLayout.OfByte; -import static java.lang.foreign.ValueLayout.OfDouble; -import static java.lang.foreign.ValueLayout.OfFloat; -import static java.lang.foreign.ValueLayout.OfInt; -import static java.lang.foreign.ValueLayout.OfLong; -import static java.lang.foreign.ValueLayout.OfShort; +import java.lang.invoke.*; +import java.lang.foreign.*; +import java.nio.ByteOrder; +import java.util.*; +import java.util.function.*; +import java.util.stream.*; -import java.lang.foreign.AddressLayout; -import java.lang.foreign.Arena; -import java.lang.foreign.FunctionDescriptor; -import java.lang.foreign.GroupLayout; -import java.lang.foreign.Linker; -import java.lang.foreign.MemoryLayout; -import java.lang.foreign.MemorySegment; -import java.lang.foreign.PaddingLayout; -import java.lang.foreign.SequenceLayout; -import java.lang.foreign.StructLayout; -import java.lang.foreign.SymbolLookup; -import java.lang.foreign.ValueLayout; -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.util.Arrays; -import java.util.stream.Collectors; +import static java.lang.foreign.ValueLayout.*; +import static java.lang.foreign.MemoryLayout.PathElement.*; public class convert_h { @@ -71,7 +55,7 @@ static MemoryLayout align(MemoryLayout layout, long align) { }; } - // Manual edit to jextract generates source + // Manual edit to jextract generated source // Comment out default symbol lookup // static final SymbolLookup SYMBOL_LOOKUP = SymbolLookup.libraryLookup(System.mapLibraryName("asnapplication"), LIBRARY_ARENA) // .or(SymbolLookup.loaderLookup()) @@ -79,16 +63,16 @@ static MemoryLayout align(MemoryLayout layout, long align) { // Make symbol lookup public static SymbolLookup SYMBOL_LOOKUP; - public static final OfBoolean C_BOOL = ValueLayout.JAVA_BOOLEAN; - public static final OfByte C_CHAR = ValueLayout.JAVA_BYTE; - public static final OfShort C_SHORT = ValueLayout.JAVA_SHORT; - public static final OfInt C_INT = ValueLayout.JAVA_INT; - public static final OfLong C_LONG_LONG = ValueLayout.JAVA_LONG; - public static final OfFloat C_FLOAT = ValueLayout.JAVA_FLOAT; - public static final OfDouble C_DOUBLE = ValueLayout.JAVA_DOUBLE; + public static final ValueLayout.OfBoolean C_BOOL = ValueLayout.JAVA_BOOLEAN; + public static final ValueLayout.OfByte C_CHAR = ValueLayout.JAVA_BYTE; + public static final ValueLayout.OfShort C_SHORT = ValueLayout.JAVA_SHORT; + public static final ValueLayout.OfInt C_INT = ValueLayout.JAVA_INT; + public static final ValueLayout.OfLong C_LONG_LONG = ValueLayout.JAVA_LONG; + public static final ValueLayout.OfFloat C_FLOAT = ValueLayout.JAVA_FLOAT; + public static final ValueLayout.OfDouble C_DOUBLE = ValueLayout.JAVA_DOUBLE; public static final AddressLayout C_POINTER = ValueLayout.ADDRESS - .withTargetLayout(MemoryLayout.sequenceLayout(Long.MAX_VALUE, JAVA_BYTE)); - public static final OfLong C_LONG = ValueLayout.JAVA_LONG; + .withTargetLayout(MemoryLayout.sequenceLayout(java.lang.Long.MAX_VALUE, JAVA_BYTE)); + public static final ValueLayout.OfLong C_LONG = ValueLayout.JAVA_LONG; private static final int _STDINT_H = (int)1L; /** * {@snippet lang=c : @@ -1127,10 +1111,12 @@ public static int _BITS_STDINT_UINTN_H() { private static class convert_bytes { public static final FunctionDescriptor DESC = FunctionDescriptor.of( - convert_h.C_LONG, + convert_h.C_INT, + convert_h.C_POINTER, convert_h.C_POINTER, convert_h.C_POINTER, convert_h.C_POINTER, + convert_h.C_LONG, convert_h.C_POINTER, convert_h.C_LONG, convert_h.C_POINTER, @@ -1145,7 +1131,7 @@ private static class convert_bytes { /** * Function descriptor for: * {@snippet lang=c : - * size_t convert_bytes(const char *pdu_name, const char *from_encoding, const char *to_encoding, const uint8_t *ibuf, size_t ibuf_len, uint8_t *obuf, size_t max_obuf_len) + * int convert_bytes(const char *pdu_name, const char *from_encoding, const char *to_encoding, const uint8_t *ibuf, size_t ibuf_len, uint8_t *obuf, size_t max_obuf_len, char *err_buf, size_t err_buf_len) * } */ public static FunctionDescriptor convert_bytes$descriptor() { @@ -1155,7 +1141,7 @@ private static class convert_bytes { /** * Downcall method handle for: * {@snippet lang=c : - * size_t convert_bytes(const char *pdu_name, const char *from_encoding, const char *to_encoding, const uint8_t *ibuf, size_t ibuf_len, uint8_t *obuf, size_t max_obuf_len) + * int convert_bytes(const char *pdu_name, const char *from_encoding, const char *to_encoding, const uint8_t *ibuf, size_t ibuf_len, uint8_t *obuf, size_t max_obuf_len, char *err_buf, size_t err_buf_len) * } */ public static MethodHandle convert_bytes$handle() { @@ -1165,7 +1151,7 @@ private static class convert_bytes { /** * Address for: * {@snippet lang=c : - * size_t convert_bytes(const char *pdu_name, const char *from_encoding, const char *to_encoding, const uint8_t *ibuf, size_t ibuf_len, uint8_t *obuf, size_t max_obuf_len) + * int convert_bytes(const char *pdu_name, const char *from_encoding, const char *to_encoding, const uint8_t *ibuf, size_t ibuf_len, uint8_t *obuf, size_t max_obuf_len, char *err_buf, size_t err_buf_len) * } */ public static MemorySegment convert_bytes$address() { @@ -1174,16 +1160,16 @@ private static class convert_bytes { /** * {@snippet lang=c : - * size_t convert_bytes(const char *pdu_name, const char *from_encoding, const char *to_encoding, const uint8_t *ibuf, size_t ibuf_len, uint8_t *obuf, size_t max_obuf_len) + * int convert_bytes(const char *pdu_name, const char *from_encoding, const char *to_encoding, const uint8_t *ibuf, size_t ibuf_len, uint8_t *obuf, size_t max_obuf_len, char *err_buf, size_t err_buf_len) * } */ - public static long convert_bytes(MemorySegment pdu_name, MemorySegment from_encoding, MemorySegment to_encoding, MemorySegment ibuf, long ibuf_len, MemorySegment obuf, long max_obuf_len) { + public static int convert_bytes(MemorySegment pdu_name, MemorySegment from_encoding, MemorySegment to_encoding, MemorySegment ibuf, long ibuf_len, MemorySegment obuf, long max_obuf_len, MemorySegment err_buf, long err_buf_len) { var mh$ = convert_bytes.HANDLE; try { if (TRACE_DOWNCALLS) { - traceDowncall("convert_bytes", pdu_name, from_encoding, to_encoding, ibuf, ibuf_len, obuf, max_obuf_len); + traceDowncall("convert_bytes", pdu_name, from_encoding, to_encoding, ibuf, ibuf_len, obuf, max_obuf_len, err_buf, err_buf_len); } - return (long)mh$.invokeExact(pdu_name, from_encoding, to_encoding, ibuf, ibuf_len, obuf, max_obuf_len); + return (int)mh$.invokeExact(pdu_name, from_encoding, to_encoding, ibuf, ibuf_len, obuf, max_obuf_len, err_buf, err_buf_len); } catch (Throwable ex$) { throw new AssertionError("should not reach here", ex$); } diff --git a/j2735-2024-ffm-lib/src/main/java/generated/max_align_t.java b/j2735-2024-ffm-lib/src/main/java/generated/max_align_t.java index ce37335..b47f227 100644 --- a/j2735-2024-ffm-lib/src/main/java/generated/max_align_t.java +++ b/j2735-2024-ffm-lib/src/main/java/generated/max_align_t.java @@ -2,15 +2,15 @@ package generated; -import static java.lang.foreign.MemoryLayout.PathElement.groupElement; -import static java.lang.foreign.ValueLayout.OfLong; - -import java.lang.foreign.Arena; -import java.lang.foreign.GroupLayout; -import java.lang.foreign.MemoryLayout; -import java.lang.foreign.MemorySegment; -import java.lang.foreign.SegmentAllocator; -import java.util.function.Consumer; +import java.lang.invoke.*; +import java.lang.foreign.*; +import java.nio.ByteOrder; +import java.util.*; +import java.util.function.*; +import java.util.stream.*; + +import static java.lang.foreign.ValueLayout.*; +import static java.lang.foreign.MemoryLayout.PathElement.*; /** * {@snippet lang=c : diff --git a/j2735-2024-ffm-lib/src/main/java/j2735ffm/AsnEncoding.java b/j2735-2024-ffm-lib/src/main/java/j2735ffm/AsnEncoding.java new file mode 100644 index 0000000..7a56bb2 --- /dev/null +++ b/j2735-2024-ffm-lib/src/main/java/j2735ffm/AsnEncoding.java @@ -0,0 +1,31 @@ +package j2735ffm; + +import lombok.Getter; + +@Getter +public enum AsnEncoding { + UPER("uper", true, true), + XER("xer", true, false), + JER("jer", false, false), + OER("oer", false, true), + INVALID("invalid", false, false); + + private final String name; + private final boolean supported; + private final boolean binary; + + AsnEncoding(String name, boolean supported, boolean binary) { + this.name = name; + this.supported = supported; + this.binary = binary; + } + + public static AsnEncoding fromName(String name) { + for (AsnEncoding encoding : AsnEncoding.values()) { + if (encoding.getName().equalsIgnoreCase(name)) { + return encoding; + } + } + throw new IllegalArgumentException("Unknown encoding: " + name); + } +} diff --git a/j2735-2024-ffm-lib/src/main/java/j2735ffm/ConvertException.java b/j2735-2024-ffm-lib/src/main/java/j2735ffm/ConvertException.java new file mode 100644 index 0000000..9b015fd --- /dev/null +++ b/j2735-2024-ffm-lib/src/main/java/j2735ffm/ConvertException.java @@ -0,0 +1,12 @@ +package j2735ffm; + +/** + * Checked exception for errors returned from the native library + */ +public class ConvertException extends Exception{ + + public ConvertException(String message) { + super(message); + } + +} diff --git a/j2735-2024-ffm-lib/src/main/java/j2735ffm/MessageFrameCodec.java b/j2735-2024-ffm-lib/src/main/java/j2735ffm/MessageFrameCodec.java index f02cc6f..ef72e1b 100644 --- a/j2735-2024-ffm-lib/src/main/java/j2735ffm/MessageFrameCodec.java +++ b/j2735-2024-ffm-lib/src/main/java/j2735ffm/MessageFrameCodec.java @@ -24,13 +24,16 @@ import static generated.convert_h.convert_bytes; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import lombok.extern.slf4j.Slf4j; +import static j2735ffm.AsnEncoding.XER; +import static j2735ffm.AsnEncoding.UPER; /** - * Functions for interconverting J2735 (2024) MessageFrames between XER, JER, and UPER encodings + * Functions for interconverting J2735 (2024) MessageFrames between XER, and UPER encodings * by calling native functions generated by asn1c. * * @author Ivan Yourshaw @@ -38,7 +41,7 @@ @Slf4j public class MessageFrameCodec { - public final static String PDU = "MessageFrame"; + public final static String MESSAGE_FRAME_PDU = "MessageFrame"; /** * Output buffer size for XER and JSON. Messages larger than this can't be produced. @@ -50,6 +53,11 @@ public class MessageFrameCodec { */ public final long uperBufferSize; + /** + * Buffer size for error messages returned from the native library. + */ + public final long errorBufferSize; + private final static Path DEFAULT_LIBRARY_PATH = Paths.get("/usr/lib/libasnapplication.so"); @Deprecated @@ -58,7 +66,7 @@ public MessageFrameCodec( long uperBufferSize, @Deprecated long messageFrameAllocateSize, @Deprecated long asnCodecCtxMaxStackSize) { - this(textBufferSize, uperBufferSize, DEFAULT_LIBRARY_PATH); + this(textBufferSize, uperBufferSize, 256L, DEFAULT_LIBRARY_PATH); } /** @@ -67,9 +75,11 @@ public MessageFrameCodec( * @param uperBufferSize - Size of the input or output buffer for UPER binary encoding. * @param libraryPath - Absolute or relative path to the native library, e.g. "/usr/lib/libasnapplication.so" */ - public MessageFrameCodec(long textBufferSize, long uperBufferSize, Path libraryPath) { + public MessageFrameCodec(long textBufferSize, long uperBufferSize, long errorBufferSize, + Path libraryPath) { this.textBufferSize = textBufferSize; this.uperBufferSize = uperBufferSize; + this.errorBufferSize = errorBufferSize; loadLibrary(libraryPath); log.info("MessageFrameCodec initialized with textBufferSize: {}, uperBufferSize: {}, libraryPath: {}", textBufferSize, uperBufferSize, libraryPath); @@ -83,16 +93,74 @@ public MessageFrameCodec(long textBufferSize, long uperBufferSize, Path libraryP */ private void loadLibrary(Path libraryPath) { // Load the library into a garbage-collected arena - Arena arena = Arena.ofAuto(); - SymbolLookup lookup = SymbolLookup.libraryLookup(libraryPath, arena); - log.info("Loaded library: {}", libraryPath); - var symbol = lookup.find("convert_bytes"); - if (symbol.isPresent()) { - log.info("found symbol convert_bytes: {}", symbol); - } else { - throw new RuntimeException("symbol 'convert_bytes' not found in the library"); + if (!Files.exists(libraryPath)) { + String errMsg = String.format("Library not found at path: %s", libraryPath); + log.error(errMsg); + throw new RuntimeException(errMsg); + } + try { + Arena arena = Arena.ofAuto(); + SymbolLookup lookup = SymbolLookup.libraryLookup(libraryPath, arena); + log.info("Loaded library: {}", libraryPath); + var symbol = lookup.find("convert_bytes"); + if (symbol.isPresent()) { + log.info("found symbol convert_bytes: {}", symbol); + } else { + throw new RuntimeException("symbol 'convert_bytes' not found in the library"); + } + // Note: Assign the lookup to a static field in the generated code. + // This will prevent the library from being garbage collected until + // the class loader that loaded the "convert_h" class is itself garbage collected. + // Normally that would happen when the JVM exits, but could happen sooner if this library is + // loaded dynamically by a custom class loader or is used in the context of OSGI or + // something. We do this instead of using the global arena to prevent memory leaks + // in case of that unlikely, but possible, scenario. + convert_h.SYMBOL_LOOKUP = lookup; + } catch (Throwable e) { + String errMsg = String.format("Error loading library: %s: %s", libraryPath, e); + log.error(errMsg); + throw new RuntimeException(errMsg, e); + } + } + + /** + * General purpose conversion function that can convert any PDU to or from + * any encoding. + * @param inputBytes Input byte array: XER or UPER binary + * @param pdu The name of the PDU, e.g., "MessageFrame", "MessageFrame", "VehicleEventFlags", etc. + * @param fromEncoding Input encoding, may be "xer" or "uper" + * @param toEncoding Output encoding, may be "xer" or "uper" + * @return The encoded message as bytes or UTF-8 string + */ + public byte[] convertGeneral(byte[] inputBytes, String pdu, AsnEncoding fromEncoding, AsnEncoding toEncoding) { + log.debug("convertGeneral PDU: {}, {} -> {}", pdu, fromEncoding, toEncoding); + if (!fromEncoding.isSupported()) { + String errMsg = String.format("Unsupported fromEncoding: %s", fromEncoding); + log.error(errMsg); + throw new IllegalArgumentException(errMsg); + } + if (!toEncoding.isSupported()) { + String errMsg = String.format("Unsupported toEncoding: %s", toEncoding); + log.error(errMsg); + throw new IllegalArgumentException(errMsg); + } + final long inputBufferSize = fromEncoding.isBinary() ? uperBufferSize : textBufferSize; + if (inputBytes.length > inputBufferSize) { + String errMsg = String.format("Input message too large: %d > %d", inputBytes.length, inputBufferSize); + log.error(errMsg); + throw new IllegalArgumentException(errMsg); + } + final long outputBufferSize = toEncoding.isBinary() ? uperBufferSize : textBufferSize; + try (var arena = Arena.ofConfined()) { + MemorySegment inputBuffer = arena.allocate(inputBufferSize); + MemorySegment outputBuffer = arena.allocate(outputBufferSize); + MemorySegment errorBuffer = arena.allocate(errorBufferSize); + return convert(arena, inputBytes, fromEncoding.getName(), + toEncoding.getName(), inputBuffer, outputBuffer, outputBufferSize, errorBuffer, + errorBufferSize, pdu); + } catch (Exception e) { + throw new RuntimeException(e); } - convert_h.SYMBOL_LOOKUP = lookup; } @@ -103,13 +171,19 @@ private void loadLibrary(Path libraryPath) { */ public byte[] xerToUper(String xer) { log.debug("xerToUper: {}", xer); + if (xer.length() > textBufferSize) { + String errMsg = String.format("Input XER message too large: %d > %d", xer.length(), textBufferSize); + log.error(errMsg); + throw new IllegalArgumentException(errMsg); + } try (var arena = Arena.ofConfined()) { MemorySegment inputBuffer = arena.allocate(textBufferSize); MemorySegment outputBuffer = arena.allocate(uperBufferSize); - return convert(arena, xer.getBytes(StandardCharsets.UTF_8), "xer", - "uper", inputBuffer, outputBuffer, uperBufferSize); + MemorySegment errorBuffer = arena.allocate(errorBufferSize); + return convert(arena, xer.getBytes(StandardCharsets.UTF_8), XER.getName(), + UPER.getName(), inputBuffer, outputBuffer, uperBufferSize, errorBuffer, + errorBufferSize, MESSAGE_FRAME_PDU); } catch (Exception e) { - log.error(e.getMessage(), e); throw new RuntimeException(e); } } @@ -121,25 +195,36 @@ public byte[] xerToUper(String xer) { */ public String uperToXer(byte[] uper) { log.trace("Received {} bytes", uper.length); + if (uper.length > uperBufferSize) { + String errMsg = String.format("Input UPER message too large: %d > %d", uper.length, uperBufferSize); + log.error(errMsg); + throw new IllegalArgumentException(errMsg); + } try (var arena = Arena.ofConfined()) { MemorySegment inputBuffer = arena.allocate(uperBufferSize); MemorySegment outputBuffer = arena.allocate(textBufferSize); - byte[] xerBytes = convert(arena, uper, "uper", "xer", - inputBuffer, outputBuffer, textBufferSize); + MemorySegment errorBuffer = arena.allocate(errorBufferSize); + byte[] xerBytes = convert(arena, uper, UPER.getName(), XER.getName(), + inputBuffer, outputBuffer, textBufferSize, errorBuffer, errorBufferSize, + MESSAGE_FRAME_PDU); return new String(xerBytes, StandardCharsets.UTF_8); + } catch (Exception e) { + throw new RuntimeException(e); } } private byte[] convert(Arena arena, final byte[] bytes, - final String fromEncoding, final String toEncoding, MemorySegment inputBuffer, - MemorySegment outputBuffer, long outputBufferSize) { + final String fromEncoding, final String toEncoding, MemorySegment inputBuffer, + MemorySegment outputBuffer, long outputBufferSize, MemorySegment errorBuffer, + long errorBufferSize, final String pdu) + throws ConvertException{ log.debug("convert: {} {}", fromEncoding, toEncoding); byte[] outputArray = null; MemorySegment heapBytes = MemorySegment.ofArray(bytes); inputBuffer.copyFrom(heapBytes); - MemorySegment pduName = arena.allocateFrom(PDU, StandardCharsets.UTF_8); + MemorySegment pduName = arena.allocateFrom(pdu, StandardCharsets.UTF_8); MemorySegment fromEncodingSeg = arena.allocateFrom(fromEncoding, StandardCharsets.UTF_8); MemorySegment toEncodingSeg = arena.allocateFrom(toEncoding, StandardCharsets.UTF_8); @@ -147,12 +232,18 @@ private byte[] convert(Arena arena, final byte[] bytes, long numOut = 0; try { numOut = convert_bytes(pduName, fromEncodingSeg, toEncodingSeg, inputBuffer, - bytes.length, outputBuffer, outputBufferSize); + bytes.length, outputBuffer, outputBufferSize, errorBuffer, errorBufferSize); } catch (Throwable ex) { - log.info("error converting"); + log.error("error converting",ex); throw ex; } log.debug("numOut: {}", numOut); + if (numOut < 0) { + // Error was returned + String error = errorBuffer.getString(0); + log.error(error); + throw new ConvertException(error); + } outputArray = new byte[(int) numOut]; MemorySegment heapOutput = MemorySegment.ofArray(outputArray); MemorySegment.copy(outputBuffer, 0, heapOutput, 0, numOut); diff --git a/j2735-2024-ffm-lib/src/test/java/j2735ffm/MessageFrameCodecTest.java b/j2735-2024-ffm-lib/src/test/java/j2735ffm/MessageFrameCodecTest.java index 31fd7db..4061f9d 100644 --- a/j2735-2024-ffm-lib/src/test/java/j2735ffm/MessageFrameCodecTest.java +++ b/j2735-2024-ffm-lib/src/test/java/j2735ffm/MessageFrameCodecTest.java @@ -15,20 +15,31 @@ */ package j2735ffm; +import static j2735ffm.AsnEncoding.INVALID; +import static j2735ffm.AsnEncoding.UPER; +import static j2735ffm.AsnEncoding.XER; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalToIgnoringCase; import static org.hamcrest.Matchers.notNullValue; +import static org.junit.jupiter.api.Assertions.assertThrows; +import java.io.IOException; import java.net.URISyntaxException; import java.net.URL; +import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.nio.file.Paths; import java.util.HexFormat; +import java.util.stream.Stream; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.io.IOUtils; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; @Slf4j public class MessageFrameCodecTest { @@ -53,7 +64,7 @@ public static void setup() { } try { Path libPath = Paths.get(url.toURI()); - codec = new MessageFrameCodec(262144L, 8192L, libPath); + codec = new MessageFrameCodec(262144L, 8192L, 256L, libPath); log.info("Created codec"); } catch (URISyntaxException e) { throw new RuntimeException(e); @@ -69,26 +80,15 @@ public void testLibraryLoaded() { @Test public void testXerToUper() { - byte[] uper = codec.xerToUper("19121110000000000000000035176222120221214221812218162212022121821852218521218522185252185221852"); + final String xer = loadResource("SPAT_MF.xml"); + byte[] uper = codec.xerToUper(xer); assertThat(uper, notNullValue()); String hex = hexFormat.formatHex(uper); log.info("hex: {}", hex); } @ParameterizedTest - @CsvSource({ - "001338000817a780000089680500204642b342b34802021a15a955a940181190acd0acd20100868555c555c00104342aae2aae002821a155715570", - "001480AD562FA8400039E8E717090F9665FE1BACC37FFFFFFFF0003BBAFDFA1FA1007FFF8000000000020214C1C100417FFFFFFE824E100A3FFFFFFFE8942102047FFFFFFE922A1026A40143FFE95D610423405D7FFEA75610322C0599FFEADFA10391C06B5FFEB7E6103CB40A03FFED2121033BC08ADFFED9A6102E8408E5FFEDE2E102BDC0885FFEDF0A1000BC019BFFF7F321FFFFC005DFFFC55A1FFFFFFFFFFFFDD1A100407FFFFFFFE1A2FFFE0000", - "001f85fe7591fd9e4f4354455420535452124c16b4fa4e724f43ef73d2f04fd7ad0ff09260b5a7d2a6249ba8936ffa29bac57176dadf5389a4000a2001c3a03dc06ba5067b8404632ae6a0503389c06966bc120c5900f771c1c6306a9a6bf319901630a0f2403124536d3e8476d9af77a7bf218901103a881122806195dcc32733526fe545fa217f819306eff7fdceaafa0230c87f355e98cb516360080a0002f348c72a3f584b11e4be99002ad4a0d12394d435cfda9fe80267e176eb96a2ff07260b5a7f4b9d40747f5ffe014b670db5da84587b200050400f55bfce9e9bc9d913782889646ff0126933eacd08beff7f5447df8cf48052da9a01005a0001bc2fa39c11fc2e7046450f0952d4dfe024ddb71fd2d3b502b9b2551e1d3f8b210e3c4000b00020c2dd46d113a3f21710e9416b92d5bfc14982d60dc94824a8e5c5095c1007d4e362e82720800270a04182240bda99c42bb6d94ff5f9a52a3014cd051a29843d4489d71d4150ae35b7533039e58186a0608ac0fc05f4bf24a4e851d3d0a801880f339e17e05ca32567a49b73b932580eecfc46064078a2e0120158f22817d1fb85eee4e66a4dfca8bf442fcca15810716140920643215dc607f829305ac1b92904951cb8a12b8200fa9c6c5d04e410004c0a0671380d2cd782418b201eee3838c60d534d7e633202c6141e4806248a6da7d08edb35eef4f7e431202207510224500c32bb9864e66a4dfca8bf442ff05260b5837252092a397142570401f538d8ba09c820009976b50fc6379b168326f9bdfe0a4c16b06e4a4125472e284ae0803ea71b1741390400132ed63059cde2d064df37c448db82d49305ad3e949305ad3e88bf939305ad3010933071674e428880448e75d46d038a29245b0b778f7001044f43ef73d2f04fd7ad0ff09260b5a7d2a6249ba8936ffa29bac57176dadf5389a4000a2001c3a03dc06ba5067b8404632ae6a0503389c06966bc120c5900f771c1c6306a9a6bf319901630a0f2403124536d3e8476d9af77a7bf218901103a881122806195dcc32733526fe545fa217f819306eff7fdceaafa0230c87f355e98cb516360080a0002f348c72a3f584b11e4be99002ad4a0d12394d435cfda9fe80267e176eb96a2ff07260b5a7f4b9d40747f5ffe014b670db5da84587b200050400f55bfce9e9bc9d913782889646ff0126933eacd08beff7f5447df8cf48052da9a01005a0001bc2fa39c11fc2e7046450f0952d4dfe024ddb71fd2d3b502b9b2551e1d3f8b210e3c4000b00020c2dd46d113a3f21710e9416b92d5bfc14982d60dc94824a8e5c5095c1007d4e362e82720800270a04182240bda99c42bb6d94ff5f9a52a3014cd051a29843d4489d71d4150ae35b7533039e58186a0608ac0fc05f4bf24a4e851d3d0a801880f339e17e05ca32567a49b73b932580eecfc46064078a2e0120158f22817d1fb85eee4e66a4dfca8bf442fcca15810716140920643215dc607f829305ac1b92904951cb8a12b8200fa9c6c5d04e410004c0a0671380d2cd782418b201eee3838c60d534d7e633202c6141e4806248a6da7d08edb35eef4f7e431202207510224500c32bb9864e66a4dfca8bf442ff05260b5837252092a397142570401f538d8ba09c820009976b50fc6379b168326f9bdfe0a4c16b06e4a4125472e284ae0803ea71b1741390400132ed63059cde2d064df37c4401b705a049305ad3e9024982d69f445fc9c982d69808499838b3a7214440224738ea3681c514922d85bbc7b8008227a1f7b9e97827ebd607f8093499f566845f7fbfaa23efc67a40296d4d00802d0000de17d1ce08fe173823228784a96a711136e0b524c16b4fa524c16b4fa22fe4e4c16b4c0424cc1c59d390a22011239df51b40e28a4916c2dde3dc004113d0fbdcf4bc13f5eb03fc049a4cfab3422fbfdfd511f7e33d2014b6a680401680006f0be8e7047f0b9c119143c254b538899b705a9260b5a7d29260b5a7d117f27260b5a60212660e2ce9c851100891cefa8da07145248b616ef1ee002089e87dee7a5e09faf581fe024d267d59a117dfefea88fbf19e900a5b5340200b40003785f473823f85ce08c8a1e12a5a9c450db82d49305ad3e949305ad3e88bf939305ad3010933071674e4288804480", - "001d697125b7da9aa31b97b0fb3ec9148495a40fed6be3446a4e5b70ec1a2752b710fbfaf5ac086673396a81677da981b16a6b4905771f424e51683a70adb0afd713837a81719aed3fa8d4e347ef1b40e42d024585c757787442477e73341aae24982d69f40e4c16b4c428d8", - "001e817b65e539dc93b843af683249404f9e0fc6b04fd122cf2ce89941dc1ab4d3d288394b59be74b1c04cfdee07bc9868311d2c1caa51f03dc764f993d0d511779e9ef22be1121c093e1af96b1d141a1ba967c329e47cf884b8beb3268e790f72270ca44c2519740d31d85f3a0e91a6bca5145e560e920d281085568f931b7067cc9e86a88b8f5957847ac6b4fa9d1b07fc2cd2e6a91f327a1aa229d5e21e478318d630a67bd8ffd0ce05537cf12267f5df5fc2794ff0804001a150fe00a679ce1c7934b4a6891be64f435445f9206bc5ff8e09b516244086367d14aef993d0d51175f310f233515cb0082fa6f907e861f0be64f435445412a1bcae64260fe3aa2470ea9ccf666f993d0d5114a9c22f0ba4406960b0cc40a0bd244e285bce95385017cefcbbac8d3070a7819776806012dd1dd66d719a089f3d143c9b843e001e4439710aacb223e510b9397770640941fcec2f9fc6a4cba57da12f160011a4d7c2fc0f7f10429d2be7409299a6cd9053c256161af6572d28cf07d9bdc620", - "0013838f65227f02487f07260b5a7ea4eea3b4400807cf548e3480e0f54afae04c14982d4a1183eefde4e4ae9d30fca80a906f32c28fceb1e486cd9fd93c2d459f18a78f3a18c1f2c7f371b0869045b061dc316d4783f3b1b5a8f02373272f5e4d3e43e28bc8d949e323784c3cb28a2c7f70c56eee2f4c349774c980780a932ee3ec7f57bc8c675aa86a1b255d837884388e1a0f23ba161109e05acd8ed8bc3de03dd7365fa25734fa22150f8d614a293c26a19a3af78506832764f1cfe2a53485e496f79e7bde2224dd7f9d839305ad3cd064f9065af84baa7369921efa9bc97ca6e6f578263bd7e14f01875bfef1e605c0a61783c42ec211d7b05260b548466f818f8f900ab51f108a63e8bc424d4912e79ddc45ed9ac9f1e46193c721453ead68310790031a7494cbf5a8b10a542d320ae0548f279541bd9236f1a4d7ec61de6626cc170b17d122a1aa087b217a403f281e27a60cacab1fd15ac7922179cb33c5c5a91e48125135a3c66ee3e5887909a8e5290f145811f925e06104656f03c644f887279bda2bcdb919bc3cc8d28adbccaa6d8be7bcc10a12ccb808721905d68b035663074e9f03703207e124c16b4fa6140d1ef05200008b6986c920383d52beb81305260b5d8460f8a4e9b40095c0d52cba5013c7cb9b735b79c1f97a7e0f319b50c13997ec7246d5c011a8493a7c0c8f3ba3957c09e21bc1374bbceb140ea4f78f0dbb3d70f3a078dbdbd8bf03a48c22b47c8673ca04a0f14ab3b6a1193eb224d4de5e8cd1ee4d67c4f3a8e4eec39e66e82ffcf3cd91254fee63fbf4f21de15d4b47781ca29bc55a1a06cc78a68a243c2f18ae8212c1e4aa4a9a968bd38661b55fbde1077ad13b07260b5a7284c1f8cbc40ec0f4460cb216c44795698e88dcf19275e0569e16c133e12bcfd74b729c79dfab729a6c7f7196629e37797fd615dda0795880e21f0cdf66275b50336d6d59e0edb37999087309ef13b682aed5e3b2c8770d3c2349bfe3a278810ad60bf7a555c5496f790de20678b60a4c16a306d3f3aa0846120af3929130033795e74cd582f374d78b4b5e001433d1bbc1fbc890dd69fa4cab02194332aff100de93cc1f5deaad78163974b54f0e288cb03593ef948282513c01d3a800e60f3a001120d58be1dd4d5dc82aceb6a218a86f1f2a2d7fc9e537a3d34b3c63c95b17b784a019a9048f33964016f6f361756ff66f2e368e15bef3440fe99e6f5f3f646e7e4f5d18685fa6f5293727ccef6748b4a5c6021e86fc0041f13640f20340c13a5b0180", - "001285837f2ac48d34380f049305ad3e982bb8ebcf8974c490985c69a3e0100912330bc58f3533b990adbd037eb2124c16b4fa42a081c0800008041efec8569c5c4491d8b4d863f49400890ce792b14bc6ff5e002873ac3d5aa6c50b973f29f90c29305acda1016000010170154469aff68b45f7e48be5608177c00425d8c8fbfb68025f181010922d087fa42018137623cf27abe4f8ff44fc561c982d69a22040e00000200095e931d65dc2bdb5b2ff8000d7f312fe5bb233fecd00181d8b72fd5666bf880206838e1f8949c5e97bac74bf4f81271ca0088000100026933647627b7e3782695a36a1a5e0f3fc2384019987e820410146899bb2b9c8fce800d34d56c97d76a64048083008be9f0e24011c1c982d69dbbf57e0b62693aa763039d84818313a4f101640406d4aed322bb441e0007ef4124c16b4fa651021040000080005ea2e4a0aa0d03f8e0087600f9370fc096bf464017e0c40d0919e29ff2a004f45b99a260904ff8e8019574b68285d9db7a9a5c312e0e4c16b4f63c0c9c76922de6a591e6ca9552d5d1a0e0b05a80bf3507260b5a6f240210800020020f1ba08077b3a2c37d8dff94b856aa493e4e4014d73f24697ffdafaf000fa5007d1ff3694feb8401dd7d138c51bd09fff100515361ccc71a2bbfa400554a33f5261933535528fc46049b8210101000042eddebe9234a98e872d5707c8c8039cca6280846565393fb4c6f11c0c983186ed3d4d68d887a3a227ba63e13020b3a44f8dcd9061fa48d2f05896411fb0049305ad3e8c04800020042e61db0e822708d17b5cc17e090024b4262ae38e8dff9b800f21e1402362efb7ce08034422a6c171d9f4241fb84093c480411000040030a6ce962ac8e060609210080154c41890612aa41eb513e30101c915ee9567cd0cfc3802402fea75e0f837be34041da19a435a063d4faf020405db37524e767bf480813783e9b8a7f698098fc3a1c982d69c59008041000b8f0e27e0183f6a695f845fd30029f92784022161d7eea0037f758d85fbe1b3f3a200e21d14fa12a43b9a8b37ed40e4c16b4d20802c4000020b8a3abbd8f6e653e7b52345f35020bca212dd11678a7db404349362bc0a6578ff0a002eb36c2134f61cab4c5063737e66024aca0100400800022699bc08da9d05f6d010ce20806a21af287c184033a8196e252a851f00080c3230b8797ce5f21adb1d709260b5a7d2541d25f8bd4586171382d50c158ece4dea2a76905ede08fd7a049345001c000002010be7db90135b7a45e64f372bce43f370fc8c0234c980f6dc33b43f4d0809702284c96cbfd0ef7ed9124c16b4fa0c120040200400109db1337c4aeff3f80a38e7248ebe9ff1f7dee484bc8f09b1fce004541b0ab498ea047fd404112d1dbdb39fbfe920d8a5564cfd9024982d69f4224401c0000022eb54640890c2a9c5e869fc1000b7e8867684a0da9f6d0049f9530cc79194f7e0c0439d7b0edd166c8bf1e002bda3d1a060f73bbbf9c33bd7e3f064c1c7410082002002e6528e72d4db8c0d2a321fe4c042b17bb989675071fdd002af233e3bf44e41fd5808333d8bc0e1b70fbf80008ef14fcb1e1994432fd7a2597e220a4c16a8c810708004005c5a6bb5f210b855fe8b4fc10804ec9b04de1fb81ff4a009eb32f765940434fa320059241f0b0a1126bf3640113d5bb33dd0f8bf9540074e6feaef682c8660089aefb23819307db654afaf50c85e57ab623e9531e761ffc815a907b6c77b31fee662b080bf2509260b5a7d34100424001000005a384e20f21454147a7d740837dcf0d32e4b203fa1400a3645cbdc49eddfd5c00728ea08d34b4443ffd100fc07edaf0a42aeff3d002e71e5f865bcf90ceda7c539fa1829305ab52004d000008176938786aa95cf5cfa48bffa0818322c5b8ea2fc7fb5100607509864fe8b0bfda011647065e12cc9b71faf009205260b5019304124c16b4fa0089a070e0c30d1010830", - "00205d7fffc37feafd3d0d511524beaba433e42540c03bf2c4840c497e676a0e4c556c35f6fffec9289090c5dd7ca7d9149826ffed175fb22fb658eb94d1b12c3510d0200ff8dbff3fcd4abfdd4b1a38dae01c267ccc19408652aa0200421b31", - "001c3172361c5e0ffff3e134666f7da521a3116a2199e8bae98ef90ccc6d4f5da29145c14e805287affa90113d0d511550014f43", - "00293fee4f4354451fe189d73d8db3487a47e59668527c20a5ee4b3eb1ad79e017f38b87cebd5ff6885eb08ddfc05a5bd727e18c7bccc1edac3cf96d7e7d0afc9e48", - "002182B72F00878190A929E86A88B1BFD51931DD5E3826BFFB8B343AFB6741300FE5C32BA3BBDE67193178F6B7A4C55D7F92AC0F0491F9F41C7FF5C0EAAC660564EC7395A3852EF80497084BD802D70CEE59F40525C774EA036AC088DCD87C1F53E812C941115332BD8AD799163C0A9C81C982D69E83F981552BFD669E5A5F2D16C74FA63E918F8F0F604A90F3A1C12E23BBAD93CB049305AD3E920B77A512C50E4CA153321E342E7B46E1E301A8A4E59014982D4A049305AD3E880A4C16A881C14BB827A1AA228030783694F4354452130736F1056E8D0514C69E86A88BA29495BE95B96477A30E24A8B850AE4165F37B57429305AA82946C0941100F20C350AD1F10EFBC12F596922EAF9F5EE842DA5CB6A3207E642A0903C5B0D861EC9F40E00D471F4F4B0459E06117072B5EC72EDD6E8A402049001556AA6ADA64EBB41999C5CFA6548032950FA47C8D7026AC31E9E66DAADAA6D910CD5064C14484DC189C9FC95A23DF37185E92AD35592B8E0E5EFD26B49962279A2B778687051070CE2BA22974CB3B0110112EADFF2FCBC29B8D28C75BF56B3AB2B823CFE3F939D142D716370A601AC78E4A5FBCF10807A140AB40F08C4344158FB9404A4FC4F5DF57B57F128327A46AD6AD1BBBBF0FF8413612022A903960AA847A131787C4361D860C2B5BD507FAF45ECD0BAF9DC89D2817EC92DDCFDD89C28532C350AD3DEA25C2100228D4347F980931D2B8DFE2BADCA94D615CA0CBEBB2A091E58BE2B51215BE98AE235E1515E348C6CA2AF567FF1CB6449305AD3E988951821392E876D923D4C0E367116255FE6BBB512CBBCD0D5A0FEA65F650A52B5E633EA40E8458E51282F562201A529BB46DA51247A862897C9696F1FB10774216AB8E3CF15D715DB2E514268A9DA38D309EF0FA0C539C893308E63BD5AFBA44436666541EA275627FC4448A5CC2AFBCB2A88869C4D09800949BB79F0573CD5AA7DE4F46A0" - }) + @MethodSource("messageFrameHex") public void uperToXer(final String uper) { // Normalize case String xer = codec.uperToXer(HexFormat.of().parseHex(uper)); @@ -100,5 +100,141 @@ public void uperToXer(final String uper) { assertThat("round trip hex differs", roundTripUperHex, equalToIgnoringCase(uper)); } + @ParameterizedTest + @MethodSource("convertData") + public void testConvertGeneral(final String pdu, final String inputHex, final String expectXer) { + byte[] inputBytes = hexFormat.parseHex(inputHex); + byte[] result = codec.convertGeneral(inputBytes, pdu, UPER, XER); + assertThat("result is null", result, notNullValue()); + if (expectXer != null) { + String xer = new String(result, StandardCharsets.UTF_8); + assertThat(xer, equalTo(expectXer)); + } + } + + @Test + public void invalidPduError() { + byte[] inputBytes = hexFormat.parseHex(VEHICLE_EVENT_FLAGS_UPER); + assertThrows( + RuntimeException.class, + () -> { + codec.convertGeneral(inputBytes, "BadPDU", UPER, XER); + } + ); + } + + @Test + public void invalidInputEncodingError() { + byte[] inputBytes = hexFormat.parseHex(VEHICLE_EVENT_FLAGS_UPER); + assertThrows( + RuntimeException.class, + () -> { + codec.convertGeneral(inputBytes, VEHICLE_EVENT_FLAGS_PDU, INVALID, XER); + } + ); + } + + @Test + public void invalidOutputEncodingError() { + byte[] inputBytes = hexFormat.parseHex(VEHICLE_EVENT_FLAGS_UPER); + assertThrows( + RuntimeException.class, + () -> { + codec.convertGeneral(inputBytes, VEHICLE_EVENT_FLAGS_PDU, UPER, INVALID); + } + ); + } + + @Test + public void malformedUperError() { + byte[] inputBytes = hexFormat.parseHex(MALFORMED_SSM); + assertThrows( + RuntimeException.class, + () -> { + codec.convertGeneral(inputBytes, SSM_PDU, UPER, XER); + } + ); + } + + @Test + public void testConvertGeneral_InputTooBig() { + byte[] inputBytes = new byte[10000]; + RuntimeException re = assertThrows( + RuntimeException.class, + () -> { + codec.convertGeneral(inputBytes, SSM_PDU, UPER, XER); + } + ); + assertThat(re.getMessage(), containsString("too large")); + } + + @Test + public void testUperToXer_InputTooBig() { + byte[] inputBytes = new byte[10000]; + RuntimeException re = assertThrows( + RuntimeException.class, + () -> { + codec.uperToXer(inputBytes); + } + ); + assertThat(re.getMessage(), containsString("too large"));; + } + + @Test + public void testXerToUper_InputTooBig() { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < 262144L + 10; i++) { + sb.append("A"); + } + String inputXer = sb.toString(); + RuntimeException re = assertThrows( + RuntimeException.class, + () -> { + codec.xerToUper(inputXer); + } + ); + assertThat(re.getMessage(), containsString("too large"));; + } + + private static Stream convertData() { + return Stream.of( + Arguments.of(VEHICLE_EVENT_FLAGS_PDU, VEHICLE_EVENT_FLAGS_UPER, VEHICLE_EVENT_FLAGS_XER), + Arguments.of(SSM_PDU, loadResource("SSM.hex"), null) + ); + } + + private static Stream messageFrameHex() { + return Stream.of( + Arguments.of(loadResource("BSM_MF.hex")), + Arguments.of(loadResource("MAP_MF.hex")), + Arguments.of(loadResource("PSM_MF.hex")), + Arguments.of(loadResource("RSM_MF.hex")), + Arguments.of(loadResource("SDSM_MF.hex")), + Arguments.of(loadResource("SDSM_MF.hex")), + Arguments.of(loadResource("SPAT1_MF.hex")), + Arguments.of(loadResource("SPAT2_MF.hex")), + Arguments.of(loadResource("SRM_MF.hex")), + Arguments.of(loadResource("SSM_MF.hex")), + Arguments.of(loadResource("TIM_MF.hex")) + ); + } + + protected static String loadResource(String name) { + String str; + try { + str = IOUtils.resourceToString("/j2735ffm/" + name, StandardCharsets.UTF_8); + log.debug("Loaded resource: {}", str); + } catch (IOException e) { + throw new RuntimeException(e); + } + return str; + } + + private static final String VEHICLE_EVENT_FLAGS_UPER = "8740FE"; + private static final String VEHICLE_EVENT_FLAGS_XER = "10000001111111"; + private static final String VEHICLE_EVENT_FLAGS_PDU = "VehicleEventFlags"; + + private static final String SSM_PDU = "SignalStatusMessage"; + private static final String MALFORMED_SSM = "65e539"; } diff --git a/j2735-2024-ffm-lib/src/test/resources/j2735ffm/BSM_MF.hex b/j2735-2024-ffm-lib/src/test/resources/j2735ffm/BSM_MF.hex new file mode 100644 index 0000000..b27d1ee --- /dev/null +++ b/j2735-2024-ffm-lib/src/test/resources/j2735ffm/BSM_MF.hex @@ -0,0 +1 @@ +001480AD562FA8400039E8E717090F9665FE1BACC37FFFFFFFF0003BBAFDFA1FA1007FFF8000000000020214C1C100417FFFFFFE824E100A3FFFFFFFE8942102047FFFFFFE922A1026A40143FFE95D610423405D7FFEA75610322C0599FFEADFA10391C06B5FFEB7E6103CB40A03FFED2121033BC08ADFFED9A6102E8408E5FFEDE2E102BDC0885FFEDF0A1000BC019BFFF7F321FFFFC005DFFFC55A1FFFFFFFFFFFFDD1A100407FFFFFFFE1A2FFFE0000 \ No newline at end of file diff --git a/j2735-2024-ffm-lib/src/test/resources/j2735ffm/MAP_MF.hex b/j2735-2024-ffm-lib/src/test/resources/j2735ffm/MAP_MF.hex new file mode 100644 index 0000000..5b085b2 --- /dev/null +++ b/j2735-2024-ffm-lib/src/test/resources/j2735ffm/MAP_MF.hex @@ -0,0 +1 @@ +001285837f2ac48d34380f049305ad3e982bb8ebcf8974c490985c69a3e0100912330bc58f3533b990adbd037eb2124c16b4fa42a081c0800008041efec8569c5c4491d8b4d863f49400890ce792b14bc6ff5e002873ac3d5aa6c50b973f29f90c29305acda1016000010170154469aff68b45f7e48be5608177c00425d8c8fbfb68025f181010922d087fa42018137623cf27abe4f8ff44fc561c982d69a22040e00000200095e931d65dc2bdb5b2ff8000d7f312fe5bb233fecd00181d8b72fd5666bf880206838e1f8949c5e97bac74bf4f81271ca0088000100026933647627b7e3782695a36a1a5e0f3fc2384019987e820410146899bb2b9c8fce800d34d56c97d76a64048083008be9f0e24011c1c982d69dbbf57e0b62693aa763039d84818313a4f101640406d4aed322bb441e0007ef4124c16b4fa651021040000080005ea2e4a0aa0d03f8e0087600f9370fc096bf464017e0c40d0919e29ff2a004f45b99a260904ff8e8019574b68285d9db7a9a5c312e0e4c16b4f63c0c9c76922de6a591e6ca9552d5d1a0e0b05a80bf3507260b5a6f240210800020020f1ba08077b3a2c37d8dff94b856aa493e4e4014d73f24697ffdafaf000fa5007d1ff3694feb8401dd7d138c51bd09fff100515361ccc71a2bbfa400554a33f5261933535528fc46049b8210101000042eddebe9234a98e872d5707c8c8039cca6280846565393fb4c6f11c0c983186ed3d4d68d887a3a227ba63e13020b3a44f8dcd9061fa48d2f05896411fb0049305ad3e8c04800020042e61db0e822708d17b5cc17e090024b4262ae38e8dff9b800f21e1402362efb7ce08034422a6c171d9f4241fb84093c480411000040030a6ce962ac8e060609210080154c41890612aa41eb513e30101c915ee9567cd0cfc3802402fea75e0f837be34041da19a435a063d4faf020405db37524e767bf480813783e9b8a7f698098fc3a1c982d69c59008041000b8f0e27e0183f6a695f845fd30029f92784022161d7eea0037f758d85fbe1b3f3a200e21d14fa12a43b9a8b37ed40e4c16b4d20802c4000020b8a3abbd8f6e653e7b52345f35020bca212dd11678a7db404349362bc0a6578ff0a002eb36c2134f61cab4c5063737e66024aca0100400800022699bc08da9d05f6d010ce20806a21af287c184033a8196e252a851f00080c3230b8797ce5f21adb1d709260b5a7d2541d25f8bd4586171382d50c158ece4dea2a76905ede08fd7a049345001c000002010be7db90135b7a45e64f372bce43f370fc8c0234c980f6dc33b43f4d0809702284c96cbfd0ef7ed9124c16b4fa0c120040200400109db1337c4aeff3f80a38e7248ebe9ff1f7dee484bc8f09b1fce004541b0ab498ea047fd404112d1dbdb39fbfe920d8a5564cfd9024982d69f4224401c0000022eb54640890c2a9c5e869fc1000b7e8867684a0da9f6d0049f9530cc79194f7e0c0439d7b0edd166c8bf1e002bda3d1a060f73bbbf9c33bd7e3f064c1c7410082002002e6528e72d4db8c0d2a321fe4c042b17bb989675071fdd002af233e3bf44e41fd5808333d8bc0e1b70fbf80008ef14fcb1e1994432fd7a2597e220a4c16a8c810708004005c5a6bb5f210b855fe8b4fc10804ec9b04de1fb81ff4a009eb32f765940434fa320059241f0b0a1126bf3640113d5bb33dd0f8bf9540074e6feaef682c8660089aefb23819307db654afaf50c85e57ab623e9531e761ffc815a907b6c77b31fee662b080bf2509260b5a7d34100424001000005a384e20f21454147a7d740837dcf0d32e4b203fa1400a3645cbdc49eddfd5c00728ea08d34b4443ffd100fc07edaf0a42aeff3d002e71e5f865bcf90ceda7c539fa1829305ab52004d000008176938786aa95cf5cfa48bffa0818322c5b8ea2fc7fb5100607509864fe8b0bfda011647065e12cc9b71faf009205260b5019304124c16b4fa0089a070e0c30d1010830 \ No newline at end of file diff --git a/j2735-2024-ffm-lib/src/test/resources/j2735ffm/PSM_MF.hex b/j2735-2024-ffm-lib/src/test/resources/j2735ffm/PSM_MF.hex new file mode 100644 index 0000000..c9e3f71 --- /dev/null +++ b/j2735-2024-ffm-lib/src/test/resources/j2735ffm/PSM_MF.hex @@ -0,0 +1 @@ +00205d7fffc37feafd3d0d511524beaba433e42540c03bf2c4840c497e676a0e4c556c35f6fffec9289090c5dd7ca7d9149826ffed175fb22fb658eb94d1b12c3510d0200ff8dbff3fcd4abfdd4b1a38dae01c267ccc19408652aa0200421b31 \ No newline at end of file diff --git a/j2735-2024-ffm-lib/src/test/resources/j2735ffm/RSM_MF.hex b/j2735-2024-ffm-lib/src/test/resources/j2735ffm/RSM_MF.hex new file mode 100644 index 0000000..82fa226 --- /dev/null +++ b/j2735-2024-ffm-lib/src/test/resources/j2735ffm/RSM_MF.hex @@ -0,0 +1 @@ +002182B72F00878190A929E86A88B1BFD51931DD5E3826BFFB8B343AFB6741300FE5C32BA3BBDE67193178F6B7A4C55D7F92AC0F0491F9F41C7FF5C0EAAC660564EC7395A3852EF80497084BD802D70CEE59F40525C774EA036AC088DCD87C1F53E812C941115332BD8AD799163C0A9C81C982D69E83F981552BFD669E5A5F2D16C74FA63E918F8F0F604A90F3A1C12E23BBAD93CB049305AD3E920B77A512C50E4CA153321E342E7B46E1E301A8A4E59014982D4A049305AD3E880A4C16A881C14BB827A1AA228030783694F4354452130736F1056E8D0514C69E86A88BA29495BE95B96477A30E24A8B850AE4165F37B57429305AA82946C0941100F20C350AD1F10EFBC12F596922EAF9F5EE842DA5CB6A3207E642A0903C5B0D861EC9F40E00D471F4F4B0459E06117072B5EC72EDD6E8A402049001556AA6ADA64EBB41999C5CFA6548032950FA47C8D7026AC31E9E66DAADAA6D910CD5064C14484DC189C9FC95A23DF37185E92AD35592B8E0E5EFD26B49962279A2B778687051070CE2BA22974CB3B0110112EADFF2FCBC29B8D28C75BF56B3AB2B823CFE3F939D142D716370A601AC78E4A5FBCF10807A140AB40F08C4344158FB9404A4FC4F5DF57B57F128327A46AD6AD1BBBBF0FF8413612022A903960AA847A131787C4361D860C2B5BD507FAF45ECD0BAF9DC89D2817EC92DDCFDD89C28532C350AD3DEA25C2100228D4347F980931D2B8DFE2BADCA94D615CA0CBEBB2A091E58BE2B51215BE98AE235E1515E348C6CA2AF567FF1CB6449305AD3E988951821392E876D923D4C0E367116255FE6BBB512CBBCD0D5A0FEA65F650A52B5E633EA40E8458E51282F562201A529BB46DA51247A862897C9696F1FB10774216AB8E3CF15D715DB2E514268A9DA38D309EF0FA0C539C893308E63BD5AFBA44436666541EA275627FC4448A5CC2AFBCB2A88869C4D09800949BB79F0573CD5AA7DE4F46A0 \ No newline at end of file diff --git a/j2735-2024-ffm-lib/src/test/resources/j2735ffm/RTCM_MF.hex b/j2735-2024-ffm-lib/src/test/resources/j2735ffm/RTCM_MF.hex new file mode 100644 index 0000000..e2825d2 --- /dev/null +++ b/j2735-2024-ffm-lib/src/test/resources/j2735ffm/RTCM_MF.hex @@ -0,0 +1 @@ +001c3172361c5e0ffff3e134666f7da521a3116a2199e8bae98ef90ccc6d4f5da29145c14e805287affa90113d0d511550014f43 \ No newline at end of file diff --git a/j2735-2024-ffm-lib/src/test/resources/j2735ffm/SDSM_MF.hex b/j2735-2024-ffm-lib/src/test/resources/j2735ffm/SDSM_MF.hex new file mode 100644 index 0000000..40322ee --- /dev/null +++ b/j2735-2024-ffm-lib/src/test/resources/j2735ffm/SDSM_MF.hex @@ -0,0 +1 @@ +00293fee4f4354451fe189d73d8db3487a47e59668527c20a5ee4b3eb1ad79e017f38b87cebd5ff6885eb08ddfc05a5bd727e18c7bccc1edac3cf96d7e7d0afc9e48 \ No newline at end of file diff --git a/j2735-2024-ffm-lib/src/test/resources/j2735ffm/SPAT1_MF.hex b/j2735-2024-ffm-lib/src/test/resources/j2735ffm/SPAT1_MF.hex new file mode 100644 index 0000000..ee8384c --- /dev/null +++ b/j2735-2024-ffm-lib/src/test/resources/j2735ffm/SPAT1_MF.hex @@ -0,0 +1 @@ +001338000817a780000089680500204642b342b34802021a15a955a940181190acd0acd20100868555c555c00104342aae2aae002821a155715570 \ No newline at end of file diff --git a/j2735-2024-ffm-lib/src/test/resources/j2735ffm/SPAT2_MF.hex b/j2735-2024-ffm-lib/src/test/resources/j2735ffm/SPAT2_MF.hex new file mode 100644 index 0000000..c1ec549 --- /dev/null +++ b/j2735-2024-ffm-lib/src/test/resources/j2735ffm/SPAT2_MF.hex @@ -0,0 +1 @@ +0013838f65227f02487f07260b5a7ea4eea3b4400807cf548e3480e0f54afae04c14982d4a1183eefde4e4ae9d30fca80a906f32c28fceb1e486cd9fd93c2d459f18a78f3a18c1f2c7f371b0869045b061dc316d4783f3b1b5a8f02373272f5e4d3e43e28bc8d949e323784c3cb28a2c7f70c56eee2f4c349774c980780a932ee3ec7f57bc8c675aa86a1b255d837884388e1a0f23ba161109e05acd8ed8bc3de03dd7365fa25734fa22150f8d614a293c26a19a3af78506832764f1cfe2a53485e496f79e7bde2224dd7f9d839305ad3cd064f9065af84baa7369921efa9bc97ca6e6f578263bd7e14f01875bfef1e605c0a61783c42ec211d7b05260b548466f818f8f900ab51f108a63e8bc424d4912e79ddc45ed9ac9f1e46193c721453ead68310790031a7494cbf5a8b10a542d320ae0548f279541bd9236f1a4d7ec61de6626cc170b17d122a1aa087b217a403f281e27a60cacab1fd15ac7922179cb33c5c5a91e48125135a3c66ee3e5887909a8e5290f145811f925e06104656f03c644f887279bda2bcdb919bc3cc8d28adbccaa6d8be7bcc10a12ccb808721905d68b035663074e9f03703207e124c16b4fa6140d1ef05200008b6986c920383d52beb81305260b5d8460f8a4e9b40095c0d52cba5013c7cb9b735b79c1f97a7e0f319b50c13997ec7246d5c011a8493a7c0c8f3ba3957c09e21bc1374bbceb140ea4f78f0dbb3d70f3a078dbdbd8bf03a48c22b47c8673ca04a0f14ab3b6a1193eb224d4de5e8cd1ee4d67c4f3a8e4eec39e66e82ffcf3cd91254fee63fbf4f21de15d4b47781ca29bc55a1a06cc78a68a243c2f18ae8212c1e4aa4a9a968bd38661b55fbde1077ad13b07260b5a7284c1f8cbc40ec0f4460cb216c44795698e88dcf19275e0569e16c133e12bcfd74b729c79dfab729a6c7f7196629e37797fd615dda0795880e21f0cdf66275b50336d6d59e0edb37999087309ef13b682aed5e3b2c8770d3c2349bfe3a278810ad60bf7a555c5496f790de20678b60a4c16a306d3f3aa0846120af3929130033795e74cd582f374d78b4b5e001433d1bbc1fbc890dd69fa4cab02194332aff100de93cc1f5deaad78163974b54f0e288cb03593ef948282513c01d3a800e60f3a001120d58be1dd4d5dc82aceb6a218a86f1f2a2d7fc9e537a3d34b3c63c95b17b784a019a9048f33964016f6f361756ff66f2e368e15bef3440fe99e6f5f3f646e7e4f5d18685fa6f5293727ccef6748b4a5c6021e86fc0041f13640f20340c13a5b0180 \ No newline at end of file diff --git a/j2735-2024-ffm-lib/src/test/resources/j2735ffm/SPAT_MF.xml b/j2735-2024-ffm-lib/src/test/resources/j2735ffm/SPAT_MF.xml new file mode 100644 index 0000000..c5e30ad --- /dev/null +++ b/j2735-2024-ffm-lib/src/test/resources/j2735ffm/SPAT_MF.xml @@ -0,0 +1 @@ +19121110000000000000000035176222120221214221812218162212022121821852218521218522185252185221852 \ No newline at end of file diff --git a/j2735-2024-ffm-lib/src/test/resources/j2735ffm/SRM_MF.hex b/j2735-2024-ffm-lib/src/test/resources/j2735ffm/SRM_MF.hex new file mode 100644 index 0000000..8d408bb --- /dev/null +++ b/j2735-2024-ffm-lib/src/test/resources/j2735ffm/SRM_MF.hex @@ -0,0 +1 @@ +001d697125b7da9aa31b97b0fb3ec9148495a40fed6be3446a4e5b70ec1a2752b710fbfaf5ac086673396a81677da981b16a6b4905771f424e51683a70adb0afd713837a81719aed3fa8d4e347ef1b40e42d024585c757787442477e73341aae24982d69f40e4c16b4c428d8 \ No newline at end of file diff --git a/j2735-2024-ffm-lib/src/test/resources/j2735ffm/SSM.hex b/j2735-2024-ffm-lib/src/test/resources/j2735ffm/SSM.hex new file mode 100644 index 0000000..3e4ea28 --- /dev/null +++ b/j2735-2024-ffm-lib/src/test/resources/j2735ffm/SSM.hex @@ -0,0 +1 @@ +65e539dc93b843af683249404f9e0fc6b04fd122cf2ce89941dc1ab4d3d288394b59be74b1c04cfdee07bc9868311d2c1caa51f03dc764f993d0d511779e9ef22be1121c093e1af96b1d141a1ba967c329e47cf884b8beb3268e790f72270ca44c2519740d31d85f3a0e91a6bca5145e560e920d281085568f931b7067cc9e86a88b8f5957847ac6b4fa9d1b07fc2cd2e6a91f327a1aa229d5e21e478318d630a67bd8ffd0ce05537cf12267f5df5fc2794ff0804001a150fe00a679ce1c7934b4a6891be64f435445f9206bc5ff8e09b516244086367d14aef993d0d51175f310f233515cb0082fa6f907e861f0be64f435445412a1bcae64260fe3aa2470ea9ccf666f993d0d5114a9c22f0ba4406960b0cc40a0bd244e285bce95385017cefcbbac8d3070a7819776806012dd1dd66d719a089f3d143c9b843e001e4439710aacb223e510b9397770640941fcec2f9fc6a4cba57da12f160011a4d7c2fc0f7f10429d2be7409299a6cd9053c256161af6572d28cf07d9bdc620 \ No newline at end of file diff --git a/j2735-2024-ffm-lib/src/test/resources/j2735ffm/SSM_MF.hex b/j2735-2024-ffm-lib/src/test/resources/j2735ffm/SSM_MF.hex new file mode 100644 index 0000000..523fdef --- /dev/null +++ b/j2735-2024-ffm-lib/src/test/resources/j2735ffm/SSM_MF.hex @@ -0,0 +1 @@ +001e817b65e539dc93b843af683249404f9e0fc6b04fd122cf2ce89941dc1ab4d3d288394b59be74b1c04cfdee07bc9868311d2c1caa51f03dc764f993d0d511779e9ef22be1121c093e1af96b1d141a1ba967c329e47cf884b8beb3268e790f72270ca44c2519740d31d85f3a0e91a6bca5145e560e920d281085568f931b7067cc9e86a88b8f5957847ac6b4fa9d1b07fc2cd2e6a91f327a1aa229d5e21e478318d630a67bd8ffd0ce05537cf12267f5df5fc2794ff0804001a150fe00a679ce1c7934b4a6891be64f435445f9206bc5ff8e09b516244086367d14aef993d0d51175f310f233515cb0082fa6f907e861f0be64f435445412a1bcae64260fe3aa2470ea9ccf666f993d0d5114a9c22f0ba4406960b0cc40a0bd244e285bce95385017cefcbbac8d3070a7819776806012dd1dd66d719a089f3d143c9b843e001e4439710aacb223e510b9397770640941fcec2f9fc6a4cba57da12f160011a4d7c2fc0f7f10429d2be7409299a6cd9053c256161af6572d28cf07d9bdc620 \ No newline at end of file diff --git a/j2735-2024-ffm-lib/src/test/resources/j2735ffm/TIM_MF.hex b/j2735-2024-ffm-lib/src/test/resources/j2735ffm/TIM_MF.hex new file mode 100644 index 0000000..cb6d3a3 --- /dev/null +++ b/j2735-2024-ffm-lib/src/test/resources/j2735ffm/TIM_MF.hex @@ -0,0 +1 @@ +001f85fe7591fd9e4f4354455420535452124c16b4fa4e724f43ef73d2f04fd7ad0ff09260b5a7d2a6249ba8936ffa29bac57176dadf5389a4000a2001c3a03dc06ba5067b8404632ae6a0503389c06966bc120c5900f771c1c6306a9a6bf319901630a0f2403124536d3e8476d9af77a7bf218901103a881122806195dcc32733526fe545fa217f819306eff7fdceaafa0230c87f355e98cb516360080a0002f348c72a3f584b11e4be99002ad4a0d12394d435cfda9fe80267e176eb96a2ff07260b5a7f4b9d40747f5ffe014b670db5da84587b200050400f55bfce9e9bc9d913782889646ff0126933eacd08beff7f5447df8cf48052da9a01005a0001bc2fa39c11fc2e7046450f0952d4dfe024ddb71fd2d3b502b9b2551e1d3f8b210e3c4000b00020c2dd46d113a3f21710e9416b92d5bfc14982d60dc94824a8e5c5095c1007d4e362e82720800270a04182240bda99c42bb6d94ff5f9a52a3014cd051a29843d4489d71d4150ae35b7533039e58186a0608ac0fc05f4bf24a4e851d3d0a801880f339e17e05ca32567a49b73b932580eecfc46064078a2e0120158f22817d1fb85eee4e66a4dfca8bf442fcca15810716140920643215dc607f829305ac1b92904951cb8a12b8200fa9c6c5d04e410004c0a0671380d2cd782418b201eee3838c60d534d7e633202c6141e4806248a6da7d08edb35eef4f7e431202207510224500c32bb9864e66a4dfca8bf442ff05260b5837252092a397142570401f538d8ba09c820009976b50fc6379b168326f9bdfe0a4c16b06e4a4125472e284ae0803ea71b1741390400132ed63059cde2d064df37c448db82d49305ad3e949305ad3e88bf939305ad3010933071674e428880448e75d46d038a29245b0b778f7001044f43ef73d2f04fd7ad0ff09260b5a7d2a6249ba8936ffa29bac57176dadf5389a4000a2001c3a03dc06ba5067b8404632ae6a0503389c06966bc120c5900f771c1c6306a9a6bf319901630a0f2403124536d3e8476d9af77a7bf218901103a881122806195dcc32733526fe545fa217f819306eff7fdceaafa0230c87f355e98cb516360080a0002f348c72a3f584b11e4be99002ad4a0d12394d435cfda9fe80267e176eb96a2ff07260b5a7f4b9d40747f5ffe014b670db5da84587b200050400f55bfce9e9bc9d913782889646ff0126933eacd08beff7f5447df8cf48052da9a01005a0001bc2fa39c11fc2e7046450f0952d4dfe024ddb71fd2d3b502b9b2551e1d3f8b210e3c4000b00020c2dd46d113a3f21710e9416b92d5bfc14982d60dc94824a8e5c5095c1007d4e362e82720800270a04182240bda99c42bb6d94ff5f9a52a3014cd051a29843d4489d71d4150ae35b7533039e58186a0608ac0fc05f4bf24a4e851d3d0a801880f339e17e05ca32567a49b73b932580eecfc46064078a2e0120158f22817d1fb85eee4e66a4dfca8bf442fcca15810716140920643215dc607f829305ac1b92904951cb8a12b8200fa9c6c5d04e410004c0a0671380d2cd782418b201eee3838c60d534d7e633202c6141e4806248a6da7d08edb35eef4f7e431202207510224500c32bb9864e66a4dfca8bf442ff05260b5837252092a397142570401f538d8ba09c820009976b50fc6379b168326f9bdfe0a4c16b06e4a4125472e284ae0803ea71b1741390400132ed63059cde2d064df37c4401b705a049305ad3e9024982d69f445fc9c982d69808499838b3a7214440224738ea3681c514922d85bbc7b8008227a1f7b9e97827ebd607f8093499f566845f7fbfaa23efc67a40296d4d00802d0000de17d1ce08fe173823228784a96a711136e0b524c16b4fa524c16b4fa22fe4e4c16b4c0424cc1c59d390a22011239df51b40e28a4916c2dde3dc004113d0fbdcf4bc13f5eb03fc049a4cfab3422fbfdfd511f7e33d2014b6a680401680006f0be8e7047f0b9c119143c254b538899b705a9260b5a7d29260b5a7d117f27260b5a60212660e2ce9c851100891cefa8da07145248b616ef1ee002089e87dee7a5e09faf581fe024d267d59a117dfefea88fbf19e900a5b5340200b40003785f473823f85ce08c8a1e12a5a9c450db82d49305ad3e949305ad3e88bf939305ad3010933071674e4288804480 \ No newline at end of file diff --git a/j2735-2024-ffm-lib/src/test/resources/j2735ffm/asnapplication.dll b/j2735-2024-ffm-lib/src/test/resources/j2735ffm/asnapplication.dll index 7c333b3..310b7e2 100644 Binary files a/j2735-2024-ffm-lib/src/test/resources/j2735ffm/asnapplication.dll and b/j2735-2024-ffm-lib/src/test/resources/j2735ffm/asnapplication.dll differ diff --git a/j2735-2024-ffm-lib/src/test/resources/j2735ffm/libasnapplication.so b/j2735-2024-ffm-lib/src/test/resources/j2735ffm/libasnapplication.so index 76e8218..7d52e7f 100644 Binary files a/j2735-2024-ffm-lib/src/test/resources/j2735ffm/libasnapplication.so and b/j2735-2024-ffm-lib/src/test/resources/j2735ffm/libasnapplication.so differ diff --git a/lib/README.md b/lib/README.md index ce37604..0f70341 100644 --- a/lib/README.md +++ b/lib/README.md @@ -1 +1,8 @@ -Regenerated native libraries go here \ No newline at end of file +Regenerated native libraries go here + +After regenerating the native libraries to here, also be sure to copy them to the [j2735-2024-ffm-lib/src/test/resources/j2735ffm](../j2735-2024-ffm-lib/src/test/resources/j2735ffm) folder since they are required for the unit tests in that Java project via: + +```bash +cp libasnapplication.so ../j2735-2024-ffm-lib/src/test/resources/j2735ffm/ +cp asnapplication.dll ../j2735-2024-ffm-lib/src/test/resources/j2735ffm/ +``` \ No newline at end of file diff --git a/lib/asnapplication.dll b/lib/asnapplication.dll index 7c333b3..310b7e2 100644 Binary files a/lib/asnapplication.dll and b/lib/asnapplication.dll differ diff --git a/lib/libasnapplication.so b/lib/libasnapplication.so index 76e8218..7d52e7f 100644 Binary files a/lib/libasnapplication.so and b/lib/libasnapplication.so differ diff --git a/src/convert.c b/src/convert.c index 8836f68..38007cd 100644 --- a/src/convert.c +++ b/src/convert.c @@ -21,34 +21,37 @@ #define PDU_Type_Ptr NULL -#ifdef _WIN64 -const void* nullptr = NULL; -#endif - extern asn_TYPE_descriptor_t *asn_pdu_collection[]; +const int RETURN_ERROR = -1; -static enum asn_transfer_syntax abbrev_to_syntax(const char * abbrev) { +static enum asn_transfer_syntax abbrev_to_syntax(const char * abbrev, char * err_buf, size_t err_buf_len) { + if (!abbrev) { + snprintf(err_buf, err_buf_len, "Error: NULL encoding parameter\n"); + return ATS_INVALID; + } if (strcmp("xer", abbrev) == 0) { return ATS_CANONICAL_XER; } if (strcmp("uper", abbrev) == 0) { return ATS_UNALIGNED_BASIC_PER; } - fprintf(stderr, "Unknown encoding: %s Expect 'xer' or 'uper'.\n", abbrev); - exit(EXIT_FAILURE); + snprintf(err_buf, err_buf_len, "Unknown encoding: %s Expect 'xer' or 'uper'.\n", abbrev); + return ATS_INVALID; } -size_t convert_bytes(const char * pdu_name, - const char * from_encoding, - const char * to_encoding, - const uint8_t * ibuf, - size_t ibuf_len, - uint8_t * obuf, - size_t max_obuf_len) { +int convert_bytes(const char * pdu_name, + const char * from_encoding, + const char * to_encoding, + const uint8_t * ibuf, + size_t ibuf_len, + uint8_t * obuf, + size_t max_obuf_len, + char * err_buf, + size_t err_buf_len) { asn_TYPE_descriptor_t *pduType = PDU_Type_Ptr; @@ -57,23 +60,31 @@ size_t convert_bytes(const char * pdu_name, if(*pdu) { pduType = *pdu; } else { - fprintf(stderr, "%s: Unrecognized PDU.\n", pdu_name); - exit(EXIT_FAILURE); + snprintf(err_buf, err_buf_len, "Unrecognized PDU: %s\n", pdu_name); + return RETURN_ERROR; } - enum asn_transfer_syntax osyntax = abbrev_to_syntax(to_encoding); - enum asn_transfer_syntax isyntax = abbrev_to_syntax(from_encoding); + enum asn_transfer_syntax osyntax = abbrev_to_syntax(to_encoding, err_buf, err_buf_len); + if (osyntax == ATS_INVALID) { + snprintf(err_buf, err_buf_len, "Unknown output encoding: %s Expect 'xer' or 'uper'.\n", to_encoding); + return RETURN_ERROR; + } + enum asn_transfer_syntax isyntax = abbrev_to_syntax(from_encoding, err_buf, err_buf_len); + if (isyntax == ATS_INVALID) { + snprintf(err_buf, err_buf_len, "Unknown input encoding: %s Expect 'xer' or 'uper'.\n", from_encoding); + return RETURN_ERROR; + } - const asn_codec_ctx_t *opt_codec_ctx = nullptr; - void *structure = nullptr; + const asn_codec_ctx_t *opt_codec_ctx = NULL; + void *structure = NULL; // Decode asn_dec_rval_t rval = asn_decode(opt_codec_ctx, isyntax, pduType, &structure, ibuf, ibuf_len); if (rval.code != RC_OK) { - fprintf(stderr, "%s: Error decoding PDU\n", pduType->name); ASN_STRUCT_FREE(*pduType, structure); - exit(EXIT_FAILURE); + snprintf(err_buf, err_buf_len, "%s: Error decoding PDU\n", pduType->name); + return RETURN_ERROR; } // Check constraints @@ -81,18 +92,18 @@ size_t convert_bytes(const char * pdu_name, size_t errlen = sizeof(errbuff); int constraint_result = asn_check_constraints(pduType, structure, errbuff, &errlen); if (constraint_result != 0) { - fprintf(stderr, "Decoding was successful, but constraint check failed, can't re-encode: %s\n", errbuff); + snprintf(err_buf, err_buf_len, "Decoding was successful, but constraint check failed, can't re-encode: %s\n", errbuff); ASN_STRUCT_FREE(*pduType, structure); - exit(EXIT_FAILURE); + return RETURN_ERROR; } // Encode - asn_encode_to_new_buffer_result_t enc_result = {NULL, 0, nullptr}; + asn_encode_to_new_buffer_result_t enc_result = {NULL, 0, NULL}; enc_result = asn_encode_to_new_buffer(opt_codec_ctx, osyntax, pduType, structure); if (!enc_result.buffer) { - fprintf(stderr, "Error encoding to %d\n", osyntax); + snprintf(err_buf, err_buf_len, "Error encoding to %d\n", osyntax); ASN_STRUCT_FREE(*pduType, structure); - exit(EXIT_FAILURE); + return RETURN_ERROR; } ASN_STRUCT_FREE(*pduType, structure); @@ -100,7 +111,9 @@ size_t convert_bytes(const char * pdu_name, if (num_encoded_bytes > max_obuf_len) { memcpy(obuf, enc_result.buffer, max_obuf_len); - fprintf(stderr, "Warning, truncating output. Max buffer size %ld is too small\n", max_obuf_len); + snprintf(err_buf, err_buf_len, "Error, truncating output. Max buffer size %ld is too small\n", max_obuf_len); + free(enc_result.buffer); + return RETURN_ERROR; } else { memcpy(obuf, enc_result.buffer, num_encoded_bytes); } diff --git a/src/convert.h b/src/convert.h index 7b178b4..76bb70a 100644 --- a/src/convert.h +++ b/src/convert.h @@ -25,25 +25,29 @@ /** Convert a byte array representation of a J2735 PDU from one encoding to another. UPER format is rww bytes, not hex. - JER and XER are 8-bit text. + XER is 8-bit text. - @param pdu_name String with the J2375 PDU, e.g., "MessageFrame", "BasicSafetyMessage". - @param from_encoding String with the name of the encoding of the input ("JER", "XER", or "UPER"). + @param pdu_name String with the J2735 PDU, e.g., "MessageFrame", "BasicSafetyMessage". + @param from_encoding String with the name of the encoding of the input ("XER", or "UPER"). @param to_encoding Target encoding for the output ("XER", or "UPER"). @param ibuf The input byte array in raw UPER, or XER text format. @param ibuf_len The length of the input byte array. @param obuf The buffer to store the output byte array. @param max_obuf_len The maximum length of the output buffer. - @return The length of the converted output byte array. + @param err_buf A buffer to store any error messages, populated with the error if the return value is -1. + @param err_buf_len The size of the error buffer. + @return The length of the converted output byte array, or -1 if there was an error doing the conversion. */ -size_t convert_bytes( +int convert_bytes( const char * pdu_name, const char * from_encoding, const char * to_encoding, const uint8_t * ibuf, size_t ibuf_len, uint8_t * obuf, - size_t max_obuf_len); + size_t max_obuf_len, + char * err_buf, + size_t err_buf_len);