Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# CHANGELOG

## 1.6.4 - 2026-08-10

- Support new region ap-southeast-3 (Indonesia)
- Support scan gateway for FSCS and FSVA
- Add an Error Handling section to the README documenting SDK and service error codes and messages
- Fix CVE-2026-46340 and CVE-2026-33871

## 1.6.3 - 2026-04-13

- Support new regions af-south-1
Expand Down
60 changes: 46 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ Creates a new instance of the `AmaasClient` class, and provisions essential sett

| Parameter | Description |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| region | The region you obtained your api key. Value provided must be one of the Vision One regions, e.g. `us-east-1`, `eu-central-1`, `ap-northeast-1`, `ap-southeast-2`, `ap-southeast-1`, `ap-south-1`, `me-central-1`,`eu-west-2`,`ca-central-1`,`af-south-1`, etc. If host is given, region will be ignored. |
| region | The region you obtained your api key. Value provided must be one of the Vision One regions, e.g. `us-east-1`, `eu-central-1`, `ap-northeast-1`, `ap-southeast-2`, `ap-southeast-1`, `ap-south-1`, `me-central-1`,`eu-west-2`,`ca-central-1`,`af-south-1`,`ap-southeast-3`, etc. If host is given, region will be ignored. |
| host | The host ip address of self hosted AMaaS scanner. Ignore if to use Trend AMaaS service |
| apikey | Your own Vision One API Key. |
| timeoutInSecs | Timeout to cancel the connection to server in seconds. Valid value is 0, 1, 2, ... ; default to 300 seconds. |
Expand All @@ -205,7 +205,7 @@ Creates a new instance of the `AmaasClient` class, and provisions essential sett

| Parameter | Description |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| region | The region you obtained your api key. Value provided must be one of the Vision One regions, e.g. `us-east-1`, `eu-central-1`, `ap-northeast-1`, `ap-southeast-2`, `ap-southeast-1`, `ap-south-1`, `me-central-1`,`eu-west-2`,`ca-central-1`,`af-south-1` ,etc. |
| region | The region you obtained your api key. Value provided must be one of the Vision One regions, e.g. `us-east-1`, `eu-central-1`, `ap-northeast-1`, `ap-southeast-2`, `ap-southeast-1`, `ap-south-1`, `me-central-1`,`eu-west-2`,`ca-central-1`,`af-south-1`,`ap-southeast-3` ,etc. |
| apikey | Your own Vision One API Key. |
| timeoutInSecs | Timeout to cancel the connection to server in seconds. Valid value is 0, 1, 2, ... ; default to 300 seconds. |

Expand Down Expand Up @@ -367,7 +367,7 @@ public class AMaasScanResultVerbose {

### `AMaasException`

The AMaasException class is the AMaaS SDK exception class.
The AMaasException class is the AMaaS SDK exception class. Every checked error the SDK raises — whether detected locally or relayed from the gRPC server — is thrown as an `AMaasException` carrying an `AMaasErrorCode`. See [Error Handling](#error-handling) below for the full reference of what callers actually receive.

```java
public final class AMaasException extends Exception {
Expand All @@ -381,19 +381,51 @@ public final class AMaasException extends Exception {

---

### `AMaasErrorCode`
## Error Handling

AMaasErrorCode is a enum type containing all the error conditions thrown by the `AMaasException` class. The error conditions are as follows:
Every checked error raised by the SDK is thrown as an `AMaasException`. Call `getErrorCode()` to get the associated `AMaasErrorCode` enum value, and `getMessage()` (inherited from `Exception`) to get the fully-formatted message text.

| Enum Type | Error Message Templates | Description |
| ------------------------------- | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| MSG_ID_ERR_INVALID_REGION | %s is not a supported region. | The region code provided to the AMaasClient constructor is not a valid region. |
| MSG_ID_ERR_MISSING_AUTH | Must provide an API key to use the client. | The API Key provided to the AMaasClient constructor cannot be empty or `null`. |
| MSG_ID_ERR_KEY_AUTH_FAILED | You are not authenticated. Invalid C1 token or Api Key | The API key is invalid. Please make sure a correct Vision One Api key is used. |
| MSG_ID_ERR_FILE_NOT_FOUND | Failed to open file. No such file or directory %s. | The given file cannot be found. Please make sure the file exists. |
| MSG_ID_ERR_FILE_NO_PERMISSION | Failed to open file. Permission denied to open %s. | There is a file access permission issue. Please make sure the SDK has read permission to the file. |
| MSG_ID_GRPC_ERROR | Received gRPC status code: %d, msg: %s. | gRpc error was reported with the status code. For details, please refer to published [gRPC Status Codes](https://grpc.github.io/grpc/core/md_doc_statuscodes.html) |
| MSG_ID_ERR_UNEXPECTED_INTERRUPT | Unexpected interrupt encountered. | An unexpected interrupt signal was received at the client. |
```java
try {
result = client.scanFile(fileName, tags);
} catch (AMaasException e) {
System.out.println("code: " + e.getErrorCode() + ", message: " + e.getMessage());
}
```

The **Source** column classifies each error:

- **SDK-native** — detected and formatted entirely on the client (bad region, missing key, file I/O, tag validation, TLS setup).
- **SDK-mapped** — triggered by a gRPC response from the service, but the caller-visible message is produced by the SDK.
- **Service** — a message produced by the service and relayed unchanged. **This SDK has no such rows** — it never forwards the service's message text, only the gRPC status code (see below).

For gRPC errors, the SDK only forwards the **numeric status code and the code's own name** (via [`io.grpc.Status.Code`](https://grpc.github.io/grpc/java/io/grpc/Status.Code.html)) — it does **not** forward the service's descriptive message text, except for `UNAUTHENTICATED`, where it substitutes a fixed message of its own. See note 1 below.

| AMaasErrorCode | Message returned to the caller | Cause | Source |
|----------------|---------------------------------|-------|--------|
| `MSG_ID_ERR_INVALID_REGION` | `<region> is not a supported region, region value should be one of <list>` | The `region` passed to the `AMaasClient` constructor is not a recognized Vision One region | SDK-native |
| `MSG_ID_ERR_MISSING_AUTH` | `Must provide an API key to use the client.` | No API key was supplied to the `AMaasClient` constructor | SDK-native |
| `MSG_ID_ERR_LOAD_SSL_CERT` | `Failed to load SSL certificate.` | The client's TLS trust material (default certificate, or the custom `caCertPath`) could not be loaded | SDK-native |
| `MSG_ID_ERR_FILE_NOT_FOUND` | `Failed to open file. No such file or directory <path>.` | The file passed to `scanFile()` / `AMaasFileReader` does not exist, or could not be opened | SDK-native |
| `MSG_ID_ERR_FILE_NO_PERMISSION` | `Failed to open file. Permission denied to open <path>.` | The SDK process does not have read permission on the file | SDK-native |
| `MSG_ID_ERR_MAX_NUMBER_OF_TAGS` | `Exceeded maximum number of tags: 8` | More than 8 tags were passed to `scanFile()` / `scanBuffer()` / `scanRun()` | SDK-native |
| `MSG_ID_ERR_LENGTH_OF_TAG` | `Tag length must be between 1 and 63: <tag>.` | A tag is `null`, empty, or longer than 63 characters | SDK-native |
| `MSG_ID_ERR_UNEXPECTED_INTERRUPT` | `Unexpected interrupt encountered.` | The calling thread was interrupted while waiting for the scan to finish | SDK-native |
| `MSG_ID_ERR_KEY_AUTH_FAILED` | `Authorization key cannot be authenticated.` | The service returned gRPC `UNAUTHENTICATED` (16) — no/invalid API key, or (per the service) the account lacks file-scan permission. The SDK always substitutes this one message for `UNAUTHENTICATED`; the service's more specific reason is not exposed to the caller. | SDK-mapped |
| `MSG_ID_GRPC_ERROR` | `Received gRPC status code: 3, msg: INVALID_ARGUMENT.` | Service reported `INVALID_ARGUMENT` (3) — too many tags, a tag too long or empty, illegal characters in `cloudAccountId`, or a malformed SHA1/SHA256 computed by the SDK | SDK-mapped |
| `MSG_ID_GRPC_ERROR` | `Received gRPC status code: 5, msg: NOT_FOUND.` | Service reported `NOT_FOUND` (5) — customer ID not found | SDK-mapped |
| `MSG_ID_GRPC_ERROR` | `Received gRPC status code: 7, msg: PERMISSION_DENIED.` | Service reported `PERMISSION_DENIED` (7) — the SDK feature is not enabled for the account | SDK-mapped |
| `MSG_ID_GRPC_ERROR` | `Received gRPC status code: 8, msg: RESOURCE_EXHAUSTED.` | Service reported `RESOURCE_EXHAUSTED` (8) — hourly scan quota exceeded, file exceeds the maximum allowed size, or scan resource could not be allocated | SDK-mapped |
| `MSG_ID_GRPC_ERROR` | `Received gRPC status code: 9, msg: FAILED_PRECONDITION.` | Service reported `FAILED_PRECONDITION` (9) — incorrect protocol stage reported by the SDK | SDK-mapped |
| `MSG_ID_GRPC_ERROR` | `Received gRPC status code: 12, msg: UNIMPLEMENTED.` | Service reported `UNIMPLEMENTED` (12) — Predictive Machine Learning (PML) requested but not supported | SDK-mapped |
| `MSG_ID_GRPC_ERROR` | `Received gRPC status code: 13, msg: INTERNAL.` | Service reported `INTERNAL` (13) — metadata retrieval failure, network connection error, generic internal error, missing preamble information, or unclear scan result | SDK-mapped |

**Notes**

1. For `MSG_ID_GRPC_ERROR`, the caller sees only the gRPC status code's number and its symbolic name (e.g. `INTERNAL`), **not** the service's descriptive text. Because several distinct service-side conditions share one gRPC code (the Cause column lists the known ones), they are indistinguishable from the exception message alone. Those service strings are not exposed through the Java SDK.
2. Service-side conditions and their code assignments are owned by the File Security service and may change independently of the SDK; this table reflects the catalog current at the time of writing.
3. Engine findings such as `ATSE_*` codes are **not** errors — they are returned inside the scan result payload (see [Sample JSON Response](#sample-json-response)), not as a gRPC status.
4. `MSG_ID_ERR_UNEXPECTED` is defined on `AMaasErrorCode` but is not currently thrown anywhere in the SDK; it is reserved for future use.

## Thread Safety

Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.6.3
1.6.4
11 changes: 11 additions & 0 deletions examples/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,17 @@
<groupId>io.grpc</groupId>
<artifactId>grpc-netty</artifactId>
<version>1.73.0</version>
<exclusions>
<exclusion>
<groupId>io.netty</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.2.15.Final</version>
</dependency>
<dependency>
<groupId>com.trend</groupId>
Expand Down
2 changes: 1 addition & 1 deletion examples/s3stream/S3Stream.java
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ public long getLength() {
* @throws IOException if read fails
*/
@Override
public int readBytes(final int offset, final byte[] buff) throws IOException {
public int readBytes(final long offset, final byte[] buff) throws IOException {
final ByteBuffer byteBuffer = ByteBuffer.wrap(buff);
return readByteRange(offset, byteBuffer);
}
Expand Down
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>com.trend</groupId>
<artifactId>file-security-java-sdk</artifactId>
<version>1.6.3</version>
<version>1.6.4</version>

<name>file-security-java-sdk</name>
<url>https://github.com/trendmicro/tm-v1-fs-java-sdk</url>
Expand Down Expand Up @@ -96,7 +96,7 @@
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.2.9.Final</version>
<version>4.2.15.Final</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
Expand Down
6 changes: 3 additions & 3 deletions protos/scan.proto
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ message C2S {
Stage stage = 1;
string file_name = 2;
uint64 rs_size = 3;
int32 offset = 4;
int64 offset = 4;
bytes chunk = 5;
bool trendx = 6;
string file_sha1 = 7;
Expand All @@ -42,10 +42,10 @@ enum Command {
message S2C {
Stage stage = 1;
Command cmd = 2;
int32 offset = 3;
int64 offset = 3;
int32 length = 4;
string result = 5;
repeated int32 bulk_offset = 6;
repeated int64 bulk_offset = 6;
repeated int32 bulk_length = 7;
string session_id = 8;
}
16 changes: 12 additions & 4 deletions src/main/java/com/trend/cloudone/amaas/AMaasBufferReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,20 @@ public String getIdentifier() {
return this.identifier;
}

public int readBytes(final int offset, final byte[] buf) throws IOException {
public int readBytes(final long offset, final byte[] buf) throws IOException {
// The in-memory buffer is backed by a Java array, which is indexed by int, so a buffer larger than Integer.MAX_VALUE bytes (~2GiB)
// is not representable here. Casting to int is safe for this implementation; only the on-disk file reader (AMaasFileReader) needs
// the full long range. In normal operation the scan engine never requests an offset beyond the declared buffer length,
// so offset always fits in int.
if (offset < 0 || offset > this.readerBuf.length) {
throw new IOException("offset out of range for buffer reader: " + offset + " (buffer length " + this.readerBuf.length + ")");
}
int intOffset = (int) offset;
int chunkLength = buf.length;
if (chunkLength + offset > this.readerBuf.length) {
chunkLength = this.readerBuf.length - offset;
if (chunkLength + intOffset > this.readerBuf.length) {
chunkLength = this.readerBuf.length - intOffset;
}
System.arraycopy(readerBuf, offset, buf, 0, chunkLength);
System.arraycopy(readerBuf, intOffset, buf, 0, chunkLength);
return chunkLength;
}
}
10 changes: 5 additions & 5 deletions src/main/java/com/trend/cloudone/amaas/AMaasClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,7 @@ public void onNext(final ScanOuterClass.S2C s2cMsg) {
return;
}
java.util.List<java.lang.Integer> bulkLength;
java.util.List<java.lang.Integer> bulkOffset;
java.util.List<java.lang.Long> bulkOffset;
if (this.bulk) {
log(Level.FINE, "enter bulk mode");
int bulkCount = s2cMsg.getBulkLengthCount();
Expand All @@ -433,17 +433,17 @@ public void onNext(final ScanOuterClass.S2C s2cMsg) {
bulkOffset = s2cMsg.getBulkOffsetList();
} else {
bulkLength = Arrays.asList(new Integer[]{s2cMsg.getLength()});
bulkOffset = Arrays.asList(new Integer[]{s2cMsg.getOffset()});
bulkOffset = Arrays.asList(new Long[]{s2cMsg.getOffset()});
}
for (int i = 0; i < bulkLength.size(); i++) {
log(Level.INFO, "Bulk read length={0} at offset={1}", bulkLength.get(i).intValue(), bulkOffset.get(i).intValue());
log(Level.INFO, "Bulk read length={0} at offset={1}", bulkLength.get(i).intValue(), bulkOffset.get(i).longValue());
byte[] bytes = new byte[bulkLength.get(i).intValue()];
try {
int rtnLength = reader.readBytes(bulkOffset.get(i).intValue(), bytes);
int rtnLength = reader.readBytes(bulkOffset.get(i).longValue(), bytes);
ByteString bytestr = ByteString.copyFrom(bytes);
this.fetchCount++;
this.fetchSize += rtnLength;
ScanOuterClass.C2S request = ScanOuterClass.C2S.newBuilder().setStage(Stage.STAGE_RUN).setChunk(bytestr).setOffset(bulkOffset.get(i).intValue()).build();
ScanOuterClass.C2S request = ScanOuterClass.C2S.newBuilder().setStage(Stage.STAGE_RUN).setChunk(bytestr).setOffset(bulkOffset.get(i).longValue()).build();

while (!callObserver.isReady()) {
try {
Expand Down
8 changes: 7 additions & 1 deletion src/main/java/com/trend/cloudone/amaas/AMaasFileReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,13 @@ public long getLength() {
return this.fileSize;
}

public int readBytes(final int offset, final byte[] buff) throws IOException {
public int readBytes(final long offset, final byte[] buff) throws IOException {
// RandomAccessFile.seek already rejects a negative offset and reads past EOF return -1, so this check is not
// strictly required for safety. It is kept for fail-fast symmetry with AMaasBufferReader and to surface a
// clearer message if the scan engine ever requests an offset outside the declared file size.
if (offset < 0 || offset > this.fileSize) {
throw new IOException("offset out of range for file reader: " + offset + " (file size " + this.fileSize + ")");
}
this.randomFile.seek(offset);
return this.randomFile.read(buff);
}
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/trend/cloudone/amaas/AMaasReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ enum HashType {
* @param buff byte array to be filled with reader's content.
* @return number of bytes read into the buffer.
*/
int readBytes(int offset, byte[] buff) throws IOException;
int readBytes(long offset, byte[] buff) throws IOException;

/**
* Method to return the hashes as a Hex string for the content read by the reader.
Expand Down
Loading
Loading