Skip to content

Commit bf7afdc

Browse files
authored
Merge pull request #153 from WalletConnect/fix/disposal-deadlock
fix: disposal deadlock
2 parents 0470b22 + 55415cc commit bf7afdc

File tree

17 files changed

+537
-443
lines changed

17 files changed

+537
-443
lines changed

Core Modules/WalletConnectSharp.Crypto/Crypto.cs

Lines changed: 43 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ namespace WalletConnectSharp.Crypto
3030
public class Crypto : ICrypto
3131
{
3232
private readonly string CRYPTO_CLIENT_SEED = $"client_ed25519_seed";
33-
33+
3434
private const string MULTICODEC_ED25519_ENCODING = "base58btc";
3535
private const string MULTICODEC_ED25519_BASE = "z";
3636
private const string MULTICODEC_ED25519_HEADER = "K36";
@@ -48,7 +48,7 @@ public class Crypto : ICrypto
4848
private const int TYPE_LENGTH = 1;
4949
private const int IV_LENGTH = 12;
5050
private const int KEY_LENGTH = 32;
51-
51+
5252
/// <summary>
5353
/// The name of the crypto module
5454
/// </summary>
@@ -71,19 +71,20 @@ public string Context
7171
return "walletconnectsharp";
7272
}
7373
}
74-
74+
7575
/// <summary>
7676
/// The current KeyChain this crypto module instance is using
7777
/// </summary>
7878
public IKeyChain KeyChain { get; private set; }
79-
79+
8080
/// <summary>
8181
/// The current storage module this crypto module instance is using
8282
/// </summary>
8383
public IKeyValueStorage Storage { get; private set; }
8484

8585
private bool _initialized;
8686
private bool _newStorage;
87+
protected bool Disposed;
8788

8889
/// <summary>
8990
/// Create a new instance of the crypto module, with a given storage module.
@@ -97,7 +98,7 @@ public Crypto(IKeyValueStorage storage)
9798
this.KeyChain = new KeyChain(storage);
9899
this.Storage = storage;
99100
}
100-
101+
101102
/// <summary>
102103
/// Create a new instance of the crypto module, with a given keychain.
103104
/// </summary>
@@ -128,7 +129,7 @@ public async Task Init()
128129
{
129130
if (_newStorage)
130131
await this.Storage.Init();
131-
132+
132133
await this.KeyChain.Init();
133134
this._initialized = true;
134135
}
@@ -159,7 +160,7 @@ public Task<string> GenerateKeyPair()
159160
var options = new KeyGenerationParameters(SecureRandom.GetInstance("SHA256PRNG"), 1);
160161
X25519KeyPairGenerator generator = new X25519KeyPairGenerator();
161162
generator.Init(options);
162-
163+
163164
var keypair = generator.GenerateKeyPair();
164165
var publicKeyData = keypair.Public as X25519PublicKeyParameters;
165166
var privateKeyData = keypair.Private as X25519PrivateKeyParameters;
@@ -281,7 +282,7 @@ public Task<string> Encrypt(EncryptParams @params)
281282

282283
var typeRaw = Bases.Base10.Decode($"{@params.Type}");
283284
var iv = @params.Iv;
284-
285+
285286
byte[] rawIv;
286287
if (iv == null)
287288
{
@@ -308,7 +309,7 @@ public Task<string> Encrypt(EncryptParams @params)
308309
{
309310
byte[] temp = new byte[encoded.Length * 3];
310311
int len = aead.ProcessBytes(encoded, 0, encoded.Length, temp, 0);
311-
312+
312313
if (len > 0)
313314
{
314315
encryptedStream.Write(temp, 0, len);
@@ -327,7 +328,7 @@ public Task<string> Encrypt(EncryptParams @params)
327328
{
328329
if (senderPublicKey == null)
329330
throw new ArgumentException("Missing sender public key for type1 envelope");
330-
331+
331332
return Task.FromResult(Convert.ToBase64String(
332333
typeRaw.Concat(senderPublicKey).Concat(rawIv).Concat(encrypted).ToArray()
333334
));
@@ -380,10 +381,7 @@ public async Task<string> Encode(string topic, IJsonRpcPayload payload, EncodeOp
380381
var message = JsonConvert.SerializeObject(payload);
381382
var results = await Encrypt(new EncryptParams()
382383
{
383-
Message = message,
384-
Type = type,
385-
SenderPublicKey = senderPublicKey,
386-
SymKey = symKey
384+
Message = message, Type = type, SenderPublicKey = senderPublicKey, SymKey = symKey
387385
});
388386

389387
return results;
@@ -398,7 +396,8 @@ public async Task<string> Encode(string topic, IJsonRpcPayload payload, EncodeOp
398396
/// <param name="options">(optional) Decoding options</param>
399397
/// <typeparam name="T">The type of the IJsonRpcPayload to convert the encoded Json to</typeparam>
400398
/// <returns>The decoded, decrypted and deserialized object of type T from an async task</returns>
401-
public async Task<T> Decode<T>(string topic, string encoded, DecodeOptions options = null) where T : IJsonRpcPayload
399+
public async Task<T> Decode<T>(string topic, string encoded, DecodeOptions options = null)
400+
where T : IJsonRpcPayload
402401
{
403402
this.IsInitialized();
404403
var @params = ValidateDecoding(encoded, options);
@@ -430,7 +429,8 @@ private EncodingParams Deserialize(string encoded)
430429
var slice3 = slice2 + IV_LENGTH;
431430
var senderPublicKey = new ArraySegment<byte>(bytes, slice1, KEY_LENGTH);
432431
var iv = new ArraySegment<byte>(bytes, slice2, IV_LENGTH);
433-
var @sealed = new ArraySegment<byte>(bytes, slice3, bytes.Length - (TYPE_LENGTH + KEY_LENGTH + IV_LENGTH));
432+
var @sealed =
433+
new ArraySegment<byte>(bytes, slice3, bytes.Length - (TYPE_LENGTH + KEY_LENGTH + IV_LENGTH));
434434

435435
return new EncodingParams()
436436
{
@@ -446,12 +446,7 @@ private EncodingParams Deserialize(string encoded)
446446
var iv = new ArraySegment<byte>(bytes, slice1, IV_LENGTH);
447447
var @sealed = new ArraySegment<byte>(bytes, slice2, bytes.Length - (IV_LENGTH + TYPE_LENGTH));
448448

449-
return new EncodingParams()
450-
{
451-
Type = typeRaw,
452-
Sealed = @sealed.ToArray(),
453-
Iv = iv.ToArray()
454-
};
449+
return new EncodingParams() { Type = typeRaw, Sealed = @sealed.ToArray(), Iv = iv.ToArray() };
455450
}
456451
}
457452

@@ -493,12 +488,7 @@ public async Task<string> SignJwt(string aud)
493488
signer.BlockUpdate(data, 0, data.Length);
494489

495490
var signature = signer.GenerateSignature();
496-
return EncodeJwt(new IridiumJWTSigned()
497-
{
498-
Header = header,
499-
Payload = payload,
500-
Signature = signature
501-
});
491+
return EncodeJwt(new IridiumJWTSigned() { Header = header, Payload = payload, Signature = signature });
502492
}
503493

504494
/// <summary>
@@ -516,8 +506,8 @@ public async Task<string> GetClientId()
516506

517507
private string EncodeJwt(IridiumJWTSigned data)
518508
{
519-
return string.Join(JWT_DELIMITER,
520-
EncodeJson(data.Header),
509+
return string.Join(JWT_DELIMITER,
510+
EncodeJson(data.Header),
521511
EncodeJson(data.Payload),
522512
EncodeSig(data.Signature)
523513
);
@@ -549,7 +539,7 @@ private string EncodeIss(Ed25519PublicKeyParameters publicKey)
549539
private Ed25519PrivateKeyParameters KeypairFromSeed(byte[] seed)
550540
{
551541
return new Ed25519PrivateKeyParameters(seed);
552-
542+
553543
/*var options = new KeyCreationParameters()
554544
{
555545
ExportPolicy = KeyExportPolicies.AllowPlaintextExport
@@ -578,10 +568,8 @@ private void IsInitialized()
578568
{
579569
if (!this._initialized)
580570
{
581-
throw WalletConnectException.FromType(ErrorType.NOT_INITIALIZED, new Dictionary<string, object>()
582-
{
583-
{ "Name", Name }
584-
});
571+
throw WalletConnectException.FromType(ErrorType.NOT_INITIALIZED,
572+
new Dictionary<string, object>() { { "Name", Name } });
585573
}
586574
}
587575

@@ -616,7 +604,7 @@ private byte[] DeriveSharedKey(string privateKeyA, string publicKeyB)
616604
{
617605
var keyB = PublicKey.Import(KeyAgreementAlgorithm.X25519, publicKeyB.HexToByteArray(),
618606
KeyBlobFormat.RawPublicKey);
619-
607+
620608
var options = new SharedSecretCreationParameters
621609
{
622610
ExportPolicy = KeyExportPolicies.AllowPlaintextArchiving
@@ -632,7 +620,7 @@ private byte[] DeriveSymmetricKey(byte[] secretKey)
632620
generator.Init(new HkdfParameters(secretKey, Array.Empty<byte>(), Array.Empty<byte>()));
633621

634622
byte[] key = new byte[32];
635-
generator.GenerateBytes(key, 0,32);
623+
generator.GenerateBytes(key, 0, 32);
636624

637625
return key;
638626
}
@@ -644,14 +632,14 @@ private string DeserializeAndDecrypt(string symKey, string encoded)
644632
var iv = param.Iv;
645633
var type = int.Parse(Bases.Base10.Encode(param.Type));
646634
var isType1 = type == TYPE_1;
647-
635+
648636
var aead = new ChaCha20Poly1305();
649637
aead.Init(false, new ParametersWithIV(new KeyParameter(symKey.HexToByteArray()), iv));
650638

651639
using MemoryStream rawDecrypted = new MemoryStream();
652640
byte[] temp = new byte[@sealed.Length];
653641
int len = aead.ProcessBytes(@sealed, 0, @sealed.Length, temp, 0);
654-
642+
655643
if (len > 0)
656644
{
657645
rawDecrypted.Write(temp, 0, len);
@@ -663,7 +651,7 @@ private string DeserializeAndDecrypt(string symKey, string encoded)
663651
{
664652
rawDecrypted.Write(temp, 0, len);
665653
}
666-
654+
667655
return Encoding.UTF8.GetString(rawDecrypted.ToArray());
668656
}
669657

@@ -687,7 +675,21 @@ private async Task<byte[]> GetClientSeed()
687675

688676
public void Dispose()
689677
{
690-
KeyChain?.Dispose();
678+
Dispose(true);
679+
GC.SuppressFinalize(this);
680+
}
681+
682+
protected virtual void Dispose(bool disposing)
683+
{
684+
if (Disposed)
685+
return;
686+
687+
if (disposing)
688+
{
689+
KeyChain?.Dispose();
690+
}
691+
692+
Disposed = true;
691693
}
692694
}
693695
}

Core Modules/WalletConnectSharp.Network.Websocket/WebsocketConnection.cs

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ public class WebsocketConnection : IJsonRpcConnection, IModule
1616
private string _url;
1717
private bool _registering;
1818
private Guid _context;
19+
protected bool Disposed;
1920

2021
/// <summary>
2122
/// The Open timeout
@@ -98,7 +99,7 @@ public WebsocketConnection(string url)
9899
{
99100
if (!Validation.IsWsUrl(url))
100101
throw new ArgumentException("Provided URL is not compatible with WebSocket connection: " + url);
101-
102+
102103
_context = Guid.NewGuid();
103104
this._url = url;
104105
}
@@ -178,7 +179,7 @@ private void OnOpen(WebsocketClient socket)
178179
{
179180
if (socket == null)
180181
return;
181-
182+
182183
socket.MessageReceived.Subscribe(OnPayload);
183184
socket.DisconnectionHappened.Subscribe(OnDisconnect);
184185

@@ -191,15 +192,15 @@ private void OnDisconnect(DisconnectionInfo obj)
191192
{
192193
if (obj.Exception != null)
193194
this.ErrorReceived?.Invoke(this, obj.Exception);
194-
195+
195196
OnClose(obj);
196197
}
197-
198+
198199
private void OnClose(DisconnectionInfo obj)
199200
{
200201
if (this._socket == null)
201202
return;
202-
203+
203204
//_socket.Dispose();
204205
this._socket = null;
205206
this._registering = false;
@@ -221,7 +222,7 @@ private void OnPayload(ResponseMessage obj)
221222
}
222223

223224
if (string.IsNullOrWhiteSpace(json)) return;
224-
225+
225226
//Console.WriteLine($"[{Name}] Got payload {json}");
226227

227228
this.PayloadReceived?.Invoke(this, json);
@@ -237,8 +238,9 @@ public async Task Close()
237238
throw new IOException("Connection already closed");
238239

239240
await _socket.Stop(WebSocketCloseStatus.NormalClosure, "Close Invoked");
240-
241-
OnClose(new DisconnectionInfo(DisconnectionType.Exit, WebSocketCloseStatus.Empty, "Close Invoked", null, null));
241+
242+
OnClose(new DisconnectionInfo(DisconnectionType.Exit, WebSocketCloseStatus.Empty, "Close Invoked", null,
243+
null));
242244
}
243245

244246
/// <summary>
@@ -303,32 +305,37 @@ public async Task SendError(IJsonRpcError requestPayload, object context)
303305
}
304306
}
305307

306-
/// <summary>
307-
/// Dispose this websocket connection. Will automatically Close this
308-
/// websocket if still connected.
309-
/// </summary>
310308
public async void Dispose()
311309
{
312-
if (Connected)
310+
Dispose(true);
311+
GC.SuppressFinalize(this);
312+
}
313+
314+
protected virtual void Dispose(bool disposing)
315+
{
316+
if (Disposed)
317+
return;
318+
319+
if (disposing)
313320
{
314-
await Close();
321+
_socket.Dispose();
315322
}
323+
324+
Disposed = true;
316325
}
317326

318-
private string addressNotFoundError = "getaddrinfo ENOTFOUND";
319-
private string connectionRefusedError = "connect ECONNREFUSED";
327+
private const string AddressNotFoundError = "getaddrinfo ENOTFOUND";
328+
private const string ConnectionRefusedError = "connect ECONNREFUSED";
329+
320330
private void OnError<T>(IJsonRpcPayload ogPayload, Exception e)
321331
{
322-
var exception = e.Message.Contains(addressNotFoundError) || e.Message.Contains(connectionRefusedError)
323-
? new IOException("Unavailable WS RPC url at " + _url) : e;
332+
var exception = e.Message.Contains(AddressNotFoundError) || e.Message.Contains(ConnectionRefusedError)
333+
? new IOException("Unavailable WS RPC url at " + _url)
334+
: e;
324335

325336
var message = exception.Message;
326-
var payload = new JsonRpcResponse<T>(ogPayload.Id, new Error()
327-
{
328-
Code = exception.HResult,
329-
Data = null,
330-
Message = message
331-
}, default(T));
337+
var payload = new JsonRpcResponse<T>(ogPayload.Id,
338+
new Error() { Code = exception.HResult, Data = null, Message = message }, default(T));
332339

333340
//Trigger the payload event, converting the new JsonRpcResponse object to JSON string
334341
this.PayloadReceived?.Invoke(this, JsonConvert.SerializeObject(payload));

Core Modules/WalletConnectSharp.Network/Interfaces/IJsonRpcProvider.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
using System;
22
using System.Threading.Tasks;
3+
using WalletConnectSharp.Common;
34
using WalletConnectSharp.Network.Models;
45

56
namespace WalletConnectSharp.Network
67
{
78
/// <summary>
89
/// The interface that represents a JSON RPC provider
910
/// </summary>
10-
public interface IJsonRpcProvider : IBaseJsonRpcProvider
11+
public interface IJsonRpcProvider : IBaseJsonRpcProvider, IModule
1112
{
1213
event EventHandler<JsonRpcPayload> PayloadReceived;
1314

@@ -18,7 +19,7 @@ public interface IJsonRpcProvider : IBaseJsonRpcProvider
1819
event EventHandler<Exception> ErrorReceived;
1920

2021
event EventHandler<string> RawMessageReceived;
21-
22+
2223
/// <summary>
2324
/// Connect this provider to the given URL
2425
/// </summary>

0 commit comments

Comments
 (0)