Skip to content

Commit 89f9377

Browse files
committed
Add interface for websockets
1 parent 1f13ade commit 89f9377

15 files changed

+228
-199
lines changed

libsignal-service-dotnet/Base64.cs

Lines changed: 44 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,48 @@
1-
using System;
2-
using System.Text;
3-
4-
namespace libsignalservice.util
1+
using System;
2+
using System.Text;
3+
4+
namespace libsignalservice.util
55
{
66
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
77
public class Base64
8-
{
9-
public static string EncodeBytes(byte[] input)
10-
{
11-
return Convert.ToBase64String(input);
12-
}
13-
14-
public static string EncodeBytesWithoutPadding(byte[] input)
15-
{
16-
String encoded = null;
17-
18-
encoded = EncodeBytes(input);
19-
20-
if (encoded[encoded.Length - 2] == '=') return encoded.Substring(0, encoded.Length - 2);
21-
else if (encoded[encoded.Length - 1] == '=') return encoded.Substring(0, encoded.Length - 1);
22-
else return encoded;
23-
}
24-
25-
public static byte[] Encode(byte[] input)
26-
{
27-
char[] chars = System.Text.Encoding.UTF8.GetString(input, 0, input.Length).ToCharArray();
28-
return Encoding.UTF8.GetBytes(Convert.ToBase64String(input));
29-
}
30-
31-
public static byte[] Decode(string input)
32-
{
33-
return Convert.FromBase64String(input);
34-
}
35-
36-
public static byte[] DecodeWithoutPadding(string input)
37-
{
38-
int padding = input.Length % 4;
39-
40-
if (padding == 1) input = input + "=";
41-
else if (padding == 2) input = input + "==";
42-
else if (padding == 3) input = input + "=";
43-
44-
return Decode(input);
45-
}
8+
{
9+
public static string EncodeBytes(byte[] input)
10+
{
11+
return Convert.ToBase64String(input);
12+
}
13+
14+
public static string EncodeBytesWithoutPadding(byte[] input)
15+
{
16+
String encoded = null;
17+
18+
encoded = EncodeBytes(input);
19+
20+
if (encoded[encoded.Length - 2] == '=') return encoded.Substring(0, encoded.Length - 2);
21+
else if (encoded[encoded.Length - 1] == '=') return encoded.Substring(0, encoded.Length - 1);
22+
else return encoded;
23+
}
24+
25+
public static byte[] Encode(byte[] input)
26+
{
27+
char[] chars = System.Text.Encoding.UTF8.GetString(input, 0, input.Length).ToCharArray();
28+
return Encoding.UTF8.GetBytes(Convert.ToBase64String(input));
29+
}
30+
31+
public static byte[] Decode(string input)
32+
{
33+
return Convert.FromBase64String(input);
34+
}
35+
36+
public static byte[] DecodeWithoutPadding(string input)
37+
{
38+
int padding = input.Length % 4;
39+
40+
if (padding == 1) input = input + "=";
41+
else if (padding == 2) input = input + "==";
42+
else if (padding == 3) input = input + "=";
43+
44+
return Decode(input);
45+
}
4646
}
47-
#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
48-
}
47+
#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
48+
}

libsignal-service-dotnet/SignalServiceAccountManager.cs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
using libsignalservice.push;
1212
using libsignalservice.push.http;
1313
using libsignalservice.util;
14+
using libsignalservice.websocket;
1415
using Strilanc.Value;
1516
using System;
1617
using System.Collections.Generic;
@@ -53,11 +54,11 @@ public SignalServiceAccountManager(SignalServiceConfiguration configuration,
5354
/// </summary>
5455
/// <param name="configuration">The URL configuration for the Signal Service</param>
5556
/// <param name="userAgent">A string which identifies the client software</param>
56-
public SignalServiceAccountManager(SignalServiceConfiguration configuration, string userAgent)
57+
/// <param name="webSocketFactory">A factory which creates websocket connection objects</param>
58+
public SignalServiceAccountManager(SignalServiceConfiguration configuration, string userAgent, ISignalWebSocketFactory webSocketFactory)
5759
{
5860
Configuration = configuration;
5961
UserAgent = userAgent;
60-
ProvisioningSocket = new ProvisioningSocket(configuration.SignalServiceUrls[0].Url);
6162
PushServiceSocket = new PushServiceSocket(configuration, new StaticCredentialsProvider(null, null, null, (int)SignalServiceAddress.DEFAULT_DEVICE_ID), userAgent);
6263
}
6364

@@ -238,11 +239,12 @@ public async Task<List<ContactTokenDetails>> GetContacts(CancellationToken token
238239
/// Request a UUID from the server for linking as a new device.
239240
/// Called by the new device.
240241
/// </summary>
241-
/// <param name="token">The UUID, Base64 encoded</param>
242+
/// <param name="token">A CancellationToken for the PrivisioningSocket's websocket connection</param>
243+
/// /// <param name="webSocketFactory">A factory which creates websocket connection objects</param>
242244
/// <returns></returns>
243-
public async Task<string> GetNewDeviceUuid(CancellationToken token)
245+
public async Task<string> GetNewDeviceUuid(CancellationToken token, ISignalWebSocketFactory webSocketFactory)
244246
{
245-
ProvisioningSocket = new ProvisioningSocket(Configuration.SignalServiceUrls[0].Url);
247+
ProvisioningSocket = new ProvisioningSocket(Configuration.SignalServiceUrls[0].Url, webSocketFactory, token);
246248
return (await ProvisioningSocket.GetProvisioningUuid(token)).Uuid;
247249
}
248250

libsignal-service-dotnet/SignalServiceMessagePipe.cs

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,19 @@ namespace libsignalservice
2323
public class SignalServiceMessagePipe
2424
{
2525
private readonly ILogger Logger = LibsignalLogging.CreateLogger<SignalServiceMessagePipe>();
26+
private readonly ISignalWebSocketFactory SignalWebSocketFactory;
2627
private readonly SignalWebSocketConnection Websocket;
27-
private readonly CredentialsProvider CredentialsProvider;
28+
private readonly ICredentialsProvider CredentialsProvider;
2829
private CancellationToken Token;
2930

30-
internal SignalServiceMessagePipe(CancellationToken token, SignalWebSocketConnection websocket, CredentialsProvider credentialsProvider)
31+
internal SignalServiceMessagePipe(CancellationToken token, SignalWebSocketConnection websocket,
32+
ICredentialsProvider credentialsProvider, ISignalWebSocketFactory webSocketFactory)
3133
{
3234
Logger.LogTrace("SignalServiceMessagePipe()");
33-
this.Token = token;
34-
this.Websocket = websocket;
35-
this.CredentialsProvider = credentialsProvider;
35+
Token = token;
36+
Websocket = websocket;
37+
CredentialsProvider = credentialsProvider;
38+
SignalWebSocketFactory = webSocketFactory;
3639
}
3740

3841
/// <summary>
@@ -42,7 +45,7 @@ internal SignalServiceMessagePipe(CancellationToken token, SignalWebSocketConnec
4245
public async Task Connect()
4346
{
4447
Logger.LogTrace("Connecting to message pipe");
45-
await Websocket.Connect(Token);
48+
await Websocket.Connect();
4649
}
4750

4851
/// <summary>
@@ -99,18 +102,19 @@ public async Task<SendMessageResponse> Send(OutgoingPushMessageList list)
99102
Body = ByteString.CopyFrom(Encoding.UTF8.GetBytes(JsonUtil.ToJson(list)))
100103
};
101104
requestmessage.Headers.Add("content-type:application/json");
102-
Logger.LogDebug("Sending message {0}", requestmessage.Id);
103-
var t = Websocket.SendRequest(requestmessage);
104-
await t;
105-
if (t.IsCompleted)
105+
var sendTask = (await Websocket.SendRequest(requestmessage)).Task;
106+
var timerCancelSource = new CancellationTokenSource();
107+
if (await Task.WhenAny(sendTask, Task.Delay(10*1000, timerCancelSource.Token)) == sendTask)
106108
{
107-
var response = t.Result;
108-
if (response.Item1 < 200 || response.Item1 >= 300)
109+
timerCancelSource.Cancel();
110+
var (Status, Body) = sendTask.Result;
111+
if (Status < 200 || Status >= 300)
109112
{
110-
Logger.LogError("Sending message {0} failed: {1}", requestmessage.Id, response.Item2);
111-
throw new IOException("non-successfull response: " + response.Item1 + " " + response.Item2);
113+
throw new IOException("non-successfull response: " + Status);
112114
}
113-
return JsonUtil.FromJson<SendMessageResponse>(response.Item2);
115+
if (Util.IsEmpty(Body))
116+
return new SendMessageResponse(false);
117+
return JsonUtil.FromJson<SendMessageResponse>(Body);
114118
}
115119
else
116120
{
@@ -134,16 +138,17 @@ public async Task<SignalServiceProfile> GetProfile(SignalServiceAddress address)
134138
Path = $"/v1/profile/{address.E164number}"
135139
};
136140

137-
var t = Websocket.SendRequest(requestMessage);
138-
await t;
139-
if (t.IsCompleted)
141+
var sendTask = (await Websocket.SendRequest(requestMessage)).Task;
142+
var timerCancelSource = new CancellationTokenSource();
143+
if (await Task.WhenAny(sendTask, Task.Delay(10 * 1000, timerCancelSource.Token)) == sendTask)
140144
{
141-
var response = t.Result;
142-
if (response.Item1 < 200 || response.Item1 >= 300)
145+
timerCancelSource.Cancel();
146+
var (Status, Body) = sendTask.Result;
147+
if (Status < 200 || Status >= 300)
143148
{
144-
throw new IOException("non-successfull response: " + response.Item1 + " " + response.Item2);
149+
throw new IOException("non-successfull response: " + Status);
145150
}
146-
return JsonUtil.FromJson<SignalServiceProfile>(response.Item2);
151+
return JsonUtil.FromJson<SignalServiceProfile>(Body);
147152
}
148153
else
149154
{

libsignal-service-dotnet/SignalServiceMessageReceiver.cs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,24 +32,21 @@ public class SignalServiceMessageReceiver
3232
private const int MAC_KEY_SIZE = 32;
3333
private readonly PushServiceSocket Socket;
3434
private readonly SignalServiceConfiguration Urls;
35-
private readonly CredentialsProvider CredentialsProvider;
35+
private readonly ICredentialsProvider CredentialsProvider;
3636
private readonly string UserAgent;
37-
private readonly ConnectivityListener ConnectivityListener;
3837

3938
/// <summary>
4039
/// Construct a SignalServiceMessageReceiver.
4140
/// </summary>
4241
/// <param name="urls">The URL of the Signal Service.</param>
4342
/// <param name="credentials">The Signal Service user's credentials</param>
4443
/// <param name="userAgent"></param>
45-
/// <param name="connectivityListener"></param>
46-
public SignalServiceMessageReceiver(SignalServiceConfiguration urls, CredentialsProvider credentials, string userAgent, ConnectivityListener connectivityListener)
44+
public SignalServiceMessageReceiver(SignalServiceConfiguration urls, ICredentialsProvider credentials, string userAgent)
4745
{
4846
Urls = urls;
4947
CredentialsProvider = credentials;
5048
Socket = new PushServiceSocket(urls, credentials, userAgent);
5149
UserAgent = userAgent;
52-
ConnectivityListener = connectivityListener;
5350
}
5451

5552
/// <summary>
@@ -111,10 +108,11 @@ public async Task<string> RetrieveAttachmentDownloadUrl(CancellationToken token,
111108
/// Callers must call <see cref="SignalServiceMessagePipe.Shutdown()"/> when finished with the pipe.
112109
/// </summary>
113110
/// <returns>A SignalServiceMessagePipe for receiving Signal Service messages.</returns>
114-
public async Task<SignalServiceMessagePipe> CreateMessagePipe(CancellationToken token)
111+
public async Task<SignalServiceMessagePipe> CreateMessagePipe(CancellationToken token, ISignalWebSocketFactory webSocketFactory)
115112
{
116-
SignalWebSocketConnection webSocket = new SignalWebSocketConnection(token, Urls.SignalServiceUrls[0].Url, CredentialsProvider, UserAgent, ConnectivityListener);
117-
var messagePipe = new SignalServiceMessagePipe(token, webSocket, CredentialsProvider);
113+
SignalWebSocketConnection webSocket = new SignalWebSocketConnection(token, Urls.SignalServiceUrls[0].Url,
114+
CredentialsProvider, UserAgent, webSocketFactory);
115+
var messagePipe = new SignalServiceMessagePipe(token, webSocket, CredentialsProvider, webSocketFactory);
118116
await messagePipe.Connect();
119117
return messagePipe;
120118
}

libsignal-service-dotnet/libsignal-service-dotnet.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
<PackageReference Include="Microsoft.Extensions.Logging" Version="1.1.2" />
3434
<PackageReference Include="Newtonsoft.Json" Version="11.0.1" />
3535
<PackageReference Include="Portable.BouncyCastle" Version="1.8.2" />
36-
<PackageReference Include="System.Net.WebSockets.Client" Version="4.3.2" />
3736
<PackageReference Include="System.ValueTuple" Version="4.4.0" />
3837
</ItemGroup>
3938

libsignal-service-dotnet/messages/SignalServiceAttachmentPointer.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
using System.IO;
2+
using System.Threading;
23

34
namespace libsignalservice.messages
45
{
56
/// <summary>
67
/// Represents a received SignalServiceAttachment "handle." This
78
/// is a pointer to the actual attachment content, which needs to be
8-
/// retrieved using <see cref="SignalServiceMessageReceiver.RetrieveAttachment(SignalServiceAttachmentPointer, Stream, int, IProgressListener)"/>
9+
/// retrieved using <see cref="SignalServiceMessageReceiver.RetrieveAttachment(CancellationToken, SignalServiceAttachmentPointer, Stream, int, IProgressListener)"/>
910
/// </summary>
1011
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
1112
public class SignalServiceAttachmentPointer : SignalServiceAttachment
Lines changed: 45 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,54 @@
1-
using Coe.WebSocketWrapper;
2-
using libsignalservice.crypto;
3-
using libsignalservice.push;
4-
using libsignalservice.websocket;
5-
using System.Collections.Concurrent;
6-
using System.Threading;
1+
using Coe.WebSocketWrapper;
2+
using libsignalservice.crypto;
3+
using libsignalservice.push;
4+
using libsignalservice.websocket;
5+
using System;
6+
using System.Collections.Concurrent;
7+
using System.IO;
8+
using System.Threading;
79
using System.Threading.Tasks;
810

9-
namespace libsignal.push
10-
{
11-
internal class ProvisioningSocket
12-
{
13-
private WebSocketWrapper WebSocket;
14-
private string WsUri;
15-
private readonly BlockingCollection<byte[]> IncomingRequests = new BlockingCollection<byte[]>(new ConcurrentQueue<byte[]>());
16-
17-
public ProvisioningSocket(string httpUri)
18-
{
19-
WsUri = httpUri.Replace("https://", "wss://")
20-
.Replace("http://", "ws://") + "/v1/websocket/provisioning/";
21-
WebSocket = new WebSocketWrapper(WsUri);
22-
WebSocket.OnMessage(Connection_OnMessage);
11+
namespace libsignal.push
12+
{
13+
internal class ProvisioningSocket
14+
{
15+
private ISignalWebSocket SignalWebSocket;
16+
private readonly string WsUri;
17+
private readonly BlockingCollection<Stream> IncomingRequests = new BlockingCollection<Stream>(new ConcurrentQueue<Stream>());
18+
19+
public ProvisioningSocket(string httpUri, ISignalWebSocketFactory webSocketFactory, CancellationToken token)
20+
{
21+
WsUri = httpUri.Replace("https://", "wss://")
22+
.Replace("http://", "ws://") + "/v1/websocket/provisioning/";
23+
SignalWebSocket = webSocketFactory.CreateSignalWebSocket(token, new Uri(WsUri));
24+
SignalWebSocket.MessageReceived += SignalWebSocket_MessageReceived;
25+
}
26+
27+
private void SignalWebSocket_MessageReceived(object sender, SignalWebSocketMessageReceivedEventArgs e)
28+
{
29+
IncomingRequests.Add(e.Message);
2330
}
2431

25-
private async Task<byte[]> TakeAsync(CancellationToken token)
32+
private async Task<Stream> TakeAsync(CancellationToken token)
2633
{
2734
return await Task.Run(() =>
2835
{
2936
return IncomingRequests.Take(token); //TODO don't block
3037
});
31-
}
32-
33-
public async Task<ProvisioningUuid> GetProvisioningUuid(CancellationToken token)
34-
{
35-
await WebSocket.Connect(token);
36-
byte[] raw = await TakeAsync(token);
37-
return ProvisioningUuid.Parser.ParseFrom(WebSocketMessage.Parser.ParseFrom(raw).Request.Body);
38-
}
39-
40-
public async Task<ProvisionMessage> GetProvisioningMessage(CancellationToken token, IdentityKeyPair tmpIdentity)
41-
{
42-
byte[] raw = await TakeAsync(token);
43-
WebSocketMessage msg = WebSocketMessage.Parser.ParseFrom(raw);
44-
return new ProvisioningCipher(null).Decrypt(tmpIdentity, msg.Request.Body.ToByteArray());
45-
}
46-
47-
private void Connection_OnMessage(byte[] obj)
48-
{
49-
IncomingRequests.Add(obj);
50-
}
51-
}
52-
}
38+
}
39+
40+
public async Task<ProvisioningUuid> GetProvisioningUuid(CancellationToken token)
41+
{
42+
await SignalWebSocket.ConnectAsync();
43+
Stream raw = await TakeAsync(token);
44+
return ProvisioningUuid.Parser.ParseFrom(WebSocketMessage.Parser.ParseFrom(raw).Request.Body);
45+
}
46+
47+
public async Task<ProvisionMessage> GetProvisioningMessage(CancellationToken token, IdentityKeyPair tmpIdentity)
48+
{
49+
Stream raw = await TakeAsync(token);
50+
WebSocketMessage msg = WebSocketMessage.Parser.ParseFrom(raw);
51+
return new ProvisioningCipher(null).Decrypt(tmpIdentity, msg.Request.Body.ToByteArray());
52+
}
53+
}
54+
}

0 commit comments

Comments
 (0)