Skip to content

Commit fe722ef

Browse files
authored
feat: add bytes methods and using embeded TextEncoder and TextDecoder (#113)
* feat: add bytes methods and using embeded TextEncoder and TextDecoder * chore: update readme * chore: removed unused dep * chore: fix types * chore: fix types
1 parent bdde1b3 commit fe722ef

34 files changed

+926
-153
lines changed

README.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@
1010
## JSI
1111

1212

13-
If you want to use with `JSI` instead of `NativeModules` you need to set
13+
If you want to use with `NativeModules` instead of `JSI` you need to set
1414

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

18-
OpenPGP.useJSI = true;
18+
OpenPGP.useJSI = false;
1919
```
2020
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
2121

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

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

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

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

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

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

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

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

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

android/fast-openpgp-adapter.cpp

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,96 @@ Java_com_fastopenpgp_FastOpenpgpModule_callNative(JNIEnv* env,
9292

9393
return result;
9494
}
95+
96+
extern "C" JNIEXPORT jbyteArray JNICALL
97+
Java_com_fastopenpgp_FastOpenpgpModule_encodeTextNative(JNIEnv* env, jobject thiz, jstring input, jstring encoding) {
98+
if (input == nullptr || encoding == nullptr) {
99+
jclass Exception = env->FindClass("java/lang/NullPointerException");
100+
env->ThrowNew(Exception, "Input parameters 'input' or 'encoding' cannot be null");
101+
return nullptr;
102+
}
103+
104+
// Convert Java Strings to C Strings
105+
const char* inputCStr = env->GetStringUTFChars(input, nullptr);
106+
const char* encodingCStr = env->GetStringUTFChars(encoding, nullptr);
107+
108+
if (inputCStr == nullptr || encodingCStr == nullptr) {
109+
jclass Exception = env->FindClass("java/lang/OutOfMemoryError");
110+
env->ThrowNew(Exception, "Failed to allocate memory for 'input' or 'encoding'");
111+
return nullptr;
112+
}
113+
114+
// Call the shared library function
115+
BytesReturn* response = OpenPGPEncodeText(const_cast<char*>(inputCStr), const_cast<char*>(encodingCStr));
116+
117+
// Release allocated resources
118+
env->ReleaseStringUTFChars(input, inputCStr);
119+
env->ReleaseStringUTFChars(encoding, encodingCStr);
120+
121+
if (response->error != nullptr) {
122+
jclass Exception = env->FindClass("java/lang/Exception");
123+
env->ThrowNew(Exception, response->error);
124+
free(response);
125+
return nullptr;
126+
}
127+
128+
// Create a new byte array to return the encoded data
129+
jbyteArray result = env->NewByteArray(response->size);
130+
if (result == nullptr) {
131+
free(response);
132+
jclass Exception = env->FindClass("java/lang/OutOfMemoryError");
133+
env->ThrowNew(Exception, "Failed to allocate memory for result");
134+
return nullptr;
135+
}
136+
137+
env->SetByteArrayRegion(result, 0, response->size, reinterpret_cast<jbyte*>(response->message));
138+
free(response);
139+
140+
return result;
141+
}
142+
143+
extern "C" JNIEXPORT jstring JNICALL
144+
Java_com_fastopenpgp_FastOpenpgpModule_decodeTextNative(JNIEnv* env, jobject thiz, jbyteArray input, jstring encoding,
145+
jint fatal, jint ignoreBOM, jint stream) {
146+
if (input == nullptr || encoding == nullptr) {
147+
jclass Exception = env->FindClass("java/lang/NullPointerException");
148+
env->ThrowNew(Exception, "Input parameters 'input' or 'encoding' cannot be null");
149+
return nullptr;
150+
}
151+
152+
// Convert Java Strings to C Strings
153+
const char* encodingCStr = env->GetStringUTFChars(encoding, nullptr);
154+
if (encodingCStr == nullptr) {
155+
jclass Exception = env->FindClass("java/lang/OutOfMemoryError");
156+
env->ThrowNew(Exception, "Failed to allocate memory for 'encoding'");
157+
return nullptr;
158+
}
159+
160+
// Convert Java byte array to C byte array
161+
jsize size = env->GetArrayLength(input);
162+
jbyte* inputBytes = env->GetByteArrayElements(input, nullptr);
163+
if (inputBytes == nullptr) {
164+
env->ReleaseStringUTFChars(encoding, encodingCStr);
165+
jclass Exception = env->FindClass("java/lang/OutOfMemoryError");
166+
env->ThrowNew(Exception, "Failed to allocate memory for 'input'");
167+
return nullptr;
168+
}
169+
170+
// Call the shared library function
171+
char* decodedString = OpenPGPDecodeText(inputBytes, size, const_cast<char*>(encodingCStr), fatal, ignoreBOM, stream);
172+
173+
// Release resources
174+
env->ReleaseStringUTFChars(encoding, encodingCStr);
175+
env->ReleaseByteArrayElements(input, inputBytes, JNI_ABORT);
176+
177+
if (decodedString == nullptr) {
178+
jclass Exception = env->FindClass("java/lang/Exception");
179+
env->ThrowNew(Exception, "Decoding failed");
180+
return nullptr;
181+
}
182+
183+
// Convert C string to Java string and return
184+
jstring result = env->NewStringUTF(decodedString);
185+
free(decodedString);
186+
return result;
187+
}

android/src/main/java/com/fastopenpgp/FastOpenpgpModule.kt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ internal class FastOpenpgpModule(reactContext: ReactApplicationContext) :
1212
external fun initialize(jsContext: Long)
1313
external fun destruct();
1414
external fun callNative(name: String, payload: ByteArray): ByteArray;
15+
external fun encodeTextNative(input: String, encoding: String): ByteArray
16+
external fun decodeTextNative(input: ByteArray, encoding: String, fatal: Int, ignoreBOM: Int, stream: Int): String
1517

1618
companion object {
1719
init {
@@ -38,6 +40,32 @@ internal class FastOpenpgpModule(reactContext: ReactApplicationContext) :
3840
}.start()
3941
}
4042

43+
@ReactMethod(isBlockingSynchronousMethod = true)
44+
fun encodeText(input: String, encoding: String): WritableArray {
45+
return try {
46+
val result = encodeTextNative(input, encoding)
47+
Arguments.createArray().apply {
48+
result.forEach { byteValue: Byte -> pushInt(byteValue.toInt() and 0xFF) }
49+
}
50+
} catch (e: Exception) {
51+
Log.e(TAG, "Encoding error", e)
52+
throw RuntimeException("ENCODE_ERROR: Failed to encode text")
53+
}
54+
}
55+
56+
@ReactMethod(isBlockingSynchronousMethod = true)
57+
fun decodeText(input: ReadableArray, encoding: String, fatal: Boolean, ignoreBOM: Boolean, stream: Boolean): String {
58+
return try {
59+
val bytes = ByteArray(input.size()) { index ->
60+
input.getInt(index).toByte()
61+
}
62+
decodeTextNative(bytes, encoding, if (fatal) 1 else 0, if (ignoreBOM) 1 else 0, if (stream) 1 else 0)
63+
} catch (e: Exception) {
64+
Log.e(TAG, "Decoding error", e)
65+
throw RuntimeException("DECODE_ERROR: Failed to decode text")
66+
}
67+
}
68+
4169
@ReactMethod(isBlockingSynchronousMethod = true)
4270
fun install(): Boolean {
4371
Log.d(TAG, "Attempting to install JSI bindings...")

android/src/main/jniLibs/arm64-v8a/libopenpgp_bridge.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ extern "C" {
8181
#endif
8282

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

8587
#ifdef __cplusplus
8688
}
Binary file not shown.

android/src/main/jniLibs/armeabi-v7a/libopenpgp_bridge.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ extern "C" {
8181
#endif
8282

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

8587
#ifdef __cplusplus
8688
}
Binary file not shown.

android/src/main/jniLibs/x86/libopenpgp_bridge.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ extern "C" {
8181
#endif
8282

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

8587
#ifdef __cplusplus
8688
}
Binary file not shown.

android/src/main/jniLibs/x86_64/libopenpgp_bridge.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ extern "C" {
8181
#endif
8282

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

8587
#ifdef __cplusplus
8688
}
Binary file not shown.

cpp/libopenpgp_bridge.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ typedef struct {
1010
extern "C" {
1111
#endif
1212
extern BytesReturn* OpenPGPBridgeCall(char* p0, void* p1, int p2);
13+
extern BytesReturn* OpenPGPEncodeText(char* input, char* encoding);
14+
extern char* OpenPGPDecodeText(void* input, int size, char* encoding, int fatal, int ignoreBOM, int stream);
1315
#ifdef __cplusplus
1416
}
1517
#endif

cpp/react-native-fast-openpgp.cpp

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,60 @@
1111
using namespace facebook;
1212

1313
namespace fastOpenPGP {
14+
15+
jsi::Value encodeText(jsi::Runtime &runtime, const jsi::String &inputValue, const jsi::String &encodingValue) {
16+
std::string inputString = inputValue.utf8(runtime);
17+
std::string encodingString = encodingValue.utf8(runtime);
18+
19+
std::vector<char> mutableInput(inputString.begin(), inputString.end());
20+
mutableInput.push_back('\0');
21+
std::vector<char> mutableEncoding(encodingString.begin(), encodingString.end());
22+
mutableEncoding.push_back('\0');
23+
24+
auto response = OpenPGPEncodeText(mutableInput.data(), mutableEncoding.data());
25+
if (response->error != nullptr) {
26+
std::string errorMessage(response->error);
27+
free(response);
28+
throw jsi::JSError(runtime, errorMessage);
29+
}
30+
31+
auto uint8ArrayConstructor = runtime.global().getPropertyAsFunction(runtime, "Uint8Array");
32+
jsi::Object uint8ArrayObject = uint8ArrayConstructor.callAsConstructor(runtime, response->size).getObject(runtime);
33+
jsi::ArrayBuffer arrayBuffer = uint8ArrayObject.getPropertyAsObject(runtime, "buffer").getArrayBuffer(runtime);
34+
memcpy(arrayBuffer.data(runtime), response->message, response->size);
35+
36+
free(response);
37+
return uint8ArrayObject;
38+
}
39+
40+
jsi::Value decodeText(jsi::Runtime &runtime, const jsi::Object &inputObject, const jsi::String &encodingValue,
41+
bool fatal, bool ignoreBOM, bool stream) {
42+
auto uint8ArrayConstructor = runtime.global().getPropertyAsFunction(runtime, "Uint8Array");
43+
if (!inputObject.instanceOf(runtime, uint8ArrayConstructor)) {
44+
throw jsi::JSError(runtime, "First argument must be a Uint8Array");
45+
}
46+
47+
// Get Uint8Array data
48+
jsi::ArrayBuffer arrayBuffer = inputObject.getPropertyAsObject(runtime, "buffer").getArrayBuffer(runtime);
49+
int byteOffset = inputObject.getProperty(runtime, "byteOffset").asNumber();
50+
int length = inputObject.getProperty(runtime, "byteLength").asNumber();
51+
52+
uint8_t *dataPointer = static_cast<uint8_t *>(arrayBuffer.data(runtime)) + byteOffset;
53+
54+
std::string encodingString = encodingValue.utf8(runtime);
55+
std::vector<char> mutableEncoding(encodingString.begin(), encodingString.end());
56+
mutableEncoding.push_back('\0');
57+
58+
char *decodedString = OpenPGPDecodeText(dataPointer, length, mutableEncoding.data(), fatal ? 1 : 0, ignoreBOM ? 1 : 0, stream ? 1 : 0);
59+
if (!decodedString) {
60+
throw jsi::JSError(runtime, "Failed to decode text");
61+
}
62+
63+
jsi::String result = jsi::String::createFromUtf8(runtime, decodedString);
64+
free(decodedString);
65+
return result;
66+
}
67+
1468
jsi::Value call(jsi::Runtime &runtime, const jsi::String &nameValue,
1569
const jsi::Object &payloadObject) {
1670
// Extract and validate name
@@ -138,6 +192,8 @@ void install(jsi::Runtime &jsiRuntime) {
138192
reject.call(runtime, error.value());
139193
} catch (const std::exception &e) {
140194
reject.call(runtime, jsi::String::createFromUtf8(runtime, e.what()));
195+
} catch (...) {
196+
reject.call(runtime, jsi::String::createFromUtf8(runtime, "Unknown error occurred"));
141197
}
142198

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

209+
auto encodeTextFunc = jsi::Function::createFromHostFunction(
210+
jsiRuntime, jsi::PropNameID::forAscii(jsiRuntime, "encodeText"), 2,
211+
[](jsi::Runtime &runtime, const jsi::Value & /*thisValue*/, const jsi::Value *arguments, size_t count) -> jsi::Value {
212+
if (count != 2) {
213+
throw jsi::JSError(runtime, "encodeText expects exactly 2 arguments: (string input, string encoding)");
214+
}
215+
if (!arguments[0].isString() || !arguments[1].isString()) {
216+
throw jsi::JSError(runtime, "Both arguments must be strings");
217+
}
218+
return encodeText(runtime, arguments[0].getString(runtime), arguments[1].getString(runtime));
219+
});
220+
221+
auto decodeTextFunc = jsi::Function::createFromHostFunction(
222+
jsiRuntime, jsi::PropNameID::forAscii(jsiRuntime, "decodeText"), 5,
223+
[](jsi::Runtime &runtime, const jsi::Value & /*thisValue*/, const jsi::Value *arguments, size_t count) -> jsi::Value {
224+
if (count != 5) {
225+
throw jsi::JSError(runtime, "decodeText expects exactly 5 arguments: (Uint8Array input, string encoding, bool fatal, bool ignoreBOM, bool stream)");
226+
}
227+
if (!arguments[0].isObject() || !arguments[0].getObject(runtime).instanceOf(runtime, runtime.global().getPropertyAsFunction(runtime, "Uint8Array")) ||
228+
!arguments[1].isString() || !arguments[2].isBool() || !arguments[3].isBool() || !arguments[4].isBool()) {
229+
throw jsi::JSError(runtime, "Invalid argument types");
230+
}
231+
232+
return decodeText(runtime, arguments[0].getObject(runtime),
233+
arguments[1].getString(runtime), arguments[2].getBool(), arguments[3].getBool(), arguments[4].getBool());
234+
});
235+
236+
jsiRuntime.global().setProperty(jsiRuntime, "FastOpenPGPEncodeText", std::move(encodeTextFunc));
237+
jsiRuntime.global().setProperty(jsiRuntime, "FastOpenPGPDecodeText", std::move(decodeTextFunc));
153238
jsiRuntime.global().setProperty(jsiRuntime, "FastOpenPGPCallPromise", std::move(bridgeCallPromise));
154239
jsiRuntime.global().setProperty(jsiRuntime, "FastOpenPGPCallSync", std::move(bridgeCallSync));
155240
}

0 commit comments

Comments
 (0)