Skip to content
Merged
Show file tree
Hide file tree
Changes from 23 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
26 changes: 22 additions & 4 deletions 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 @@ -18,14 +19,15 @@ def __init__(
collections: list[str],
port: int | None = None,
disable_tls: bool = False,
identity: CertKeyPair | None = None,
):
self.database = database
"""The database that the listener will be serving"""

self.collections = collections
"""The collections within the database that the listener will be serving"""

self.port = port
self.__port = port
"""
The port that the listener will request to listen on
(if None, the OS will choose). Once start is called,
Expand All @@ -38,20 +40,36 @@ def __init__(
"""If True, TLS will be disabled for the listener"""

self.__original_port = port
self.__identity = identity
self.__index = database._index
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.
@property
def port(self) -> int:
assert self.__port is not None, "Listener port not set"
return self.__port

def set_identity(self):
self.__identity = create_leaf_certificate(f"Test Server {self.__index}")

async def start(self) -> None:
"""Start listening for incoming connections"""
with self.__tracer.start_as_current_span("start_listener"):
request = self.__request_factory.create_request(
TestServerRequestType.START_LISTENER,
db=self.database.name,
collections=self.collections,
port=self.port,
port=self.__port,
disable_tls=self.disable_tls,
identity=self.__identity,
)
resp = await self.__request_factory.send_request(self.__index, request)
if resp.error is not None:
Expand All @@ -60,7 +78,7 @@ async def start(self) -> None:
return

cast_resp = cast(PostStartListenerResponseMethods, resp)
self.port = cast_resp.port
self.__port = cast_resp.port
self.__id = cast_resp.listener_id

async def stop(self) -> None:
Expand All @@ -76,5 +94,5 @@ async def stop(self) -> None:
cbl_trace(resp.error.message)
return

self.port = self.__original_port
self.__port = self.__original_port
self.__id = ""
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
9 changes: 9 additions & 0 deletions client/src/cbltest/v1/requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -680,12 +680,14 @@ def __init__(
collections: list[str],
port: int | None = None,
disable_tls: bool = False,
identity: CertKeyPair | None = None,
):
super().__init__()
self.__database = db
self.__collections = collections
self.__port = port
self.__disable_tls = disable_tls
self.__identity = identity

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

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
2 changes: 1 addition & 1 deletion client/src/cbltest/version.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from typing import Final

# For hatchling to easily detect the version
__version__ = "2.0.2"
__version__ = "2.0.3"

# Typed version for outside use
VERSION: Final[str] = __version__
Expand Down
8 changes: 4 additions & 4 deletions servers/ios/TestServer.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
409F44172DEE187600BB7851 /* GetMultipeerReplicatorStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 409F44162DEE187600BB7851 /* GetMultipeerReplicatorStatus.swift */; };
409F442C2DEE228300BB7851 /* MultipeerReplicatorStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 409F442B2DEE228300BB7851 /* MultipeerReplicatorStatus.swift */; };
409F443D2DF0EB3800BB7851 /* MultipeerReplicatorCAAuthenticator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 409F443C2DF0EB3800BB7851 /* MultipeerReplicatorCAAuthenticator.swift */; };
409F443F2DF1016C00BB7851 /* MultipeerReplicatorIdentity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 409F443E2DF1016C00BB7851 /* MultipeerReplicatorIdentity.swift */; };
409F443F2DF1016C00BB7851 /* TLSIdentityData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 409F443E2DF1016C00BB7851 /* TLSIdentityData.swift */; };
409F45302DFA7BC900BB7851 /* FileDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 409F452F2DFA7BC900BB7851 /* FileDownloader.swift */; };
40AA34992CAF5D20004F4D08 /* StopReplicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40AA34982CAF5D20004F4D08 /* StopReplicator.swift */; };
40AA349D2CAF6755004F4D08 /* CollectionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40AA349C2CAF6755004F4D08 /* CollectionSpec.swift */; };
Expand Down Expand Up @@ -116,7 +116,7 @@
409F44162DEE187600BB7851 /* GetMultipeerReplicatorStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetMultipeerReplicatorStatus.swift; sourceTree = "<group>"; };
409F442B2DEE228300BB7851 /* MultipeerReplicatorStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultipeerReplicatorStatus.swift; sourceTree = "<group>"; };
409F443C2DF0EB3800BB7851 /* MultipeerReplicatorCAAuthenticator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultipeerReplicatorCAAuthenticator.swift; sourceTree = "<group>"; };
409F443E2DF1016C00BB7851 /* MultipeerReplicatorIdentity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultipeerReplicatorIdentity.swift; sourceTree = "<group>"; };
409F443E2DF1016C00BB7851 /* TLSIdentityData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TLSIdentityData.swift; sourceTree = "<group>"; };
409F452F2DFA7BC900BB7851 /* FileDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileDownloader.swift; sourceTree = "<group>"; };
40A219D62CDC3E790038204C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
40AA34982CAF5D20004F4D08 /* StopReplicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StopReplicator.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -330,7 +330,7 @@
40AA34A62CAF9BC8004F4D08 /* GetDocumentRequest.swift */,
409F443C2DF0EB3800BB7851 /* MultipeerReplicatorCAAuthenticator.swift */,
409F43FF2DEA561A00BB7851 /* MultipeerReplicatorConfiguration.swift */,
409F443E2DF1016C00BB7851 /* MultipeerReplicatorIdentity.swift */,
409F443E2DF1016C00BB7851 /* TLSIdentityData.swift */,
409F442B2DEE228300BB7851 /* MultipeerReplicatorStatus.swift */,
404619CF2CB9A761003835C7 /* NewSession.swift */,
40BDA8D32AF20AAB008DB256 /* PerformMaintenanceConfiguration.swift */,
Expand Down Expand Up @@ -510,7 +510,7 @@
40BDA8032AF1C13B008DB256 /* GetReplicatorStatus.swift in Sources */,
40BDA8012AF1C13B008DB256 /* SnapshotDocuments.swift in Sources */,
4F0E10A52F30FCD900025FB2 /* MultipeerTransport.swift in Sources */,
409F443F2DF1016C00BB7851 /* MultipeerReplicatorIdentity.swift in Sources */,
409F443F2DF1016C00BB7851 /* TLSIdentityData.swift in Sources */,
40BDA8E42AF20AAB008DB256 /* ContentTypes.swift in Sources */,
40BDA8152AF1C15C008DB256 /* AnyCodable.swift in Sources */,
40AA34A32CAF8C4C004F4D08 /* ReplicationConflictResolverFactory.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1620"
version = "1.7">
version = "1.8">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ extension ContentTypes {
let peerGroupID: String
let database: String
let collections: [ReplicationCollection]
let identity: MultipeerReplicatorIdentity
let identity: TLSIdentityData
let authenticator: MultipeerReplicatorCAAuthenticator?
let transports: [MultipeerTransport]?
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ extension ContentTypes {
let collections: [String]
let port: UInt16?
let disableTLS: Bool?
let identity: TLSIdentityData?

public var description: String {
var result: String = "Endpoint Listener Configuration:\n"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ extension ContentTypes {
let peerGroupID: String
let database: String
let collections: [ReplicationCollection]
let identity: MultipeerReplicatorIdentity
let identity: TLSIdentityData
let authenticator: MultipeerReplicatorCAAuthenticator?
let transports: [MultipeerTransport]?
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// MultipeerReplicatorIdentity.swift
// TLSIdentityData.swift
// TestServer
//
// Created by Pasin Suriyentrakorn on 6/4/25.
Expand All @@ -12,7 +12,7 @@ extension ContentTypes {
case PKCS12 = "PKCS12"
}

struct MultipeerReplicatorIdentity : Content {
struct TLSIdentityData : Content {
let encoding: IdentityDataEncoding
let data: String
let password: String?
Expand Down
7 changes: 6 additions & 1 deletion servers/ios/TestServer/Handlers/StartListener.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@ 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 id = try dbManager.startListener(dbName: listenerStartRq.database,
collections: listenerStartRq.collections,
port: listenerStartRq.port,
disableTLS: disableTLS,
identity: listenerStartRq.identity)

return ContentTypes.Listener(id: id, port: listenerStartRq.port)
}
Expand Down
2 changes: 1 addition & 1 deletion servers/ios/TestServer/Server/TestServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import Vapor
class TestServer : ObservableObject {
var app : Vapor.Application

public static let maxAPIVersion = 1
public static let maxAPIVersion = 2

public static let serverID = UUID()

Expand Down
37 changes: 28 additions & 9 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.TLSIdentityData? ) throws -> UUID {
var collectionsArr: [Collection] = []

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

if !listenerConfig.disableTLS {
let label = "ios-p2p-\(dbName)"
let importedIdentity: TLSIdentity

do {
if let id = identity {
importedIdentity = try DatabaseManager.createTLSIdentity( for: id, label:label)
} else {
guard let existingIdentity = try TLSIdentity.identity(withLabel: label) else {

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.

Let do not do this logic to avoid using any stale identity which may cause a test failure that is difficult to figure out. So if the created identity is nil, we can throw the error right away.

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.

You can do something like this

listenerConfig.disableTLS = disableTLS

if let id = identity {
    guard let tlsIdentity = try DatabaseManager.createTLSIdentity( for: id, label:label) else { 
        throw TestServerError.badRequest("Failed to import TLS identity")
    }
    listenerConfig.tlsIdentity = tlsIdentity
}

@borrrden borrrden Apr 16, 2026

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.

This will fail if a replicator is created twice with the same label, as is done in one of the tests. I had requested something like this to indicate passing nil is the way to say "use the previous existing identity"

throw TestServerError.badRequest(
"TLS enabled but no existing TLS identity found for label \(label)"
)
}
importedIdentity = existingIdentity
}
} catch {
throw TestServerError.badRequest("Failed to import TLS identity")
}

listenerConfig.tlsIdentity = importedIdentity
}
Comment thread
barkha06 marked this conversation as resolved.
let listener = URLEndpointListener(config: listenerConfig)

let listenerID = UUID()
Expand Down Expand Up @@ -298,8 +318,8 @@ class DatabaseManager {

public func startMultipeerReplicator(config: ContentTypes.MultipeerReplicatorConfiguration) throws -> UUID {
Log.log(level: .debug, message: "Starting Multipeer Replicator")

let identity = try DatabaseManager.multipeerReplicatorIdentity(for: config)
let label = "ios-multipeer-\(config.peerGroupID)"
let identity = try DatabaseManager.createTLSIdentity(for: config.identity, label:label)

let authenticator = try DatabaseManager.multipeerAuthenticator(for: config.authenticator)

Expand Down Expand Up @@ -710,15 +730,14 @@ class DatabaseManager {
}
}

private static func multipeerReplicatorIdentity(for config: ContentTypes.MultipeerReplicatorConfiguration) throws -> TLSIdentity {
let label = "ios-multipeer-\(config.peerGroupID)"
private static func createTLSIdentity(for identityData: ContentTypes.TLSIdentityData, label: String) throws -> TLSIdentity {

guard let data = Data(base64Encoded: config.identity.data) else {
throw TestServerError.badRequest("Invalid multipeer replictor's identity data")
guard let data = Data(base64Encoded: identityData.data) else {
throw TestServerError.badRequest("Invalid TLS identity data")
}

try TLSIdentity.deleteIdentity(withLabel: label)
return try TLSIdentity.importIdentity(withData: data, password: config.identity.password, label: label)
return try TLSIdentity.importIdentity(withData: data, password: identityData.password, label: label)
}

private static func multipeerAuthenticator(for config: ContentTypes.MultipeerReplicatorCAAuthenticator?) throws -> MultipeerCertificateAuthenticator {
Expand Down
17 changes: 11 additions & 6 deletions spec/api/api.yaml
Comment thread
barkha06 marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ info:
```

Changes
2.0.3 (04/17/2026)
* Add identity property to [/startListener](#operation/startListener)

2.0.2 (07/24/2025)
* Add peer ID property to return value of [/startMultipeerReplicator](#operation/startMultipeerReplicator)

Expand Down Expand Up @@ -152,7 +155,7 @@ info:
* Changed DocumentReplication.flags type from int to array of enums.
* Added 'enableDocumentListener' to ReplicatorConfiguration.
* Added a note that any enum values are case insensitive.
version: 2.0.2
version: 2.0.3
tags:
- name: API
description: The API endpoints of the test server
Expand Down Expand Up @@ -783,7 +786,7 @@ paths:
operationId: startListener
requestBody:
description: |-
The request object containing the collections to share and, optionally, the port to listen on and disableTLS flag.
The request object containing the collections to share and, optionally, the port to listen on, disableTLS flag and TLS Identity.
content:
application/json:
schema:
Expand All @@ -803,6 +806,8 @@ paths:
example: 12345
disableTLS:
type: boolean
identity:
$ref: '#/components/schemas/TLSIdentityData'
required: true
responses:
'200':
Expand Down Expand Up @@ -880,7 +885,7 @@ paths:
items:
$ref: "#/components/schemas/ReplicationCollection"
identity:
$ref: '#/components/schemas/MultipeerReplicatorIdentity'
$ref: '#/components/schemas/TLSIdentityData'
authenticator:
$ref: '#/components/schemas/MultipeerReplicatorCAAuthenticator'
transports:
Expand Down Expand Up @@ -1329,10 +1334,10 @@ components:
transport:
type: string
enum: ['WIFI', 'BLUETOOTH']
MultipeerReplicatorIdentity:
TLSIdentityData:
description: |-
MultipeerReplicatorIdentity is used to specify a TLS certificate and key particular
for a multipeer replciator so that it can identify itself.
TLSIdentityData is used to specify a TLS certificate and key particular
for a replicator so that it can identify itself.
type: object
required: ['encoding', 'data']
properties:
Expand Down
Loading
Loading