Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
0c4641b
Modified prebuild Jenkinsfile and android build.gradle to fix kotlin …
barkha06 Apr 8, 2026
e808a27
Modified prebuild Jenkinsfile and android build.gradle to fix kotlin …
barkha06 Apr 8, 2026
73afb3b
Added peer to peer TLS changes for IOS testserver and P2P tests
barkha06 Apr 13, 2026
3569675
Fixed syntax errors
barkha06 Apr 13, 2026
8fed5b8
Added spec file
barkha06 Apr 13, 2026
edbedae
Added spec file
barkha06 Apr 13, 2026
a92a751
Fixed lint errors
barkha06 Apr 13, 2026
7733116
Fixed ty check errors
barkha06 Apr 13, 2026
4cf6a57
Fixed format issue
barkha06 Apr 13, 2026
aa18542
Update servers/ios/TestServer/ContentTypes/StartListenerRequest.swift
barkha06 Apr 13, 2026
d19e200
Update tests/QE/test_peer_to_peer.py to require minimum 3 testserver
barkha06 Apr 13, 2026
4334ff5
Fixed indentation
barkha06 Apr 14, 2026
7a7d04c
Fixed indentation
barkha06 Apr 15, 2026
715d404
Changed MultipeerReplicatorIdentity to TLSIdentityData
barkha06 Apr 15, 2026
6e82c53
Fixed syntax issues
barkha06 Apr 15, 2026
f0769a7
Removed reuseIdentity
barkha06 Apr 16, 2026
02356af
Added set_identity()
barkha06 Apr 16, 2026
f79c858
Modified API spec and testserver version
barkha06 Apr 16, 2026
ff7d8fb
Modified IOS testserver version
barkha06 Apr 16, 2026
1317e69
Added space after TLSIdentityData?
barkha06 Apr 17, 2026
e60bfe8
Modified API spec - version and changes
barkha06 Apr 17, 2026
2dec1b6
Modified API spec - version and changes
barkha06 Apr 17, 2026
6606b2d
Made port a private property and added a getter
barkha06 Apr 17, 2026
d5633c6
Removed version check test step from spec
barkha06 Apr 18, 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
17 changes: 16 additions & 1 deletion client/src/cbltest/api/listener.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from opentelemetry.trace import get_tracer

from cbltest.api.database import Database
from cbltest.api.x509_certificate import CertKeyPair, create_leaf_certificate
from cbltest.logging import cbl_error, cbl_trace
from cbltest.requests import TestServerRequestType
from cbltest.response_types import PostStartListenerResponseMethods
Expand All @@ -16,8 +17,10 @@ def __init__(
self,
database: Database,
collections: list[str],
port: int | None = None,
port: int = 59840,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't see a reason to make this have a default.

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.

This is a required parameter because the replication URL needs to be generated based on the port, so the ty lint check failed since it was marked optional but it needs to have int value later.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It will still have an int value after it is started. I think the ty lint isn't getting all of the information.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I still don't get why the above is not enough to satisfy this situation.

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.

I am not sure, but here's the failure:
--> /Users/barkha.goyal/Desktop/temp2/couchbase-lite-tests/tests/QE/test_peer_to_peer.py:376:24 | 374 | all_dbs[1], 375 | endpoint=cblpytest.test_servers[0].replication_url( 376 | "db1", listener1.port, tls=True | ^^^^^^^^^^^^^^ Expected int, found Unknown | int | None 377 | ), 378 | replicator_type=replicator_type, | info: Element None of this union is not assignable to int info: Method defined here --> /Users/barkha.goyal/Desktop/temp2/couchbase-lite-tests/client/src/cbltest/api/testserver.py:146:9 | 144 | await self.__request_factory.send_request(self.__index, request) 145 | 146 | def replication_url(self, db_name: str, port: int, tls: bool = False): | ^^^^^^^^^^^^^^^ --------- Parameter declared here 147 | """

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.

One way to avoid would this is to make port a private parameter and define a getter for it, since the getter will always return an int, the ty lint would accept it.

disable_tls: bool = False,
identity: CertKeyPair | None = None,
reuse_identity: bool = False,
Comment thread
barkha06 marked this conversation as resolved.
Outdated
):
self.database = database
"""The database that the listener will be serving"""
Expand All @@ -38,11 +41,21 @@ def __init__(
"""If True, TLS will be disabled for the listener"""

self.__original_port = port
self.__identity = identity
self.__index = database._index
if not identity:
self.__identity = create_leaf_certificate(f"Test Server {self.__index}")
self.reuse_identity = reuse_identity
self.__request_factory = database._request_factory
self.__tracer = get_tracer(__name__, VERSION)
self.__id: str = ""

@property
def identity(self) -> CertKeyPair:
"""Gets the identity used by the replicator"""
assert self.__identity is not None, "Listener identity not initialized"
return self.__identity

Comment thread
barkha06 marked this conversation as resolved.
async def start(self) -> None:
"""Start listening for incoming connections"""
with self.__tracer.start_as_current_span("start_listener"):
Expand All @@ -52,6 +65,8 @@ async def start(self) -> None:
collections=self.collections,
port=self.port,
disable_tls=self.disable_tls,
identity=self.__identity,
reuse_identity=self.reuse_identity,
)
resp = await self.__request_factory.send_request(self.__index, request)
if resp.error is not None:
Expand Down
5 changes: 3 additions & 2 deletions client/src/cbltest/api/testserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,12 +143,13 @@ async def log(self, msg: str) -> None:
)
await self.__request_factory.send_request(self.__index, request)

def replication_url(self, db_name: str, port: int):
def replication_url(self, db_name: str, port: int, tls: bool = False):
"""
Returns the URL of the replication endpoint for this test server
"""
ws_scheme = "ws://" # For now not using secure

if tls:
ws_scheme = "wss://"
_assert_not_null(db_name, "db_name")

parsed = urlparse(self.url)
Expand Down
14 changes: 14 additions & 0 deletions client/src/cbltest/v1/requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -680,12 +680,16 @@ def __init__(
collections: list[str],
port: int | None = None,
disable_tls: bool = False,
identity: CertKeyPair | None = None,
reuse_identity: bool = False,
):
super().__init__()
self.__database = db
self.__collections = collections
self.__port = port
self.__disable_tls = disable_tls
self.__identity = identity
self.__reuse_identity = reuse_identity

def to_json(self) -> Any:
json: dict[str, Any] = {
Expand All @@ -699,6 +703,16 @@ def to_json(self) -> Any:
if self.__disable_tls:
json["disableTLS"] = self.__disable_tls

if self.__reuse_identity:
json["reuseIdentity"] = self.__reuse_identity

if self.__identity is not None:
json["identity"] = {
"encoding": "PKCS12",
"data": base64.b64encode(self.__identity.pfx_bytes()).decode("utf-8"),
"password": self.__identity.password,
}

Comment thread
barkha06 marked this conversation as resolved.
return json


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,16 @@ extension ContentTypes {
let collections: [String]
let port: UInt16?
let disableTLS: Bool?
let identity: MultipeerReplicatorIdentity?
Comment thread
barkha06 marked this conversation as resolved.
Outdated
let reuseIdentity: Bool?
Comment thread
barkha06 marked this conversation as resolved.
Outdated

public var description: String {
var result: String = "Endpoint Listener Configuration:\n"
result += "\tdatabase: \(database)\n"
result += "\tcollection: \(collections.joined(separator: ", "))\n"
result += "\tport: \(port ?? 0)\n"
result += "\tdisableTLS: \(disableTLS ?? false)\n"
result += "\treuseIdentity: \(reuseIdentity ?? false)\n"
return result
}
}
Expand Down
3 changes: 2 additions & 1 deletion servers/ios/TestServer/Handlers/StartListener.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ extension Handlers {

let dbManager = req.databaseManager
let disableTLS = listenerStartRq.disableTLS ?? false
let id = try dbManager.startListener(dbName: listenerStartRq.database, collections: listenerStartRq.collections, port: listenerStartRq.port, disableTLS: disableTLS)
let reuseIdentity = listenerStartRq.reuseIdentity ?? false
let id = try dbManager.startListener(dbName: listenerStartRq.database, collections: listenerStartRq.collections, port: listenerStartRq.port, disableTLS: disableTLS,identity: listenerStartRq.identity,reuseIdentity: reuseIdentity)
Comment thread
barkha06 marked this conversation as resolved.
Outdated

return ContentTypes.Listener(id: id, port: listenerStartRq.port)
}
Expand Down
33 changes: 31 additions & 2 deletions servers/ios/TestServer/Utils/DatabaseManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ class DatabaseManager {
}
}

public func startListener(dbName: String, collections: [String], port: UInt16?, disableTLS: Bool = false) throws -> UUID {
public func startListener(dbName: String, collections: [String], port: UInt16?, disableTLS: Bool = false, identity:ContentTypes.MultipeerReplicatorIdentity, reuseIdentity: Bool = false) throws -> UUID {
var collectionsArr: [Collection] = []

guard let database = databases[dbName]
Expand All @@ -107,7 +107,36 @@ class DatabaseManager {
var listenerConfig = URLEndpointListenerConfiguration(collections: collectionsArr)
listenerConfig.port = port
listenerConfig.disableTLS = disableTLS

if !listenerConfig.disableTLS {
let label = "ios-p2p-\(dbName)"
guard let data = Data(base64Encoded: identity.data) else {
throw TestServerError.badRequest("Invalid replicator identity data")
}
if !reuseIdentity {
try TLSIdentity.deleteIdentity(withLabel: label)
}
let importedIdentity: TLSIdentity
do {
if reuseIdentity {
guard let existingIdentity = try TLSIdentity.identity(withLabel: label) else {
throw TestServerError.badRequest(
"reuseIdentity=true but no existing TLS identity found for label \(label)"
)
}
importedIdentity = existingIdentity
} else {
importedIdentity = try TLSIdentity.importIdentity(
withData: data,
password: identity.password,
label: label
)
}
} catch {
throw TestServerError.badRequest("Failed to import TLS identity")
Comment thread
barkha06 marked this conversation as resolved.
Outdated
}
Comment thread
barkha06 marked this conversation as resolved.

listenerConfig.tlsIdentity = importedIdentity
}
let listener = URLEndpointListener(config: listenerConfig)

let listenerID = UUID()
Expand Down
100 changes: 100 additions & 0 deletions spec/tests/QE/test_peer_to_peer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Peer-to-Peer Tests

This document describes the test cases for Couchbase Lite peer-to-peer URLEndpointListener replication, covering concurrent updates, various topology setups (one-to-many, many-to-one), multiple databases, and server failure/restart scenarios.

## test_peer_to_peer_concurrent_replication

Test peer-to-peer replication between a listener and a client, including concurrent document updates on both devices while replication is active.

1. Verify CBL version >= 2.8.0 on all test servers.
Comment thread
barkha06 marked this conversation as resolved.
Outdated
2. Reset local databases and create `db1` on Device-1 and Device-2.
3. Start listener on Device-1:
* database: `db1`
* collections: `["_default._default"]`
* port: 59840
4. Add `num_of_docs` documents to `db1` on Device-2.
5. Setup and start a replicator on Device-2:
* endpoint: Device-1's listener URL
* collections: `["_default._default"]`
* type: `replicator_type`
* continuous: `continuous`
6. Wait for replication to complete (Target activity: IDLE if continuous, STOPPED if not).
7. Check that all documents are replicated correctly and both databases have the same content.
8. Perform concurrent updates to documents on both the listener (Device-1) and the client (Device-2).
9. Wait for replication to complete after the updates.
10. Check that all updated documents are replicated correctly and databases match.
11. Stop the listener on Device-1.

## test_peer_to_peer_oneClient_toManyServers

Test a peer-to-peer topology where a single client device replicates concurrently to multiple listener servers (one-to-many).

1. Verify CBL version >= 2.8.0 on all test servers.
2. Reset local databases and create `db1` on Device-1, Device-2, and Device-3.
3. Add `num_of_docs` documents to `db1` on Device-1.
4. Start listeners on Device-2 and Device-3 (database: `db1`, collections: `["_default._default"]`, port: 59840).
5. Setup and start Replicator 1 on Device-1 pointing to Device-2's listener endpoint.
6. Setup and start Replicator 2 on Device-1 pointing to Device-3's listener endpoint.
7. Wait for both Replicator 1 and Replicator 2 to complete replication.
8. Check that all documents are replicated correctly across all three databases.
9. Stop the listeners on Device-2 and Device-3.

## test_peer_to_peer_oneServer_toManyClients

Test a peer-to-peer topology where multiple client devices replicate concurrently from a single listener server (many-to-one).

1. Verify CBL version >= 2.8.0 on all test servers.
2. Reset local databases and create `db1` on Device-1, Device-2, and Device-3.
3. Add `num_of_docs` documents to `db1` on Device-1.
4. Start a listener on Device-1 (database: `db1`, collections: `["_default._default"]`, port: 59840).
5. Setup and start a Replicator on Device-2 pointing to Device-1's listener endpoint.
6. Setup and start a Replicator on Device-3 pointing to Device-1's listener endpoint.
7. Wait for both replicators on Device-2 and Device-3 to complete.
8. Check that all documents are replicated correctly across all three databases.
9. Stop the listener on Device-1.

## test_peer_to_peer_oneServer_twoClients_on_single_db

Test peer-to-peer replication behavior when multiple distinct replication sessions are established from a single client database to the same listener endpoint.

1. Verify CBL version >= 2.8.0 on all test servers.
2. Reset local databases and create `db1` on Device-1 and Device-2.
3. Add `num_of_docs` documents to `db1` on Device-1.
4. Start a listener on Device-1 (database: `db1`, collections: `["_default._default"]`, port: 59840).
5. Setup and start three separate replicator sessions on Device-2, all using Device-2's `db1` and pointing to Device-1's listener endpoint.
6. Wait for replication to complete on all 3 sessions.
7. Check that all documents are replicated correctly and both databases match.
8. Stop the listener on Device-1.

## test_peer_to_peer_replication_with_multiple_dbs

Test concurrent peer-to-peer replication scaling across multiple databases (DB1, DB2, DB3) on two devices.

1. Verify CBL version >= 2.8.0 on all test servers.
2. Create and reset 3 local databases (`db1`, `db2`, `db3`) on Device-1 and Device-2.
3. Add `num_of_docs` documents to each of the 3 databases on Device-1.
4. Start 3 listeners on Device-2, one for each database:
* `db1` on port 59840
* `db2` on port 59841
* `db3` on port 59842
5. Setup and start 3 separate replicators on Device-1 corresponding to each database, pointing to their respective listener endpoints on Device-2.
6. Wait for replication to complete on all 3 replicator sessions.
7. Check that all documents are replicated correctly across all database pairs (Device-1 DBs match Device-2 DBs).
8. Stop all 3 listeners on Device-2.

## test_peer_to_peer_with_server_down

Test the resilience of peer-to-peer replication when the listener server goes offline and is restarted during active client database updates.

1. Verify CBL version >= 2.8.0 on all test servers.
2. Reset local databases and create `db1` on Device-1 and Device-2.
3. Add `num_of_docs` documents to `db1` on Device-2.
4. Start a listener on Device-1 (database: `db1`, collections: `["_default._default"]`, port: 59840).
5. Setup and start a replicator on Device-2 pointing to Device-1's listener endpoint.
6. Asynchronously execute the following actions concurrently:
* Stop the listener on Device-1.
* Start a new listener on Device-1 on the same port, re-using the previous TLS identity.
* Perform document updates on Device-2.
7. Restart the replicator on Device-2 if necessary, and wait for replication to complete.
8. Check that all updated documents are successfully replicated to Device-1 despite the listener restart.
9. Stop the new listener on Device-1.
Loading
Loading