From 990a9c8f547a1ed7474a7dda85dbd30e7a293d6b Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Mon, 29 Sep 2025 14:59:56 +0530 Subject: [PATCH 1/5] Implement optional close method check for BallerinaInputStream --- .../stdlib/crypto/BallerinaInputStream.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/BallerinaInputStream.java b/native/src/main/java/io/ballerina/stdlib/crypto/BallerinaInputStream.java index 6c0a5e8f..9bf5739c 100644 --- a/native/src/main/java/io/ballerina/stdlib/crypto/BallerinaInputStream.java +++ b/native/src/main/java/io/ballerina/stdlib/crypto/BallerinaInputStream.java @@ -18,6 +18,9 @@ package io.ballerina.stdlib.crypto; import io.ballerina.runtime.api.Environment; +import io.ballerina.runtime.api.types.MethodType; +import io.ballerina.runtime.api.types.ObjectType; +import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.utils.StringUtils; import io.ballerina.runtime.api.values.BArray; import io.ballerina.runtime.api.values.BError; @@ -27,6 +30,7 @@ import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; +import java.util.Arrays; import java.util.Objects; /** @@ -48,10 +52,19 @@ public class BallerinaInputStream extends InputStream { private final BStream ballerinaStream; private ByteBuffer buffer = null; private boolean endOfStream = false; + private boolean hasCloseMethod = true; public BallerinaInputStream(Environment environment, BStream ballerinaStream) { this.ballerinaStream = ballerinaStream; this.environment = environment; + + // Implementing a close method for a Ballerina stream is optional + // Need to check if the stream has a close method by accessing the iterator object type methods + Type iteratorType = ballerinaStream.getIteratorObj().getOriginalType(); + if (iteratorType instanceof ObjectType iteratorObjectType) { + MethodType[] methods = iteratorObjectType.getMethods(); + hasCloseMethod = Arrays.stream(methods).anyMatch(method -> method.getName().equals(BAL_STREAM_CLOSE)); + } } @Override @@ -71,6 +84,9 @@ public int read() throws IOException { @Override public void close() throws IOException { + if (!hasCloseMethod) { + return; + } Object result = callBalStreamMethod(BAL_STREAM_CLOSE); if (result instanceof BError bError) { throw new IOException((bError).getMessage()); From c4107e50f5de000dd42c3cee4c7392c99402c9ac Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Mon, 29 Sep 2025 15:17:48 +0530 Subject: [PATCH 2/5] Add a test case --- ballerina/tests/encrypt_decrypt_pgp_test.bal | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/ballerina/tests/encrypt_decrypt_pgp_test.bal b/ballerina/tests/encrypt_decrypt_pgp_test.bal index 0b84d0e6..a61d5f7f 100644 --- a/ballerina/tests/encrypt_decrypt_pgp_test.bal +++ b/ballerina/tests/encrypt_decrypt_pgp_test.bal @@ -138,3 +138,19 @@ isolated function testNegativeEncryptAndDecryptStreamWithPgpInvalidPassphrase() test:assertFail("Should return a crypto Error"); } } + +@test:Config +isolated function testInputStreamWithoutCloseForPgpEncrypt() returns error? { + byte[] passphrase = "qCr3bv@5mj5n4eY".toBytes(); + string[] names = ["alice", "charlie"]; + stream inputStream = from string name in names + select name.toString().toBytes(); + stream encryptedStream = check encryptStreamAsPgp(inputStream, PGP_PUBLIC_KEY_PATH); + stream decryptStreamAsPgp = check decryptStreamFromPgp(encryptedStream, PGP_PRIVATE_KEY_PATH, passphrase); + byte[] actualBytes = []; + check from byte[] bytes in decryptStreamAsPgp + do { + actualBytes.push(...bytes); + }; + test:assertEquals(check string:fromBytes(actualBytes), "alicecharlie"); +} From f61cf7214a3d8d72ba137635a3023dded329afc4 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Mon, 29 Sep 2025 15:23:13 +0530 Subject: [PATCH 3/5] [Automated] Update the native jar versions --- ballerina/Ballerina.toml | 6 +++--- ballerina/Dependencies.toml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ballerina/Ballerina.toml b/ballerina/Ballerina.toml index 6c7f6f3d..6ca89f60 100644 --- a/ballerina/Ballerina.toml +++ b/ballerina/Ballerina.toml @@ -1,7 +1,7 @@ [package] org = "ballerina" name = "crypto" -version = "2.9.0" +version = "2.9.1" authors = ["Ballerina"] keywords = ["security", "hash", "hmac", "sign", "encrypt", "decrypt", "private key", "public key"] repository = "https://github.com/ballerina-platform/module-ballerina-crypto" @@ -15,8 +15,8 @@ graalvmCompatible = true [[platform.java21.dependency]] groupId = "io.ballerina.stdlib" artifactId = "crypto-native" -version = "2.9.0" -path = "../native/build/libs/crypto-native-2.9.0.jar" +version = "2.9.1" +path = "../native/build/libs/crypto-native-2.9.1-SNAPSHOT.jar" [[platform.java21.dependency]] groupId = "org.bouncycastle" diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index d79d394d..6488f734 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -10,7 +10,7 @@ distribution-version = "2201.12.0" [[package]] org = "ballerina" name = "crypto" -version = "2.9.0" +version = "2.9.1" dependencies = [ {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, From 8fa6cea9cb121a41db91a65e761213d0b1d5fcf0 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Mon, 29 Sep 2025 17:09:25 +0530 Subject: [PATCH 4/5] Update changelog --- changelog.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/changelog.md b/changelog.md index 22bd71cc..5d6e24a4 100644 --- a/changelog.md +++ b/changelog.md @@ -5,6 +5,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +### Fixed +- [Implement optional close method check for BStream](https://github.com/ballerina-platform/ballerina-library/issues/8288) + +## [2.9.0] - 2025-03-12 + ### Changed - [Update OIDS of NIST approved post quantum algorithms](https://github.com/ballerina-platform/ballerina-library/issues/7678) From f299f7d6973640362b3355b7c3663be5e3e4bc5f Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Mon, 29 Sep 2025 17:13:09 +0530 Subject: [PATCH 5/5] Refactor BallerinaInputStream to determine close method presence at initialization --- .../io/ballerina/stdlib/crypto/BallerinaInputStream.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/BallerinaInputStream.java b/native/src/main/java/io/ballerina/stdlib/crypto/BallerinaInputStream.java index 9bf5739c..ec3da25e 100644 --- a/native/src/main/java/io/ballerina/stdlib/crypto/BallerinaInputStream.java +++ b/native/src/main/java/io/ballerina/stdlib/crypto/BallerinaInputStream.java @@ -52,18 +52,21 @@ public class BallerinaInputStream extends InputStream { private final BStream ballerinaStream; private ByteBuffer buffer = null; private boolean endOfStream = false; - private boolean hasCloseMethod = true; + private final boolean hasCloseMethod; public BallerinaInputStream(Environment environment, BStream ballerinaStream) { this.ballerinaStream = ballerinaStream; this.environment = environment; // Implementing a close method for a Ballerina stream is optional - // Need to check if the stream has a close method by accessing the iterator object type methods + // There is no Ballerina runtime API to check if the stream has a close method + // So accessing the iterator object type methods to check if it has a close method Type iteratorType = ballerinaStream.getIteratorObj().getOriginalType(); if (iteratorType instanceof ObjectType iteratorObjectType) { MethodType[] methods = iteratorObjectType.getMethods(); hasCloseMethod = Arrays.stream(methods).anyMatch(method -> method.getName().equals(BAL_STREAM_CLOSE)); + } else { + hasCloseMethod = false; } }