Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update preview to main #2311

Merged
merged 34 commits into from
Mar 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
4564243
doc(client, service): Update documents about the Proxy property for v…
jamdavi Dec 8, 2021
27685d1
Cleanup IDE warnings. From 442 down to 31. (#2254)
azabbasi Dec 8, 2021
2cf35f3
Fix documented exception types thrown by methods in device client. (#…
azabbasi Dec 9, 2021
4618ef3
(Fix: DeviceClient): Fix the concurrency issue in MQTT stack (#2234)
azabbasi Dec 9, 2021
5245797
Update RegistryManager bulk API unit tests (#2258)
Dec 9, 2021
d22f9f3
feat(hub-svc): add support and tests for configurations export (#2250)
Dec 15, 2021
2b05e47
Temporarily disable import devices/configs test (#2263)
Jan 6, 2022
89973ed
fix(iot-svc): update debug assert (#2264)
Jan 6, 2022
0582733
Consolidate keyvault writes, and fix error about null (#2266)
Jan 6, 2022
4de5519
Cleanup code in Hub RTAC (#2268)
Jan 7, 2022
8f9d565
fix(deviceClient): Fix issue with AMQP connection pool and TokenRefer…
azabbasi Jan 7, 2022
f8b92fc
Move more LA steps under conditional (#2270)
Jan 10, 2022
823fbec
Disable import/export of config until bug fix (#2273)
Jan 10, 2022
7a26eda
Adding RBAC support for provisioning SDK (#2262)
dylanbulfinMS Jan 10, 2022
7508dd2
Adding Common 0.7.1 (#2272)
jamdavi Jan 11, 2022
49e0854
Fix doc comment list bullets (#2275)
Jan 11, 2022
f811e08
sdl(all): Create SBOM for net packages (#2261)
jamdavi Jan 11, 2022
f072c94
Streamline an RBAC test (#2274)
Jan 11, 2022
1229b72
Exclude low pri .net targets in PR builds (#2277)
Jan 11, 2022
cbbad60
Update IDeviceIdentity interface to add doc comments. (#2282)
azabbasi Jan 13, 2022
313d885
Fix remaining doc comment bullets (#2285)
Jan 14, 2022
65749c2
Give e2e appId permission on the DPS instance (#2283)
Jan 14, 2022
f114662
fix(DeviceClient): Avoid NRE after client dispose (#2286)
azabbasi Jan 18, 2022
5e522cd
Revert "Adding RBAC support for provisioning SDK (#2262)" (#2289)
azabbasi Jan 26, 2022
6cce47a
Version bump for 2021-01-26 release (#2291)
azabbasi Jan 26, 2022
5a05eb3
Update service clients instantiation doc comments (#2290)
Jan 27, 2022
d20e5bd
Fix class field constants casing (#2292)
Jan 27, 2022
89f83b9
Disable checks for config in import e2e test (#2293)
Jan 27, 2022
e4ef736
Update iteration path in vsts.yaml (#2296)
Feb 1, 2022
3beea8a
fix(iot-device): Update QoS settings to AtLeastOnce for mqtt layer su…
abhipsaMisra Feb 9, 2022
a9841c6
Update CODEOWNERS (#2304)
jamdavi Feb 15, 2022
ea504ae
Update to Azure.Core 1.22.0 to update dependency on System.Text.Encod…
Feb 28, 2022
e861cb4
Merge branch 'main' into dbulfin/preview
dylanbulfinMS Mar 1, 2022
97fe5c2
Fix issues with merging
dylanbulfinMS Mar 1, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# Track1 .NET Azure IoT Hub and DPS SDKs

* @drwill-ms @timtay-microsoft @abhipsaMisra @azabbasi @jamdavi @andyk-ms @brycewang-microsoft
* @drwill-ms @timtay-microsoft @abhipsaMisra @azabbasi @andyk-ms @brycewang-microsoft
18 changes: 9 additions & 9 deletions iothub/device/src/DeviceClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ public void SetRetryPolicy(IRetryPolicy retryPolicy)
/// </summary>
/// <remarks>
/// You cannot reject or abandon messages over MQTT protocol.
/// For more details, see https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-messages-c2d#the-cloud-to-device-message-life-cycle.
/// For more details, see https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-messages-c2d#the-cloud-to-device-message-life-cycle.
drwill-ms marked this conversation as resolved.
Show resolved Hide resolved
/// </remarks>
/// <param name="cancellationToken">A cancellation token to cancel the operation.</param>
/// <exception cref="IotHubCommunicationException">Thrown when the operation has been canceled. The inner exception will be <see cref="OperationCanceledException"/>.</exception>
Expand All @@ -333,7 +333,7 @@ public void SetRetryPolicy(IRetryPolicy retryPolicy)
/// </summary>
/// <remarks>
/// You cannot reject or abandon messages over MQTT protocol.
/// For more details, see https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-messages-c2d#the-cloud-to-device-message-life-cycle.
/// For more details, see https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-messages-c2d#the-cloud-to-device-message-life-cycle.
/// </remarks>
/// <returns>The received message or null if there was no message until the specified time has elapsed.</returns>
public Task<Message> ReceiveAsync(TimeSpan timeout) => InternalClient.ReceiveAsync(timeout);
Expand Down Expand Up @@ -383,7 +383,7 @@ public Task SetReceiveMessageHandlerAsync(ReceiveMessageCallback messageHandler,
/// </summary>
/// <remarks>
/// You cannot abandon a message over MQTT protocol.
/// For more details, see https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-messages-c2d#the-cloud-to-device-message-life-cycle.
/// For more details, see https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-messages-c2d#the-cloud-to-device-message-life-cycle.
/// </remarks>
/// <param name="lockToken">The message lockToken.</param>
public Task AbandonAsync(string lockToken) => InternalClient.AbandonAsync(lockToken);
Expand All @@ -393,7 +393,7 @@ public Task SetReceiveMessageHandlerAsync(ReceiveMessageCallback messageHandler,
/// </summary>
/// <remarks>
/// You cannot abandon a message over MQTT protocol.
/// For more details, see https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-messages-c2d#the-cloud-to-device-message-life-cycle.
/// For more details, see https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-messages-c2d#the-cloud-to-device-message-life-cycle.
/// </remarks>
/// <param name="lockToken">The message lockToken.</param>
/// <param name="cancellationToken">A cancellation token to cancel the operation.</param>
Expand All @@ -405,7 +405,7 @@ public Task SetReceiveMessageHandlerAsync(ReceiveMessageCallback messageHandler,
/// </summary>
/// <remarks>
/// You cannot abandon a message over MQTT protocol.
/// For more details, see https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-messages-c2d#the-cloud-to-device-message-life-cycle.
/// For more details, see https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-messages-c2d#the-cloud-to-device-message-life-cycle.
/// </remarks>
/// <param name="message">The message to abandon.</param>
public Task AbandonAsync(Message message) => InternalClient.AbandonAsync(message);
Expand All @@ -415,7 +415,7 @@ public Task SetReceiveMessageHandlerAsync(ReceiveMessageCallback messageHandler,
/// </summary>
/// <remarks>
/// You cannot abandon a message over MQTT protocol.
/// For more details, see https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-messages-c2d#the-cloud-to-device-message-life-cycle.
/// For more details, see https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-messages-c2d#the-cloud-to-device-message-life-cycle.
/// </remarks>
/// <param name="message">The message to abandon.</param>
/// <param name="cancellationToken">A cancellation token to cancel the operation.</param>
Expand All @@ -427,7 +427,7 @@ public Task SetReceiveMessageHandlerAsync(ReceiveMessageCallback messageHandler,
/// </summary>
/// <remarks>
/// You cannot reject a message over MQTT protocol.
/// For more details, see https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-messages-c2d#the-cloud-to-device-message-life-cycle.
/// For more details, see https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-messages-c2d#the-cloud-to-device-message-life-cycle.
/// </remarks>
/// <param name="lockToken">The message lockToken.</param>
public Task RejectAsync(string lockToken) => InternalClient.RejectAsync(lockToken);
Expand All @@ -449,7 +449,7 @@ public Task SetReceiveMessageHandlerAsync(ReceiveMessageCallback messageHandler,
/// </summary>
/// <remarks>
/// You cannot reject a message over MQTT protocol.
/// For more details, see https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-messages-c2d#the-cloud-to-device-message-life-cycle.
/// For more details, see https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-messages-c2d#the-cloud-to-device-message-life-cycle.
/// </remarks>
/// <param name="message">The message.</param>
public Task RejectAsync(Message message) => InternalClient.RejectAsync(message);
Expand All @@ -459,7 +459,7 @@ public Task SetReceiveMessageHandlerAsync(ReceiveMessageCallback messageHandler,
/// </summary>
/// <remarks>
/// You cannot reject a message over MQTT protocol.
/// For more details, see https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-messages-c2d#the-cloud-to-device-message-life-cycle.
/// For more details, see https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-messages-c2d#the-cloud-to-device-message-life-cycle.
/// </remarks>
/// <param name="message">The message to reject.</param>
/// <param name="cancellationToken">A cancellation token to cancel the operation.</param>
Expand Down
36 changes: 20 additions & 16 deletions iothub/device/src/Transport/Mqtt/MqttTransportHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@
using Newtonsoft.Json;

#if NET5_0

using TaskCompletionSource = System.Threading.Tasks.TaskCompletionSource;

#else
using TaskCompletionSource = Microsoft.Azure.Devices.Shared.TaskCompletionSource;
#endif
Expand Down Expand Up @@ -112,7 +114,8 @@ internal sealed class MqttTransportHandler : TransportHandler, IMqttIotHubEventH
private readonly Func<IPAddress[], int, Task<IChannel>> _channelFactory;
private readonly Queue<string> _completionQueue;
private readonly MqttIotHubAdapterFactory _mqttIotHubAdapterFactory;
private readonly QualityOfService _qos;
private readonly QualityOfService _qosSendPacketToService;
private readonly QualityOfService _qosReceivePacketFromService;
private readonly bool _retainMessagesAcrossSessions;
private readonly object _syncRoot = new object();
private readonly RetryPolicy _closeRetryPolicy;
Expand Down Expand Up @@ -173,7 +176,8 @@ internal MqttTransportHandler(
_deviceboundMessageFilter = string.Format(CultureInfo.InvariantCulture, DeviceBoundMessagesTopicFilter, iotHubConnectionString.DeviceId);
_deviceboundMessagePrefix = string.Format(CultureInfo.InvariantCulture, DeviceBoundMessagesTopicPrefix, iotHubConnectionString.DeviceId);

_qos = settings.PublishToServerQoS;
_qosSendPacketToService = settings.PublishToServerQoS;
drwill-ms marked this conversation as resolved.
Show resolved Hide resolved
_qosReceivePacketFromService = settings.ReceivingQoS;

// If the CleanSession flag is set to false, C2D messages will be retained across device sessions, i.e. the device
// will receive the C2D messages that were sent to it while it was disconnected.
Expand Down Expand Up @@ -299,7 +303,7 @@ public override async Task<Message> ReceiveAsync(CancellationToken cancellationT
}

await WaitUntilC2dMessageArrivesAsync(cancellationToken).ConfigureAwait(false);
return ProcessMessage();
return ProcessC2dMessage();
}
finally
{
Expand Down Expand Up @@ -338,7 +342,7 @@ public override async Task<Message> ReceiveAsync(TimeoutHelper timeoutHelper)
using var cts = new CancellationTokenSource(timeout);

await WaitUntilC2dMessageArrivesAsync(cts.Token).ConfigureAwait(false);
return ProcessMessage();
return ProcessC2dMessage();
}
finally
{
Expand All @@ -347,20 +351,20 @@ public override async Task<Message> ReceiveAsync(TimeoutHelper timeoutHelper)
}
}

private Message ProcessMessage()
private Message ProcessC2dMessage()
{
Message message = null;

try
{
if (Logging.IsEnabled)
Logging.Enter(this, message, $"Will begin processing received C2D message, queue size={_messageQueue.Count}", nameof(ProcessMessage));
Logging.Enter(this, message, $"Will begin processing received C2D message, queue size={_messageQueue.Count}", nameof(ProcessC2dMessage));

lock (_syncRoot)
{
if (_messageQueue.TryDequeue(out message))
{
if (_qos == QualityOfService.AtLeastOnce)
if (_qosReceivePacketFromService == QualityOfService.AtLeastOnce)
{
_completionQueue.Enqueue(message.LockToken);
}
Expand All @@ -374,7 +378,7 @@ private Message ProcessMessage()
finally
{
if (Logging.IsEnabled)
Logging.Exit(this, message, $"Processed received C2D message with Id={message?.MessageId}", nameof(ProcessMessage));
Logging.Exit(this, message, $"Processed received C2D message with Id={message?.MessageId}", nameof(ProcessC2dMessage));
}
}

Expand All @@ -398,7 +402,7 @@ public override async Task CompleteAsync(string lockToken, CancellationToken can
cancellationToken.ThrowIfCancellationRequested();
EnsureValidState();

if (_qos == QualityOfService.AtMostOnce)
if (_qosReceivePacketFromService == QualityOfService.AtMostOnce)
{
throw new IotHubException("Complete is not allowed for QoS 0.", isTransient: false);
}
Expand Down Expand Up @@ -565,7 +569,7 @@ private async Task HandleIncomingMessagesAsync()
if (Logging.IsEnabled)
Logging.Enter(this, "Process C2D message via callback", nameof(HandleIncomingMessagesAsync));

Message message = ProcessMessage();
Message message = ProcessC2dMessage();

// We are intentionally not awaiting _deviceMessageReceivedListener callback.
// This is a user-supplied callback that isn't required to be awaited by us. We can simply invoke it and continue.
Expand Down Expand Up @@ -652,7 +656,7 @@ private async Task HandleIncomingEventMessageAsync(Message message)
// Add the endpoint as a SystemProperty
message.SystemProperties.Add(MessageSystemPropertyNames.InputName, inputName);

if (_qos == QualityOfService.AtLeastOnce)
if (_qosReceivePacketFromService == QualityOfService.AtLeastOnce)
{
lock (_syncRoot)
{
Expand Down Expand Up @@ -833,7 +837,7 @@ public override async Task EnableMethodsAsync(CancellationToken cancellationToke
// Codes_SRS_CSHARP_MQTT_TRANSPORT_18_001: `EnableMethodsAsync` shall subscribe using the '$iothub/methods/POST/' topic filter.
// Codes_SRS_CSHARP_MQTT_TRANSPORT_18_002: `EnableMethodsAsync` shall wait for a SUBACK for the subscription request.
// Codes_SRS_CSHARP_MQTT_TRANSPORT_18_003: `EnableMethodsAsync` shall return failure if the subscription request fails.
await _channel.WriteAsync(new SubscribePacket(0, new SubscriptionRequest(MethodPostTopicFilter, QualityOfService.AtMostOnce))).ConfigureAwait(true);
await _channel.WriteAsync(new SubscribePacket(0, new SubscriptionRequest(MethodPostTopicFilter, _qosReceivePacketFromService))).ConfigureAwait(true);
}

public override async Task DisableMethodsAsync(CancellationToken cancellationToken)
Expand All @@ -855,7 +859,7 @@ public override async Task EnableEventReceiveAsync(bool isAnEdgeModule, Cancella
// Codes_SRS_CSHARP_MQTT_TRANSPORT_33_021: `EnableEventReceiveAsync` shall subscribe using the 'devices/{0}/modules/{1}/' topic filter.
// Codes_SRS_CSHARP_MQTT_TRANSPORT_33_022: `EnableEventReceiveAsync` shall wait for a SUBACK for the subscription request.
// Codes_SRS_CSHARP_MQTT_TRANSPORT_33_023: `EnableEventReceiveAsync` shall return failure if the subscription request fails.
await _channel.WriteAsync(new SubscribePacket(0, new SubscriptionRequest(_receiveEventMessageFilter, _qos))).ConfigureAwait(true);
await _channel.WriteAsync(new SubscribePacket(0, new SubscriptionRequest(_receiveEventMessageFilter, _qosReceivePacketFromService))).ConfigureAwait(true);
}

public override async Task DisableEventReceiveAsync(bool isAnEdgeModule, CancellationToken cancellationToken)
Expand Down Expand Up @@ -897,7 +901,7 @@ public override async Task EnableTwinPatchAsync(CancellationToken cancellationTo
// Codes_SRS_CSHARP_MQTT_TRANSPORT_18_010: `EnableTwinPatchAsync` shall subscribe using the '$iothub/twin/PATCH/properties/desired/#' topic filter.
// Codes_SRS_CSHARP_MQTT_TRANSPORT_18_011: `EnableTwinPatchAsync` shall wait for a SUBACK on the subscription request.
// Codes_SRS_CSHARP_MQTT_TRANSPORT_18_012: `EnableTwinPatchAsync` shall return failure if the subscription request fails.
await _channel.WriteAsync(new SubscribePacket(0, new SubscriptionRequest(TwinPatchTopicFilter, QualityOfService.AtMostOnce))).ConfigureAwait(true);
await _channel.WriteAsync(new SubscribePacket(0, new SubscriptionRequest(TwinPatchTopicFilter, _qosReceivePacketFromService))).ConfigureAwait(true);

if (Logging.IsEnabled)
Logging.Exit(this, cancellationToken, nameof(EnableTwinPatchAsync));
Expand Down Expand Up @@ -1076,7 +1080,7 @@ private async Task SubscribeCloudToDeviceMessagesAsync()
if (TryStateTransition(TransportState.Open, TransportState.Subscribing))
{
await _channel
.WriteAsync(new SubscribePacket(0, new SubscriptionRequest(_deviceboundMessageFilter, QualityOfService.AtLeastOnce)))
.WriteAsync(new SubscribePacket(0, new SubscriptionRequest(_deviceboundMessageFilter, _qosReceivePacketFromService)))
.ConfigureAwait(true);

if (TryStateTransition(TransportState.Subscribing, TransportState.Receiving)
Expand All @@ -1095,7 +1099,7 @@ private Task SubscribeTwinResponsesAsync()
0,
new SubscriptionRequest(
TwinResponseTopicFilter,
QualityOfService.AtMostOnce)));
_qosReceivePacketFromService)));
}

private bool ParseResponseTopic(string topicName, out int status)
Expand Down
3 changes: 2 additions & 1 deletion iothub/service/src/JobProperties.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ namespace Microsoft.Azure.Devices
{
/// <summary>
/// Contains properties of a Job.
/// See online <a href="https://docs.microsoft.com/rest/api/iothub/service/createimportexportjob">documentation</a> for more infomration.
/// See online <a href="https://docs.microsoft.com/en-us/rest/api/iothub/service/createimportexportjob">documentation</a>
/// for more infomration.
/// </summary>
public class JobProperties
{
Expand Down
2 changes: 1 addition & 1 deletion iothub/service/src/Microsoft.Azure.Devices.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,6 @@
<PackageReference Include="System.Diagnostics.TraceSource" Version="4.3.0" />
<PackageReference Include="System.Diagnostics.Contracts" Version="4.3.0" />
<PackageReference Include="Microsoft.Rest.ClientRuntime" Version="2.3.21" />
<PackageReference Include="Azure.Core" Version="1.9.0" />
<PackageReference Include="Azure.Core" Version="1.22.0" />
</ItemGroup>
</Project>
5 changes: 3 additions & 2 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,12 +205,13 @@ This table shows previous LTS releases and end dates.

| Release | LTS Start Date | Maintenance End Date | LTS End Date |
| :----------------------------------------------------------------------------------------------------------------------------: | :------------: | :------------------: | :----------: |
| [2022-01-18](https://github.com/Azure/azure-iot-sdk-csharp/releases/tag/lts_2021-3-18-patch4) <sub>patch 4 of 2021-03-18</sub> | 2022-01-18 | 2022-03-18 | 2024-03-17 |
| [2021-10-19](https://github.com/Azure/azure-iot-sdk-csharp/releases/tag/lts_2021-3-18-patch3) <sub>patch 3 of 2021-03-18</sub> | 2021-10-19 | 2022-03-18 | 2024-03-17 |
| [2021-8-12](https://github.com/Azure/azure-iot-sdk-csharp/releases/tag/lts_2021-3-18_patch2) <sub>patch 2 of 2021-03-18</sub> | 2021-08-12 | 2022-03-18 | 2024-03-17 |
| [2021-8-10](https://github.com/Azure/azure-iot-sdk-csharp/releases/tag/lts_2020-8-19_patch2) <sub>patch 2 of 2021-08-19</sub> | 2021-08-10 | 2021-08-19 | 2023-08-19 |
| [2021-8-10](https://github.com/Azure/azure-iot-sdk-csharp/releases/tag/lts_2020-8-19_patch2) <sub>patch 2 of 2020-08-19</sub> | 2020-08-10 | 2021-08-19 | 2023-08-19 |
| [2021-6-23](https://github.com/Azure/azure-iot-sdk-csharp/releases/tag/lts_2021-3-18_patch1) <sub>patch 1 of 2021-03-18 | 2020-06-23 | 2022-03-18 | 2024-03-17 |
| [2021-3-18](https://github.com/Azure/azure-iot-sdk-csharp/releases/tag/lts_2021-3-18) | 2020-03-18 | 2022-03-18 | 2024-03-17 |
| [2020-9-23](https://github.com/Azure/azure-iot-sdk-csharp/releases/tag/lts_2020-8-19_patch1) <sub>patch 1 of 2021-08-19</sub> | 2020-09-23 | 2021-08-19 | 2023-08-19 |
| [2020-9-23](https://github.com/Azure/azure-iot-sdk-csharp/releases/tag/lts_2020-8-19_patch1) <sub>patch 1 of 2020-08-19</sub> | 2020-09-23 | 2021-08-19 | 2023-08-19 |
| [2020-8-19](https://github.com/Azure/azure-iot-sdk-csharp/releases/tag/lts_2020-8-19) | 2020-08-19 | 2021-08-19 | 2023-08-19 |
| [2020-4-3](https://github.com/Azure/azure-iot-sdk-csharp/releases/tag/lts_2020-1-31_patch1) <sub>patch 1 of 2020-01-31</sub> | 2020-04-03 | 2021-01-30 | 2023-01-30 |
| [2020-1-31](https://github.com/Azure/azure-iot-sdk-csharp/releases/tag/lts_2020-1-31) | 2020-01-31 | 2021-01-30 | 2023-01-30 |
Expand Down