diff --git a/lib/src/main/java/org/asamk/signal/manager/Manager.java b/lib/src/main/java/org/asamk/signal/manager/Manager.java index e6a3ae3b..ee494dfd 100644 --- a/lib/src/main/java/org/asamk/signal/manager/Manager.java +++ b/lib/src/main/java/org/asamk/signal/manager/Manager.java @@ -64,6 +64,10 @@ import java.util.Map; import java.util.Optional; import java.util.Set; +import org.asamk.signal.manager.api.CallInfo; +import org.asamk.signal.manager.api.CallOffer; +import org.asamk.signal.manager.api.TurnServer; + public interface Manager extends Closeable { static boolean isValidNumber(final String e164Number, final String countryCode) { @@ -413,6 +417,30 @@ public interface Manager extends Closeable { InputStream retrieveSticker(final StickerPackId stickerPackId, final int stickerId) throws IOException; + // --- Voice call methods --- + + CallInfo startCall(RecipientIdentifier.Single recipient) throws IOException, UnregisteredRecipientException; + + CallInfo acceptCall(long callId) throws IOException; + + void hangupCall(long callId) throws IOException; + + void rejectCall(long callId) throws IOException; + + List listActiveCalls(); + + void sendCallOffer(RecipientIdentifier.Single recipient, CallOffer offer) throws IOException, UnregisteredRecipientException; + + void sendCallAnswer(RecipientIdentifier.Single recipient, long callId, byte[] answerOpaque) throws IOException, UnregisteredRecipientException; + + void sendIceUpdate(RecipientIdentifier.Single recipient, long callId, List iceCandidates) throws IOException, UnregisteredRecipientException; + + void sendHangup(RecipientIdentifier.Single recipient, long callId, MessageEnvelope.Call.Hangup.Type type) throws IOException, UnregisteredRecipientException; + + void sendBusy(RecipientIdentifier.Single recipient, long callId) throws IOException, UnregisteredRecipientException; + + List getTurnServerInfo() throws IOException; + @Override void close(); diff --git a/lib/src/main/java/org/asamk/signal/manager/api/CallInfo.java b/lib/src/main/java/org/asamk/signal/manager/api/CallInfo.java new file mode 100644 index 00000000..30b5d20d --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/api/CallInfo.java @@ -0,0 +1,21 @@ +package org.asamk.signal.manager.api; + +public record CallInfo( + long callId, + State state, + RecipientAddress recipient, + String inputDeviceName, + String outputDeviceName, + boolean isOutgoing +) { + + public enum State { + IDLE, + RINGING_INCOMING, + RINGING_OUTGOING, + CONNECTING, + CONNECTED, + RECONNECTING, + ENDED + } +} diff --git a/lib/src/main/java/org/asamk/signal/manager/api/CallOffer.java b/lib/src/main/java/org/asamk/signal/manager/api/CallOffer.java new file mode 100644 index 00000000..2c4aa251 --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/api/CallOffer.java @@ -0,0 +1,13 @@ +package org.asamk.signal.manager.api; + +public record CallOffer( + long callId, + Type type, + byte[] opaque +) { + + public enum Type { + AUDIO, + VIDEO + } +} diff --git a/lib/src/main/java/org/asamk/signal/manager/api/TurnServer.java b/lib/src/main/java/org/asamk/signal/manager/api/TurnServer.java new file mode 100644 index 00000000..8ffd03bf --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/api/TurnServer.java @@ -0,0 +1,10 @@ +package org.asamk.signal.manager.api; + +import java.util.List; + +public record TurnServer( + String username, + String password, + List urls +) { +} diff --git a/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java b/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java index 47eec0c0..b94bf8ba 100644 --- a/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java +++ b/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java @@ -15,6 +15,7 @@ import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.SignalSessionLock; import org.whispersystems.signalservice.api.account.AccountApi; import org.whispersystems.signalservice.api.attachment.AttachmentApi; +import org.whispersystems.signalservice.api.calling.CallingApi; import org.whispersystems.signalservice.api.cds.CdsApi; import org.whispersystems.signalservice.api.certificate.CertificateApi; import org.whispersystems.signalservice.api.crypto.SignalServiceCipher; @@ -76,6 +77,7 @@ public class SignalDependencies { private StorageServiceApi storageServiceApi; private CertificateApi certificateApi; private AttachmentApi attachmentApi; + private CallingApi callingApi; private MessageApi messageApi; private KeysApi keysApi; private GroupsV2Operations groupsV2Operations; @@ -255,6 +257,13 @@ public class SignalDependencies { () -> attachmentApi = new AttachmentApi(getAuthenticatedSignalWebSocket(), getPushServiceSocket())); } + public CallingApi getCallingApi() { + return getOrCreate(() -> callingApi, + () -> callingApi = new CallingApi(getAuthenticatedSignalWebSocket(), + getUnauthenticatedSignalWebSocket(), + getPushServiceSocket())); + } + public MessageApi getMessageApi() { return getOrCreate(() -> messageApi, () -> messageApi = new MessageApi(getAuthenticatedSignalWebSocket(), diff --git a/lib/src/main/proto/rtp_data.proto b/lib/src/main/proto/rtp_data.proto new file mode 100644 index 00000000..d0b96913 --- /dev/null +++ b/lib/src/main/proto/rtp_data.proto @@ -0,0 +1,32 @@ +// In-call control messages carried over the RTP data channel. +// signal-cli hand-codes the parsing in RtpDataProtobuf.java rather than using protoc. +syntax = "proto2"; + +package rtp_data; + +option java_package = "org.asamk.signal.manager.calling.proto"; +option java_outer_classname = "RtpDataProtos"; + +message Accepted {} + +message Hangup { + optional uint32 id = 1; +} + +message SenderStatus { + optional bool audio_enabled = 1; + optional bool video_enabled = 2; + optional bool sharing_screen = 3; +} + +message Receiver { + optional uint32 id = 1; +} + +// Top-level RTP data message +message Data { + optional Accepted accepted = 1; + optional Hangup hangup = 2; + optional SenderStatus sender_status = 3; + optional Receiver receiver = 4; +} diff --git a/lib/src/main/proto/signaling.proto b/lib/src/main/proto/signaling.proto new file mode 100644 index 00000000..2e180f27 --- /dev/null +++ b/lib/src/main/proto/signaling.proto @@ -0,0 +1,32 @@ +// RingRTC signaling protobuf definitions +// These define the structure of the opaque blobs inside Signal call Offer/Answer messages. +// signal-cli hand-codes the parsing in SignalingProtobuf.java rather than using protoc. +syntax = "proto2"; + +package signaling; + +option java_package = "org.asamk.signal.manager.calling.proto"; +option java_outer_classname = "SignalingProtos"; + +message VideoCodec { + enum Type { + VP8 = 0; + H264 = 1; + VP9 = 2; + } + optional Type type = 1; + optional uint32 level = 2; +} + +message ConnectionParametersV4 { + optional bytes public_key = 1; // x25519 public key (32 bytes) + optional string ice_ufrag = 2; + optional string ice_pwd = 3; + repeated VideoCodec receive_video_codecs = 4; + optional uint64 max_bitrate_bps = 5; +} + +// The top-level opaque blob inside an OfferMessage or AnswerMessage +message Opaque { + optional ConnectionParametersV4 connection_parameters_v4 = 1; +}