Skip to content

Smdn.Net.EchonetLite.RouteB version 2.0.0-preview1

Pre-release
Pre-release
Compare
Choose a tag to compare
@smdn smdn released this 11 Dec 13:34
· 365 commits to main since this release
2afe0aa

Released package

Release notes

The full release notes are available at gist.

Change log

Change log in this release:

API changes

API changes in this release:
diff --git a/doc/api-list/Smdn.Net.EchonetLite.RouteB/Smdn.Net.EchonetLite.RouteB-net6.0.apilist.cs b/doc/api-list/Smdn.Net.EchonetLite.RouteB/Smdn.Net.EchonetLite.RouteB-net6.0.apilist.cs
new file mode 100644
index 0000000..ed56b16
--- /dev/null
+++ b/doc/api-list/Smdn.Net.EchonetLite.RouteB/Smdn.Net.EchonetLite.RouteB-net6.0.apilist.cs
@@ -0,0 +1,74 @@
+// Smdn.Net.EchonetLite.RouteB.dll (Smdn.Net.EchonetLite.RouteB-2.0.0-preview1)
+//   Name: Smdn.Net.EchonetLite.RouteB
+//   AssemblyVersion: 2.0.0.0
+//   InformationalVersion: 2.0.0-preview1+72e57d7daf6b52fc6ecc4ed745e175a1893e8d90
+//   TargetFramework: .NETCoreApp,Version=v6.0
+//   Configuration: Release
+//   Referenced assemblies:
+//     Microsoft.Extensions.DependencyInjection.Abstractions, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60
+//     Smdn.Net.EchonetLite.Transport, Version=2.0.0.0, Culture=neutral
+//     System.Memory, Version=6.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51
+//     System.Net.Primitives, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+//     System.Runtime, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+#nullable enable annotations
+
+using System;
+using System.Buffers;
+using System.Net;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
+using Smdn.Net.EchonetLite.RouteB.Credentials;
+using Smdn.Net.EchonetLite.RouteB.Transport;
+using Smdn.Net.EchonetLite.Transport;
+
+namespace Smdn.Net.EchonetLite.RouteB.Credentials {
+  public interface IRouteBCredential : IDisposable {
+    void WriteIdTo(IBufferWriter<byte> buffer);
+    void WritePasswordTo(IBufferWriter<byte> buffer);
+  }
+
+  public interface IRouteBCredentialIdentity {
+  }
+
+  public interface IRouteBCredentialProvider {
+    IRouteBCredential GetCredential(IRouteBCredentialIdentity identity);
+  }
+
+  public static class RouteBCredentialServiceCollectionExtensions {
+    public static IServiceCollection AddRouteBCredential(this IServiceCollection services, IRouteBCredentialProvider credentialProvider) {}
+    public static IServiceCollection AddRouteBCredential(this IServiceCollection services, string id, string password) {}
+  }
+
+  public static class RouteBCredentials {
+    public const int AuthenticationIdLength = 32;
+    public const int PasswordLength = 12;
+  }
+}
+
+namespace Smdn.Net.EchonetLite.RouteB.Transport {
+  public interface IRouteBEchonetLiteHandlerBuilder {
+    IServiceCollection Services { get; }
+  }
+
+  public interface IRouteBEchonetLiteHandlerFactory {
+    ValueTask<RouteBEchonetLiteHandler> CreateAsync(CancellationToken cancellationToken);
+  }
+
+  public abstract class RouteBEchonetLiteHandler : EchonetLiteHandler {
+    protected RouteBEchonetLiteHandler() {}
+
+    public abstract IPAddress? PeerAddress { get; }
+
+    public ValueTask ConnectAsync(IRouteBCredential credential, CancellationToken cancellationToken = default) {}
+    protected abstract ValueTask ConnectAsyncCore(IRouteBCredential credential, CancellationToken cancellationToken);
+    public ValueTask DisconnectAsync(CancellationToken cancellationToken = default) {}
+    protected abstract ValueTask DisconnectAsyncCore(CancellationToken cancellationToken);
+  }
+
+  public static class RouteBEchonetLiteHandlerBuilderServiceCollectionExtensions {
+    public static IServiceCollection AddRouteBHandler(this IServiceCollection services, Action<IRouteBEchonetLiteHandlerBuilder> configure) {}
+  }
+}
+// API list generated by Smdn.Reflection.ReverseGenerating.ListApi.MSBuild.Tasks v1.4.1.0.
+// Smdn.Reflection.ReverseGenerating.ListApi.Core v1.3.1.0 (https://github.com/smdn/Smdn.Reflection.ReverseGenerating)
diff --git a/doc/api-list/Smdn.Net.EchonetLite.RouteB/Smdn.Net.EchonetLite.RouteB-net8.0.apilist.cs b/doc/api-list/Smdn.Net.EchonetLite.RouteB/Smdn.Net.EchonetLite.RouteB-net8.0.apilist.cs
new file mode 100644
index 0000000..b9ccbb0
--- /dev/null
+++ b/doc/api-list/Smdn.Net.EchonetLite.RouteB/Smdn.Net.EchonetLite.RouteB-net8.0.apilist.cs
@@ -0,0 +1,74 @@
+// Smdn.Net.EchonetLite.RouteB.dll (Smdn.Net.EchonetLite.RouteB-2.0.0-preview1)
+//   Name: Smdn.Net.EchonetLite.RouteB
+//   AssemblyVersion: 2.0.0.0
+//   InformationalVersion: 2.0.0-preview1+72e57d7daf6b52fc6ecc4ed745e175a1893e8d90
+//   TargetFramework: .NETCoreApp,Version=v8.0
+//   Configuration: Release
+//   Referenced assemblies:
+//     Microsoft.Extensions.DependencyInjection.Abstractions, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60
+//     Smdn.Net.EchonetLite.Transport, Version=2.0.0.0, Culture=neutral
+//     System.Memory, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51
+//     System.Net.Primitives, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+//     System.Runtime, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+#nullable enable annotations
+
+using System;
+using System.Buffers;
+using System.Net;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
+using Smdn.Net.EchonetLite.RouteB.Credentials;
+using Smdn.Net.EchonetLite.RouteB.Transport;
+using Smdn.Net.EchonetLite.Transport;
+
+namespace Smdn.Net.EchonetLite.RouteB.Credentials {
+  public interface IRouteBCredential : IDisposable {
+    void WriteIdTo(IBufferWriter<byte> buffer);
+    void WritePasswordTo(IBufferWriter<byte> buffer);
+  }
+
+  public interface IRouteBCredentialIdentity {
+  }
+
+  public interface IRouteBCredentialProvider {
+    IRouteBCredential GetCredential(IRouteBCredentialIdentity identity);
+  }
+
+  public static class RouteBCredentialServiceCollectionExtensions {
+    public static IServiceCollection AddRouteBCredential(this IServiceCollection services, IRouteBCredentialProvider credentialProvider) {}
+    public static IServiceCollection AddRouteBCredential(this IServiceCollection services, string id, string password) {}
+  }
+
+  public static class RouteBCredentials {
+    public const int AuthenticationIdLength = 32;
+    public const int PasswordLength = 12;
+  }
+}
+
+namespace Smdn.Net.EchonetLite.RouteB.Transport {
+  public interface IRouteBEchonetLiteHandlerBuilder {
+    IServiceCollection Services { get; }
+  }
+
+  public interface IRouteBEchonetLiteHandlerFactory {
+    ValueTask<RouteBEchonetLiteHandler> CreateAsync(CancellationToken cancellationToken);
+  }
+
+  public abstract class RouteBEchonetLiteHandler : EchonetLiteHandler {
+    protected RouteBEchonetLiteHandler() {}
+
+    public abstract IPAddress? PeerAddress { get; }
+
+    public ValueTask ConnectAsync(IRouteBCredential credential, CancellationToken cancellationToken = default) {}
+    protected abstract ValueTask ConnectAsyncCore(IRouteBCredential credential, CancellationToken cancellationToken);
+    public ValueTask DisconnectAsync(CancellationToken cancellationToken = default) {}
+    protected abstract ValueTask DisconnectAsyncCore(CancellationToken cancellationToken);
+  }
+
+  public static class RouteBEchonetLiteHandlerBuilderServiceCollectionExtensions {
+    public static IServiceCollection AddRouteBHandler(this IServiceCollection services, Action<IRouteBEchonetLiteHandlerBuilder> configure) {}
+  }
+}
+// API list generated by Smdn.Reflection.ReverseGenerating.ListApi.MSBuild.Tasks v1.4.1.0.
+// Smdn.Reflection.ReverseGenerating.ListApi.Core v1.3.1.0 (https://github.com/smdn/Smdn.Reflection.ReverseGenerating)
diff --git a/doc/api-list/Smdn.Net.EchonetLite.RouteB/Smdn.Net.EchonetLite.RouteB-netstandard2.1.apilist.cs b/doc/api-list/Smdn.Net.EchonetLite.RouteB/Smdn.Net.EchonetLite.RouteB-netstandard2.1.apilist.cs
new file mode 100644
index 0000000..19dcb57
--- /dev/null
+++ b/doc/api-list/Smdn.Net.EchonetLite.RouteB/Smdn.Net.EchonetLite.RouteB-netstandard2.1.apilist.cs
@@ -0,0 +1,72 @@
+// Smdn.Net.EchonetLite.RouteB.dll (Smdn.Net.EchonetLite.RouteB-2.0.0-preview1)
+//   Name: Smdn.Net.EchonetLite.RouteB
+//   AssemblyVersion: 2.0.0.0
+//   InformationalVersion: 2.0.0-preview1+72e57d7daf6b52fc6ecc4ed745e175a1893e8d90
+//   TargetFramework: .NETStandard,Version=v2.1
+//   Configuration: Release
+//   Referenced assemblies:
+//     Microsoft.Extensions.DependencyInjection.Abstractions, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60
+//     Smdn.Net.EchonetLite.Transport, Version=2.0.0.0, Culture=neutral
+//     netstandard, Version=2.1.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51
+#nullable enable annotations
+
+using System;
+using System.Buffers;
+using System.Net;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
+using Smdn.Net.EchonetLite.RouteB.Credentials;
+using Smdn.Net.EchonetLite.RouteB.Transport;
+using Smdn.Net.EchonetLite.Transport;
+
+namespace Smdn.Net.EchonetLite.RouteB.Credentials {
+  public interface IRouteBCredential : IDisposable {
+    void WriteIdTo(IBufferWriter<byte> buffer);
+    void WritePasswordTo(IBufferWriter<byte> buffer);
+  }
+
+  public interface IRouteBCredentialIdentity {
+  }
+
+  public interface IRouteBCredentialProvider {
+    IRouteBCredential GetCredential(IRouteBCredentialIdentity identity);
+  }
+
+  public static class RouteBCredentialServiceCollectionExtensions {
+    public static IServiceCollection AddRouteBCredential(this IServiceCollection services, IRouteBCredentialProvider credentialProvider) {}
+    public static IServiceCollection AddRouteBCredential(this IServiceCollection services, string id, string password) {}
+  }
+
+  public static class RouteBCredentials {
+    public const int AuthenticationIdLength = 32;
+    public const int PasswordLength = 12;
+  }
+}
+
+namespace Smdn.Net.EchonetLite.RouteB.Transport {
+  public interface IRouteBEchonetLiteHandlerBuilder {
+    IServiceCollection Services { get; }
+  }
+
+  public interface IRouteBEchonetLiteHandlerFactory {
+    ValueTask<RouteBEchonetLiteHandler> CreateAsync(CancellationToken cancellationToken);
+  }
+
+  public abstract class RouteBEchonetLiteHandler : EchonetLiteHandler {
+    protected RouteBEchonetLiteHandler() {}
+
+    public abstract IPAddress? PeerAddress { get; }
+
+    public ValueTask ConnectAsync(IRouteBCredential credential, CancellationToken cancellationToken = default) {}
+    protected abstract ValueTask ConnectAsyncCore(IRouteBCredential credential, CancellationToken cancellationToken);
+    public ValueTask DisconnectAsync(CancellationToken cancellationToken = default) {}
+    protected abstract ValueTask DisconnectAsyncCore(CancellationToken cancellationToken);
+  }
+
+  public static class RouteBEchonetLiteHandlerBuilderServiceCollectionExtensions {
+    public static IServiceCollection AddRouteBHandler(this IServiceCollection services, Action<IRouteBEchonetLiteHandlerBuilder> configure) {}
+  }
+}
+// API list generated by Smdn.Reflection.ReverseGenerating.ListApi.MSBuild.Tasks v1.4.1.0.
+// Smdn.Reflection.ReverseGenerating.ListApi.Core v1.3.1.0 (https://github.com/smdn/Smdn.Reflection.ReverseGenerating)

Full changes

Full changes in this release:
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 <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);
+}
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 <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 { }
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 <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);
+}
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 <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;
+  }
+}
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 <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;
+}
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 <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);
+  }
+}
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 <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; }
+}
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 <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
+  );
+}
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..ae5c5a2
--- /dev/null
+++ b/src/Smdn.Net.EchonetLite.RouteB/Smdn.Net.EchonetLite.RouteB.Transport/RouteBEchonetLiteHandler.cs
@@ -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 IP 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
+  );
+}
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 <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));
+}
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 <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;
+  }
+}
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..85b0cee
--- /dev/null
+++ b/src/Smdn.Net.EchonetLite.RouteB/Smdn.Net.EchonetLite.RouteB.csproj
@@ -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>2.0.0</VersionPrefix>
+    <VersionSuffix>preview1</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="2.0.0-preview1" 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>

Notes

Full Changelog: releases/Smdn.Net.EchonetLite.Transport-2.0.0-preview1...releases/Smdn.Net.EchonetLite.RouteB-2.0.0-preview1