chat: Expose a raw_grpc endpoint

This commit is contained in:
Jordan Rose 2026-05-08 16:59:59 -07:00
parent 9903175a51
commit fb9407cbcb
7 changed files with 134 additions and 15 deletions

View File

@ -217,6 +217,8 @@ internal object Native {
@JvmStatic
public external fun AuthenticatedChatConnection_send_message_java(asyncRuntime: ObjectHandle, chat: ObjectHandle, destination: ByteArray, timestamp: Long, deviceIds: IntArray, registrationIds: IntArray, contents: Array<Object>, onlineOnly: Boolean, isUrgent: Boolean): CompletableFuture<Void?>
@JvmStatic
public external fun AuthenticatedChatConnection_send_raw_grpc(asyncRuntime: ObjectHandle, chat: ObjectHandle, service: String, method: String, payload: ByteArray): CompletableFuture<ByteArray>
@JvmStatic
public external fun AuthenticatedChatConnection_send_sync_message_java(asyncRuntime: ObjectHandle, chat: ObjectHandle, timestamp: Long, deviceIds: IntArray, registrationIds: IntArray, contents: Array<Object>, isUrgent: Boolean): CompletableFuture<Void?>
@JvmStatic @Throws(Exception::class)
@ -1307,6 +1309,8 @@ internal object Native {
public external fun UnauthenticatedChatConnection_send_message(asyncRuntime: ObjectHandle, chat: ObjectHandle, destination: ByteArray, timestamp: Long, deviceIds: IntArray, registrationIds: IntArray, contents: Array<ByteArray>, authKind: Int, authBuffer: ByteArray?, onlineOnly: Boolean, isUrgent: Boolean): CompletableFuture<Void?>
@JvmStatic
public external fun UnauthenticatedChatConnection_send_multi_recipient_message(asyncRuntime: ObjectHandle, chat: ObjectHandle, payload: ByteArray, timestamp: Long, auth: ByteArray?, onlineOnly: Boolean, isUrgent: Boolean): CompletableFuture<Array<Object>>
@JvmStatic
public external fun UnauthenticatedChatConnection_send_raw_grpc(asyncRuntime: ObjectHandle, chat: ObjectHandle, service: String, method: String, payload: ByteArray): CompletableFuture<ByteArray>
@JvmStatic @Throws(Exception::class)
public external fun UnidentifiedSenderMessageContent_Deserialize(data: ByteArray): ObjectHandle

View File

@ -475,6 +475,7 @@ type NativeFunctions = {
UnauthenticatedChatConnection_connect: (asyncRuntime: Wrapper<TokioAsyncContext>, connectionManager: Wrapper<ConnectionManager>, languages: string[]) => CancellablePromise<UnauthenticatedChatConnection>;
UnauthenticatedChatConnection_init_listener: (chat: Wrapper<UnauthenticatedChatConnection>, listener: ChatListener) => void;
UnauthenticatedChatConnection_send: (asyncRuntime: Wrapper<TokioAsyncContext>, chat: Wrapper<UnauthenticatedChatConnection>, httpRequest: Wrapper<HttpRequest>, timeoutMillis: number) => CancellablePromise<ChatResponse>;
UnauthenticatedChatConnection_send_raw_grpc: (asyncRuntime: Wrapper<TokioAsyncContext>, chat: Wrapper<UnauthenticatedChatConnection>, service: string, method: string, payload: Uint8Array<ArrayBuffer>) => CancellablePromise<Uint8Array<ArrayBuffer>>;
UnauthenticatedChatConnection_disconnect: (asyncRuntime: Wrapper<TokioAsyncContext>, chat: Wrapper<UnauthenticatedChatConnection>) => CancellablePromise<void>;
UnauthenticatedChatConnection_info: (chat: Wrapper<UnauthenticatedChatConnection>) => ChatConnectionInfo;
UnauthenticatedChatConnection_look_up_username_hash: (asyncRuntime: Wrapper<TokioAsyncContext>, chat: Wrapper<UnauthenticatedChatConnection>, hash: Uint8Array<ArrayBuffer>) => CancellablePromise<Uuid | null>;
@ -485,6 +486,7 @@ type NativeFunctions = {
AuthenticatedChatConnection_connect: (asyncRuntime: Wrapper<TokioAsyncContext>, connectionManager: Wrapper<ConnectionManager>, username: string, password: string, receiveStories: boolean, languages: string[]) => CancellablePromise<AuthenticatedChatConnection>;
AuthenticatedChatConnection_init_listener: (chat: Wrapper<AuthenticatedChatConnection>, listener: ChatListener) => void;
AuthenticatedChatConnection_send: (asyncRuntime: Wrapper<TokioAsyncContext>, chat: Wrapper<AuthenticatedChatConnection>, httpRequest: Wrapper<HttpRequest>, timeoutMillis: number) => CancellablePromise<ChatResponse>;
AuthenticatedChatConnection_send_raw_grpc: (asyncRuntime: Wrapper<TokioAsyncContext>, chat: Wrapper<AuthenticatedChatConnection>, service: string, method: string, payload: Uint8Array<ArrayBuffer>) => CancellablePromise<Uint8Array<ArrayBuffer>>;
AuthenticatedChatConnection_disconnect: (asyncRuntime: Wrapper<TokioAsyncContext>, chat: Wrapper<AuthenticatedChatConnection>) => CancellablePromise<void>;
AuthenticatedChatConnection_info: (chat: Wrapper<AuthenticatedChatConnection>) => ChatConnectionInfo;
ServerMessageAck_SendStatus: (ack: Wrapper<ServerMessageAck>, status: number) => void;
@ -1050,6 +1052,7 @@ const { registerErrors,
UnauthenticatedChatConnection_connect,
UnauthenticatedChatConnection_init_listener,
UnauthenticatedChatConnection_send,
UnauthenticatedChatConnection_send_raw_grpc,
UnauthenticatedChatConnection_disconnect,
UnauthenticatedChatConnection_info,
UnauthenticatedChatConnection_look_up_username_hash,
@ -1060,6 +1063,7 @@ const { registerErrors,
AuthenticatedChatConnection_connect,
AuthenticatedChatConnection_init_listener,
AuthenticatedChatConnection_send,
AuthenticatedChatConnection_send_raw_grpc,
AuthenticatedChatConnection_disconnect,
AuthenticatedChatConnection_info,
ServerMessageAck_SendStatus,
@ -1627,6 +1631,7 @@ export { registerErrors,
UnauthenticatedChatConnection_connect,
UnauthenticatedChatConnection_init_listener,
UnauthenticatedChatConnection_send,
UnauthenticatedChatConnection_send_raw_grpc,
UnauthenticatedChatConnection_disconnect,
UnauthenticatedChatConnection_info,
UnauthenticatedChatConnection_look_up_username_hash,
@ -1637,6 +1642,7 @@ export { registerErrors,
AuthenticatedChatConnection_connect,
AuthenticatedChatConnection_init_listener,
AuthenticatedChatConnection_send,
AuthenticatedChatConnection_send_raw_grpc,
AuthenticatedChatConnection_disconnect,
AuthenticatedChatConnection_info,
ServerMessageAck_SendStatus,

View File

@ -43,6 +43,7 @@ exclude = [
"CPromisebool",
"CPromiseFfiCdsiLookupResponse",
"CPromiseMutPointerRegistrationService",
"CPromiseOwnedBufferOfc_uchar",
"FfiCdsiLookupResponse",
"FfiCdsiLookupResponseEntry",
"FfiChatListenerStruct",

View File

@ -125,6 +125,27 @@ async fn UnauthenticatedChatConnection_send(
.await
}
#[bridge_io(TokioAsyncContext)]
async fn UnauthenticatedChatConnection_send_raw_grpc(
chat: &UnauthenticatedChatConnection,
service: String,
method: String,
payload: Box<[u8]>,
) -> Result<Vec<u8>, RequestError<Infallible>> {
chat.as_typed(|chat| {
Box::pin(libsignal_net_chat::grpc::raw_grpc(
"unauth",
chat.0
.shared_h2_connection()
.expect("requires an H2 connection"),
&service,
&method,
payload.into_vec(),
))
})
.await
}
#[bridge_io(TokioAsyncContext)]
async fn UnauthenticatedChatConnection_disconnect(chat: &UnauthenticatedChatConnection) {
chat.disconnect().await
@ -306,6 +327,27 @@ async fn AuthenticatedChatConnection_send(
.await
}
#[bridge_io(TokioAsyncContext)]
async fn AuthenticatedChatConnection_send_raw_grpc(
chat: &AuthenticatedChatConnection,
service: String,
method: String,
payload: Box<[u8]>,
) -> Result<Vec<u8>, RequestError<Infallible>> {
chat.as_typed(|chat| {
Box::pin(libsignal_net_chat::grpc::raw_grpc(
"auth",
chat.0
.shared_h2_connection()
.expect("requires an H2 connection"),
&service,
&method,
payload.into_vec(),
))
})
.await
}
#[bridge_io(TokioAsyncContext)]
async fn AuthenticatedChatConnection_disconnect(chat: &AuthenticatedChatConnection) {
chat.disconnect().await

View File

@ -78,6 +78,68 @@ impl<T: GrpcService + Clone + Sync> GrpcServiceProvider for T {
}
}
/// A tonic encoder and decoder that passes byte buffers through unchanged, letting tonic
/// add the gRPC framing and nothing else.
struct PassthroughCodec;
impl tonic::codec::Codec for PassthroughCodec {
type Encode = Vec<u8>;
type Decode = Vec<u8>;
type Encoder = Self;
type Decoder = Self;
fn encoder(&mut self) -> Self::Encoder {
PassthroughCodec
}
fn decoder(&mut self) -> Self::Decoder {
PassthroughCodec
}
}
impl tonic::codec::Encoder for PassthroughCodec {
type Item = Vec<u8>;
type Error = tonic::Status;
fn encode(
&mut self,
item: Self::Item,
dst: &mut tonic::codec::EncodeBuf<'_>,
) -> Result<(), Self::Error> {
use bytes::BufMut;
dst.put(&item[..]);
Ok(())
}
}
impl tonic::codec::Decoder for PassthroughCodec {
type Item = Vec<u8>;
type Error = tonic::Status;
fn decode(
&mut self,
src: &mut tonic::codec::DecodeBuf<'_>,
) -> Result<Option<Self::Item>, Self::Error> {
use bytes::Buf;
Ok(Some(src.copy_to_bytes(src.remaining()).into()))
}
}
pub fn raw_grpc(
log_tag: &'static str,
service_provider: impl GrpcServiceProvider,
service_name: &str,
method: &str,
payload: Vec<u8>,
) -> impl Future<Output = Result<Vec<u8>, RequestError<Infallible>>> {
let mut client = tonic::client::Grpc::new(service_provider.service());
let path = http::uri::PathAndQuery::from_maybe_shared(format!("/{service_name}/{method}"))
.expect("valid URI path");
log_and_send(log_tag, method, || async move {
let response = client
.unary(tonic::Request::new(payload), path, PassthroughCodec)
.await?;
Ok(response.into_inner())
})
}
async fn log_and_send<F, R, E>(
log_tag: &'static str,
log_safe_description: &str,

View File

@ -658,6 +658,21 @@ typedef struct {
size_t length;
} SignalBorrowedSliceOfConstPointerCiphertextMessage;
/**
* A C callback used to report the results of Rust futures.
*
* cbindgen will produce independent C types like `SignalCPromisei32` and
* `SignalCPromiseProtocolAddress`.
*
* This derives Copy because it behaves like a C type; nevertheless, a promise should still only be
* completed once.
*/
typedef struct {
void (*complete)(SignalFfiError *error, const SignalOwnedBuffer *result, const void *context);
const void *context;
SignalCancellationId cancellation_id;
} SignalCPromiseOwnedBufferOfc_uchar;
/**
* A wrapper type for raw UUIDs, because C treats arrays specially in argument position.
*/
@ -1746,6 +1761,8 @@ SignalFfiError *signal_authenticated_chat_connection_send(SignalCPromiseFfiChatR
SignalFfiError *signal_authenticated_chat_connection_send_message(SignalCPromisebool *promise, SignalConstPointerTokioAsyncContext async_runtime, SignalConstPointerAuthenticatedChatConnection chat, const SignalServiceIdFixedWidthBinaryBytes *destination, uint64_t timestamp, SignalBorrowedSliceOfu32 device_ids, SignalBorrowedSliceOfu32 registration_ids, SignalBorrowedSliceOfConstPointerCiphertextMessage contents, bool online_only, bool is_urgent);
SignalFfiError *signal_authenticated_chat_connection_send_raw_grpc(SignalCPromiseOwnedBufferOfc_uchar *promise, SignalConstPointerTokioAsyncContext async_runtime, SignalConstPointerAuthenticatedChatConnection chat, const char *service, const char *method, SignalBorrowedBuffer payload);
SignalFfiError *signal_authenticated_chat_connection_send_sync_message(SignalCPromisebool *promise, SignalConstPointerTokioAsyncContext async_runtime, SignalConstPointerAuthenticatedChatConnection chat, uint64_t timestamp, SignalBorrowedSliceOfu32 device_ids, SignalBorrowedSliceOfu32 registration_ids, SignalBorrowedSliceOfConstPointerCiphertextMessage contents, bool is_urgent);
SignalFfiError *signal_backup_auth_credential_check_valid_contents(SignalBorrowedBuffer params_bytes);
@ -2793,6 +2810,8 @@ SignalFfiError *signal_unauthenticated_chat_connection_send_message(SignalCPromi
SignalFfiError *signal_unauthenticated_chat_connection_send_multi_recipient_message(SignalCPromiseOwnedBufferOfServiceIdFixedWidthBinaryBytes *promise, SignalConstPointerTokioAsyncContext async_runtime, SignalConstPointerUnauthenticatedChatConnection chat, SignalBorrowedBuffer payload, uint64_t timestamp, SignalBorrowedBuffer auth, bool online_only, bool is_urgent);
SignalFfiError *signal_unauthenticated_chat_connection_send_raw_grpc(SignalCPromiseOwnedBufferOfc_uchar *promise, SignalConstPointerTokioAsyncContext async_runtime, SignalConstPointerUnauthenticatedChatConnection chat, const char *service, const char *method, SignalBorrowedBuffer payload);
SignalFfiError *signal_unidentified_sender_message_content_deserialize(SignalMutPointerUnidentifiedSenderMessageContent *out, SignalBorrowedBuffer data);
SignalFfiError *signal_unidentified_sender_message_content_destroy(SignalMutPointerUnidentifiedSenderMessageContent p);

View File

@ -241,21 +241,6 @@ typedef struct {
SignalTestingSemaphore *raw;
} SignalMutPointerTestingSemaphore;
/**
* A C callback used to report the results of Rust futures.
*
* cbindgen will produce independent C types like `SignalCPromisei32` and
* `SignalCPromiseProtocolAddress`.
*
* This derives Copy because it behaves like a C type; nevertheless, a promise should still only be
* completed once.
*/
typedef struct {
void (*complete)(SignalFfiError *error, const SignalOwnedBuffer *result, const void *context);
const void *context;
SignalRawCancellationId cancellation_id;
} SignalCPromiseOwnedBufferOfc_uchar;
typedef struct {
SignalTestingValueHolder *raw;
} SignalMutPointerTestingValueHolder;