diff --git a/Cargo.lock b/Cargo.lock index afc6219ad..c50ae6fec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1824,6 +1824,7 @@ dependencies = [ "http 1.1.0", "jni 0.21.1", "libsignal-bridge-macros", + "libsignal-core", "libsignal-message-backup", "libsignal-net", "libsignal-protocol", diff --git a/bin/update_versions.py b/bin/update_versions.py index 9bddb70d1..d52da39db 100755 --- a/bin/update_versions.py +++ b/bin/update_versions.py @@ -32,6 +32,7 @@ PODSPEC_PATTERN = re.compile(r"^(.*\.version\s+=\s+')(.*)(')") GRADLE_PATTERN = re.compile(r'^(\s+version\s+=\s+")(.*)(")') NODE_PATTERN = re.compile(r'^(\s+"version": ")(.*)(")') CARGO_PATTERN = re.compile(r'^(version = ")(.*)(")') +RUST_PATTERN = re.compile(r'^(pub const VERSION: &str = ")(.*)(")') def bridge_path(bridge): @@ -48,6 +49,7 @@ def main(): update_version('LibSignalClient.podspec', PODSPEC_PATTERN, new_version) update_version(os.path.join('java', 'build.gradle'), GRADLE_PATTERN, new_version) update_version(os.path.join('node', 'package.json'), NODE_PATTERN, new_version) + update_version(os.path.join('rust', 'core', 'src', 'version.rs'), RUST_PATTERN, new_version) update_version(bridge_path('ffi'), CARGO_PATTERN, new_version) update_version(bridge_path('jni'), CARGO_PATTERN, new_version) update_version(bridge_path('node'), CARGO_PATTERN, new_version) diff --git a/java/client/src/main/java/org/signal/libsignal/net/Network.java b/java/client/src/main/java/org/signal/libsignal/net/Network.java index b1ac49746..c0eb484b7 100644 --- a/java/client/src/main/java/org/signal/libsignal/net/Network.java +++ b/java/client/src/main/java/org/signal/libsignal/net/Network.java @@ -35,9 +35,9 @@ public class Network { */ private final Svr3 svr3; - public Network(Environment env) { + public Network(Environment env, String userAgent) { this.tokioAsyncContext = new TokioAsyncContext(); - this.connectionManager = new ConnectionManager(env); + this.connectionManager = new ConnectionManager(env, userAgent); this.svr3 = new Svr3(this); } @@ -45,7 +45,7 @@ public class Network { * Sets the proxy host to be used for all new connections (until overridden). * *

Sets a domain name and port to be used to proxy all new outgoing connections. The proxy can - * be overridden by calling this method again or unset by calling {@link clearProxy}. + * be overridden by calling this method again or unset by calling {@link #clearProxy}. */ public void setProxy(String host, int port) { this.connectionManager.setProxy(host, port); @@ -54,7 +54,7 @@ public class Network { /** * Ensures that future connections will be made directly, not through a proxy. * - *

Clears any proxy configuration set via {@link setProxy}. If none was set, calling this + *

Clears any proxy configuration set via {@link #setProxy}. If none was set, calling this * method is a no-op. */ public void clearProxy() { @@ -116,8 +116,8 @@ public class Network { } static class ConnectionManager extends NativeHandleGuard.SimpleOwner { - private ConnectionManager(Environment env) { - super(Native.ConnectionManager_new(env.value)); + private ConnectionManager(Environment env, String userAgent) { + super(Native.ConnectionManager_new(env.value, userAgent)); } private void setProxy(String host, int port) { diff --git a/java/client/src/test/java/org/signal/libsignal/net/ChatServiceTest.java b/java/client/src/test/java/org/signal/libsignal/net/ChatServiceTest.java index 237f6b113..ff166fc70 100644 --- a/java/client/src/test/java/org/signal/libsignal/net/ChatServiceTest.java +++ b/java/client/src/test/java/org/signal/libsignal/net/ChatServiceTest.java @@ -16,6 +16,8 @@ import org.signal.libsignal.util.TestEnvironment; public class ChatServiceTest { + private static final String USER_AGENT = "test"; + private static final int EXPECTED_STATUS = 200; private static final String EXPECTED_MESSAGE = "OK"; @@ -24,7 +26,7 @@ public class ChatServiceTest { private static final Map EXPECTED_HEADERS = Map.of( - "user-agent", "test", + "content-type", "application/octet-stream", "forwarded", "1.1.1.1"); @Test @@ -106,7 +108,7 @@ public class ChatServiceTest { final String PROXY_SERVER = TestEnvironment.get("LIBSIGNAL_TESTING_PROXY_SERVER"); Assume.assumeNotNull(PROXY_SERVER); - final Network net = new Network(Network.Environment.STAGING); + final Network net = new Network(Network.Environment.STAGING, USER_AGENT); final ChatService chat = net.createChatService("", ""); // Just make sure we can connect. chat.connectUnauthenticated().get(); @@ -119,7 +121,7 @@ public class ChatServiceTest { Assume.assumeNotNull(PROXY_SERVER); // The default TLS proxy config doesn't support staging, so we connect to production. - final Network net = new Network(Network.Environment.PRODUCTION); + final Network net = new Network(Network.Environment.PRODUCTION, USER_AGENT); final String[] proxyComponents = PROXY_SERVER.split(":"); switch (proxyComponents.length) { case 1: diff --git a/java/client/src/test/java/org/signal/libsignal/net/Svr3Test.java b/java/client/src/test/java/org/signal/libsignal/net/Svr3Test.java index 70949d371..4b63fe9d2 100644 --- a/java/client/src/test/java/org/signal/libsignal/net/Svr3Test.java +++ b/java/client/src/test/java/org/signal/libsignal/net/Svr3Test.java @@ -21,6 +21,8 @@ import org.signal.libsignal.util.TestEnvironment; public class Svr3Test { + private static final String USER_AGENT = "test"; + private final byte[] STORED_SECRET = Hex.fromStringCondensedAssert( "d2ae1668ac8a2bfd6170498332babad7cd72b9314631559a361310eee0a8adc6"); @@ -49,7 +51,7 @@ public class Svr3Test { @Test public void backupAndRestore() throws Exception { - Network net = new Network(Network.Environment.STAGING); + Network net = new Network(Network.Environment.STAGING, USER_AGENT); byte[] restored = net.svr3() .backup(STORED_SECRET, "password", 2, this.auth) @@ -60,7 +62,7 @@ public class Svr3Test { @Test public void noMoreTries() throws Exception { - Network net = new Network(Network.Environment.STAGING); + Network net = new Network(Network.Environment.STAGING, USER_AGENT); // Backup and first restore should succeed byte[] shareSet = net.svr3().backup(STORED_SECRET, "password", 1, this.auth).get(); net.svr3().restore("password", shareSet, this.auth).get(); @@ -75,7 +77,7 @@ public class Svr3Test { @Test public void failedRestore() throws Exception { - Network net = new Network(Network.Environment.STAGING); + Network net = new Network(Network.Environment.STAGING, USER_AGENT); byte[] shareSet = net.svr3().backup(STORED_SECRET, "password", 1, this.auth).get(); try { net.svr3().restore("wrong password", shareSet, this.auth).get(); @@ -87,7 +89,7 @@ public class Svr3Test { @Test public void zeroTries() throws Exception { - Network net = new Network(Network.Environment.STAGING); + Network net = new Network(Network.Environment.STAGING, USER_AGENT); assertThrows( IllegalArgumentException.class, () -> net.svr3().backup(STORED_SECRET, "password", 0, this.auth).get()); @@ -95,7 +97,7 @@ public class Svr3Test { @Test public void badSecret() throws Exception { - Network net = new Network(Network.Environment.STAGING); + Network net = new Network(Network.Environment.STAGING, USER_AGENT); try { net.svr3().backup(new byte[31], "password", 1, this.auth).get(); } catch (ExecutionException ex) { @@ -106,7 +108,7 @@ public class Svr3Test { @Test public void badShareSet() throws Exception { - Network net = new Network(Network.Environment.STAGING); + Network net = new Network(Network.Environment.STAGING, USER_AGENT); byte[] shareSet = net.svr3().backup(STORED_SECRET, "password", 1, this.auth).get(); shareSet[0] ^= 0xff; try { diff --git a/java/shared/java/org/signal/libsignal/internal/Native.java b/java/shared/java/org/signal/libsignal/internal/Native.java index 894e586bc..a020688c1 100644 --- a/java/shared/java/org/signal/libsignal/internal/Native.java +++ b/java/shared/java/org/signal/libsignal/internal/Native.java @@ -175,7 +175,7 @@ public final class Native { public static native void ConnectionManager_Destroy(long handle); public static native void ConnectionManager_clear_proxy(long connectionManager); - public static native long ConnectionManager_new(int environment); + public static native long ConnectionManager_new(int environment, String userAgent); public static native void ConnectionManager_set_proxy(long connectionManager, String host, int port); public static native void CreateCallLinkCredentialPresentation_CheckValidContents(byte[] presentationBytes) throws Exception; diff --git a/node/Native.d.ts b/node/Native.d.ts index 7dc6e6f94..d0b7ec784 100644 --- a/node/Native.d.ts +++ b/node/Native.d.ts @@ -178,7 +178,7 @@ export function CiphertextMessage_FromPlaintextContent(m: Wrapper): Buffer; export function CiphertextMessage_Type(msg: Wrapper): number; export function ConnectionManager_clear_proxy(connectionManager: Wrapper): void; -export function ConnectionManager_new(environment: number): ConnectionManager; +export function ConnectionManager_new(environment: number, userAgent: string): ConnectionManager; export function ConnectionManager_set_ipv6_enabled(connectionManager: Wrapper, ipv6Enabled: boolean): void; export function ConnectionManager_set_proxy(connectionManager: Wrapper, host: string, port: number): void; export function CreateCallLinkCredentialPresentation_CheckValidContents(presentationBytes: Buffer): void; diff --git a/node/ts/net.ts b/node/ts/net.ts index f15407547..9a3487dff 100644 --- a/node/ts/net.ts +++ b/node/ts/net.ts @@ -186,9 +186,11 @@ export class Net { */ svr3: Svr3Client; - constructor(env: Environment) { + constructor(env: Environment, userAgent: string) { this.asyncContext = newNativeHandle(Native.TokioAsyncContext_new()); - this.connectionManager = newNativeHandle(Native.ConnectionManager_new(env)); + this.connectionManager = newNativeHandle( + Native.ConnectionManager_new(env, userAgent) + ); this.svr3 = new Svr3ClientImpl(this.asyncContext, this.connectionManager); } diff --git a/node/ts/test/NetTest.ts b/node/ts/test/NetTest.ts index 67810c8ec..954d1b2e7 100644 --- a/node/ts/test/NetTest.ts +++ b/node/ts/test/NetTest.ts @@ -18,6 +18,8 @@ use(chaiAsPromised); util.initLogger(); config.truncateThreshold = 0; +const userAgent = 'test'; + describe('chat service api', () => { it('converts ChatServiceError to native', () => { expect(() => Native.TESTING_ChatServiceErrorConvert()) @@ -34,7 +36,7 @@ describe('chat service api', () => { it('converts Response object to native', () => { const status = 200; const headers: ReadonlyArray<[string, string]> = [ - ['user-agent', 'test'], + ['content-type', 'application/octet-stream'], ['forwarded', '1.1.1.1'], ]; const expectedWithContent: ChatResponse = { @@ -69,11 +71,11 @@ describe('chat service api', () => { const verb = 'GET'; const path = '/test'; - const userAgent = 'test'; + const contentType = 'application/octet-stream'; const forwarded = '1.1.1.1'; const content = Buffer.from('content'); const headers: Array<[string, string]> = [ - ['user-agent', userAgent], + ['content-type', contentType], ['forwarded', forwarded], ]; @@ -88,8 +90,8 @@ describe('chat service api', () => { expect(Native.TESTING_ChatRequestGetPath(request)).equals(path); expect(Native.TESTING_ChatRequestGetBody(request)).deep.equals(content); expect( - Native.TESTING_ChatRequestGetHeaderValue(request, 'user-agent') - ).equals(userAgent); + Native.TESTING_ChatRequestGetHeaderValue(request, 'content-type') + ).equals(contentType); expect( Native.TESTING_ChatRequestGetHeaderValue(request, 'forwarded') ).equals(forwarded); @@ -129,7 +131,7 @@ describe('chat service api', () => { }); it('can connect unauthenticated', async () => { - const net = new Net(Environment.Staging); + const net = new Net(Environment.Staging, userAgent); const chatService = net.newChatService(); await chatService.connectUnauthenticated(); await chatService.disconnect(); @@ -140,7 +142,7 @@ describe('chat service api', () => { assert(PROXY_SERVER, 'checked above'); // The default TLS proxy config doesn't support staging, so we connect to production. - const net = new Net(Environment.Production); + const net = new Net(Environment.Production, userAgent); const [host = PROXY_SERVER, port = '443'] = PROXY_SERVER.split(':', 2); net.setProxy(host, parseInt(port, 10)); @@ -243,7 +245,7 @@ describe('cdsi lookup', () => { describe('SVR3', () => { const USERNAME = randomBytes(16).toString('hex'); - const SVR3 = new Net(Environment.Staging).svr3; + const SVR3 = new Net(Environment.Staging, userAgent).svr3; function make_auth(): Readonly { const otp = Native.CreateOTPFromBase64( diff --git a/rust/bridge/shared/Cargo.toml b/rust/bridge/shared/Cargo.toml index 17477b9a6..412c7bd5c 100644 --- a/rust/bridge/shared/Cargo.toml +++ b/rust/bridge/shared/Cargo.toml @@ -14,6 +14,7 @@ license = "AGPL-3.0-only" attest = { path = "../../attest" } device-transfer = { path = "../../device-transfer" } libsignal-bridge-macros = { path = "macros" } +libsignal-core = { path = "../../core" } libsignal-message-backup = { path = "../../message-backup" } libsignal-net = { path = "../../net" } libsignal-protocol = { path = "../../protocol" } diff --git a/rust/bridge/shared/src/net.rs b/rust/bridge/shared/src/net.rs index 0cb61382f..70aaaa647 100644 --- a/rust/bridge/shared/src/net.rs +++ b/rust/bridge/shared/src/net.rs @@ -24,7 +24,7 @@ use libsignal_net::chat::{ use libsignal_net::enclave::{ Cdsi, EnclaveEndpoint, EnclaveEndpointConnection, EnclaveKind, Nitro, PpssSetup, Sgx, Tpm2Snp, }; -use libsignal_net::env::{Env, Svr3Env}; +use libsignal_net::env::{add_user_agent_header, Env, Svr3Env}; use libsignal_net::infra::connection_manager::MultiRouteConnectionManager; use libsignal_net::infra::dns::DnsResolver; use libsignal_net::infra::tcp_ssl::{ @@ -106,7 +106,7 @@ impl RefUnwindSafe for ConnectionManager {} impl ConnectionManager { const DEFAULT_CONNECT_TIMEOUT: Duration = Duration::from_secs(10); - fn new(environment: Environment) -> Self { + fn new(environment: Environment, user_agent: String) -> Self { let dns_resolver = DnsResolver::new_with_static_fallback(environment.env().static_fallback()); let transport_connector = @@ -116,6 +116,7 @@ impl ConnectionManager { .env() .chat_domain_config .connection_params_with_fallback(); + let chat_connection_params = add_user_agent_header(chat_connection_params, &user_agent); let chat_ws_config = make_ws_config(chat_endpoint, Self::DEFAULT_CONNECT_TIMEOUT); Self { chat: EndpointConnection::new_multi( @@ -123,11 +124,11 @@ impl ConnectionManager { Self::DEFAULT_CONNECT_TIMEOUT, chat_ws_config, ), - cdsi: Self::endpoint_connection(environment.env().cdsi), + cdsi: Self::endpoint_connection(environment.env().cdsi, &user_agent), svr3: ( - Self::endpoint_connection(environment.env().svr3.sgx()), - Self::endpoint_connection(environment.env().svr3.nitro()), - Self::endpoint_connection(environment.env().svr3.tpm2snp()), + Self::endpoint_connection(environment.env().svr3.sgx(), &user_agent), + Self::endpoint_connection(environment.env().svr3.nitro(), &user_agent), + Self::endpoint_connection(environment.env().svr3.tpm2snp(), &user_agent), ), transport_connector, } @@ -135,8 +136,10 @@ impl ConnectionManager { fn endpoint_connection( endpoint: EnclaveEndpoint<'static, E>, + user_agent: &str, ) -> EnclaveEndpointConnection { let params = endpoint.domain_config.connection_params_with_fallback(); + let params = add_user_agent_header(params, user_agent); EnclaveEndpointConnection::new_multi( endpoint.mr_enclave, params, @@ -146,8 +149,11 @@ impl ConnectionManager { } #[bridge_fn] -fn ConnectionManager_new(environment: AsType) -> ConnectionManager { - ConnectionManager::new(environment.into_inner()) +fn ConnectionManager_new( + environment: AsType, + user_agent: String, +) -> ConnectionManager { + ConnectionManager::new(environment.into_inner(), user_agent) } #[bridge_fn] @@ -559,6 +565,6 @@ mod test { #[test_case(Environment::Staging; "staging")] #[test_case(Environment::Prod; "prod")] fn can_create_connection_manager(env: Environment) { - let _ = ConnectionManager::new(env); + let _ = ConnectionManager::new(env, "test-user-agent".to_string()); } } diff --git a/rust/bridge/shared/src/testing/net.rs b/rust/bridge/shared/src/testing/net.rs index 4aefeaeaa..9bf64bc7b 100644 --- a/rust/bridge/shared/src/testing/net.rs +++ b/rust/bridge/shared/src/testing/net.rs @@ -150,7 +150,10 @@ fn TESTING_ChatServiceResponseConvert( false => None, }; let mut headers = HeaderMap::new(); - headers.append(http::header::USER_AGENT, HeaderValue::from_static("test")); + headers.append( + http::header::CONTENT_TYPE, + HeaderValue::from_static("application/octet-stream"), + ); headers.append(http::header::FORWARDED, HeaderValue::from_static("1.1.1.1")); Ok(ChatResponse { status: StatusCode::OK, diff --git a/rust/core/src/lib.rs b/rust/core/src/lib.rs index 08a1dcea0..fa8d6726d 100644 --- a/rust/core/src/lib.rs +++ b/rust/core/src/lib.rs @@ -4,6 +4,9 @@ // mod address; +mod version; + pub use address::{ Aci, DeviceId, Pni, ProtocolAddress, ServiceId, ServiceIdFixedWidthBinaryBytes, ServiceIdKind, }; +pub use version::VERSION; diff --git a/rust/core/src/version.rs b/rust/core/src/version.rs new file mode 100644 index 000000000..59c97247f --- /dev/null +++ b/rust/core/src/version.rs @@ -0,0 +1,8 @@ +// +// Copyright 2024 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + +// The value of this constant is updated by the script +// and should not be manually modified +pub const VERSION: &str = "0.45.1"; diff --git a/rust/net/src/auth.rs b/rust/net/src/auth.rs index eda57c68c..bf0894c81 100644 --- a/rust/net/src/auth.rs +++ b/rust/net/src/auth.rs @@ -17,7 +17,10 @@ pub trait HttpBasicAuth { impl From for HttpRequestDecorator { fn from(value: T) -> Self { - HttpRequestDecorator::HeaderAuth(basic_authorization(value.username(), value.password())) + HttpRequestDecorator::Header( + http::header::AUTHORIZATION, + basic_authorization(value.username(), value.password()), + ) } } diff --git a/rust/net/src/chat.rs b/rust/net/src/chat.rs index 7bb0deaff..c7fcf413a 100644 --- a/rust/net/src/chat.rs +++ b/rust/net/src/chat.rs @@ -427,8 +427,10 @@ fn build_authorized_chat_service( username: String, password: String, ) -> AuthorizedChatService { - let header_auth_decorator = - HttpRequestDecorator::HeaderAuth(basic_authorization(&username, &password)); + let header_auth_decorator = HttpRequestDecorator::Header( + http::header::AUTHORIZATION, + basic_authorization(&username, &password), + ); // ws authorized let chat_over_ws_auth = ServiceWithReconnect::new( diff --git a/rust/net/src/env.rs b/rust/net/src/env.rs index 65270939c..c86a140f6 100644 --- a/rust/net/src/env.rs +++ b/rust/net/src/env.rs @@ -192,6 +192,20 @@ impl DomainConfig { } } +pub fn add_user_agent_header( + mut connection_params_list: Vec, + user_agent: &str, +) -> Vec { + let with_lib_version = format!("{} libsignal/{}", user_agent, libsignal_core::VERSION); + connection_params_list.iter_mut().for_each(|cp| { + cp.http_request_decorator.add(HttpRequestDecorator::Header( + http::header::USER_AGENT, + http::header::HeaderValue::try_from(&with_lib_version).expect("valid header string"), + )); + }); + connection_params_list +} + pub struct ProxyConfig { route_type: RouteType, hostname: &'static str, diff --git a/rust/net/src/infra.rs b/rust/net/src/infra.rs index b5c5815c2..77facde6d 100644 --- a/rust/net/src/infra.rs +++ b/rust/net/src/infra.rs @@ -52,11 +52,8 @@ impl IpType { /// A collection of commonly used decorators for HTTP requests. #[derive(Clone, Debug)] pub enum HttpRequestDecorator { - /// Adds the following header to the request: - /// ```text - /// Authorization: Basic base64(:) - /// ``` - HeaderAuth(String), + /// Adds a specific header to the request + Header(http::header::HeaderName, http::header::HeaderValue), /// Prefixes the path portion of the request with the given string. PathPrefix(&'static str), /// Applies generic decoration logic. @@ -72,6 +69,12 @@ impl From for HttpRequestDecoratorSeq { } } +impl HttpRequestDecoratorSeq { + pub fn add(&mut self, decorator: HttpRequestDecorator) { + self.0.push(decorator) + } +} + /// Contains all information required to establish an HTTP connection to the remote endpoint: /// - `sni` value to be used in TLS, /// - `host` value to be used for DNS resolution an in the HTTP requests headers, @@ -194,7 +197,7 @@ impl HttpRequestDecorator { fn decorate_request(&self, request_builder: http::request::Builder) -> http::request::Builder { match self { Self::Generic(decorator) => decorator(request_builder), - Self::HeaderAuth(auth) => request_builder.header(::http::header::AUTHORIZATION, auth), + Self::Header(name, value) => request_builder.header(name, value), Self::PathPrefix(prefix) => { let uri = request_builder.uri_ref().expect("request has URI set"); let mut parts = (*uri).clone().into_parts(); @@ -454,8 +457,11 @@ pub(crate) mod test { fn test_header_auth_decorator() { let expected = "Basic dXNybm06cHNzd2Q="; let builder = Request::get("https://chat.signal.org/"); - let builder = HttpRequestDecorator::HeaderAuth(basic_authorization("usrnm", "psswd")) - .decorate_request(builder); + let builder = HttpRequestDecorator::Header( + http::header::AUTHORIZATION, + basic_authorization("usrnm", "psswd"), + ) + .decorate_request(builder); let (parts, _) = builder.body(()).unwrap().into_parts(); assert_eq!( expected, diff --git a/rust/net/src/utils.rs b/rust/net/src/utils.rs index 11da5b8f3..dc604e25b 100644 --- a/rust/net/src/utils.rs +++ b/rust/net/src/utils.rs @@ -6,15 +6,16 @@ use base64::prelude::{Engine as _, BASE64_STANDARD}; use futures_util::stream::FuturesUnordered; use futures_util::StreamExt; +use http::HeaderValue; use std::future; use std::future::Future; use std::time::Duration; /// Constructs the value of the `Authorization` header for the `Basic` auth scheme. -pub(crate) fn basic_authorization(username: &str, password: &str) -> String { +pub(crate) fn basic_authorization(username: &str, password: &str) -> HeaderValue { let auth = BASE64_STANDARD.encode(format!("{}:{}", username, password).as_bytes()); let auth = format!("Basic {}", auth); - auth + HeaderValue::try_from(auth).expect("valid header value") } /// Requires a `Future` to complete before the specified duration has elapsed. diff --git a/swift/Sources/LibSignalClient/Net.swift b/swift/Sources/LibSignalClient/Net.swift index b1b130072..2a5dc8b3a 100644 --- a/swift/Sources/LibSignalClient/Net.swift +++ b/swift/Sources/LibSignalClient/Net.swift @@ -26,9 +26,9 @@ public class Net { public let svr3: Svr3Client /// Creates a new `Net` instance that enables interacting with services in the given Signal environment. - public init(env: Environment) { + public init(env: Environment, userAgent: String) { self.asyncContext = TokioAsyncContext() - self.connectionManager = ConnectionManager(env: env) + self.connectionManager = ConnectionManager(env: env, userAgent: userAgent) self.svr3 = Svr3Client(self.asyncContext, self.connectionManager) } @@ -373,9 +373,9 @@ internal class TokioAsyncContext: NativeHandleOwner { } internal class ConnectionManager: NativeHandleOwner { - convenience init(env: Net.Environment) { + convenience init(env: Net.Environment, userAgent: String) { var handle: OpaquePointer? - failOnError(signal_connection_manager_new(&handle, env.rawValue)) + failOnError(signal_connection_manager_new(&handle, env.rawValue, userAgent)) self.init(owned: handle!) } diff --git a/swift/Sources/SignalFfi/signal_ffi.h b/swift/Sources/SignalFfi/signal_ffi.h index e2e3e7b47..1ac8130bc 100644 --- a/swift/Sources/SignalFfi/signal_ffi.h +++ b/swift/Sources/SignalFfi/signal_ffi.h @@ -1397,7 +1397,7 @@ SignalFfiError *signal_tokio_async_context_new(SignalTokioAsyncContext **out); SignalFfiError *signal_tokio_async_context_destroy(SignalTokioAsyncContext *p); -SignalFfiError *signal_connection_manager_new(SignalConnectionManager **out, uint8_t environment); +SignalFfiError *signal_connection_manager_new(SignalConnectionManager **out, uint8_t environment, const char *user_agent); SignalFfiError *signal_connection_manager_set_proxy(const SignalConnectionManager *connection_manager, const char *host, uint16_t port); diff --git a/swift/Tests/LibSignalClientTests/ChatServiceTests.swift b/swift/Tests/LibSignalClientTests/ChatServiceTests.swift index 4e4a7442a..649b92e85 100644 --- a/swift/Tests/LibSignalClientTests/ChatServiceTests.swift +++ b/swift/Tests/LibSignalClientTests/ChatServiceTests.swift @@ -12,10 +12,11 @@ import SignalFfi import XCTest final class ChatServiceTests: TestCaseBase { + private static let userAgent = "test" private static let expectedStatus: UInt16 = 200 private static let expectedMessage = "OK" private static let expectedContent = "content".data(using: .utf8) - private static let expectedHeaders = ["user-agent": "test", "forwarded": "1.1.1.1"] + private static let expectedHeaders = ["content-type": "application/octet-stream", "forwarded": "1.1.1.1"] func testConvertResponse() throws { do { @@ -111,7 +112,7 @@ final class ChatServiceTests: TestCaseBase { throw XCTSkip() } - let net = Net(env: .staging) + let net = Net(env: .staging, userAgent: Self.userAgent) let chat = net.createChatService(username: "", password: "") // Just make sure we can connect. try await chat.connectUnauthenticated() @@ -124,7 +125,7 @@ final class ChatServiceTests: TestCaseBase { } // The default TLS proxy config doesn't support staging, so we connect to production. - let net = Net(env: .production) + let net = Net(env: .production, userAgent: Self.userAgent) let host: Substring let port: UInt16 if let colonIndex = PROXY_SERVER.firstIndex(of: ":") { diff --git a/swift/Tests/LibSignalClientTests/NetTests.swift b/swift/Tests/LibSignalClientTests/NetTests.swift index 621ba05ac..5280fd89e 100644 --- a/swift/Tests/LibSignalClientTests/NetTests.swift +++ b/swift/Tests/LibSignalClientTests/NetTests.swift @@ -11,6 +11,8 @@ import Foundation import SignalFfi import XCTest +let userAgent: String = "test" + final class NetTests: XCTestCase { func testCdsiLookupResultConversion() async throws { let ACI_UUID = "9d0652a3-dcc3-4d11-975f-74d61598733f" @@ -114,7 +116,7 @@ final class NetTests: XCTestCase { token: nil, returnAcisWithoutUaks: false ) - let net = Net(env: .staging) + let net = Net(env: .staging, userAgent: userAgent) let lookup = try await net.cdsiLookup(auth: auth, request: request) let response = try await lookup.complete() @@ -139,7 +141,7 @@ final class Svr3Tests: TestCaseBase { func testBackupAndRestore() async throws { let auth = try Auth(username: self.username, enclaveSecret: self.getEnclaveSecret()) - let net = Net(env: .staging) + let net = Net(env: .staging, userAgent: userAgent) let shareSet = try await net.svr3.backup( self.storedSecret, @@ -158,7 +160,7 @@ final class Svr3Tests: TestCaseBase { func testInvalidPassword() async throws { let auth = try Auth(username: self.username, enclaveSecret: self.getEnclaveSecret()) - let net = Net(env: .staging) + let net = Net(env: .staging, userAgent: userAgent) let shareSet = try await net.svr3.backup( self.storedSecret, @@ -183,7 +185,7 @@ final class Svr3Tests: TestCaseBase { func testCorruptedShareSet() async throws { let auth = try Auth(username: self.username, enclaveSecret: self.getEnclaveSecret()) - let net = Net(env: .staging) + let net = Net(env: .staging, userAgent: userAgent) var shareSet = try await net.svr3.backup( self.storedSecret, @@ -210,7 +212,7 @@ final class Svr3Tests: TestCaseBase { func testMaxRetries() async throws { let auth = try Auth(username: self.username, enclaveSecret: self.getEnclaveSecret()) - let net = Net(env: .staging) + let net = Net(env: .staging, userAgent: userAgent) let shareSet = try await net.svr3.backup( self.storedSecret, @@ -241,7 +243,7 @@ final class Svr3Tests: TestCaseBase { func testMaxRetriesAfterFailure() async throws { let auth = try Auth(username: self.username, enclaveSecret: self.getEnclaveSecret()) - let net = Net(env: .staging) + let net = Net(env: .staging, userAgent: userAgent) let shareSet = try await net.svr3.backup( self.storedSecret, @@ -279,7 +281,7 @@ final class Svr3Tests: TestCaseBase { func testInvalidMaxTries() async throws { let auth = try Auth(username: self.username, enclaveSecret: self.getEnclaveSecret()) - let net = Net(env: .staging) + let net = Net(env: .staging, userAgent: userAgent) do { _ = try await net.svr3.backup( @@ -298,7 +300,7 @@ final class Svr3Tests: TestCaseBase { func testInvalidSecretSize() async throws { let auth = try Auth(username: self.username, enclaveSecret: self.getEnclaveSecret()) - let net = Net(env: .staging) + let net = Net(env: .staging, userAgent: userAgent) do { _ = try await net.svr3.backup(