Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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
25 changes: 24 additions & 1 deletion library/src/main/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,22 @@ set(STATIC_BUNDLE ON)
set(ENABLE_ONIONREQ OFF)
add_subdirectory(../../../../libsession-util libsession)

include(FetchContent)


set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
set(WEBP_BUILD_ANIM_UTILS ON CACHE BOOL "" FORCE)
set(WEBP_BUILD_LIBWEBPMUX ON CACHE BOOL "" FORCE)

FetchContent_Declare(
webp
GIT_REPOSITORY https://chromium.googlesource.com/webm/libwebp
GIT_TAG v1.6.0
)

FetchContent_MakeAvailable(webp)


set(SOURCES
user_profile.cpp
user_groups.cpp
Expand All @@ -44,6 +60,8 @@ set(SOURCES
ed25519.cpp
curve25519.cpp
hash.cpp
attachments.cpp
webp_utils.cpp
)

add_library( # Sets the name of the library.
Expand Down Expand Up @@ -78,4 +96,9 @@ target_link_libraries( # Specifies the target library.
libsodium::sodium-internal
# Links the target library to the log library
# included in the NDK.
${log-lib})
${log-lib}
webp
libwebpmux
webpdemux
webpdecoder
)
79 changes: 79 additions & 0 deletions library/src/main/cpp/attachments.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#include <jni.h>
#include <session/attachments.hpp>
#include "jni_utils.h"

using namespace session::attachment;
using namespace jni_utils;

extern "C"
JNIEXPORT jlong JNICALL
Java_network_loki_messenger_libsession_1util_encrypt_Attachments_encryptedSize(JNIEnv *env,
jobject thiz,
jlong plaintext_size) {
return encrypted_size(plaintext_size);
}

extern "C"
JNIEXPORT jlong JNICALL
Java_network_loki_messenger_libsession_1util_encrypt_Attachments_decryptedMaxSize(JNIEnv *env,
jobject thiz,
jlong ciphertext_size) {
auto s = decrypted_max_size(ciphertext_size);
if (s.has_value()) {
return *s;
} else {
env->ThrowNew(env->FindClass("java/lang/IllegalArgumentException"),
"ciphertext_size too small to be a valid encrypted attachment");
return 0;
}
}

extern "C"
JNIEXPORT jbyteArray JNICALL
Java_network_loki_messenger_libsession_1util_encrypt_Attachments_encryptBytes(JNIEnv *env,
jobject thiz,
jbyteArray seed,
jbyteArray plaintext_in,
jint plaintext_in_offset,
jint plaintext_in_len,
jbyteArray cipher_out,
jint cipher_out_offset,
jint cipher_out_len,
jint domain) {
return run_catching_cxx_exception_or_throws<jbyteArray>(env, [=] {
auto key = encrypt(
JavaByteArrayRef(env, seed).get_as_bytes(),
JavaByteArrayRef(env, plaintext_in).get_as_bytes().subspan(plaintext_in_offset, plaintext_in_len),
static_cast<Domain>(domain),
JavaByteArrayRef(env, cipher_out).get_as_bytes().subspan(cipher_out_offset, cipher_out_len)
);


return util::bytes_from_span(env, std::span(reinterpret_cast<unsigned char *>(key.data()), key.size()));
});
}

extern "C"
JNIEXPORT jlong JNICALL
Java_network_loki_messenger_libsession_1util_encrypt_Attachments_decryptBytes(JNIEnv *env,
jobject thiz,
jbyteArray key,
jbyteArray cipher_in,
jint cipher_in_offset,
jint cipher_in_len,
jbyteArray plain_out,
jint plain_out_offset,
jint plain_out_len) {
return run_catching_cxx_exception_or_throws<jlong>(env, [=] {
JavaByteArrayRef key_ref(env, key);

return decrypt(
JavaByteArrayRef(env, cipher_in).get_as_bytes().subspan(cipher_in_offset, cipher_in_len),
std::span<std::byte, ENCRYPT_KEY_SIZE>(
reinterpret_cast<std::byte *>(key_ref.get().data()),
ENCRYPT_KEY_SIZE
),
JavaByteArrayRef(env, plain_out).get_as_bytes().subspan(plain_out_offset, plain_out_len)
);
});
}
20 changes: 20 additions & 0 deletions library/src/main/cpp/jni_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -199,11 +199,31 @@ namespace jni_utils {
env->ReleaseByteArrayElements(byte_array, reinterpret_cast<jbyte *>(data.data()), 0);
}

jbyteArray java_array() const {
return byte_array;
}

const unsigned char * bytes() const {
return data.data();
}

unsigned char *bytes() {
return data.data();
}

size_t size() const {
return data.size();
}

// Get the data as a span. Only valid during the lifetime of this object.
std::span<unsigned char> get() const {
return data;
}

std::span<std::byte> get_as_bytes() const {
return std::span<std::byte>(reinterpret_cast<std::byte *>(data.data()), data.size());
}

std::vector<unsigned char> copy() const {
return std::vector(data.begin(), data.end());
}
Expand Down
148 changes: 148 additions & 0 deletions library/src/main/cpp/webp_utils.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
#include <jni.h>
#include "jni_utils.h"

#include <webp/mux.h>
#include <webp/demux.h>
#include <webp/decode.h>
#include <webp/encode.h>

template<typename T>
using WebPPtr = std::unique_ptr<T, void (*)(T *)>;

extern "C"
JNIEXPORT jbyteArray JNICALL
Java_network_loki_messenger_libsession_1util_image_WebPUtils_reencodeWebPAnimation(JNIEnv *env,
jobject thiz,
jbyteArray input,
jint target_width,
jint target_height) {
jni_utils::JavaByteArrayRef input_ref(env, input);
WebPData input_data = {
.bytes = input_ref.bytes(),
.size = input_ref.size(),
};

WebPAnimDecoderOptions opts;
WebPAnimDecoderOptionsInit(&opts);

opts.color_mode = MODE_RGBA;
opts.use_threads = 1;

WebPPtr<WebPAnimDecoder> decoder(WebPAnimDecoderNew(&input_data, &opts), &WebPAnimDecoderDelete);

if (!decoder) {
env->ThrowNew(env->FindClass("java/lang/RuntimeException"),
"Failed to create animation decoder");
return nullptr;
}

WebPAnimInfo info;
if (!WebPAnimDecoderGetInfo(decoder.get(), &info)) {
env->ThrowNew(env->FindClass("java/lang/RuntimeException"),
"Failed to get animation info");
return nullptr;
}

WebPPtr<WebPAnimEncoder> encoder(WebPAnimEncoderNew(target_width, target_height, nullptr), &WebPAnimEncoderDelete);
if (!encoder) {
env->ThrowNew(env->FindClass("java/lang/RuntimeException"),
"Failed to create animation encoder");
return 0;
}


int ts_mills = 0;
uint8_t *frame_data = nullptr;

while (WebPAnimDecoderGetNext(decoder.get(), &frame_data, &ts_mills)) {
WebPPicture pic;
WebPPictureInit(&pic);

// First, import the frame into a picture
pic.width = info.canvas_width;
pic.height = info.canvas_height;
pic.use_argb = 0;
if (!WebPPictureImportRGBA(&pic, frame_data, info.canvas_width * 4)) {
env->ThrowNew(env->FindClass("java/lang/RuntimeException"),
"Failed to import frame into picture");
return nullptr;
}

// If the target size is different, rescale the picture
if (target_width != info.canvas_width || target_height != info.canvas_height) {
// Re-scale the picture to the target size
if (!WebPPictureRescale(&pic, target_width, target_height)) {
WebPPictureFree(&pic);
env->ThrowNew(env->FindClass("java/lang/RuntimeException"),
"Failed to rescale picture");
return nullptr;
}
}

// Now add the picture to the encoder
WebPConfig encode_config;
WebPConfigInit(&encode_config);
encode_config.quality = 95.f;

auto encode_succeeded = WebPAnimEncoderAdd(encoder.get(), &pic, ts_mills, &encode_config);

// Free the picture as sonn as we're done with it
WebPPictureFree(&pic);

if (!encode_succeeded) {
env->ThrowNew(env->FindClass("java/lang/RuntimeException"),
"Failed to add frame to encoder");
return nullptr;
}
}

WebPData out;
WebPDataInit(&out);

if (!WebPAnimEncoderAssemble(encoder.get(), &out)) {
env->ThrowNew(env->FindClass("java/lang/RuntimeException"),
"Failed to assemble animation");
return nullptr;
}

jni_utils::JavaByteArrayRef out_ref(env, env->NewByteArray(out.size));
std::memcpy(out_ref.bytes(), out.bytes, out.size);
WebPDataClear(&out);

return out_ref.java_array();
}

extern "C"
JNIEXPORT jboolean JNICALL
Java_network_loki_messenger_libsession_1util_image_WebPUtils_isWebPAnimation(JNIEnv *env,
jobject thiz,
jbyteArray input) {
jni_utils::JavaByteArrayRef input_ref(env, input);

WebPBitstreamFeatures features;

if (WebPGetFeatures(input_ref.bytes(), input_ref.size(), &features) != VP8_STATUS_OK) {
return false;
}

return features.has_animation != 0;
}

extern "C"
JNIEXPORT jintArray JNICALL
Java_network_loki_messenger_libsession_1util_image_WebPUtils_getWebPDimensions(JNIEnv *env,
jobject thiz,
jbyteArray input) {
jni_utils::JavaByteArrayRef input_ref(env, input);

int width, height;
if (!WebPGetInfo(input_ref.bytes(), input_ref.size(), &width, &height)) {
return nullptr;
}

jint dimen[] = { width, height };

jintArray result = env->NewIntArray(2);
env->SetIntArrayRegion(result, 0, 2, dimen);
return result;
}
Loading