diff --git a/src/Smdn.Net.EchonetLite.RouteB/Smdn.Net.EchonetLite.RouteB.Credentials/IRouteBCredential.cs b/src/Smdn.Net.EchonetLite.RouteB/Smdn.Net.EchonetLite.RouteB.Credentials/IRouteBCredential.cs new file mode 100644 index 0000000..757503f --- /dev/null +++ b/src/Smdn.Net.EchonetLite.RouteB/Smdn.Net.EchonetLite.RouteB.Credentials/IRouteBCredential.cs @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: 2023 smdn +// SPDX-License-Identifier: MIT +using System; +using System.Buffers; + +namespace Smdn.Net.EchonetLite.RouteB.Credentials; + +/// +/// Provides a mechanism for abstracting credentials used for the route B authentication. +/// +public interface IRouteBCredential : IDisposable { + void WriteIdTo(IBufferWriter buffer); + void WritePasswordTo(IBufferWriter buffer); +} diff --git a/src/Smdn.Net.EchonetLite.RouteB/Smdn.Net.EchonetLite.RouteB.Credentials/IRouteBCredentialIdentity.cs b/src/Smdn.Net.EchonetLite.RouteB/Smdn.Net.EchonetLite.RouteB.Credentials/IRouteBCredentialIdentity.cs new file mode 100644 index 0000000..22acb97 --- /dev/null +++ b/src/Smdn.Net.EchonetLite.RouteB/Smdn.Net.EchonetLite.RouteB.Credentials/IRouteBCredentialIdentity.cs @@ -0,0 +1,8 @@ +// SPDX-FileCopyrightText: 2023 smdn +// SPDX-License-Identifier: MIT +namespace Smdn.Net.EchonetLite.RouteB.Credentials; + +/// +/// Provides a mechanism for abstracting identities corresponding to credentials used for the route B authentication. +/// +public interface IRouteBCredentialIdentity { } diff --git a/src/Smdn.Net.EchonetLite.RouteB/Smdn.Net.EchonetLite.RouteB.Credentials/IRouteBCredentialProvider.cs b/src/Smdn.Net.EchonetLite.RouteB/Smdn.Net.EchonetLite.RouteB.Credentials/IRouteBCredentialProvider.cs new file mode 100644 index 0000000..6ed9ab7 --- /dev/null +++ b/src/Smdn.Net.EchonetLite.RouteB/Smdn.Net.EchonetLite.RouteB.Credentials/IRouteBCredentialProvider.cs @@ -0,0 +1,11 @@ +// SPDX-FileCopyrightText: 2023 smdn +// SPDX-License-Identifier: MIT +namespace Smdn.Net.EchonetLite.RouteB.Credentials; + +/// +/// Provides a mechanism to select the corresponding to the and +/// provide it to the route B authentication. +/// +public interface IRouteBCredentialProvider { + IRouteBCredential GetCredential(IRouteBCredentialIdentity identity); +} diff --git a/src/Smdn.Net.EchonetLite.RouteB/Smdn.Net.EchonetLite.RouteB.Credentials/RouteBCredentialServiceCollectionExtensions.cs b/src/Smdn.Net.EchonetLite.RouteB/Smdn.Net.EchonetLite.RouteB.Credentials/RouteBCredentialServiceCollectionExtensions.cs new file mode 100644 index 0000000..1440b9e --- /dev/null +++ b/src/Smdn.Net.EchonetLite.RouteB/Smdn.Net.EchonetLite.RouteB.Credentials/RouteBCredentialServiceCollectionExtensions.cs @@ -0,0 +1,56 @@ +// SPDX-FileCopyrightText: 2023 smdn +// SPDX-License-Identifier: MIT +using System; + +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; + +namespace Smdn.Net.EchonetLite.RouteB.Credentials; + +public static class RouteBCredentialServiceCollectionExtensions { + /// + /// Adds to . + /// This overload creates that holds route-B ID and password in plaintext. + /// + /// The to add services to. + /// A plaintext route-B ID used for the route B authentication. + /// A plaintext password used for the route B authentication. + public static IServiceCollection AddRouteBCredential( + this IServiceCollection services, + string id, + string password + ) + => AddRouteBCredential( + services: services ?? throw new ArgumentNullException(nameof(services)), +#pragma warning disable CA2000 + credentialProvider: new SingleIdentityPlainTextRouteBCredentialProvider( + id: id ?? throw new ArgumentNullException(nameof(id)), + password: password ?? throw new ArgumentNullException(nameof(password)) + ) +#pragma warning restore CA2000 + ); + + /// + /// Adds to . + /// + /// The to add services to. + /// A used for authentication to the route B for the smart meter. + public static IServiceCollection AddRouteBCredential( + this IServiceCollection services, + IRouteBCredentialProvider credentialProvider + ) + { +#pragma warning disable CA1510 + if (services is null) + throw new ArgumentNullException(nameof(services)); + if (credentialProvider is null) + throw new ArgumentNullException(nameof(credentialProvider)); +#pragma warning restore CA1510 + + services.TryAdd( + ServiceDescriptor.Singleton(typeof(IRouteBCredentialProvider), credentialProvider) + ); + + return services; + } +} diff --git a/src/Smdn.Net.EchonetLite.RouteB/Smdn.Net.EchonetLite.RouteB.Credentials/RouteBCredentials.cs b/src/Smdn.Net.EchonetLite.RouteB/Smdn.Net.EchonetLite.RouteB.Credentials/RouteBCredentials.cs new file mode 100644 index 0000000..2c68280 --- /dev/null +++ b/src/Smdn.Net.EchonetLite.RouteB/Smdn.Net.EchonetLite.RouteB.Credentials/RouteBCredentials.cs @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2023 smdn +// SPDX-License-Identifier: MIT +namespace Smdn.Net.EchonetLite.RouteB.Credentials; + +public static class RouteBCredentials { + /// + /// HEMS-スマートメーターBルート(低圧電力メーター)運用ガイドライン[第4.0版]7.Bルート認証IDの定義 + /// + public const int AuthenticationIdLength = 32; + + /// + /// HEMS-スマートメーターBルート(低圧電力メーター)運用ガイドライン[第4.0版]7.Bルート認証IDの定義 + /// + public const int PasswordLength = 12; +} diff --git a/src/Smdn.Net.EchonetLite.RouteB/Smdn.Net.EchonetLite.RouteB.Credentials/SingleIdentityPlainTextRouteBCredentialProvider.cs b/src/Smdn.Net.EchonetLite.RouteB/Smdn.Net.EchonetLite.RouteB.Credentials/SingleIdentityPlainTextRouteBCredentialProvider.cs new file mode 100644 index 0000000..90934fc --- /dev/null +++ b/src/Smdn.Net.EchonetLite.RouteB/Smdn.Net.EchonetLite.RouteB.Credentials/SingleIdentityPlainTextRouteBCredentialProvider.cs @@ -0,0 +1,45 @@ +// SPDX-FileCopyrightText: 2023 smdn +// SPDX-License-Identifier: MIT +using System; +using System.Buffers; +using System.Text; + +namespace Smdn.Net.EchonetLite.RouteB.Credentials; + +internal sealed class SingleIdentityPlainTextRouteBCredentialProvider : IRouteBCredentialProvider, IRouteBCredential { + private readonly string id; + private readonly string password; + +#pragma warning disable IDE0290 + public SingleIdentityPlainTextRouteBCredentialProvider(string id, string password) +#pragma warning restore IDE0290 + { + this.id = id; + this.password = password; + } + + IRouteBCredential IRouteBCredentialProvider.GetCredential(IRouteBCredentialIdentity identity) => this; + + void IDisposable.Dispose() { /* nothing to do */ } + + void IRouteBCredential.WriteIdTo(IBufferWriter buffer) + => Write(id, buffer); + + void IRouteBCredential.WritePasswordTo(IBufferWriter buffer) + => Write(password, buffer); + + private static void Write(string str, IBufferWriter buffer) + { +#pragma warning disable CA1510 + if (buffer is null) + throw new ArgumentNullException(nameof(buffer)); +#pragma warning restore CA1510 + + var bytesWritten = Encoding.ASCII.GetBytes( + str, + buffer.GetSpan(Encoding.ASCII.GetByteCount(str)) + ); + + buffer.Advance(bytesWritten); + } +} diff --git a/src/Smdn.Net.EchonetLite.RouteB/Smdn.Net.EchonetLite.RouteB.Transport/IRouteBEchonetLiteHandlerBuilder.cs b/src/Smdn.Net.EchonetLite.RouteB/Smdn.Net.EchonetLite.RouteB.Transport/IRouteBEchonetLiteHandlerBuilder.cs new file mode 100644 index 0000000..c7860da --- /dev/null +++ b/src/Smdn.Net.EchonetLite.RouteB/Smdn.Net.EchonetLite.RouteB.Transport/IRouteBEchonetLiteHandlerBuilder.cs @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2023 smdn +// SPDX-License-Identifier: MIT +using Microsoft.Extensions.DependencyInjection; + +namespace Smdn.Net.EchonetLite.RouteB.Transport; + +/// +/// An interface for configuring providers. +/// +public interface IRouteBEchonetLiteHandlerBuilder { + /// + /// Gets the where services are configured. + /// + IServiceCollection Services { get; } +} diff --git a/src/Smdn.Net.EchonetLite.RouteB/Smdn.Net.EchonetLite.RouteB.Transport/IRouteBEchonetLiteHandlerFactory.cs b/src/Smdn.Net.EchonetLite.RouteB/Smdn.Net.EchonetLite.RouteB.Transport/IRouteBEchonetLiteHandlerFactory.cs new file mode 100644 index 0000000..ca8b34d --- /dev/null +++ b/src/Smdn.Net.EchonetLite.RouteB/Smdn.Net.EchonetLite.RouteB.Transport/IRouteBEchonetLiteHandlerFactory.cs @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: 2023 smdn +// SPDX-License-Identifier: MIT +using System.Threading; +using System.Threading.Tasks; + +namespace Smdn.Net.EchonetLite.RouteB.Transport; + +public interface IRouteBEchonetLiteHandlerFactory { + ValueTask CreateAsync( + CancellationToken cancellationToken + ); +} diff --git a/src/Smdn.Net.EchonetLite.RouteB/Smdn.Net.EchonetLite.RouteB.Transport/RouteBEchonetLiteHandler.cs b/src/Smdn.Net.EchonetLite.RouteB/Smdn.Net.EchonetLite.RouteB.Transport/RouteBEchonetLiteHandler.cs new file mode 100644 index 0000000..a61e529 --- /dev/null +++ b/src/Smdn.Net.EchonetLite.RouteB/Smdn.Net.EchonetLite.RouteB.Transport/RouteBEchonetLiteHandler.cs @@ -0,0 +1,70 @@ +// SPDX-FileCopyrightText: 2023 smdn +// SPDX-License-Identifier: MIT +using System; +using System.Net; +using System.Threading; +using System.Threading.Tasks; + +using Smdn.Net.EchonetLite.RouteB.Credentials; +using Smdn.Net.EchonetLite.Transport; + +namespace Smdn.Net.EchonetLite.RouteB.Transport; + +public abstract class RouteBEchonetLiteHandler : EchonetLiteHandler { + /// + /// Gets the represents the IPv6 address address of the peer device (i.e., smart electricity meter) to which this handler is currently connected. + /// + public abstract IPAddress? PeerAddress { get; } + + public ValueTask ConnectAsync( + IRouteBCredential credential, + CancellationToken cancellationToken = default + ) + { +#pragma warning disable CA1510 + if (credential is null) + throw new ArgumentNullException(nameof(credential)); +#pragma warning restore CA1510 + + ThrowIfDisposed(); + + return Core(); + + async ValueTask Core() + { + await ConnectAsyncCore( + credential: credential, + cancellationToken: cancellationToken + ).ConfigureAwait(false); + + StartReceiving(); + } + } + + protected abstract ValueTask ConnectAsyncCore( + IRouteBCredential credential, + CancellationToken cancellationToken + ); + + public ValueTask DisconnectAsync( + CancellationToken cancellationToken = default + ) + { + ThrowIfDisposed(); + + return Core(); + + async ValueTask Core() + { + await DisconnectAsyncCore( + cancellationToken: cancellationToken + ).ConfigureAwait(false); + + await StopReceivingAsync().ConfigureAwait(false); + } + } + + protected abstract ValueTask DisconnectAsyncCore( + CancellationToken cancellationToken + ); +} diff --git a/src/Smdn.Net.EchonetLite.RouteB/Smdn.Net.EchonetLite.RouteB.Transport/RouteBEchonetLiteHandlerBuilder.cs b/src/Smdn.Net.EchonetLite.RouteB/Smdn.Net.EchonetLite.RouteB.Transport/RouteBEchonetLiteHandlerBuilder.cs new file mode 100644 index 0000000..3434cee --- /dev/null +++ b/src/Smdn.Net.EchonetLite.RouteB/Smdn.Net.EchonetLite.RouteB.Transport/RouteBEchonetLiteHandlerBuilder.cs @@ -0,0 +1,11 @@ +// SPDX-FileCopyrightText: 2023 smdn +// SPDX-License-Identifier: MIT +using System; + +using Microsoft.Extensions.DependencyInjection; + +namespace Smdn.Net.EchonetLite.RouteB.Transport; + +internal sealed class RouteBEchonetLiteHandlerBuilder(IServiceCollection services) : IRouteBEchonetLiteHandlerBuilder { + public IServiceCollection Services { get; } = services ?? throw new ArgumentNullException(nameof(services)); +} diff --git a/src/Smdn.Net.EchonetLite.RouteB/Smdn.Net.EchonetLite.RouteB.Transport/RouteBEchonetLiteHandlerBuilderServiceCollectionExtensions.cs b/src/Smdn.Net.EchonetLite.RouteB/Smdn.Net.EchonetLite.RouteB.Transport/RouteBEchonetLiteHandlerBuilderServiceCollectionExtensions.cs new file mode 100644 index 0000000..12445ff --- /dev/null +++ b/src/Smdn.Net.EchonetLite.RouteB/Smdn.Net.EchonetLite.RouteB.Transport/RouteBEchonetLiteHandlerBuilderServiceCollectionExtensions.cs @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: 2023 smdn +// SPDX-License-Identifier: MIT +using System; + +using Microsoft.Extensions.DependencyInjection; + +namespace Smdn.Net.EchonetLite.RouteB.Transport; + +public static class RouteBEchonetLiteHandlerBuilderServiceCollectionExtensions { + /// + /// Adds to . + /// + /// The to add services to. + /// The to configure the added . + public static IServiceCollection AddRouteBHandler( + this IServiceCollection services, + Action configure + ) + { +#pragma warning disable CA1510 + if (services is null) + throw new ArgumentNullException(nameof(services)); + if (configure is null) + throw new ArgumentNullException(nameof(configure)); +#pragma warning restore CA1510 + + configure(new RouteBEchonetLiteHandlerBuilder(services)); + + return services; + } +} diff --git a/src/Smdn.Net.EchonetLite.RouteB/Smdn.Net.EchonetLite.RouteB.csproj b/src/Smdn.Net.EchonetLite.RouteB/Smdn.Net.EchonetLite.RouteB.csproj new file mode 100644 index 0000000..a4bba90 --- /dev/null +++ b/src/Smdn.Net.EchonetLite.RouteB/Smdn.Net.EchonetLite.RouteB.csproj @@ -0,0 +1,46 @@ + + + + + netstandard2.1;net6.0;net8.0 + 1.0.0 + + enable + true + true + CS1591;$(NoWarn) + + + + + smdn + Copyright © 2024 smdn. + + + + + + + Route-B;B-Route;smart-meter;smart-energy-meter;$(PackageTags) + $(GenerateNupkgReadmeFileDependsOnTargets);GenerateReadmeFileContent + + + + + + + + + + + + +