Skip to content

feat: add bytes methods and using embeded TextEncoder and TextDecoder #113

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Mar 15, 2025
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
11 changes: 9 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@
## JSI


If you want to use with `JSI` instead of `NativeModules` you need to set
If you want to use with `NativeModules` instead of `JSI` you need to set

```typescript
import OpenPGP from "react-native-fast-openpgp";

OpenPGP.useJSI = true;
OpenPGP.useJSI = false;
```
if you need to use generate methods it is a good idea to disable it, because for now JSI will block your UI but it is faster compared to NativeModules

Expand All @@ -27,9 +27,11 @@ if you need to use generate methods it is a good idea to disable it, because for
import OpenPGP from "react-native-fast-openpgp";

const encrypted = await OpenPGP.encrypt(message: string, publicKey: string, signedEntity?: Entity, fileHints?: FileHints, options?: KeyOptions ): Promise<string>;
const encrypted = await OpenPGP.encryptBytes(message: Uint8Array, publicKey: string, signedEntity?: Entity, fileHints?: FileHints, options?: KeyOptions ): Promise<Uint8Array>;
const outputFile = await OpenPGP.encryptFile(inputFile: string, outputFile: string, publicKey: string, signedEntity?: Entity, fileHints?: FileHints, options?: KeyOptions): Promise<number>;

const encryptedSymmetric = await OpenPGP.encryptSymmetric(message: string, passphrase: string, fileHints?: FileHints, options?: KeyOptions ): Promise<string>;
const encryptedSymmetric = await OpenPGP.encryptSymmetricBytes(message: Uint8Array, passphrase: string, fileHints?: FileHints, options?: KeyOptions ): Promise<Uint8Array>;
const outputFile = await OpenPGP.encryptSymmetricFile(inputFile: string, outputFile: string, passphrase: string, fileHints?: FileHints, options?: KeyOptions ): Promise<number> ;
```

Expand All @@ -38,9 +40,11 @@ const outputFile = await OpenPGP.encryptSymmetricFile(inputFile: string, outputF
import OpenPGP from "react-native-fast-openpgp";

const decrypted = await OpenPGP.decrypt(message: string, privateKey: string, passphrase: string, options?: KeyOptions ): Promise<string>;
const decrypted = await OpenPGP.decryptBytes(message: Uint8Array, privateKey: string, passphrase: string, options?: KeyOptions ): Promise<Uint8Array>;
const outputFile = await OpenPGP.decryptFile(inputFile: string, outputFile: string, privateKey: string, passphrase: string, options?: KeyOptions ): Promise<number>;

const decryptedSymmetric = await OpenPGP.decryptSymmetric(message: string, passphrase: string, options?: KeyOptions ): Promise<string>;
const decryptedSymmetric = await OpenPGP.decryptSymmetricBytes(message: Uint8Array, passphrase: string, options?: KeyOptions ): Promise<Uint8Array>;
const outputFile = await OpenPGP.decryptSymmetricFile(inputFile: string, outputFile: string, passphrase: string, options?: KeyOptions ): Promise<number> ;
```

Expand All @@ -49,9 +53,12 @@ const outputFile = await OpenPGP.decryptSymmetricFile(inputFile: string, outputF
import OpenPGP from "react-native-fast-openpgp";

const signed = await OpenPGP.sign(message: string, privateKey: string, passphrase: string, options?: KeyOptions ): Promise<string>;
const signed = await OpenPGP.signBytes(message: Uint8Array, privateKey: string, passphrase: string, options?: KeyOptions ): Promise<Uint8Array>;
const signed = await OpenPGP.signBytesToString(message: Uint8Array, privateKey: string, passphrase: string, options?: KeyOptions ): Promise<string>;
const signed = await OpenPGP.signFile(inputFile: string, privateKey: string, passphrase: string, options?: KeyOptions ): Promise<string>;

const verified = await OpenPGP.verify(signature: string, message: string, publicKey: string ): Promise<boolean>;
const verified = await OpenPGP.verifyBytes(signature: string, message: Uint8Array, publicKey: string ): Promise<boolean>;
const verified = await OpenPGP.verifyFile(signature: string, inputFile: string,publicKey: string): Promise<boolean>;
```

Expand Down
93 changes: 93 additions & 0 deletions android/fast-openpgp-adapter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,96 @@ Java_com_fastopenpgp_FastOpenpgpModule_callNative(JNIEnv* env,

return result;
}

extern "C" JNIEXPORT jbyteArray JNICALL
Java_com_fastopenpgp_FastOpenpgpModule_encodeTextNative(JNIEnv* env, jobject thiz, jstring input, jstring encoding) {
if (input == nullptr || encoding == nullptr) {
jclass Exception = env->FindClass("java/lang/NullPointerException");
env->ThrowNew(Exception, "Input parameters 'input' or 'encoding' cannot be null");
return nullptr;
}

// Convert Java Strings to C Strings
const char* inputCStr = env->GetStringUTFChars(input, nullptr);
const char* encodingCStr = env->GetStringUTFChars(encoding, nullptr);

if (inputCStr == nullptr || encodingCStr == nullptr) {
jclass Exception = env->FindClass("java/lang/OutOfMemoryError");
env->ThrowNew(Exception, "Failed to allocate memory for 'input' or 'encoding'");
return nullptr;
}

// Call the shared library function
BytesReturn* response = OpenPGPEncodeText(const_cast<char*>(inputCStr), const_cast<char*>(encodingCStr));

// Release allocated resources
env->ReleaseStringUTFChars(input, inputCStr);
env->ReleaseStringUTFChars(encoding, encodingCStr);

if (response->error != nullptr) {
jclass Exception = env->FindClass("java/lang/Exception");
env->ThrowNew(Exception, response->error);
free(response);
return nullptr;
}

// Create a new byte array to return the encoded data
jbyteArray result = env->NewByteArray(response->size);
if (result == nullptr) {
free(response);
jclass Exception = env->FindClass("java/lang/OutOfMemoryError");
env->ThrowNew(Exception, "Failed to allocate memory for result");
return nullptr;
}

env->SetByteArrayRegion(result, 0, response->size, reinterpret_cast<jbyte*>(response->message));
free(response);

return result;
}

extern "C" JNIEXPORT jstring JNICALL
Java_com_fastopenpgp_FastOpenpgpModule_decodeTextNative(JNIEnv* env, jobject thiz, jbyteArray input, jstring encoding,
jint fatal, jint ignoreBOM, jint stream) {
if (input == nullptr || encoding == nullptr) {
jclass Exception = env->FindClass("java/lang/NullPointerException");
env->ThrowNew(Exception, "Input parameters 'input' or 'encoding' cannot be null");
return nullptr;
}

// Convert Java Strings to C Strings
const char* encodingCStr = env->GetStringUTFChars(encoding, nullptr);
if (encodingCStr == nullptr) {
jclass Exception = env->FindClass("java/lang/OutOfMemoryError");
env->ThrowNew(Exception, "Failed to allocate memory for 'encoding'");
return nullptr;
}

// Convert Java byte array to C byte array
jsize size = env->GetArrayLength(input);
jbyte* inputBytes = env->GetByteArrayElements(input, nullptr);
if (inputBytes == nullptr) {
env->ReleaseStringUTFChars(encoding, encodingCStr);
jclass Exception = env->FindClass("java/lang/OutOfMemoryError");
env->ThrowNew(Exception, "Failed to allocate memory for 'input'");
return nullptr;
}

// Call the shared library function
char* decodedString = OpenPGPDecodeText(inputBytes, size, const_cast<char*>(encodingCStr), fatal, ignoreBOM, stream);

// Release resources
env->ReleaseStringUTFChars(encoding, encodingCStr);
env->ReleaseByteArrayElements(input, inputBytes, JNI_ABORT);

if (decodedString == nullptr) {
jclass Exception = env->FindClass("java/lang/Exception");
env->ThrowNew(Exception, "Decoding failed");
return nullptr;
}

// Convert C string to Java string and return
jstring result = env->NewStringUTF(decodedString);
free(decodedString);
return result;
}
28 changes: 28 additions & 0 deletions android/src/main/java/com/fastopenpgp/FastOpenpgpModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ internal class FastOpenpgpModule(reactContext: ReactApplicationContext) :
external fun initialize(jsContext: Long)
external fun destruct();
external fun callNative(name: String, payload: ByteArray): ByteArray;
external fun encodeTextNative(input: String, encoding: String): ByteArray
external fun decodeTextNative(input: ByteArray, encoding: String, fatal: Int, ignoreBOM: Int, stream: Int): String

companion object {
init {
Expand All @@ -38,6 +40,32 @@ internal class FastOpenpgpModule(reactContext: ReactApplicationContext) :
}.start()
}

@ReactMethod(isBlockingSynchronousMethod = true)
fun encodeText(input: String, encoding: String): WritableArray {
return try {
val result = encodeTextNative(input, encoding)
Arguments.createArray().apply {
result.forEach { byteValue: Byte -> pushInt(byteValue.toInt() and 0xFF) }
}
} catch (e: Exception) {
Log.e(TAG, "Encoding error", e)
throw RuntimeException("ENCODE_ERROR: Failed to encode text")
}
}

@ReactMethod(isBlockingSynchronousMethod = true)
fun decodeText(input: ReadableArray, encoding: String, fatal: Boolean, ignoreBOM: Boolean, stream: Boolean): String {
return try {
val bytes = ByteArray(input.size()) { index ->
input.getInt(index).toByte()
}
decodeTextNative(bytes, encoding, if (fatal) 1 else 0, if (ignoreBOM) 1 else 0, if (stream) 1 else 0)
} catch (e: Exception) {
Log.e(TAG, "Decoding error", e)
throw RuntimeException("DECODE_ERROR: Failed to decode text")
}
}

@ReactMethod(isBlockingSynchronousMethod = true)
fun install(): Boolean {
Log.d(TAG, "Attempting to install JSI bindings...")
Expand Down
2 changes: 2 additions & 0 deletions android/src/main/jniLibs/arm64-v8a/libopenpgp_bridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ extern "C" {
#endif

extern BytesReturn* OpenPGPBridgeCall(char* name, void* payload, int payloadSize);
extern BytesReturn* OpenPGPEncodeText(char* input, char* encoding);
extern char* OpenPGPDecodeText(void* input, int size, char* encoding, int fatal, int ignoreBOM, int stream);

#ifdef __cplusplus
}
Expand Down
Binary file modified android/src/main/jniLibs/arm64-v8a/libopenpgp_bridge.so
Binary file not shown.
2 changes: 2 additions & 0 deletions android/src/main/jniLibs/armeabi-v7a/libopenpgp_bridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ extern "C" {
#endif

extern BytesReturn* OpenPGPBridgeCall(char* name, void* payload, int payloadSize);
extern BytesReturn* OpenPGPEncodeText(char* input, char* encoding);
extern char* OpenPGPDecodeText(void* input, int size, char* encoding, int fatal, int ignoreBOM, int stream);

#ifdef __cplusplus
}
Expand Down
Binary file modified android/src/main/jniLibs/armeabi-v7a/libopenpgp_bridge.so
Binary file not shown.
2 changes: 2 additions & 0 deletions android/src/main/jniLibs/x86/libopenpgp_bridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ extern "C" {
#endif

extern BytesReturn* OpenPGPBridgeCall(char* name, void* payload, int payloadSize);
extern BytesReturn* OpenPGPEncodeText(char* input, char* encoding);
extern char* OpenPGPDecodeText(void* input, int size, char* encoding, int fatal, int ignoreBOM, int stream);

#ifdef __cplusplus
}
Expand Down
Binary file modified android/src/main/jniLibs/x86/libopenpgp_bridge.so
Binary file not shown.
2 changes: 2 additions & 0 deletions android/src/main/jniLibs/x86_64/libopenpgp_bridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ extern "C" {
#endif

extern BytesReturn* OpenPGPBridgeCall(char* name, void* payload, int payloadSize);
extern BytesReturn* OpenPGPEncodeText(char* input, char* encoding);
extern char* OpenPGPDecodeText(void* input, int size, char* encoding, int fatal, int ignoreBOM, int stream);

#ifdef __cplusplus
}
Expand Down
Binary file modified android/src/main/jniLibs/x86_64/libopenpgp_bridge.so
Binary file not shown.
2 changes: 2 additions & 0 deletions cpp/libopenpgp_bridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ typedef struct {
extern "C" {
#endif
extern BytesReturn* OpenPGPBridgeCall(char* p0, void* p1, int p2);
extern BytesReturn* OpenPGPEncodeText(char* input, char* encoding);
extern char* OpenPGPDecodeText(void* input, int size, char* encoding, int fatal, int ignoreBOM, int stream);
#ifdef __cplusplus
}
#endif
85 changes: 85 additions & 0 deletions cpp/react-native-fast-openpgp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,60 @@
using namespace facebook;

namespace fastOpenPGP {

jsi::Value encodeText(jsi::Runtime &runtime, const jsi::String &inputValue, const jsi::String &encodingValue) {
std::string inputString = inputValue.utf8(runtime);
std::string encodingString = encodingValue.utf8(runtime);

std::vector<char> mutableInput(inputString.begin(), inputString.end());
mutableInput.push_back('\0');
std::vector<char> mutableEncoding(encodingString.begin(), encodingString.end());
mutableEncoding.push_back('\0');

auto response = OpenPGPEncodeText(mutableInput.data(), mutableEncoding.data());
if (response->error != nullptr) {
std::string errorMessage(response->error);
free(response);
throw jsi::JSError(runtime, errorMessage);
}

auto uint8ArrayConstructor = runtime.global().getPropertyAsFunction(runtime, "Uint8Array");
jsi::Object uint8ArrayObject = uint8ArrayConstructor.callAsConstructor(runtime, response->size).getObject(runtime);
jsi::ArrayBuffer arrayBuffer = uint8ArrayObject.getPropertyAsObject(runtime, "buffer").getArrayBuffer(runtime);
memcpy(arrayBuffer.data(runtime), response->message, response->size);

free(response);
return uint8ArrayObject;
}

jsi::Value decodeText(jsi::Runtime &runtime, const jsi::Object &inputObject, const jsi::String &encodingValue,
bool fatal, bool ignoreBOM, bool stream) {
auto uint8ArrayConstructor = runtime.global().getPropertyAsFunction(runtime, "Uint8Array");
if (!inputObject.instanceOf(runtime, uint8ArrayConstructor)) {
throw jsi::JSError(runtime, "First argument must be a Uint8Array");
}

// Get Uint8Array data
jsi::ArrayBuffer arrayBuffer = inputObject.getPropertyAsObject(runtime, "buffer").getArrayBuffer(runtime);
int byteOffset = inputObject.getProperty(runtime, "byteOffset").asNumber();
int length = inputObject.getProperty(runtime, "byteLength").asNumber();

uint8_t *dataPointer = static_cast<uint8_t *>(arrayBuffer.data(runtime)) + byteOffset;

std::string encodingString = encodingValue.utf8(runtime);
std::vector<char> mutableEncoding(encodingString.begin(), encodingString.end());
mutableEncoding.push_back('\0');

char *decodedString = OpenPGPDecodeText(dataPointer, length, mutableEncoding.data(), fatal ? 1 : 0, ignoreBOM ? 1 : 0, stream ? 1 : 0);
if (!decodedString) {
throw jsi::JSError(runtime, "Failed to decode text");
}

jsi::String result = jsi::String::createFromUtf8(runtime, decodedString);
free(decodedString);
return result;
}

jsi::Value call(jsi::Runtime &runtime, const jsi::String &nameValue,
const jsi::Object &payloadObject) {
// Extract and validate name
Expand Down Expand Up @@ -138,6 +192,8 @@ void install(jsi::Runtime &jsiRuntime) {
reject.call(runtime, error.value());
} catch (const std::exception &e) {
reject.call(runtime, jsi::String::createFromUtf8(runtime, e.what()));
} catch (...) {
reject.call(runtime, jsi::String::createFromUtf8(runtime, "Unknown error occurred"));
}

return jsi::Value::undefined();
Expand All @@ -150,6 +206,35 @@ void install(jsi::Runtime &jsiRuntime) {
return promise;
});

auto encodeTextFunc = jsi::Function::createFromHostFunction(
jsiRuntime, jsi::PropNameID::forAscii(jsiRuntime, "encodeText"), 2,
[](jsi::Runtime &runtime, const jsi::Value & /*thisValue*/, const jsi::Value *arguments, size_t count) -> jsi::Value {
if (count != 2) {
throw jsi::JSError(runtime, "encodeText expects exactly 2 arguments: (string input, string encoding)");
}
if (!arguments[0].isString() || !arguments[1].isString()) {
throw jsi::JSError(runtime, "Both arguments must be strings");
}
return encodeText(runtime, arguments[0].getString(runtime), arguments[1].getString(runtime));
});

auto decodeTextFunc = jsi::Function::createFromHostFunction(
jsiRuntime, jsi::PropNameID::forAscii(jsiRuntime, "decodeText"), 5,
[](jsi::Runtime &runtime, const jsi::Value & /*thisValue*/, const jsi::Value *arguments, size_t count) -> jsi::Value {
if (count != 5) {
throw jsi::JSError(runtime, "decodeText expects exactly 5 arguments: (Uint8Array input, string encoding, bool fatal, bool ignoreBOM, bool stream)");
}
if (!arguments[0].isObject() || !arguments[0].getObject(runtime).instanceOf(runtime, runtime.global().getPropertyAsFunction(runtime, "Uint8Array")) ||
!arguments[1].isString() || !arguments[2].isBool() || !arguments[3].isBool() || !arguments[4].isBool()) {
throw jsi::JSError(runtime, "Invalid argument types");
}

return decodeText(runtime, arguments[0].getObject(runtime),
arguments[1].getString(runtime), arguments[2].getBool(), arguments[3].getBool(), arguments[4].getBool());
});

jsiRuntime.global().setProperty(jsiRuntime, "FastOpenPGPEncodeText", std::move(encodeTextFunc));
jsiRuntime.global().setProperty(jsiRuntime, "FastOpenPGPDecodeText", std::move(decodeTextFunc));
jsiRuntime.global().setProperty(jsiRuntime, "FastOpenPGPCallPromise", std::move(bridgeCallPromise));
jsiRuntime.global().setProperty(jsiRuntime, "FastOpenPGPCallSync", std::move(bridgeCallSync));
}
Expand Down
Loading