Skip to content

Commit

Permalink
import Smdn.Net.EchonetLite.RouteB
Browse files Browse the repository at this point in the history
  • Loading branch information
smdn committed Apr 4, 2024
1 parent 19bf542 commit 717e72f
Show file tree
Hide file tree
Showing 12 changed files with 334 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
// SPDX-License-Identifier: MIT
using System;
using System.Buffers;

namespace Smdn.Net.EchonetLite.RouteB.Credentials;

/// <summary>
/// Provides a mechanism for abstracting credentials used for the route B authentication.
/// </summary>
public interface IRouteBCredential : IDisposable {
void WriteIdTo(IBufferWriter<byte> buffer);
void WritePasswordTo(IBufferWriter<byte> buffer);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
// SPDX-License-Identifier: MIT
namespace Smdn.Net.EchonetLite.RouteB.Credentials;

/// <summary>
/// Provides a mechanism for abstracting identities corresponding to credentials used for the route B authentication.
/// </summary>
public interface IRouteBCredentialIdentity { }
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
// SPDX-License-Identifier: MIT
namespace Smdn.Net.EchonetLite.RouteB.Credentials;

/// <summary>
/// Provides a mechanism to select the <see cref="IRouteBCredential"/> corresponding to the <see cref="IRouteBCredentialIdentity"/> and
/// provide it to the route B authentication.
/// </summary>
public interface IRouteBCredentialProvider {
IRouteBCredential GetCredential(IRouteBCredentialIdentity identity);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
// 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 {
/// <summary>
/// Adds <see cref="IRouteBCredentialProvider"/> to <see cref="IServiceCollection"/>.
/// This overload creates <see cref="IRouteBCredentialProvider"/> that holds route-B ID and password in plaintext.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/> to add services to.</param>
/// <param name="id">A plaintext route-B ID used for the route B authentication.</param>
/// <param name="password">A plaintext password used for the route B authentication.</param>
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
);

/// <summary>
/// Adds <see cref="IRouteBCredentialProvider"/> to <see cref="IServiceCollection"/>.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/> to add services to.</param>
/// <param name="credentialProvider">A <see cref="IRouteBCredentialProvider"/> used for authentication to the route B for the smart meter.</param>
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;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
// SPDX-License-Identifier: MIT
namespace Smdn.Net.EchonetLite.RouteB.Credentials;

public static class RouteBCredentials {
/// <seealso href="https://www.meti.go.jp/committee/kenkyukai/shoujo/smart_house/pdf/009_s03_00.pdf">
/// HEMS-スマートメーターBルート(低圧電力メーター)運用ガイドライン[第4.0版]7.Bルート認証IDの定義
/// </seealso>
public const int AuthenticationIdLength = 32;

/// <seealso href="https://www.meti.go.jp/committee/kenkyukai/shoujo/smart_house/pdf/009_s03_00.pdf">
/// HEMS-スマートメーターBルート(低圧電力メーター)運用ガイドライン[第4.0版]7.Bルート認証IDの定義
/// </seealso>
public const int PasswordLength = 12;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
// 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<byte> buffer)
=> Write(id, buffer);

void IRouteBCredential.WritePasswordTo(IBufferWriter<byte> buffer)
=> Write(password, buffer);

private static void Write(string str, IBufferWriter<byte> 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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
// SPDX-License-Identifier: MIT
using Microsoft.Extensions.DependencyInjection;

namespace Smdn.Net.EchonetLite.RouteB.Transport;

/// <summary>
/// An interface for configuring <see cref="RouteBEchonetLiteHandler"/> providers.
/// </summary>
public interface IRouteBEchonetLiteHandlerBuilder {
/// <summary>
/// Gets the <see cref="IServiceCollection"/> where <see cref="RouteBEchonetLiteHandler"/> services are configured.
/// </summary>
IServiceCollection Services { get; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
// SPDX-License-Identifier: MIT
using System.Threading;
using System.Threading.Tasks;

namespace Smdn.Net.EchonetLite.RouteB.Transport;

public interface IRouteBEchonetLiteHandlerFactory {
ValueTask<RouteBEchonetLiteHandler> CreateAsync(
CancellationToken cancellationToken
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
// 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 {
/// <summary>
/// Gets the <see cref="IPAddress"/> represents the IPv6 address address of the peer device (i.e., smart electricity meter) to which this handler is currently connected.
/// </summary>
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
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
// 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));
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
// SPDX-License-Identifier: MIT
using System;

using Microsoft.Extensions.DependencyInjection;

namespace Smdn.Net.EchonetLite.RouteB.Transport;

public static class RouteBEchonetLiteHandlerBuilderServiceCollectionExtensions {
/// <summary>
/// Adds <see cref="IRouteBEchonetLiteHandlerBuilder"/> to <see cref="IServiceCollection"/>.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/> to add services to.</param>
/// <param name="configure">The <see cref="Action{IRouteBEchonetLiteHandlerBuilder}"/> to configure the added <see cref="IRouteBEchonetLiteHandlerBuilder"/>.</param>
public static IServiceCollection AddRouteBHandler(
this IServiceCollection services,
Action<IRouteBEchonetLiteHandlerBuilder> 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;
}
}
46 changes: 46 additions & 0 deletions src/Smdn.Net.EchonetLite.RouteB/Smdn.Net.EchonetLite.RouteB.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<!--
SPDX-FileCopyrightText: 2024 smdn <smdn@smdn.jp>
SPDX-License-Identifier: MIT
-->
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>netstandard2.1;net6.0;net8.0</TargetFrameworks>
<VersionPrefix>1.0.0</VersionPrefix>
<VersionSuffix></VersionSuffix>
<Nullable>enable</Nullable>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<NoWarn>CS1591;$(NoWarn)</NoWarn> <!-- CS1591: Missing XML comment for publicly visible type or member 'Type_or_Member' -->
<RootNamespace/> <!-- empty the root namespace so that the namespace is determined only by the directory name, for code style rule IDE0030 -->
</PropertyGroup>

<PropertyGroup Label="assembly attributes">
<Authors>smdn</Authors>
<Copyright>Copyright © 2024 smdn.</Copyright>
<Description>
<![CDATA[スマート電力量メータとの情報伝達手段である「Bルート」を介してECHONET Lite規格の通信を扱うための抽象クラス`RouteBEchonetLiteHandler`を提供します。 また、その際に使用される認証情報を扱うための抽象インターフェイス`IRouteBCredential`を提供します。]]>
</Description>
</PropertyGroup>

<PropertyGroup Label="package properties">
<PackageTags>Route-B;B-Route;smart-meter;smart-energy-meter;$(PackageTags)</PackageTags>
<GenerateNupkgReadmeFileDependsOnTargets>$(GenerateNupkgReadmeFileDependsOnTargets);GenerateReadmeFileContent</GenerateNupkgReadmeFileDependsOnTargets>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
<ProjectOrPackageReference ReferencePackageVersion="1.0.0" Include="..\Smdn.Net.EchonetLite.Transport\Smdn.Net.EchonetLite.Transport.csproj" />
</ItemGroup>

<Target Name="GenerateReadmeFileContent">
<PropertyGroup>
<PackageReadmeFileContent><![CDATA[# $(PackageId) $(PackageVersion)
$(Description)
## Contributing
This project welcomes contributions, feedbacks and suggestions. You can contribute to this project by submitting [Issues]($(RepositoryUrl)/issues/new/choose) or [Pull Requests]($(RepositoryUrl)/pulls/) on the [GitHub repository]($(RepositoryUrl)).
]]></PackageReadmeFileContent>
</PropertyGroup>
</Target>
</Project>

0 comments on commit 717e72f

Please sign in to comment.