Skip to content

Commit

Permalink
fix issue #76 check the dev EUI at device creation (#100)
Browse files Browse the repository at this point in the history
* fix #88 (#92)

Co-authored-by: ben salim <[email protected]>

* fix #76 check the DevEui at device creation
  • Loading branch information
Sben65 authored Jan 25, 2022
1 parent 43fefeb commit 3ee24d4
Show file tree
Hide file tree
Showing 8 changed files with 738 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -199,14 +199,14 @@
if (response.IsSuccessStatusCode)
{
Snackbar.Add($"Device {Device.DeviceID} has been successfully created!", Severity.Success);
// Go back to the list of devices
NavManager.NavigateTo("devices");
}
else
{
Snackbar.Add($"Oh oh, something went wrong while creating device {Device.DeviceID}...", Severity.Error);
Snackbar.Add($"Oh oh, something went wrong while creating device {Device.DeviceID}... <br> {errorMsg}", Severity.Error);
}

// Go back to the list of devices
NavManager.NavigateTo("devices");

}

Expand Down
13 changes: 12 additions & 1 deletion src/AzureIoTHub.Portal/Server/Controllers/DevicesController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ namespace AzureIoTHub.Portal.Server.Controllers
using System.Threading.Tasks;
using Azure.Data.Tables;
using AzureIoTHub.Portal.Server.Factories;
using AzureIoTHub.Portal.Server.Helpers;
using AzureIoTHub.Portal.Server.Managers;
using AzureIoTHub.Portal.Server.Mappers;
using AzureIoTHub.Portal.Server.Services;
Expand Down Expand Up @@ -85,6 +86,11 @@ public async Task<IActionResult> CreateDeviceAsync(DeviceDetails device)
{
try
{
if (!Eui.TryParse(device.DeviceID, out ulong deviceIdConvert))
{
throw new InvalidOperationException("the device id is in the wrong format.");
}

// Create a new Twin from the form's fields.
var newTwin = new Twin()
{
Expand All @@ -102,7 +108,12 @@ public async Task<IActionResult> CreateDeviceAsync(DeviceDetails device)
catch (DeviceAlreadyExistsException e)
{
this.logger.LogError($"{device.DeviceID} - Create device failed", e);
return this.BadRequest();
return this.BadRequest(e.Message);
}
catch (InvalidOperationException e)
{
this.logger?.LogError("{a0} - Create device failed \n {a1}", device.DeviceID, e.Message);
return this.BadRequest(e.Message);
}
}

Expand Down
5 changes: 5 additions & 0 deletions src/AzureIoTHub.Portal/Server/Helpers/DeviceHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -162,5 +162,10 @@ public static List<GatewayModule> RetrieveModuleList(Twin twin, int moduleCount)
return list;
}
}

public static bool IsValidDevEUI(ulong value)
{
return value is not 0 and not 0xffff_ffff_ffff_ffff;
}
}
}
166 changes: 166 additions & 0 deletions src/AzureIoTHub.Portal/Server/Helpers/Eui.g.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
#nullable enable

namespace AzureIoTHub.Portal.Server.Helpers
{
using System;
using System.Buffers.Binary;

readonly partial record struct DevEui : IFormattable
{
public const int Size = sizeof(ulong);

readonly ulong value;

public DevEui(ulong value) => this.value = value;

public ulong AsUInt64 => this.value;

public override string ToString() => ToString(null, null);

public static DevEui Read(ReadOnlySpan<byte> buffer) =>
new(BinaryPrimitives.ReadUInt64LittleEndian(buffer));

public static DevEui Read(ref ReadOnlySpan<byte> buffer)
{
var result = Read(buffer);
buffer = buffer[Size..];
return result;
}

public Span<byte> Write(Span<byte> buffer)
{
BinaryPrimitives.WriteUInt64LittleEndian(buffer, this.value);
return buffer[Size..];
}

public static DevEui Parse(ReadOnlySpan<char> input) =>
TryParse(input, out var result) ? result : throw new FormatException();

public static bool TryParse(ReadOnlySpan<char> input, out DevEui result)
{
if (Eui.TryParse(input, out var raw))
{
result = new DevEui(raw);
return true;
}
else
{
result = default;
return false;
}
}

public string ToString(string? format, IFormatProvider? formatProvider) => Eui.Format(this.value, format);

public string ToHex() => ToHex(null);
public string ToHex(LetterCase letterCase) => ToHex(null, letterCase);
public string ToHex(char? separator) => ToHex(separator, LetterCase.Upper);
public string ToHex(char? separator, LetterCase letterCase) => Eui.ToHex(this.value, separator, letterCase);
}

readonly partial record struct JoinEui : IFormattable
{
public const int Size = sizeof(ulong);

readonly ulong value;

public JoinEui(ulong value) => this.value = value;

public ulong AsUInt64 => this.value;

public override string ToString() => ToString(null, null);

public static JoinEui Read(ReadOnlySpan<byte> buffer) =>
new(BinaryPrimitives.ReadUInt64LittleEndian(buffer));

public static JoinEui Read(ref ReadOnlySpan<byte> buffer)
{
var result = Read(buffer);
buffer = buffer[Size..];
return result;
}

public Span<byte> Write(Span<byte> buffer)
{
BinaryPrimitives.WriteUInt64LittleEndian(buffer, this.value);
return buffer[Size..];
}

public static JoinEui Parse(ReadOnlySpan<char> input) =>
TryParse(input, out var result) ? result : throw new FormatException();

public static bool TryParse(ReadOnlySpan<char> input, out JoinEui result)
{
if (Eui.TryParse(input, out var raw))
{
result = new JoinEui(raw);
return true;
}
else
{
result = default;
return false;
}
}

public string ToString(string? format, IFormatProvider? formatProvider) => Eui.Format(this.value, format);

public string ToHex() => ToHex(null);
public string ToHex(LetterCase letterCase) => ToHex(null, letterCase);
public string ToHex(char? separator) => ToHex(separator, LetterCase.Upper);
public string ToHex(char? separator, LetterCase letterCase) => Eui.ToHex(this.value, separator, letterCase);
}

readonly partial record struct StationEui : IFormattable
{
public const int Size = sizeof(ulong);

readonly ulong value;

public StationEui(ulong value) => this.value = value;

public ulong AsUInt64 => this.value;

public override string ToString() => ToString(null, null);

public static StationEui Read(ReadOnlySpan<byte> buffer) =>
new(BinaryPrimitives.ReadUInt64LittleEndian(buffer));

public static StationEui Read(ref ReadOnlySpan<byte> buffer)
{
var result = Read(buffer);
buffer = buffer[Size..];
return result;
}

public Span<byte> Write(Span<byte> buffer)
{
BinaryPrimitives.WriteUInt64LittleEndian(buffer, this.value);
return buffer[Size..];
}

public static StationEui Parse(ReadOnlySpan<char> input) =>
TryParse(input, out var result) ? result : throw new FormatException();

public static bool TryParse(ReadOnlySpan<char> input, out StationEui result)
{
if (Eui.TryParse(input, out var raw))
{
result = new StationEui(raw);
return true;
}
else
{
result = default;
return false;
}
}

public string ToString(string? format, IFormatProvider? formatProvider) => Eui.Format(this.value, format);

public string ToHex() => ToHex(null);
public string ToHex(LetterCase letterCase) => ToHex(null, letterCase);
public string ToHex(char? separator) => ToHex(separator, LetterCase.Upper);
public string ToHex(char? separator, LetterCase letterCase) => Eui.ToHex(this.value, separator, letterCase);
}
}
126 changes: 126 additions & 0 deletions src/AzureIoTHub.Portal/Server/Helpers/EuiHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// Copyright (c) CGI France. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

#nullable enable

namespace AzureIoTHub.Portal.Server.Helpers
{
using System;
using System.Buffers.Binary;

[Flags]
public enum EuiParseOptions
{
None = 0,

/// <summary>
/// Forbids a syntactically correct DevEUI from being parsed for which validation like
/// <see cref="DevEui.IsValid"/> will return <c>false</c>.
/// </summary>
ForbidInvalid = 1,
}

/// <summary>
/// Global end-device ID in IEEE EUI-64 (64-bit Extended Unique Identifier) address space
/// that uniquely identifies the end-device.
/// </summary>
/// <remarks>
/// <para>
/// For OTAA devices, the DevEUI MUST be stored in the end-device before the Join procedure
/// is executed. ABP devices do not need the DevEUI to be stored in the device itself, but
/// it is recommended to do so.</para>
/// <para>
/// EUI are 8 bytes multi-octet fields and are transmitted as little endian.</para>
/// </remarks>
public partial record struct DevEui
{
public bool IsValid => Eui.IsValid(this.value);

public static bool TryParse(ReadOnlySpan<char> input, EuiParseOptions options, out DevEui result)
{
if (TryParse(input, out var candidate)
&& ((options & EuiParseOptions.ForbidInvalid) == EuiParseOptions.None || candidate.IsValid))
{
result = candidate;
return true;
}

result = default;
return false;
}
}

/// <summary>
/// Global application ID in IEEE EUI-64 (64-bit Extended Unique Identifier) address space
/// that uniquely identifies the Join Server that is able to assist in the processing of
/// the Join procedure and the session keys derivation.
/// </summary>
/// <remarks>
/// <para>
/// For OTAA devices, the JoinEUI MUST be stored in the end-device before the Join procedure
/// is executed. The JoinEUI is not required for ABP only end-devices.</para>
/// <para>
/// EUI are 8 bytes multi-octet fields and are transmitted as little endian.</para>
/// </remarks>
public partial record struct JoinEui { }

/// <summary>
/// ID in IEEE EUI-64 (64-bit Extended Unique Identifier) address space that uniquely identifies
/// a station.
/// </summary>
/// <remarks>
/// EUI are 8 bytes multi-octet fields and are transmitted as little endian.
/// </remarks>
public partial record struct StationEui
{
public bool IsValid => Eui.IsValid(this.value);
}

internal static class Eui
{
public static string Format(ulong value, string? format)
{
return format switch
{
#pragma warning disable format
null or "G" or "N" => ToHex(value, LetterCase.Upper),
"d" => ToHex(value, '-', LetterCase.Lower),
"D" => ToHex(value, '-', LetterCase.Upper),
"E" => ToHex(value, ':', LetterCase.Upper),
"e" => ToHex(value, ':', LetterCase.Lower),
"n" or "g" => ToHex(value, LetterCase.Lower),
"I" => Id6.Format(value, Id6.FormatOptions.FixedWidth),
"i" => Id6.Format(value, Id6.FormatOptions.FixedWidth | Id6.FormatOptions.Lowercase),
#pragma warning restore format
_ => throw new FormatException(@"Format string can only be null, ""G"", ""g"", ""D"", ""d"", ""I"", ""i"", ""N"", ""n"", ""E"" or ""e"".")
};
}

public static string ToHex(ulong value) => ToHex(value, null);

public static string ToHex(ulong value, LetterCase letterCase) => ToHex(value, null, letterCase);

public static string ToHex(ulong value, char? separator) => ToHex(value, separator, LetterCase.Upper);

public static string ToHex(ulong value, char? separator, LetterCase letterCase)
{
Span<byte> bytes = stackalloc byte[sizeof(ulong)];
BinaryPrimitives.WriteUInt64BigEndian(bytes, value);
var length = separator is null ? bytes.Length * 2 : (bytes.Length * 3) - 1;
var chars = length <= 128 ? stackalloc char[length] : new char[length];
Hexadecimal.Write(bytes, chars, separator, letterCase);
return new string(chars);
}

public static bool TryParse(ReadOnlySpan<char> input, out ulong result) =>
input.Length switch
{
23 => Hexadecimal.TryParse(input, out result, '-') // e.g. "88:99:AA:BB:CC:DD:EE:FF"
|| Hexadecimal.TryParse(input, out result, ':'), // e.g. "88-99-AA-BB-CC-DD-EE-FF"
16 => Hexadecimal.TryParse(input, out result), // e.g. "8899AABBCCDDEEFF"
_ => Id6.TryParse(input, out result) // e.g. "8899:AABB:CCDD:EEFF"
};

public static bool IsValid(ulong value) => value is not 0 and not 0xffff_ffff_ffff_ffff;
}
}
Loading

0 comments on commit 3ee24d4

Please sign in to comment.