diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..c7f9d1a --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,95 @@ +# AGENTS.md + +This file provides guidance to AI agents when working with code in this repository. +## Build Commands + +All commands run from the `imagedecoder/` directory: + +```bash +# Build and run tests +mvn clean install + +# Skip tests +mvn clean install -DskipTests + +# Run tests only +mvn test + +# Run a single test class +mvn test -Dtest=OpenJpegDecoderTest +mvn test -Dtest=WsqDecoderTest + +# Sonar analysis (requires secrets) +mvn verify sonar:sonar -Psonar +``` + +Build requires Java 21 with `--enable-preview` enabled (configured in both pom.xml files). Tests use the `maven-surefire-plugin` with several `--add-opens` JVM flags. + +## Architecture + +This is a pure-Java biometric image decoding library for the MOSIP identity platform. It ports two native C codec libraries to Java: + +- **JPEG2000** — ported from [OpenJPEG](https://github.com/lessandro/nbis) (`openjp2`) +- **WSQ** — ported from [NBIS WSQ](https://github.com/lessandro/nbis) + +### Module Layout + +- **`imagedecoder/`** — the published library (`io.mosip.imagedecoder:imagedecoder`) +- **`sample/`** — standalone CLI demo app that reads image files from disk and decodes them + +### Core API + +The single entry point is `IImageDecoderApi`: + +```java +Response decode(DecoderRequestInfo requestInfo); +``` + +- `DecoderRequestInfo` — takes raw image bytes (`imageData`) and a `isBufferedImage` flag +- `DecoderResponseInfo` — returns image metadata (width, height, DPI, color space, bit rate, compression ratio, lossless flag) plus the decoded pixel data as base64url-encoded bytes and optionally a `BufferedImage` +- `Response` — wraps any response with `statusCode`, `statusMessage`, and `response` + +Two implementations: +- `OpenJpegDecoder` — handles JPEG2000 (`.jp2`) +- `WsqDecoder` — handles WSQ (`.wsq`) + +### Package Structure (`imagedecoder/src/main/java/io/mosip/imagedecoder/`) + +| Package | Purpose | +|---|---| +| `spi/` | Public API interface (`IImageDecoderApi`) | +| `model/` | Request/response models; also C-struct mirrors under `model/openjpeg/` and `model/wsq/` | +| `openjpeg/` | JPEG2000 codec implementation — `OpenJpegDecoder` + many `*Helper` classes | +| `wsq/` | WSQ codec implementation — `WsqDecoder` + many `*Helper` classes | +| `constant/` | Error codes and named constants for both codecs | +| `exceptions/` | `DecoderException` | +| `util/` | `Base64UrlUtil`, `ByteStreamUtil`, `ByteSwapperUtil`, and codec-specific math/image utils | +| `logger/` | Thin wrapper around `kernel-logger-logback` | + +### Key Design Details + +The codec implementations (`openjpeg/` and `wsq/`) are direct Java ports of C code. The `model/openjpeg/` and `model/wsq/` packages contain Java classes that mirror C structs from the original libraries. `ByteBufferContext` is used to simulate C-style sequential byte reads. + +The `*Helper` classes (e.g., `J2KHelper`, `WsqDecoderHelper`) are large stateless utility classes containing the ported algorithm logic. They are called by the `Decoder` classes. + +Logging follows the MOSIP convention: `logger.info(LOGGER_SESSIONID, LOGGER_IDTYPE, LOGGER_EMPTY, message)`. + +### Sample Application + +The `sample/` module runs from its `target/` directory after `mvn package`: + +```bash +# Decode JPEG2000 files from a folder +java -cp sample-imagedecoder-*.jar;lib\* io.mosip.imagedecoder.sample.SampleImageDecoderApplication \ + "io.mosip.imagedecoder.image.type=0" "io.mosip.imagedecoder.image.folder.path=/BiometricInfo" + +# Decode WSQ files from a folder +java -cp sample-imagedecoder-*.jar;lib\* io.mosip.imagedecoder.sample.SampleImageDecoderApplication \ + "io.mosip.imagedecoder.image.type=1" "io.mosip.imagedecoder.image.folder.path=/BiometricInfo" +``` + +Image type: `0` = JP2000, `1` = WSQ. + +### CI/CD + +GitHub Actions (`.github/workflows/push-trigger.yml`) triggers on pushes to `master`, `develop*`, `1.*`, `release*`. It reuses shared MOSIP workflows from `mosip/kattu@master-java21` for build, Nexus publish, and Sonar analysis. Publishing to Maven Central uses the `central-publishing-maven-plugin` with `autoPublish=false`. \ No newline at end of file diff --git a/imagedecoder/pom.xml b/imagedecoder/pom.xml index c6303b4..4ead849 100644 --- a/imagedecoder/pom.xml +++ b/imagedecoder/pom.xml @@ -6,7 +6,7 @@ io.mosip.imagedecoder imagedecoder - 0.0.1-SNAPSHOT + 0.10.0-SNAPSHOT Imagedecoder http://github.com/mosip/imagedecoder @@ -40,9 +40,9 @@ 3.0.1 - 1.2.1-SNAPSHOT - 1.2.1-SNAPSHOT - 1.2.1-SNAPSHOT + 1.4.0-SNAPSHOT + 1.4.0-SNAPSHOT + 1.4.0-SNAPSHOT diff --git a/imagedecoder/src/main/java/io/mosip/imagedecoder/util/ByteStreamUtil.java b/imagedecoder/src/main/java/io/mosip/imagedecoder/util/ByteStreamUtil.java index 3df0ce3..45005c5 100644 --- a/imagedecoder/src/main/java/io/mosip/imagedecoder/util/ByteStreamUtil.java +++ b/imagedecoder/src/main/java/io/mosip/imagedecoder/util/ByteStreamUtil.java @@ -53,6 +53,12 @@ public int getBuffer(ByteBufferContext byteBufCont, byte[] target, int size) { public int getBufferU(ByteBufferContext byteBufCont, byte[] target, int size) { int pos = byteBufCont.getBuffer().position(); + // Validate that the requested size does not exceed remaining bytes + int remaining = byteBufCont.getBuffer().remaining(); + if (size > remaining) { + size = remaining; + } + System.arraycopy(byteBufCont.getBuffer().array(), pos, target, 0, size); byteBufCont.getBuffer().position(pos + size); return size; @@ -60,6 +66,11 @@ public int getBufferU(ByteBufferContext byteBufCont, byte[] target, int size) { public int getBufferU(ByteBufferContext byteBufCont, byte[] target, int dstPos, int size) { int pos = byteBufCont.getBuffer().position(); + // Validate that the requested size does not exceed remaining bytes + int remaining = byteBufCont.getBuffer().remaining(); + if (size > remaining) { + size = remaining; + } System.arraycopy(byteBufCont.getBuffer().array(), pos, target, dstPos, size); byteBufCont.getBuffer().position(pos + size); return size; diff --git a/imagedecoder/src/main/java/io/mosip/imagedecoder/wsq/WsqFetHelper.java b/imagedecoder/src/main/java/io/mosip/imagedecoder/wsq/WsqFetHelper.java index 3bd2861..794fe19 100644 --- a/imagedecoder/src/main/java/io/mosip/imagedecoder/wsq/WsqFetHelper.java +++ b/imagedecoder/src/main/java/io/mosip/imagedecoder/wsq/WsqFetHelper.java @@ -161,7 +161,6 @@ public int reallocFet(WsqFet fet, int newlen) { @SuppressWarnings({ "java:S135", "java:S2629", "java:S3626" }) public int extractFet(StringBuilder value, char[] feature, WsqFet fet) { int item; - for (item = 0; (item < fet.getNum()); item++) { if (fet.getNames()[item] != null && feature != null) { if (StringUtil.getInstance().stringCompare(fet.getNames()[item].trim(), diff --git a/imagedecoder/src/main/java/io/mosip/imagedecoder/wsq/WsqTableIOHelper.java b/imagedecoder/src/main/java/io/mosip/imagedecoder/wsq/WsqTableIOHelper.java index 8545c1c..a54fa02 100644 --- a/imagedecoder/src/main/java/io/mosip/imagedecoder/wsq/WsqTableIOHelper.java +++ b/imagedecoder/src/main/java/io/mosip/imagedecoder/wsq/WsqTableIOHelper.java @@ -6,12 +6,11 @@ import java.nio.charset.StandardCharsets; import java.text.MessageFormat; - -import io.mosip.kernel.core.logger.spi.Logger; -import io.mosip.imagedecoder.logger.ImageDecoderLogger; +import java.util.Arrays; import io.mosip.imagedecoder.constant.wsq.WsqConstant; import io.mosip.imagedecoder.constant.wsq.WsqErrorCode; +import io.mosip.imagedecoder.logger.ImageDecoderLogger; import io.mosip.imagedecoder.model.ByteBufferContext; import io.mosip.imagedecoder.model.wsq.WsqFet; import io.mosip.imagedecoder.model.wsq.WsqHeaderForm; @@ -21,6 +20,7 @@ import io.mosip.imagedecoder.util.ByteStreamUtil; import io.mosip.imagedecoder.util.StringUtil; import io.mosip.imagedecoder.util.wsq.WsqUtil; +import io.mosip.kernel.core.logger.spi.Logger; public class WsqTableIOHelper { private Logger logger = ImageDecoderLogger.getLogger(WsqTableIOHelper.class); @@ -397,9 +397,11 @@ private int getComment(byte[] comment, ByteBufferContext cbufptr /* current byte /* have one here by default due to the calloc of one extra byte at */ /* the end. */ } catch (Exception ex) { - ex.printStackTrace(); - comment = " ".toString().getBytes(); + logger.info(LOGGER_SESSIONID, LOGGER_IDTYPE, LOGGER_EMPTY, "getComment : comment empty " + ex.getLocalizedMessage()); + Arrays.fill(comment, (byte) '\0'); } + // Add a null terminator at the end + comment[comment.length-1] = '\0'; // Null terminator (ASCII 0) return ret; } @@ -441,7 +443,6 @@ public int getWsqHeaderForm(WsqHeaderForm headerForm, /* frame header structure headerForm.setWsqEncoder((int) ByteStreamUtil.getInstance().getUByte(cbufptr)); headerForm.setSoftware(ByteStreamUtil.getInstance().getUShort(cbufptr)); - return ret; } @@ -482,9 +483,9 @@ public int getWsqNistCom(WsqFet nistcom, byte[] imageData, int imagelength) { int cs = hdrSize - 2; /* Allocate including a possible NULL terminator. */ byte[] comment = new byte[cs + 1]; - if ((ret = getComment(comment, cbufptr)) != 0) return ret; + commentText.append(new String(comment, StandardCharsets.UTF_8)); if ((ret = WsqFetHelper.getInstance().string2fet(nistcom, commentText.toString().toCharArray())) != 0) { diff --git a/sample/pom.xml b/sample/pom.xml index 74c07c2..7f0d10d 100644 --- a/sample/pom.xml +++ b/sample/pom.xml @@ -5,7 +5,7 @@ io.mosip.imagedecoder sample-imagedecoder jar - 0.0.1-SNAPSHOT + 0.10.0-SNAPSHOT imagedecoder http://maven.apache.org Image decoder testing sample @@ -32,17 +32,17 @@ 3.1.2 3.0.2 - 1.6.7 + 1.6.14 3.2.5 3.0.1 - 1.2.1-SNAPSHOT - 1.2.1-SNAPSHOT - 1.2.1-SNAPSHOT - 0.0.1-SNAPSHOT + 1.4.0-SNAPSHOT + 1.4.0-SNAPSHOT + 1.4.0-SNAPSHOT + 0.10.0-SNAPSHOT diff --git a/sample/run_jp2000_decoder.bat b/sample/run_jp2000_decoder.bat index 86fcc30..8e435b5 100644 --- a/sample/run_jp2000_decoder.bat +++ b/sample/run_jp2000_decoder.bat @@ -1 +1 @@ -java -cp sample-imagedecoder-0.0.1-SNAPSHOT.jar;lib\* io.mosip.imagedecoder.sample.SampleImageDecoderApplication "io.mosip.imagedecoder.image.type=0" "io.mosip.imagedecoder.image.folder.path=/BiometricInfo" \ No newline at end of file +java -cp sample-imagedecoder-0.10.0-SNAPSHOT.jar;lib\* io.mosip.imagedecoder.sample.SampleImageDecoderApplication "io.mosip.imagedecoder.image.type=0" "io.mosip.imagedecoder.image.folder.path=/BiometricInfo" \ No newline at end of file diff --git a/sample/run_wsq_decoder.bat b/sample/run_wsq_decoder.bat index 0111dac..d6f77bd 100644 --- a/sample/run_wsq_decoder.bat +++ b/sample/run_wsq_decoder.bat @@ -1 +1 @@ -java -cp sample-imagedecoder-0.0.1-SNAPSHOT.jar;lib\* io.mosip.imagedecoder.sample.SampleImageDecoderApplication "io.mosip.imagedecoder.image.type=1" "io.mosip.imagedecoder.image.folder.path=/BiometricInfo" \ No newline at end of file +java -cp sample-imagedecoder-0.10.0-SNAPSHOT.jar;lib\* io.mosip.imagedecoder.sample.SampleImageDecoderApplication "io.mosip.imagedecoder.image.type=1" "io.mosip.imagedecoder.image.folder.path=/BiometricInfo" \ No newline at end of file