Skip to content

Commit

Permalink
feat (FileUploader.cs): BeginUpload() now employs fail-safe settings …
Browse files Browse the repository at this point in the history
…for certain android models that are known to be problematic (like the Samsung A8) if the calling environment has passed in the default ('null') settings
  • Loading branch information
ksidirop-laerdal committed Oct 18, 2024
1 parent 304492e commit 7ee056e
Show file tree
Hide file tree
Showing 21 changed files with 222 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,13 @@ public void BeginUpload_ShouldThrowArgumentException_GivenInvalidRemoteFilePath(
using var eventsMonitor = fileUploader.Monitor();

// Act
var work = new Func<EFileUploaderVerdict>(() => fileUploader.BeginUpload(remoteFilePath, mockedFileData));
var work = new Func<EFileUploaderVerdict>(() => fileUploader.BeginUpload(
hostDeviceModel: "foobar",
hostDeviceManufacturer: "acme corp.",

data: mockedFileData,
remoteFilePath: remoteFilePath
));

// Assert
work.Should().ThrowExactly<ArgumentException>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ public async Task MultipleFilesUploadAsync_ShouldCompleteSuccessfully_GivenNoFil
using var eventsMonitor = fileUploader.Monitor();

// Act
var work = new Func<Task>(async () => await fileUploader.UploadAsync(new Dictionary<string, IEnumerable<byte[]>>(0)));
var work = new Func<Task>(async () => await fileUploader.UploadAsync(
hostDeviceModel: "foobar",
hostDeviceManufacturer: "acme corp.",
remoteFilePathsAndTheirData: new Dictionary<string, IEnumerable<byte[]>>(0)
));

// Assert
await work.Should().CompleteWithinAsync(500.Milliseconds());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ public async Task MultipleFilesUploadAsync_ShouldCompleteSuccessfully_GivenVario

// Act
var work = new Func<Task<IEnumerable<string>>>(async () => await fileUploader.UploadAsync(
hostDeviceModel: "foobar",
hostDeviceManufacturer: "acme corp.",

maxTriesPerUpload: 4,
remoteFilePathsAndTheirData: remoteFilePathsToTest
));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@ public async Task MultipleFilesUploadAsync_ShouldThrowArgumentException_GivenPat
using var eventsMonitor = fileUploader.Monitor();

// Act
var work = new Func<Task>(async () => await fileUploader.UploadAsync(remoteFilePaths.ToDictionary(x => x, x => new byte[] { 1 })));
var work = new Func<Task>(async () => await fileUploader.UploadAsync(
hostDeviceModel: "foobar",
hostDeviceManufacturer: "acme corp.",
remoteFilePathsAndTheirData: remoteFilePaths.ToDictionary(x => x, _ => new byte[] { 1 })
));

// Assert
await work.Should().ThrowAsync<ArgumentException>().WithTimeoutInMs(500); //dont use throwexactlyasync<> here
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,12 @@ public async Task MultipleFilesUploadAsync_ShouldThrowNullArgumentException_Give
using var eventsMonitor = fileUploader.Monitor();

// Act
var work = new Func<Task>(async () => await fileUploader.UploadAsync<byte[]>(remoteFilePathsAndTheirData: null));
var work = new Func<Task>(async () => await fileUploader.UploadAsync<byte[]>(
hostDeviceModel: "foobar",
hostDeviceManufacturer: "acme corp.",

remoteFilePathsAndTheirData: null
));

// Assert
await work.Should().ThrowExactlyAsync<ArgumentNullException>().WithTimeoutInMs(500);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ public async Task SingleFileUploadAsync_ShouldCompleteSuccessfullyByFallingBackT

// Act
var work = new Func<Task>(() => fileUploader.UploadAsync(
hostDeviceModel: "foobar",
hostDeviceManufacturer: "acme corp.",

data: stream,
maxTriesCount: maxTriesCount,
remoteFilePath: remoteFilePath
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ int sleepTimeBetweenRetriesInMs

// Act
var work = new Func<Task>(() => fileUploader.UploadAsync(
hostDeviceModel: "foobar",
hostDeviceManufacturer: "acme corp.",

data: new byte[] { 1, 2, 3 },
maxTriesCount: maxTriesCount,
remoteFilePath: remoteFilePath,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ public async Task SingleFileUploadAsync_ShouldCompleteSuccessfully_GivenVariousS

// Act
var work = new Func<Task>(() => fileUploader.UploadAsync(
hostDeviceModel: "foobar",
hostDeviceManufacturer: "acme corp.",

data: stream,
maxTriesCount: maxTriesCount,
remoteFilePath: remoteFilePath,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ public async Task SingleFileUploadAsync_ShouldConditionallyAutodisposeGivenStrea

// Act
var work = new Func<Task>(() => fileUploader.UploadAsync(
hostDeviceModel: "foobar",
hostDeviceManufacturer: "acme corp.",

data: streamProvider,
maxTriesCount: 1,
remoteFilePath: remoteFilePath,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ public async Task SingleFileUploadAsync_ShouldThrowAllUploadAttemptsFailedExcept

// Act
var work = new Func<Task>(() => fileUploader.UploadAsync(
hostDeviceModel: "foobar",
hostDeviceManufacturer: "acme corp.",

data: mockedFileData,
maxTriesCount: maxTriesCount,
remoteFilePath: remoteFilePath
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ public async Task SingleFileUploadAsync_ShouldThrowAllUploadAttemptsFailedExcept

// Act
var work = new Func<Task>(() => fileUploader.UploadAsync(
hostDeviceModel: "foobar",
hostDeviceManufacturer: "acme corp.",

data: mockedFileData, //doesnt really matter we just want to ensure that the method fails early and doesnt retry
maxTriesCount: maxTriesCount,
remoteFilePath: remoteFilePath,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,13 @@ public async Task SingleFileUploadAsync_ShouldThrowArgumentException_GivenEmptyR
using var eventsMonitor = fileUploader.Monitor();

// Act
var work = new Func<Task>(() => fileUploader.UploadAsync(mockedFileData, remoteFilePath));
var work = new Func<Task>(() => fileUploader.UploadAsync(
hostDeviceModel: "foobar",
hostDeviceManufacturer: "acme corp.",

data: mockedFileData,
remoteFilePath: remoteFilePath
));

// Assert
await work.Should().ThrowExactlyAsync<ArgumentException>().WithTimeoutInMs(500);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ int maxTriesCount

// Act
var work = new Func<Task>(() => fileUploader.UploadAsync(
hostDeviceModel: "foobar",
hostDeviceManufacturer: "acme corp.",

data: mockedFileData, //doesnt really matter we just want to ensure that the method fails early and doesnt retry
maxTriesCount: maxTriesCount,
remoteFilePath: remoteFilePath,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,14 @@ public async Task SingleFileUploadAsync_ShouldThrowUnauthorizedErrorException_Gi
var fileUploader = new McuMgr.FileUploader.FileUploader(mockedNativeFileUploaderProxy);

// Act
var work = new Func<Task>(() => fileUploader.UploadAsync(data: new byte[] { 1 }, remoteFilePath: "/path/to/file.bin", maxTriesCount: 2));
var work = new Func<Task>(() => fileUploader.UploadAsync(
hostDeviceModel: "foobar",
hostDeviceManufacturer: "acme corp.",

data: new byte[] { 1 },
maxTriesCount: 2,
remoteFilePath: "/path/to/file.bin"
));

// Assert
(await work.Should().ThrowExactlyAsync<AllUploadAttemptsFailedException>()).WithInnerExceptionExactly<UploadUnauthorizedException>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,13 @@ public async Task SingleFileUploadAsync_ShouldThrowUploadCancelledException_Give

fileUploader.Cancel(reason: cancellationReason);
});
var work = new Func<Task>(() => fileUploader.UploadAsync(mockedFileData, remoteFilePath));
var work = new Func<Task>(() => fileUploader.UploadAsync(
hostDeviceModel: "foobar",
hostDeviceManufacturer: "acme corp.",

data: mockedFileData,
remoteFilePath: remoteFilePath
));

// Assert
await work.Should().ThrowExactlyAsync<UploadCancelledException>().WithTimeoutInMs((int)5.Seconds().TotalMilliseconds);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,13 @@ public async Task SingleFileUploadAsync_ShouldThrowUploadInternalErrorException_
var fileUploader = new McuMgr.FileUploader.FileUploader(mockedNativeFileUploaderProxy);

// Act
var work = new Func<Task>(() => fileUploader.UploadAsync(data: new byte[] { 1 }, remoteFilePath: "/path/to/file.bin"));
var work = new Func<Task>(() => fileUploader.UploadAsync(
hostDeviceModel: "foobar",
hostDeviceManufacturer: "acme corp.",

data: new byte[] { 1 },
remoteFilePath: "/path/to/file.bin"
));

// Assert
(await work.Should().ThrowExactlyAsync<UploadInternalErrorException>()).WithInnerExceptionExactly<Exception>("native symbols not loaded blah blah");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ public async Task SingleFileUploadAsync_ShouldThrowUploadTimeoutException_GivenT

// Act
var work = new Func<Task>(() => fileUploader.UploadAsync(
hostDeviceModel: "foobar",
hostDeviceManufacturer: "acme corp.",

data: new byte[] { 1 },
remoteFilePath: remoteFilePath,
timeoutForUploadInMs: 100
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,43 @@
using System.Collections.Generic;
using System.Linq;

namespace Laerdal.McuMgr.Common.Constants
{
public readonly struct AndroidTidbits
{
/// <summary>List of known problematic android-devices that have issues with BLE connection stability and have to use fail-safe ble settings to work.</summary><br/><br/>
/// Inspired by <a href="https://github.com/polarofficial/polar-ble-sdk/blob/master/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/common/ble/PhoneUtils.kt"/>
static public HashSet<(string Manufacturer, string DeviceModel)> KnownProblematicDevices { get; } = new (string Manufacturer, string DeviceModel)[]
{
("Motorola", "moto g20"),
("Motorola", "moto e20"),
("Motorola", "moto e30"),
("Motorola", "moto e32"),
("Motorola", "moto e40"),

("Nokia", "Nokia G21"),
("Nokia", "Nokia G11"),
("Nokia", "Nokia T20"),

("Realme", "RMX3261"), //C21Y
("Realme", "RMX3262"), //C21Y
("Realme", "RMX3265"), //C25Y
("Realme", "RMX3269"), //C25Y
("Realme", "RMP2105"), //Pad Mini
("Realme", "RMP2106"), //Pad Mini

("Infinix", "Infinix X675"), //Hot 11 2022

("HTC", "Wildfire E2 plus"),

("Micromax", "IN_2b"),
("Micromax", "IN_2c"),

("Samsung", "SM-X200"), //Galaxy Tab A8
}
.Select(x => (x.Manufacturer.Trim().ToLowerInvariant(), x.DeviceModel.Trim().ToLowerInvariant())) //vital
.ToHashSet();

/// <summary>
/// Failsafe settings for the BLE connection used in Androids to perform various operations: installing firmware, resetting the device, erasing firmwares, uploading files,
/// downloading files. These settings are enforced automagically when the ble connection turns out to be unstable and unreliable during the aforementioned operations.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;

namespace Laerdal.McuMgr.Common.Constants
{
public readonly struct AppleTidbits
{
// ReSharper disable once CollectionNeverUpdated.Global
/// <summary>List of known problematic android-devices that have issues with BLE connection stability and have to use fail-safe ble settings to work.</summary><br/><br/>
/// Inspired by <a href="https://github.com/polarofficial/polar-ble-sdk/blob/master/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/common/ble/PhoneUtils.kt"/>
static public HashSet<(string Manufacturer, string DeviceModel)> KnownProblematicDevices { get; } = new (string Manufacturer, string DeviceModel)[]
{
// ("Apple", "XYZ"), // just a placeholder - no apple devices are known to have issues with BLE connection stability at the time of this writing
}
.Select(x => (x.Manufacturer.Trim().ToLowerInvariant(), x.DeviceModel.Trim().ToLowerInvariant()))
.ToHashSet();

/// <summary>
/// Failsafe settings for the BLE connection used in Apple platforms (iOS / MacCatalyst) to perform various operations: installing firmware, resetting the device,
/// erasing firmwares, uploading files, downloading files. These settings are enforced automagically when the ble connection turns out to be unstable and unreliable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ public interface IFileUploaderCommandable
/// Allowed types for TData are 'Stream', 'Func&lt;Stream&gt;', 'Func&lt;Task&lt;Stream&gt;&gt;', 'Func&lt;ValueTask&lt;Stream&gt;&gt;', 'byte[]' and 'IEnumerable&lt;byte&gt;'.
/// </remarks>
/// <param name="remoteFilePathsAndTheirData">The files to upload.</param>
/// <param name="hostDeviceManufacturer">The manufacturer of the host-device</param>
/// <param name="hostDeviceModel">The device-model of the host-device</param>
/// <param name="sleepTimeBetweenRetriesInMs">The time to sleep between each retry after a failed try.</param>
/// <param name="timeoutPerUploadInMs">The amount of time to wait for each upload to complete before bailing out.</param>
/// <param name="maxTriesPerUpload">Maximum amount of tries per upload before bailing out. In case of errors the mechanism will try "maxTriesPerUpload" before bailing out.</param>
Expand All @@ -41,6 +43,8 @@ public interface IFileUploaderCommandable
/// <param name="memoryAlignment">(Android only) Set the selected memory alignment. Defaults to 4 to match Nordic devices.</param>
Task<IEnumerable<string>> UploadAsync<TData>(
IDictionary<string, TData> remoteFilePathsAndTheirData,
string hostDeviceManufacturer,
string hostDeviceModel,
int sleepTimeBetweenRetriesInMs = 100,
int timeoutPerUploadInMs = -1,
int maxTriesPerUpload = 10,
Expand All @@ -64,6 +68,8 @@ Task<IEnumerable<string>> UploadAsync<TData>(
/// </remarks>
/// <param name="localData">The local data to upload.</param>
/// <param name="remoteFilePath">The remote file-path to upload the data to.</param>
/// <param name="hostDeviceManufacturer">The manufacturer of the host-device</param>
/// <param name="hostDeviceModel">The device-model of the host-device</param>
/// <param name="timeoutForUploadInMs">The amount of time to wait for the upload to complete before bailing out.</param>
/// <param name="maxTriesCount">The maximum amount of tries before bailing out with <see cref="AllUploadAttemptsFailedException"/>.</param>
/// <param name="sleepTimeBetweenRetriesInMs">The time to sleep between each retry after a failed try.</param>
Expand All @@ -88,6 +94,8 @@ Task<IEnumerable<string>> UploadAsync<TData>(
Task UploadAsync<TData>(
TData localData,
string remoteFilePath,
string hostDeviceManufacturer,
string hostDeviceModel,
int timeoutForUploadInMs = -1,
int maxTriesCount = 10,
int sleepTimeBetweenRetriesInMs = 1_000,
Expand All @@ -105,6 +113,8 @@ Task UploadAsync<TData>(
/// </summary>
/// <param name="remoteFilePath">The remote file-path to upload the data to.</param>
/// <param name="data">The file-data.</param>
/// <param name="hostDeviceManufacturer">The manufacturer of the host-device</param>
/// <param name="hostDeviceModel">The device-model of the host-device</param>
/// <param name="pipelineDepth">(iOS only) If set to a value larger than 1, this enables SMP Pipelining, wherein multiple packets of data ('chunks') are sent at
/// once before awaiting a response, which can lead to a big increase in transfer speed if the receiving hardware supports this feature.</param>
/// <param name="byteAlignment">(iOS only) When PipelineLength is larger than 1 (SMP Pipelining Enabled) it's necessary to set this in order for the stack
Expand All @@ -124,6 +134,8 @@ Task UploadAsync<TData>(
EFileUploaderVerdict BeginUpload(
string remoteFilePath,
byte[] data,
string hostDeviceManufacturer,
string hostDeviceModel,
int? pipelineDepth = null,
int? byteAlignment = null,
int? initialMtuSize = null,
Expand Down
Loading

0 comments on commit 7ee056e

Please sign in to comment.