Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
c029675
BT changes for common
Aniket392 Feb 17, 2026
76792ba
Update cmakelist and bluetoothpeer enterprise check
Aniket392 Feb 17, 2026
25bd509
Updated as build failure due to wrong checks and static declaration
Aniket392 Feb 18, 2026
193881a
Updating multipeer replicator argument to take protocol
Aniket392 Feb 23, 2026
ccfcd40
Added release for BT peer and removed onIncommingConnection
Aniket392 Feb 27, 2026
ed78477
Added public transport APIs and removed UUID sync
Aniket392 Mar 3, 2026
1c16c31
Updated the BluetoothPeer object creation in Java instead of native side
Aniket392 Mar 4, 2026
6e5c4f6
Updating addpeer and C4Peer creation and protocol usage
Aniket392 Mar 5, 2026
fa3eb2a
Conversion of MultipeerTransport and Enumset in java rather than in J…
Aniket392 Mar 6, 2026
f2d2aa6
Adding replicatorTansport API
Aniket392 Mar 11, 2026
0d1afb9
BT Socket Factory creation and connecting with java code
Aniket392 Mar 11, 2026
4c1a7dd
Socketfactory register and fromnative removal and location update
Aniket392 Mar 14, 2026
c5c9b8f
Updated Socketfactory's classes from java to android path
Aniket392 Mar 14, 2026
85d2f87
Hashmap update and initating the C4BtSocketFactory
Aniket392 Mar 20, 2026
bb4b679
Changes needed to make it possible for me to build
borrrden Mar 24, 2026
5240212
Delete dangling local ref
borrrden Mar 26, 2026
d20415b
Use byte array slice in its own scope
borrrden Mar 26, 2026
10e64ad
Fix JNI linkage in NativeBluetoothPeer
pasin Mar 26, 2026
43562e5
Use the proper context and directly use string_view
borrrden Mar 27, 2026
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
6 changes: 5 additions & 1 deletion common/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ add_library( LiteCoreJNI SHARED
${JNI_SRC}/native_c4listener.cc
${JNI_SRC}/native_c4multipeerreplicator.cc
${JNI_SRC}/native_c4peerdiscoveryprovider.cc
${JNI_SRC}/native_c4btsocketfactory.cc
${JNI_SRC}/native_bluetoothpeer.cc
${JNI_SRC}/MetadataHelper.cc
${JNI_SRC}/native_c4observer.cc
Expand Down Expand Up @@ -96,6 +97,9 @@ target_link_options(

target_link_libraries( LiteCoreJNI
LiteCore
# Below are needed on Android. If this gets moved to Java as well this will need
# to be changed
"log"
c++_static
c++abi
)

22 changes: 18 additions & 4 deletions common/main/cpp/MetadataHelper.cc
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,12 @@ namespace litecore::jni {
jstring jKey = (jstring)env->CallObjectMethod(entry, getKeyMethod);
jbyteArray jValue = (jbyteArray)env->CallObjectMethod(entry, getValueMethod);

std::string key = JstringToUTF8(env, jKey);
jbyteArraySlice value(env, jValue);
{
std::string key = JstringToUTF8(env, jKey);
jbyteArraySlice value(env, jValue);

metadata[std::string(key)] = fleece::alloc_slice(value);
metadata[std::string(key)] = fleece::alloc_slice(value);
}

env->DeleteLocalRef(entry);
env->DeleteLocalRef(jKey);
Expand All @@ -41,6 +43,9 @@ namespace litecore::jni {

env->DeleteLocalRef(entrySet);
env->DeleteLocalRef(entries);
env->DeleteLocalRef(mapClass);
env->DeleteLocalRef(setClass);
env->DeleteLocalRef(entryClass);

return metadata;
}
Expand All @@ -50,7 +55,16 @@ namespace litecore::jni {
// Create HashMap
jclass hashMapClass = env->FindClass("java/util/HashMap");
jmethodID hashMapInit = env->GetMethodID(hashMapClass, "<init>", "(I)V");
jobject hashMap = env->NewObject(hashMapClass, hashMapInit);


size_t rawSize = metadata.size();
jint size = (rawSize > 10000) ? 0 : static_cast<jint>(rawSize);

jobject hashMap = env->NewObject(hashMapClass, hashMapInit, size);
if (env->ExceptionCheck()) {
env->ExceptionClear();
return env->NewObject(hashMapClass, hashMapInit, 0);
}

// Put method for HashMap
jmethodID hashMapPut = env->GetMethodID(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
#include <jni.h>

#ifdef __cplusplus
extern "C++" {
extern "C" {
#endif

/*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#ifndef ANDROID_COM_COUCHBASE_LITE_INTERNAL_CORE_IMPL_NATIVEC4BTSOCKETFACTORY_H
#define ANDROID_COM_COUCHBASE_LITE_INTERNAL_CORE_IMPL_NATIVEC4BTSOCKETFACTORY_H
#include <jni.h>

#ifdef __cplusplus
extern "C++" {
#endif

JNIEXPORT jlong JNICALL
Java_com_couchbase_lite_internal_core_impl_NativeC4BTSocketFactory_registerBTSocketFactory(
JNIEnv*, jclass);

JNIEXPORT jlong JNICALL
Java_com_couchbase_lite_internal_core_impl_NativeC4BTSocketFactory_fromNative(
JNIEnv*, jclass, jlong, jstring, jstring, jlong, jstring, jint);

#ifdef __cplusplus
}
#endif

#endif //ANDROID_COM_COUCHBASE_LITE_INTERNAL_CORE_IMPL_NATIVEC4BTSOCKETFACTORY_H
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
#define COUCHBASE_LITE_JAVA_EE_ROOT_COM_COUCHBASE_LITE_INTERNAL_CORE_IMPL_NATIVEC4PEERDISCOVERYPROVIDER_H

#ifdef __cplusplus
extern "C++" {
extern "C" {
#endif

JNIEXPORT jlong JNICALL
Expand Down Expand Up @@ -50,10 +50,23 @@ JNIEXPORT jlongArray JNICALL
Java_com_couchbase_lite_internal_core_impl_NativeC4PeerDiscoveryProvider_peersWithProvider(
JNIEnv *env, jclass thiz, jlong providerPtr);

JNIEXPORT jstring JNICALL
JNIEXPORT jbyteArray JNICALL
Java_com_couchbase_lite_internal_core_impl_NativeC4PeerDiscoveryProvider_serviceUuidFromPeerGroup(
JNIEnv* env, jclass, jstring peerGroup);

JNIEXPORT void JNICALL
Java_com_couchbase_lite_internal_core_impl_NativeC4PeerDiscoveryProvider_createIncomingSocket(
JNIEnv* env, jclass,
jlong providerPtr,
jlong peerPtr,
jlong btSocketHandle,
jstring jUrl);

JNIEXPORT jlong JNICALL
Java_com_couchbase_lite_internal_core_impl_NativeC4PeerDiscoveryProvider_getSocketFactory(
JNIEnv* env, jclass,
jlong providerPtr);

#ifdef __cplusplus
}
#endif
Expand Down
6 changes: 3 additions & 3 deletions common/main/cpp/native_bluetoothpeer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ static C4Peer* getPeer(jlong peerPtr) {
return reinterpret_cast<C4Peer*>(peerPtr);
}

extern "C++" {
extern "C" {
JNIEXPORT jstring JNICALL
Java_com_couchbase_lite_internal_core_impl_NativeBluetoothPeer_getId(
JNIEnv *env, jclass clazz, jlong peerPtr) {
Expand Down Expand Up @@ -75,7 +75,7 @@ extern "C++" {
JNIEnv *env, jclass clazz, jlong peerPtr, jstring jurl) {
std::string url = JstringToUTF8(env, jurl);

BluetoothPeer *peer = static_cast<BluetoothPeer*>(getPeer(peerPtr));
auto *peer = dynamic_cast<BluetoothPeer*>(getPeer(peerPtr));
if (!peer) return;

peer->resolvingUrl(url, kC4NoError);
Expand All @@ -87,4 +87,4 @@ extern "C++" {
fleece::release(reinterpret_cast<const fleece::RefCounted*>(peerPtr));
}
}
#endif
#endif
12 changes: 12 additions & 0 deletions common/main/cpp/native_bluetoothpeer_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@

#include "c4PeerDiscovery.hh"
#include "c4Error.h"
#include "c4Socket.h"

namespace litecore::jni {
/// The BT C4SocketFactory — defined in native_c4btsocketfactory.cc
extern const C4SocketFactory kBTSocketFactory;
}

struct BTNativeHandle {
jlong btSocketHandle; // key into Java's sSocketHandles map
std::string peerID; // BT MAC address / CBL peer ID
};


class BluetoothPeer final : public C4Peer {
using C4Peer::C4Peer;
Expand Down
211 changes: 211 additions & 0 deletions common/main/cpp/native_c4btsocketfactory.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
#if defined(COUCHBASE_ENTERPRISE) && defined(__ANDROID__)

#include "c4Socket.h"
#include "c4Socket.hh"
#include "c4SocketTypes.h"
#include "native_glue.hh"
#include "native_bluetoothpeer_internal.h"
#include "com_couchbase_lite_internal_core_impl_NativeC4BTSocketFactory.h"

#include <string>

using namespace litecore;
using namespace litecore::jni;

// ─────────────────────────────────────────────────────────────────────────────
// Java class / method cache
// ─────────────────────────────────────────────────────────────────────────────

static jclass cls_C4BTSocketFactory;
static jmethodID m_open;
static jmethodID m_write;
static jmethodID m_completedReceive;
static jmethodID m_close;
static jmethodID m_dispose;
static jmethodID m_attached;

bool litecore::jni::initC4BTSocketFactory(JNIEnv* env) {
jclass localCls = env->FindClass(
"com/couchbase/lite/internal/p2p/ble/BluetoothSocketFactory");
if (!localCls) return false;

cls_C4BTSocketFactory = reinterpret_cast<jclass>(env->NewGlobalRef(localCls));
env->DeleteLocalRef(localCls);
if (!cls_C4BTSocketFactory) return false;

// static void open(long c4socketPeer, long factoryToken, String peerID)
m_open = env->GetStaticMethodID(cls_C4BTSocketFactory,
"open", "(JJLjava/lang/String;)V");
if (!m_open) return false;

// static void write(long c4socketPeer, byte[] data)
m_write = env->GetStaticMethodID(cls_C4BTSocketFactory,
"write", "(J[B)V");
if (!m_write) return false;

// static void completedReceive(long c4socketPeer, long byteCount)
m_completedReceive = env->GetStaticMethodID(cls_C4BTSocketFactory,
"completedReceive", "(JJ)V");
if (!m_completedReceive) return false;

// static void close(long c4socketPeer)
m_close = env->GetStaticMethodID(cls_C4BTSocketFactory,
"close", "(J)V");
if (!m_close) return false;

// static void dispose(long c4socketPeer)
m_dispose = env->GetStaticMethodID(cls_C4BTSocketFactory,
"dispose", "(J)V");
if (!m_dispose) return false;

// static void attached(long c4socketPeer, long btSocketHandle, String peerID)
m_attached = env->GetStaticMethodID(cls_C4BTSocketFactory,
"attached", "(JJLjava/lang/String;)V");
if (!m_attached) return false;

jniLog("C4BTSocketFactory: callbacks initialized");
return true;
}

// ─────────────────────────────────────────────────────────────────────────────
// C4SocketFactory callback implementations
// ─────────────────────────────────────────────────────────────────────────────

static void btOpen(C4Socket* socket,
const C4Address* addr,
C4Slice options,
void* context) {
JNIEnv* env = nullptr;
jint envState = attachJVM(&env, "btOpen");
if (envState != JNI_OK && envState != JNI_EDETACHED) return;

c4socket_retain(socket);

// addr->hostname carries the CBL peer-ID / BT MAC address as a C4Slice.
// toJString(env, C4Slice) is the correct helper (same as native_c4socket.cc).
jstring jPeerID = toJString(env, addr->hostname);

env->CallStaticVoidMethod(
cls_C4BTSocketFactory, m_open,
(jlong) socket,
(jlong) context, // factoryToken = context set at registration
jPeerID);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on https://developer.android.com/ndk/guides/jni-tips, it's a good practice to call ExceptionCheck() and ExceptionClear() to handle both caught and runtime exception when calling Java code such as

if (env->ExceptionCheck()) {
        env->ExceptionDescribe(); // Log to logcat or stderr
        env->ExceptionClear();
    }

You may check if we have don't this anywhere else or have any helper function for this. If not, I suggest to create a helper function to do this or create a ticket to take care this later if the method doesn't declare throws.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently in code base i can't see similar checks
I will create ticket for same


if (envState == JNI_EDETACHED) {
detachJVM("btOpen");
} else {
if (jPeerID) env->DeleteLocalRef(jPeerID);
}
}

static void btWrite(C4Socket* socket, C4SliceResult allocatedData) {
JNIEnv* env = nullptr;
jint envState = attachJVM(&env, "btWrite");
if (envState != JNI_OK && envState != JNI_EDETACHED) {
c4slice_free(allocatedData);
return;
}

jbyteArray jData = toJByteArray(env, allocatedData);
c4slice_free(allocatedData);

env->CallStaticVoidMethod(cls_C4BTSocketFactory, m_write, (jlong) socket, jData);

if (envState == JNI_EDETACHED) {
detachJVM("btWrite");
} else {
if (jData) env->DeleteLocalRef(jData);
}
}

static void btCompletedReceive(C4Socket* socket, size_t byteCount) {
JNIEnv* env = nullptr;
jint envState = attachJVM(&env, "btCompletedReceive");
if (envState != JNI_OK && envState != JNI_EDETACHED) return;

env->CallStaticVoidMethod(cls_C4BTSocketFactory, m_completedReceive,
(jlong) socket, (jlong) byteCount);

if (envState == JNI_EDETACHED) detachJVM("btCompletedReceive");
}

static void btClose(C4Socket* socket) {
JNIEnv* env = nullptr;
jint envState = attachJVM(&env, "btClose");
if (envState != JNI_OK && envState != JNI_EDETACHED) return;

env->CallStaticVoidMethod(cls_C4BTSocketFactory, m_close, (jlong) socket);

if (envState == JNI_EDETACHED) detachJVM("btClose");
}

static void btDispose(C4Socket* socket) {
auto* ctx = static_cast<BTNativeHandle*>(socket->getNativeHandle());
if (ctx) {
socket->setNativeHandle(nullptr);
delete ctx;
}

JNIEnv* env = nullptr;
jint envState = attachJVM(&env, "btDispose");
if (envState != JNI_OK && envState != JNI_EDETACHED) return;

env->CallStaticVoidMethod(cls_C4BTSocketFactory, m_dispose, (jlong) socket);

if (envState == JNI_EDETACHED) detachJVM("btDispose");
}

/**
* btAttached – called by LiteCore after c4socket_fromNative creates the
* peripheral (incoming) C4Socket.
*
* Retrieves the BTNativeHandle stored by fromNative via setNativeHandle(),
* forwards {btSocketHandle, peerID} to Java, then frees the handle.
*/
static void btAttached(C4Socket* socket) {
auto* ctx = static_cast<BTNativeHandle*>(socket->getNativeHandle());
if (!ctx) return;

// Clear immediately so btDispose won't double-free.
socket->setNativeHandle(nullptr);

JNIEnv* env = nullptr;
jint envState = attachJVM(&env, "btAttached");
if (envState != JNI_OK && envState != JNI_EDETACHED) {
delete ctx;
return;
}

jstring jPeerID = env->NewStringUTF((ctx->peerID).c_str());

env->CallStaticVoidMethod(
cls_C4BTSocketFactory, m_attached,
(jlong) socket,
ctx->btSocketHandle,
jPeerID);

delete ctx;

if (envState == JNI_EDETACHED) {
detachJVM("btAttached");
} else {
if (jPeerID) env->DeleteLocalRef(jPeerID);
}
}

// ─────────────────────────────────────────────────────────────────────────────
// The factory struct
// ─────────────────────────────────────────────────────────────────────────────
namespace litecore::jni {
const C4SocketFactory kBTSocketFactory{
.framing = kC4WebSocketClientFraming,
.context = nullptr, // filled in at registration time
.open = btOpen,
.write = btWrite,
.completedReceive = btCompletedReceive,
.close = btClose,
.dispose = btDispose,
.attached = btAttached,
};
}
#endif // COUCHBASE_ENTERPRISE && __ANDROID__
Loading