/// <summary> /// Removes a record from cache and registry and invalidates results cache. /// </summary> /// <param name="record"></param> /// <param name="ct"></param> /// <returns></returns> public async Task RemoveAsync(INameRecord record, CancellationToken ct) { _cache.TryRemove(record.Id, out record); await RemoveRecordAsync(record, ct); base.Invalidate(); }
/// <summary> /// Creates a device in the IoT Hub device registry /// </summary> /// <param name="record"></param> /// <param name="ct"></param> /// <returns></returns> internal async Task <IoTHubRecord> AddRecordAsync(INameRecord record, CancellationToken ct) { try { await _http.CallAsync(CreateUri("/devices/" + record.Id.ToLowerInvariant()), Http.Put, async h => { h.Add(HttpRequestHeader.Authorization.ToString(), await GetSasTokenAsync(3600).ConfigureAwait(false)); h.Add(HttpRequestHeader.UserAgent.ToString(), _clientId); }, (sc, h) => { if (sc == HttpStatusCode.Conflict || sc == HttpStatusCode.PreconditionFailed) { throw new TransientException(); } }, ct, @"{""deviceId"": """ + record.Id + @"""}", "application/json").ConfigureAwait(false); ProxyEventSource.Log.RecordAdded(this, record); return(new IoTHubRecord(record)); } catch (TransientException) { // Retrieve the twin object and update it var result = await GetRecordAsync(record, ct); if (result == null) { return(null); } result.Assign(record); return(result); } catch (Exception e) { throw ProxyEventSource.Log.Rethrow(e, this); } }
/// <summary> /// Check whether the record is connected /// </summary> /// <param name="record"></param> /// <param name="ct"></param> /// <returns></returns> internal async Task <bool> IsConnectedAsync(INameRecord record, CancellationToken ct) { try { var stream = await _http.StreamAsync( CreateUri("/devices/" + record.Id), Http.Get, async h => { h.Add(HttpRequestHeader.Authorization.ToString(), await IoTHubService.GetSasTokenAsync(_hubConnectionString, 3600).ConfigureAwait(false)); h.Add(HttpRequestHeader.UserAgent.ToString(), _clientId); }, (sc, h) => { if (sc == HttpStatusCode.NotFound) { throw new TransientException(); } }, ct).ConfigureAwait(false); using (stream) using (var sr = new StreamReader(stream)) using (JsonReader reader = new JsonTextReader(sr)) { var response = (JObject)JToken.ReadFrom(reader); return(response["connectionState"].ToString().Equals( "Connected", StringComparison.CurrentCultureIgnoreCase)); } } catch (TransientException) { return(false); } catch (Exception e) { throw ProxyEventSource.Log.Rethrow(e, this); } }
/// <summary> /// Retrieves a single device twin based record from IoT Hub /// </summary> /// <param name="record"></param> /// <param name="ct"></param> /// <returns></returns> internal async Task <IoTHubRecord> GetRecordAsync(INameRecord record, CancellationToken ct) { try { var stream = await _http.StreamAsync( CreateUri("/twins/" + record.Id), Http.Get, async h => { h.Add(HttpRequestHeader.Authorization.ToString(), await IoTHubService.GetSasTokenAsync(_hubConnectionString, 3600).ConfigureAwait(false)); h.Add(HttpRequestHeader.UserAgent.ToString(), _clientId); }, (sc, h) => { if (sc == HttpStatusCode.NotFound) { throw new TransientException(); } }, ct).ConfigureAwait(false); using (stream) using (var sr = new StreamReader(stream)) using (JsonReader reader = new JsonTextReader(sr)) { return(new IoTHubRecord((JObject)JToken.ReadFrom(reader))); } } catch (TransientException) { ProxyEventSource.Log.RecordRemoved(this, record); return(null); } catch (Exception e) { throw ProxyEventSource.Log.Rethrow(e, this); } }
/// <summary> /// Copy constructor for record /// </summary> /// <param name="record"></param> public Record(INameRecord record) { Address = record.Address; Type = record.Type; Id = record.Id; Name = record.Name; }
/// <summary> /// Matches proxy records using optional host as guidance for limiting results. /// </summary> /// <param name="record"></param> /// <param name="host"></param> /// <returns></returns> public static bool IsProxyForHost(this INameRecord record, SocketAddress host) { if (!record.Type.HasFlag(NameRecordType.Proxy)) { return(false); } if (host is ProxySocketAddress address) { if (!string.IsNullOrEmpty(address.Domain)) { if (string.IsNullOrEmpty(record.Domain)) { // Proxy without domain, do not use it. return(false); } if (address.Domain == record.Domain) { // Same domain return(true); } // If record domain is a super domain of the address one, also match ok return(address.Domain.EndsWith("." + record.Domain, StringComparison.CurrentCultureIgnoreCase)); } // No domain to match, thus match all. } return(true); }
/// <summary> /// Invoke method and return response /// </summary> /// <param name="record"></param> /// <param name="request"></param> /// <param name="timeout"></param> /// <param name="ct"></param> /// <returns></returns> internal async Task <Message> InvokeDeviceMethodAsync(INameRecord record, Message request, TimeSpan timeout, CancellationToken ct) { request.Proxy = record.Address; request.DeviceId = record.Id; return(await InvokeDeviceMethodAsync(request, timeout, ct).ConfigureAwait(false)); }
/// <summary> /// Invoke method on proxy /// </summary> /// <param name="proxy"></param> /// <param name="request"></param> /// <param name="ct"></param> /// <returns></returns> public async Task <Message> CallAsync(INameRecord proxy, Message request, TimeSpan timeout, CancellationToken ct) { using (var invoker = new IoTHubInvoker(_hubConnectionString)) { return(await invoker.CallAsync(proxy, request, timeout, ct)); } }
/// <summary> /// Copy constructor for record /// </summary> /// <param name="record"></param> public NameRecord(INameRecord record) { Address = record.Address; Type = record.Type; Id = record.Id; Name = record.Name; ReferenceSet.AddRange(record.References); }
/// <summary> /// Match record against address and type /// </summary> /// <param name="record"></param> /// <param name="address"></param> /// <param name="type"></param> /// <returns></returns> public static bool Matches(this INameRecord record, Reference address, NameRecordType type) { if (!record.Type.HasFlag(type)) { return(false); } return(address == Reference.All || record.Address.Equals(address)); }
/// <summary> /// Invoke method and return response /// </summary> /// <param name="record"></param> /// <param name="request"></param> /// <param name="timeout"></param> /// <param name="ct"></param> /// <returns></returns> public async Task <Message> CallAsync(INameRecord record, Message request, TimeSpan timeout, CancellationToken ct) { using (var message = request.Clone()) { message.Proxy = record.Address; message.DeviceId = record.Id; return(await CallAsync(message, timeout, ct).ConfigureAwait(false)); } }
/// <summary> /// Constructor creating a method based polled stream. /// </summary> /// <param name="iothub"></param> /// <param name="streamId"></param> /// <param name="remoteId"></param> /// <param name="link"></param> /// <param name="connectionString"></param> public IoTHubStream(IoTHubService iothub, Reference streamId, Reference remoteId, INameRecord link, ConnectionString connectionString) { _iotHub = iothub; _streamId = streamId; _remoteId = remoteId; _link = link; ConnectionString = connectionString; }
/// <summary> /// Constructor for proxy link object /// </summary> /// <param name="socket"></param> /// <param name="proxy"></param> /// <param name="remoteId"></param> /// <param name="localAddress"></param> /// <param name="peerAddress"></param> internal ProxyLink(ProxySocket socket, INameRecord proxy, Reference remoteId, SocketAddress localAddress, SocketAddress peerAddress) { _socket = socket ?? throw new ArgumentNullException(nameof(socket)); Proxy = proxy ?? throw new ArgumentNullException(nameof(proxy)); RemoteId = remoteId ?? throw new ArgumentNullException(nameof(remoteId)); LocalAddress = localAddress ?? throw new ArgumentNullException(nameof(localAddress)); PeerAddress = peerAddress ?? throw new ArgumentNullException(nameof(peerAddress)); }
/// <summary> /// Comparison /// </summary> /// <param name="that"></param> /// <returns></returns> public bool Equals(INameRecord that) { return (IsEqual(Address, that.Address) && IsEqual(Type, that.Type) && IsEqual(Id, that.Id) && IsEqual(Name, that.Name) && ReferenceSet.SetEquals(that.References) ); }
/// <summary> /// Copy constructor for record /// </summary> /// <param name="record"></param> public NameRecord(INameRecord record) { Address = record.Address; Type = record.Type; Id = record.Id; Name = record.Name; Domain = record.Domain; LastActivity = record.LastActivity; ReferenceSet.AddRange(record.References); }
/// <summary> /// Constructor creating a method based polled stream. /// </summary> /// <param name="streamId"></param> /// <param name="remoteId"></param> /// <param name="link"></param> /// <param name="connectionString"></param> public IoTHubStream(ConnectionString hubConnectionString, Reference streamId, Reference remoteId, INameRecord link, ConnectionString connectionString) { _hubConnectionString = hubConnectionString ?? throw new ArgumentNullException(nameof(hubConnectionString)); _streamId = streamId; _remoteId = remoteId; _link = link; ConnectionString = connectionString; }
/// <summary> /// Match record against name and type /// </summary> /// <param name="record"></param> /// <param name="name"></param> /// <param name="type"></param> /// <returns></returns> public static bool Matches(this INameRecord record, string name, NameRecordType type) { if (!record.Type.HasFlag(type)) { return(false); } return(name == null || (record.Name.Equals(name, StringComparison.CurrentCultureIgnoreCase) || record.Id.Equals(name, StringComparison.CurrentCultureIgnoreCase))); }
/// <summary> /// Constructor for proxy link object /// </summary> /// <param name="socket">Owning proxy link</param> /// <param name="remoteId">Remote endpoint id assigned by proxy</param> /// <param name="localAddress">Remote local address</param> /// <param name="peerAddress">Remote peer address</param> /// <returns></returns> internal ProxyLink(ProxySocket socket, INameRecord proxy, Reference remoteId, SocketAddress localAddress, SocketAddress peerAddress) { _streamId = new Reference(); _socket = socket; Proxy = proxy; RemoteId = remoteId; LocalAddress = localAddress; PeerAddress = peerAddress; }
/// <summary> /// Invoke method and return response /// </summary> /// <param name="record"></param> /// <param name="request"></param> /// <param name="timeout"></param> /// <param name="ct"></param> /// <returns></returns> private async Task <Message> TryInvokeDeviceMethodAsync(INameRecord record, Message request, TimeSpan timeout, CancellationToken ct) { try { return(await InvokeDeviceMethodAsync( record, request, timeout, ct).ConfigureAwait(false)); } catch { return(null); } }
/// <summary> /// Create stream connection through iot hub methods. /// </summary> /// <param name="streamId">Local reference address of the stream</param> /// <param name="remoteId">Remote reference of link</param> /// <param name="proxy">The proxy server</param> /// <returns></returns> public Task <IConnection> CreateConnectionAsync(Reference streamId, Reference remoteId, INameRecord proxy, CodecId encoding) { IConnection conn = new IoTHubStream(_hubConnectionString, streamId, remoteId, proxy, null); // TODO: Revisit: At this point we could either a) look up a host from the registry // then use it to create a dedicated stream with connection string or b) create an // adhoc dr stream record in the registry. return(Task.FromResult(conn)); }
/// <summary> /// Link one remote endpoint /// </summary> /// <param name="proxy"></param> /// <param name="ct"></param> /// <returns></returns> private async Task <IProxyLink> CreateLinkAsync(INameRecord proxy, CancellationToken ct) { ProxyEventSource.Log.LinkCreate(this, proxy.Name, Info.Address); // Create link, i.e. perform bind, connect, listen, etc. on proxy Message response = await Provider.ControlChannel.CallAsync(proxy, new Message(Id, Reference.Null, new LinkRequest { Properties = Info }), ct); if (response == null || response.Error != (int)SocketError.Success) { ProxyEventSource.Log.LinkFailure(this, proxy.Name, Info, response, null); return(null); } var linkResponse = response.Content as LinkResponse; if (linkResponse == null) { ProxyEventSource.Log.LinkFailure(this, proxy.Name, Info, response, null); return(null); } // now create local link and open link for streaming var link = new ProxyLink(this, proxy, linkResponse.LinkId, linkResponse.LocalAddress, linkResponse.PeerAddress); try { // Broker connection string to proxy var openRequest = await link.BeginOpenAsync(ct).ConfigureAwait(false); ProxyEventSource.Log.LinkOpen(this, proxy.Name, Info.Address); await Provider.ControlChannel.CallAsync(proxy, new Message(Id, linkResponse.LinkId, openRequest), ct).ConfigureAwait(false); // Wait until remote side opens stream connection bool success = await link.TryCompleteOpenAsync(ct).ConfigureAwait(false); if (success) { ProxyEventSource.Log.LinkComplete(this, proxy.Name, Info.Address); return(link); } } catch (Exception e) { // Try to close remote side await link.CloseAsync(CancellationToken.None).ConfigureAwait(false); ProxyEventSource.Log.LinkFailure(this, proxy.Name, Info.Address, null, e); } return(null); }
/// <summary> /// Comparison /// </summary> /// <param name="that"></param> /// <returns></returns> public bool Equals(INameRecord that) { return (IsEqual(Address, that.Address) && IsEqual(Type, that.Type) && IsEqual(Id, that.Id) && IsEqual(Name, that.Name) && IsEqual(Domain, that.Domain) && IsEqual(LastActivity, that.LastActivity) && ReferenceSet.SetEquals(that.References) ); }
/// <summary> /// Create stream connection through iot hub methods. /// </summary> /// <param name="streamId">Local reference id of the stream</param> /// <param name="remoteId">Remote reference of link</param> /// <param name="proxy">The proxy server</param> /// <returns></returns> public Task <IConnection> CreateConnectionAsync(Reference streamId, Reference remoteId, INameRecord proxy) { IConnection conn = new IoTHubStream(this, streamId, remoteId, proxy, null); // TODO: Revisit: At this point we could either a) look up a host from the registry // then use it to create a dedicated stream with connection string or b) create an // adhoc dr stream entry in the registry. However, due to overall IoTHub performance // characteristic there is at this point no performance benefit from doing so. return(Task.FromResult(conn)); }
/// <summary> /// Registers a new record with device registry. Host records are /// synchronized lazily at every cache refresh to avoid spamming the /// registry... /// </summary> /// <param name="proxy"></param> /// <param name="name"></param> /// <param name="ct"></param> /// <returns></returns> public async Task AddOrUpdateAsync(INameRecord record, CancellationToken ct) { if (NameRecordType.Host == (record.Type & NameRecordType.Host)) { _cache.AddOrUpdate(record.Id, s => record, (s, r) => r.Assign(record)); } else { await UpdateRecordAsync(record, ct); base.Invalidate(); } }
/// <summary> /// Returns a new connection /// </summary> /// <param name="streamId">Local reference id of the stream</param> /// <param name="remoteId">Remote reference of link</param> /// <param name="proxy">The proxy server</param> /// <returns></returns> public Task <IConnection> CreateConnectionAsync(Reference streamId, Reference remoteId, INameRecord proxy) { var uri = new UriBuilder(_uri); uri.Scheme = "wss"; uri.Path = streamId.ToString(); var connection = new WebSocketMessageStream( this, streamId, new ConnectionString(uri.Uri, "proxy", "secret")); _streamMap.AddOrUpdate(streamId, connection, (r, s) => connection); return(Task.FromResult((IConnection)connection)); }
/// <summary> /// Creates new connection /// </summary> /// <param name="streamId">Local reference id of the stream</param> /// <param name="remoteId">Remote reference of link</param> /// <param name="proxy">The proxy server</param> /// <param name="encoding">The encoding to use</param> /// <returns></returns> public Task <IConnection> CreateConnectionAsync(Reference streamId, Reference remoteId, INameRecord proxy, CodecId encoding) { var uri = new UriBuilder(_uri) { Scheme = _secure ? "wss" : "ws", Path = streamId.ToString() }; var connection = new WebSocketConnection(this, streamId, remoteId, encoding, ConnectionString.CreateWithEndpointAndToken(uri.Uri, "proxy", "secret")); _connectionMap.AddOrUpdate(streamId, connection, (r, s) => connection); return(Task.FromResult((IConnection)connection)); }
/// <summary> /// Comparison /// </summary> /// <param name="that"></param> /// <returns></returns> public bool Equals(INameRecord that) { if (that == null) { return(false); } return (Address.Equals(that.Address) && Type.Equals(that.Type) && Id.Equals(that.Id) && Name.Equals(that.Name, StringComparison.CurrentCultureIgnoreCase) && References.SameAs(that.References) ); }
/// <summary> /// Copies members from passed in record /// </summary> /// <param name="record"></param> public INameRecord Assign(INameRecord record) { if (!Id.Equals(record.Id)) { return(record); } Type = record.Type; Name = record.Name; ReferenceSet.Clear(); ReferenceSet.AddRange(record.References); return(this); }
/// <summary> /// Invoke method on proxy /// </summary> /// <param name="proxy"></param> /// <param name="request"></param> /// <param name="ct"></param> /// <returns></returns> public async Task <Message> CallAsync(INameRecord proxy, Message request, CancellationToken ct) { try { return(await InvokeDeviceMethodAsync(proxy, request, TimeSpan.FromMinutes(3), ct).ConfigureAwait(false)); } catch (OperationCanceledException) { } catch (Exception e) { ProxyEventSource.Log.HandledExceptionAsError(this, e); } return(null); }
/// <summary> /// Creates connection /// </summary> /// <param name="streamId">Local reference id of the stream</param> /// <param name="remoteId">Remote reference of link</param> /// <param name="proxy">The proxy server</param> /// <returns></returns> public async Task <IConnection> CreateConnectionAsync(Reference streamId, Reference remoteId, INameRecord proxy) { var uri = new UriBuilder(_uri); uri.Scheme = "http"; var token = await _tokenProvider.GetTokenAsync(uri.ToString(), TimeSpan.FromHours(24)).ConfigureAwait(false); var connection = new RelayStream( this, streamId, new ConnectionString(_uri, "proxy", token.TokenString)); _streamMap.AddOrUpdate(streamId, connection, (r, s) => connection); return(connection); }