main/Smdn.Net.SkStackIP-1.0.0
Created
February 12, 2024 12:09
-
-
Save smdn/aed8fcf57a9ae4295bd8d83c10dafbcd to your computer and use it in GitHub Desktop.
Smdn.Net.SkStackIP 1.0.0 Release Notes
- 2024-02-12 add package tags
- 2024-02-12 update assembly version
- 2024-02-12 add and update package description/readme
- 2024-01-02 delete unnecessary warning suppression
- 2024-01-02 implemenet non-deferred exception throwing
- 2024-01-02 delete unusing member
- 2024-01-02 validate that the iargument is valid ASCII or not for the SKSETPWD and SKSETRBID command
- 2024-01-02 delete unnecessary type cast
- 2024-01-02 use IUtf8SpanParsable.Parse if available
- 2024-01-02 improve not to use Encoding for formatting
- 2024-01-02 refactor
- 2024-01-02 use UTF-8 string literals instead
- 2024-01-01 delete dead codes
- 2024-01-01 change SkStackTokenParser.ExpectSequence to accept ReadOnlySpan<byte> instead of ReadOnlyMemory<byte>, and use UTF-8 literal instead
- 2024-01-01 change SkStackTokenParser.[Try]ExpectToken to accept ReadOnlySpan<byte> instead of ReadOnlyMemory<byte>
- 2024-01-01 refactor and delete unneccessary overloads
- 2024-01-01 improve SkStackEventParser to be able to accept ReadOnlySpan<byte> as an expecting token instead of ReadOnlyMemory<byte>
- 2024-01-01 add target framework net8.0
- 2024-01-01 suppress warning CA1859
- 2024-01-01 fix warning IDE0055: Fix formatting
- 2024-01-01 fix parameter name
- 2024-01-01 fix warning IDE0031: Null check can be simplified
- 2024-01-01 fix warning SA1402: File may only contain a single type
- 2024-01-01 fix warning IDE1006: Naming rule violation
- 2023-11-19 add SkStackClient.ReceiveResponseDelay
- 2023-11-19 compliant the naming rules
- 2023-11-19 make method internal
- 2023-11-19 add SkStackClient.ReceiveUdpPollingInterval
- 2023-11-19 remove outdated comment
- 2023-11-19 fix not to call PipeReader.AdvanceTo() twice for consumed or examined buffers
- 2023-11-18 reduce package dependency of Smdn.Fundamental.Buffer
- 2023-11-18 improve logging to mask passed password when sending SKSETPWD command
- 2023-11-18 introduce ISkStackCommandLineWriter and simplify writing command line arguments
- 2023-11-18 introduce SkStackCommandLineWriter and replace DuplicateBufferWriter with it
- 2023-11-17 expose ILogger to the child classes
- 2023-11-17 improve logging to use logger scope
- 2023-11-16 fix file name
- 2023-11-16 add SkStackUdpSendResultIndeterminateException and throw it instead to clarify the situation
- 2023-11-16 beautify
- 2023-11-16 fix to wrap exceptions occured while parsing into SkStackUnexpectedResponseException
- 2023-10-13 improve exception message string
- 2023-10-13 bump Polly.Core up to 8.0.0
- 2023-08-17 add third-party notices
- 2023-08-17 use NullResilienceStrategy instead
- 2023-08-14 disable CS8629 for release build
- 2023-08-14 improve resiliency of SendUdpEchonetLiteAsync by using ResilienceStrategy
- 2023-07-11 fix SendSKSENDTOAsync to handle EVENT 21 properly and return the final result as a method return value
- 2023-07-09 refactor
- 2023-07-09 improve XML comment docs
- 2023-07-09 change AuthenticateAsPanaClientAsync to accept byte sequence instead of char
- 2023-07-07 change AuthenticateAsPanaClientAsync() to throw if supplied PAA address is not supported
- 2023-07-07 improve naming
- 2023-07-07 improve AuthenticateAsPanaClientAsync() to return established session info
- 2023-07-02 add overloads that accept MAC address for the destination PAA as well as IP address
- 2023-07-02 use decimal for representing RSSI values instead of double
- 2023-07-02 delete unnecessary using directives
- 2023-07-02 remove instantiation specifying serial ports and reduce dependency on System.IO.Ports
- 2023-07-02 imporve TerminatePanaSessionAsync
- 2023-07-02 add Async suffix
- 2023-07-02 add MemberNotNullWhenAttribute to SkStackClient.IsPanaSessionAlive
- 2023-06-27 add shorthands for flash memory functions
- 2023-06-25 add shorthands for ECHONET Lite functions
- 2023-06-25 add SkStackUdpPortHandle.None
- 2023-06-25 add shorthands for PANA functions
- 2023-06-24 add shorthands for IP functions
- 2023-06-24 delete dead code
- 2023-06-24 improve SkStackChannel.FindByChannelNumber
- 2023-06-24 add SkStackClient.IsPanaSessionAlive
- 2023-06-24 use IBufferWriter instead of returning allocated buffer
- 2023-06-24 fix indent
- 2023-06-23 add shorthands for UDP functions
- 2023-06-23 change ERXUDPDataFormat property to be set by the constructor and change the accessibility to 'protected'
- 2023-06-22 rename file
- 2023-06-22 rename UdpReceiveAsync to ReceiveUdpAsync
- 2023-06-22 make OpenSerialPortStream and ThrowIfDisposed protected
- 2023-06-22 add a ctor overload to allow specifying whether or not to close the specified Stream
- 2023-06-21 move a member to appropriate file
- 2023-06-21 rename files
- 2023-06-17 split implementation file
- 2023-06-17 fix naming to follow the convention
- 2023-06-17 fix redundant naming
- 2023-06-16 rename parameter
- 2023-06-16 change to accept ReadOnlyMemory<char> instead of string
- 2023-06-15 add SkStackChannel.Empty/IsEmpty
- 2023-06-14 improve XML comment docs
- 2023-06-12 apply dotnet format
- 2023-06-12 change SkStackClient to accept ILogger directly instead of IServiceProvider
- 2023-06-11 propagate CancellationToken
- 2023-06-11 enable nullability annotations and warnings
- 2023-06-09 fix warnings on .NET 7 SDK
- 2023-06-09 fix SAxxxx warnings
- 2023-06-09 fix CAxxxx warnings
- 2023-06-08 fix IDExxxx warnings
- 2023-06-08 move type to the appropriate namespace
- 2023-06-08 use file-scoped namespace declaration
- 2023-06-03 redefine package version
- 2023-06-03 update package references
- 2023-06-03 set target framework net6.0 instead of net5.0
- 2023-06-03 use Smdn.MSBuild.DefineConstants.NETSdkApi
- 2023-06-03 catch up the repository template updates
- 2021-08-24 refactor
- 2021-08-24 use Smdn.Formats.Hexadecimal instead
- 2021-08-23 use SequenceReader<T>.TryCopyTo instead
- 2021-08-23 use SequenceReader<T>.Remaining instead
- 2021-08-23 replace extensional implementations with Smdn.Fundamental.*
- 2021-08-21 define CLSCompliantAttribute with AssemblyAttribute in project file instead of cs files
- 2021-08-01 add SkStackClient.PanaSessionPeerAddress
- 2021-08-01 modify SendSKUDPPORTAsync to return newly set port
- 2021-07-31 publicate the constructor
- 2021-07-31 remove SkStackClient.BaseStream
- 2021-07-31 hide SkStackEventHandler from public API
- 2021-07-31 add fallback for netstandard2.1
- 2021-07-31 add event args type constraints
- 2021-07-31 implement SKDSLEEP
- 2021-07-31 simplify handling events that are triggered by commands
- 2021-07-24 add events for handling PANA session events
- 2021-07-23 simplify
- 2021-07-23 add support for ERXUDP data with hexadecimal ASCII format
- 2021-07-23 add debugging code
- 2021-07-23 improve processing echoback line of SKSENDTO
- 2021-07-22 define SkStackKnownPortNumbers
- 2021-07-22 add support for receiving, capturing and reading UDP packets
- 2021-07-21 add debugging code
- 2021-07-21 minor fix
- 2021-07-21 make ReadAsync as a critical section
- 2021-07-21 improved to be able to identify between the state of Ignore and Incomplete.
- 2021-07-21 delete SkStackUdpReceiveEvent.Data and improve not to allocate copy of received data
- 2021-07-21 add utility methods for throwing argument exception
- 2021-07-20 define exception types which represents error code ERXX
- 2021-07-20 implement SKSAVE/SKLOAD/SKERASE
- 2021-07-19 add support for commands and response status lines which end with CR
- 2021-07-19 fix to log SkStackUnexpectedResponseException thrown by ProcessNotificationalEvents
- 2021-07-19 fix to rethrow caught SkStackUnexpectedResponseException
- 2021-07-19 fix and redefine log levels
- 2021-07-19 add logging SkStackUnexpectedResponseException
- 2021-07-19 fix output format of ToString
- 2021-07-17 fix exception message
- 2021-07-17 apply dotnet format
- 2021-07-17 add initial implementation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/doc/api-list/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP-net6.0.apilist.cs b/doc/api-list/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP-net6.0.apilist.cs | |
new file mode 100644 | |
index 0000000..2c1c882 | |
--- /dev/null | |
+++ b/doc/api-list/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP-net6.0.apilist.cs | |
@@ -0,0 +1,466 @@ | |
+// Smdn.Net.SkStackIP.dll (Smdn.Net.SkStackIP-1.0.0) | |
+// Name: Smdn.Net.SkStackIP | |
+// AssemblyVersion: 1.0.0.0 | |
+// InformationalVersion: 1.0.0+655c155832ea35fece55fe3cd2467b473922319c | |
+// TargetFramework: .NETCoreApp,Version=v6.0 | |
+// Configuration: Release | |
+// Referenced assemblies: | |
+// Microsoft.Extensions.Logging.Abstractions, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60 | |
+// Polly.Core, Version=8.0.0.0, Culture=neutral, PublicKeyToken=c8a3ffc3f8f825cc | |
+// Smdn.Fundamental.ControlPicture, Version=3.0.0.1, Culture=neutral | |
+// Smdn.Fundamental.PrintableEncoding.Hexadecimal, Version=3.0.0.0, Culture=neutral | |
+// System.Collections, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a | |
+// System.ComponentModel.Primitives, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a | |
+// System.IO.Pipelines, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51 | |
+// System.Linq, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a | |
+// System.Memory, Version=6.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51 | |
+// System.Net.NetworkInformation, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a | |
+// System.Net.Primitives, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a | |
+// System.ObjectModel, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a | |
+// System.Runtime, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a | |
+// System.Runtime.InteropServices, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a | |
+// System.Threading, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a | |
+#nullable enable annotations | |
+ | |
+using System; | |
+using System.Buffers; | |
+using System.Collections.Generic; | |
+using System.ComponentModel; | |
+using System.Diagnostics.CodeAnalysis; | |
+using System.IO; | |
+using System.IO.Pipelines; | |
+using System.Net; | |
+using System.Net.NetworkInformation; | |
+using System.Threading; | |
+using System.Threading.Tasks; | |
+using Microsoft.Extensions.Logging; | |
+using Polly; | |
+using Smdn.Net.SkStackIP; | |
+using Smdn.Net.SkStackIP.Protocol; | |
+ | |
+namespace Smdn.Net.SkStackIP { | |
+ public enum SkStackERXUDPDataFormat : int { | |
+ Binary = 0, | |
+ HexAsciiText = 1, | |
+ } | |
+ | |
+ public enum SkStackErrorCode : int { | |
+ ER01 = 1, | |
+ ER02 = 2, | |
+ ER03 = 3, | |
+ ER04 = 4, | |
+ ER05 = 5, | |
+ ER06 = 6, | |
+ ER07 = 7, | |
+ ER08 = 8, | |
+ ER09 = 9, | |
+ ER10 = 10, | |
+ Undefined = 0, | |
+ } | |
+ | |
+ public enum SkStackEventNumber : byte { | |
+ ActiveScanCompleted = 34, | |
+ BeaconReceived = 32, | |
+ EchoRequestReceived = 5, | |
+ EnergyDetectScanCompleted = 31, | |
+ NeighborAdvertisementReceived = 2, | |
+ NeighborSolicitationReceived = 1, | |
+ PanaSessionEstablishmentCompleted = 37, | |
+ PanaSessionEstablishmentError = 36, | |
+ PanaSessionExpired = 41, | |
+ PanaSessionTerminationCompleted = 39, | |
+ PanaSessionTerminationRequestReceived = 38, | |
+ PanaSessionTerminationTimedOut = 40, | |
+ TransmissionTimeControlLimitationActivated = 50, | |
+ TransmissionTimeControlLimitationDeactivated = 51, | |
+ UdpSendCompleted = 33, | |
+ Undefined = 0, | |
+ WakeupSignalReceived = 192, | |
+ } | |
+ | |
+ public enum SkStackResponseStatus : int { | |
+ Fail = -1, | |
+ Ok = 1, | |
+ Undetermined = 0, | |
+ } | |
+ | |
+ public enum SkStackUdpEncryption : byte { | |
+ EncryptIfAble = 2, | |
+ ForceEncrypt = 1, | |
+ ForcePlainText = 0, | |
+ } | |
+ | |
+ public enum SkStackUdpPortHandle : byte { | |
+ Handle1 = 1, | |
+ Handle2 = 2, | |
+ Handle3 = 3, | |
+ Handle4 = 4, | |
+ Handle5 = 5, | |
+ Handle6 = 6, | |
+ None = 0, | |
+ } | |
+ | |
+ public abstract class SkStackActiveScanOptions : ICloneable { | |
+ public static SkStackActiveScanOptions Default { get; } | |
+ public static SkStackActiveScanOptions Null { get; } | |
+ public static SkStackActiveScanOptions ScanUntilFind { get; } | |
+ | |
+ public static SkStackActiveScanOptions Create(IEnumerable<int> scanDurationGenerator, PhysicalAddress paaMacAddress) {} | |
+ public static SkStackActiveScanOptions Create(IEnumerable<int> scanDurationGenerator, Predicate<SkStackPanDescription>? paaSelector = null) {} | |
+ | |
+ protected SkStackActiveScanOptions() {} | |
+ | |
+ public abstract SkStackActiveScanOptions Clone(); | |
+ object ICloneable.Clone() {} | |
+ } | |
+ | |
+ public class SkStackClient : IDisposable { | |
+ public static readonly TimeSpan SKSCANDefaultDuration; // = "00:00:00.0480000" | |
+ public static readonly TimeSpan SKSCANMaxDuration; // = "00:02:37.2960000" | |
+ public static readonly TimeSpan SKSCANMinDuration; // = "00:00:00.0192000" | |
+ | |
+ public event EventHandler<SkStackPanaSessionEventArgs>? PanaSessionEstablished; | |
+ public event EventHandler<SkStackPanaSessionEventArgs>? PanaSessionExpired; | |
+ public event EventHandler<SkStackPanaSessionEventArgs>? PanaSessionTerminated; | |
+ public event EventHandler<SkStackEventArgs>? Slept; | |
+ public event EventHandler<SkStackEventArgs>? WokeUp; | |
+ | |
+ public SkStackClient(PipeWriter sender, PipeReader receiver, SkStackERXUDPDataFormat erxudpDataFormat = SkStackERXUDPDataFormat.Binary, ILogger? logger = null) {} | |
+ public SkStackClient(Stream stream, bool leaveStreamOpen = true, SkStackERXUDPDataFormat erxudpDataFormat = SkStackERXUDPDataFormat.Binary, ILogger? logger = null) {} | |
+ | |
+ public SkStackERXUDPDataFormat ERXUDPDataFormat { get; protected set; } | |
+ [MemberNotNullWhen(true, "PanaSessionPeerAddress")] | |
+ public bool IsPanaSessionAlive { [MemberNotNullWhen(true, "PanaSessionPeerAddress")] get; } | |
+ protected ILogger? Logger { get; } | |
+ public IPAddress? PanaSessionPeerAddress { get; } | |
+ public TimeSpan ReceiveResponseDelay { get; set; } | |
+ public TimeSpan ReceiveUdpPollingInterval { get; set; } | |
+ public ISynchronizeInvoke? SynchronizingObject { get; set; } | |
+ | |
+ public ValueTask<IReadOnlyList<SkStackPanDescription>> ActiveScanAsync(ReadOnlyMemory<byte> rbid, ReadOnlyMemory<byte> password, SkStackActiveScanOptions? scanOptions = null, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackPanaSessionInfo> AuthenticateAsPanaClientAsync(ReadOnlyMemory<byte> rbid, ReadOnlyMemory<byte> password, IPAddress paaAddress, SkStackChannel channel, int panId, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackPanaSessionInfo> AuthenticateAsPanaClientAsync(ReadOnlyMemory<byte> rbid, ReadOnlyMemory<byte> password, IPAddress paaAddress, int channelNumber, int panId, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackPanaSessionInfo> AuthenticateAsPanaClientAsync(ReadOnlyMemory<byte> rbid, ReadOnlyMemory<byte> password, PhysicalAddress paaMacAddress, SkStackChannel channel, int panId, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackPanaSessionInfo> AuthenticateAsPanaClientAsync(ReadOnlyMemory<byte> rbid, ReadOnlyMemory<byte> password, PhysicalAddress paaMacAddress, int channelNumber, int panId, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackPanaSessionInfo> AuthenticateAsPanaClientAsync(ReadOnlyMemory<byte> rbid, ReadOnlyMemory<byte> password, SkStackActiveScanOptions? scanOptions = null, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackPanaSessionInfo> AuthenticateAsPanaClientAsync(ReadOnlyMemory<byte> rbid, ReadOnlyMemory<byte> password, SkStackPanDescription pan, CancellationToken cancellationToken = default) {} | |
+ public async ValueTask<IPAddress> ConvertToIPv6LinkLocalAddressAsync(PhysicalAddress macAddress, CancellationToken cancellationToken = default) {} | |
+ public ValueTask DisableFlashMemoryAutoLoadAsync(CancellationToken cancellationToken = default) {} | |
+ protected virtual void Dispose(bool disposing) {} | |
+ public void Dispose() {} | |
+ public ValueTask EnableFlashMemoryAutoLoadAsync(CancellationToken cancellationToken = default) {} | |
+ public async ValueTask<IReadOnlyList<IPAddress>> GetAvailableAddressListAsync(CancellationToken cancellationToken = default) {} | |
+ public async ValueTask<IReadOnlyList<SkStackUdpPort>> GetListeningUdpPortListAsync(CancellationToken cancellationToken = default) {} | |
+ public async ValueTask<IReadOnlyDictionary<IPAddress, PhysicalAddress>> GetNeighborCacheListAsync(CancellationToken cancellationToken = default) {} | |
+ public async ValueTask<IReadOnlyList<SkStackUdpPortHandle>> GetUnusedUdpPortHandleListAsync(CancellationToken cancellationToken = default) {} | |
+ public async ValueTask LoadFlashMemoryAsync(CancellationToken cancellationToken = default) {} | |
+ public async ValueTask<SkStackUdpPort> PrepareUdpPortAsync(int port, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<IPAddress> ReceiveUdpAsync(int port, IBufferWriter<byte> buffer, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<IPAddress> ReceiveUdpEchonetLiteAsync(IBufferWriter<byte> buffer, CancellationToken cancellationToken = default) {} | |
+ public ValueTask SaveFlashMemoryAsync(SkStackFlashMemoryWriteRestriction restriction, CancellationToken cancellationToken = default) {} | |
+ internal protected ValueTask<SkStackResponse<TPayload>> SendCommandAsync<TPayload>(ReadOnlyMemory<byte> command, Action<ISkStackCommandLineWriter>? writeArguments, SkStackSequenceParser<TPayload> parseResponsePayload, SkStackProtocolSyntax? syntax = null, bool throwIfErrorStatus = true, CancellationToken cancellationToken = default) {} | |
+ internal protected async ValueTask<SkStackResponse> SendCommandAsync(ReadOnlyMemory<byte> command, Action<ISkStackCommandLineWriter>? writeArguments = null, SkStackProtocolSyntax? syntax = null, bool throwIfErrorStatus = true, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse> SendSKADDNBRAsync(IPAddress ipv6Address, PhysicalAddress macAddress, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse<string>> SendSKAPPVERAsync(CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse> SendSKDSLEEPAsync(bool waitUntilWakeUp = false, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse> SendSKERASEAsync(CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse<(IPAddress LinkLocalAddress, PhysicalAddress MacAddress, SkStackChannel Channel, int PanId, int Addr16)>> SendSKINFOAsync(CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse> SendSKJOINAsync(IPAddress ipv6address, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse<IPAddress>> SendSKLL64Async(PhysicalAddress macAddress, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse> SendSKLOADAsync(CancellationToken cancellationToken = default) {} | |
+ public ValueTask<(SkStackResponse Response, IPAddress Address)> SendSKREJOINAsync(CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse> SendSKRESETAsync(CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse> SendSKSAVEAsync(CancellationToken cancellationToken = default) {} | |
+ public ValueTask<(SkStackResponse Response, IReadOnlyList<SkStackPanDescription> PanDescriptions)> SendSKSCANActiveScanAsync(TimeSpan duration = default, uint channelMask = uint.MaxValue, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<(SkStackResponse Response, IReadOnlyList<SkStackPanDescription> PanDescriptions)> SendSKSCANActiveScanAsync(int durationFactor, uint channelMask = uint.MaxValue, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<(SkStackResponse Response, IReadOnlyList<SkStackPanDescription> PanDescriptions)> SendSKSCANActiveScanPairAsync(TimeSpan duration = default, uint channelMask = uint.MaxValue, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<(SkStackResponse Response, IReadOnlyList<SkStackPanDescription> PanDescriptions)> SendSKSCANActiveScanPairAsync(int durationFactor, uint channelMask = uint.MaxValue, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<(SkStackResponse Response, IReadOnlyDictionary<SkStackChannel, decimal> ScanResult)> SendSKSCANEnergyDetectScanAsync(TimeSpan duration = default, uint channelMask = uint.MaxValue, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<(SkStackResponse Response, IReadOnlyDictionary<SkStackChannel, decimal> ScanResult)> SendSKSCANEnergyDetectScanAsync(int durationFactor, uint channelMask = uint.MaxValue, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<(SkStackResponse Response, bool IsCompletedSuccessfully)> SendSKSENDTOAsync(SkStackUdpPort port, IPAddress destinationAddress, int destinationPort, ReadOnlyMemory<byte> data, SkStackUdpEncryption encryption = SkStackUdpEncryption.EncryptIfAble, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<(SkStackResponse Response, bool IsCompletedSuccessfully)> SendSKSENDTOAsync(SkStackUdpPort port, IPEndPoint destination, ReadOnlyMemory<byte> data, SkStackUdpEncryption encryption = SkStackUdpEncryption.EncryptIfAble, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<(SkStackResponse Response, bool IsCompletedSuccessfully)> SendSKSENDTOAsync(SkStackUdpPortHandle handle, IPAddress destinationAddress, int destinationPort, ReadOnlyMemory<byte> data, SkStackUdpEncryption encryption = SkStackUdpEncryption.EncryptIfAble, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<(SkStackResponse Response, bool IsCompletedSuccessfully)> SendSKSENDTOAsync(SkStackUdpPortHandle handle, IPEndPoint destination, ReadOnlyMemory<byte> data, SkStackUdpEncryption encryption = SkStackUdpEncryption.EncryptIfAble, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse> SendSKSETPWDAsync(ReadOnlyMemory<byte> password, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse> SendSKSETPWDAsync(ReadOnlyMemory<char> password, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse> SendSKSETRBIDAsync(ReadOnlyMemory<byte> id, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse> SendSKSETRBIDAsync(ReadOnlyMemory<char> id, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse<TValue>> SendSKSREGAsync<TValue>(SkStackRegister.RegisterEntry<TValue> register, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse> SendSKSREGAsync<TValue>(SkStackRegister.RegisterEntry<TValue> register, TValue @value, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse<IReadOnlyList<IPAddress>>> SendSKTABLEAvailableAddressListAsync(CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse<IReadOnlyList<SkStackUdpPort>>> SendSKTABLEListeningPortListAsync(CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse<IReadOnlyDictionary<IPAddress, PhysicalAddress>>> SendSKTABLENeighborCacheListAsync(CancellationToken cancellationToken = default) {} | |
+ public async ValueTask<(SkStackResponse Response, bool IsCompletedSuccessfully)> SendSKTERMAsync(CancellationToken cancellationToken = default) {} | |
+ public ValueTask<(SkStackResponse Response, SkStackUdpPort UdpPort)> SendSKUDPPORTAsync(SkStackUdpPortHandle handle, int port, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse> SendSKUDPPORTUnsetAsync(SkStackUdpPortHandle handle, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse<Version>> SendSKVERAsync(CancellationToken cancellationToken = default) {} | |
+ public ValueTask SendUdpEchonetLiteAsync(ReadOnlyMemory<byte> buffer, ResiliencePipeline? resiliencePipeline = null, CancellationToken cancellationToken = default) {} | |
+ protected async ValueTask SetFlashMemoryAutoLoadAsync(bool trueIfEnable, CancellationToken cancellationToken = default) {} | |
+ public void StartCapturingUdpReceiveEvents(int port) {} | |
+ public void StopCapturingUdpReceiveEvents(int port) {} | |
+ public ValueTask<bool> TerminatePanaSessionAsync(CancellationToken cancellationToken = default) {} | |
+ protected void ThrowIfDisposed() {} | |
+ internal protected void ThrowIfPanaSessionAlreadyEstablished() {} | |
+ [MemberNotNull("PanaSessionPeerAddress")] | |
+ internal protected void ThrowIfPanaSessionIsNotEstablished() {} | |
+ } | |
+ | |
+ public class SkStackCommandNotSupportedException : SkStackErrorResponseException { | |
+ } | |
+ | |
+ public class SkStackErrorResponseException : SkStackResponseException { | |
+ public SkStackErrorCode ErrorCode { get; } | |
+ public string ErrorText { get; } | |
+ public SkStackResponse Response { get; } | |
+ } | |
+ | |
+ public class SkStackEventArgs : EventArgs { | |
+ public SkStackEventNumber EventNumber { get; } | |
+ } | |
+ | |
+ public class SkStackFlashMemoryIOException : SkStackErrorResponseException { | |
+ } | |
+ | |
+ public abstract class SkStackFlashMemoryWriteRestriction { | |
+ public static SkStackFlashMemoryWriteRestriction CreateGrantIfElapsed(TimeSpan interval) {} | |
+ public static SkStackFlashMemoryWriteRestriction DangerousCreateAlwaysGrant() {} | |
+ | |
+ protected SkStackFlashMemoryWriteRestriction() {} | |
+ | |
+ internal protected abstract bool IsRestricted(); | |
+ } | |
+ | |
+ public static class SkStackKnownPortNumbers { | |
+ public const int EchonetLite = 3610; | |
+ public const int Pana = 716; | |
+ } | |
+ | |
+ public class SkStackPanaSessionEstablishmentException : SkStackPanaSessionException { | |
+ } | |
+ | |
+ public sealed class SkStackPanaSessionEventArgs : SkStackEventArgs { | |
+ public IPAddress PanaSessionPeerAddress { get; } | |
+ } | |
+ | |
+ public abstract class SkStackPanaSessionException : InvalidOperationException { | |
+ public IPAddress Address { get; } | |
+ public SkStackEventNumber EventNumber { get; } | |
+ } | |
+ | |
+ public sealed class SkStackPanaSessionInfo { | |
+ public SkStackChannel Channel { get; } | |
+ public IPAddress LocalAddress { get; } | |
+ public PhysicalAddress LocalMacAddress { get; } | |
+ public int PanId { get; } | |
+ public IPAddress PeerAddress { get; } | |
+ public PhysicalAddress PeerMacAddress { get; } | |
+ } | |
+ | |
+ public static class SkStackRegister { | |
+ public abstract class RegisterEntry<TValue> { | |
+ private protected delegate bool ExpectValueFunc(ref SequenceReader<byte> reader, out TValue @value); | |
+ | |
+ public bool IsReadable { get; } | |
+ public bool IsWritable { get; } | |
+ public TValue MaxValue { get; } | |
+ public TValue MinValue { get; } | |
+ public string Name { get; } | |
+ } | |
+ | |
+ public static SkStackRegister.RegisterEntry<bool> AcceptIcmpEcho { get; } | |
+ public static SkStackRegister.RegisterEntry<ulong> AccumulatedSendTimeInMilliseconds { get; } | |
+ public static SkStackRegister.RegisterEntry<SkStackChannel> Channel { get; } | |
+ public static SkStackRegister.RegisterEntry<bool> EnableAutoLoad { get; } | |
+ public static SkStackRegister.RegisterEntry<bool> EnableAutoReauthentication { get; } | |
+ public static SkStackRegister.RegisterEntry<bool> EnableEchoback { get; } | |
+ public static SkStackRegister.RegisterEntry<bool> EncryptIPMulticast { get; } | |
+ public static SkStackRegister.RegisterEntry<uint> FrameCounter { get; } | |
+ public static SkStackRegister.RegisterEntry<bool> IsSendingRestricted { get; } | |
+ public static SkStackRegister.RegisterEntry<ReadOnlyMemory<byte>> PairingId { get; } | |
+ public static SkStackRegister.RegisterEntry<ushort> PanId { get; } | |
+ public static SkStackRegister.RegisterEntry<TimeSpan> PanaSessionLifetimeInSeconds { get; } | |
+ public static SkStackRegister.RegisterEntry<bool> RespondBeaconRequest { get; } | |
+ public static SkStackRegister.RegisterEntry<SkStackChannel> S02 { get; } | |
+ public static SkStackRegister.RegisterEntry<ushort> S03 { get; } | |
+ public static SkStackRegister.RegisterEntry<uint> S07 { get; } | |
+ public static SkStackRegister.RegisterEntry<ReadOnlyMemory<byte>> S0A { get; } | |
+ public static SkStackRegister.RegisterEntry<bool> S15 { get; } | |
+ public static SkStackRegister.RegisterEntry<TimeSpan> S16 { get; } | |
+ public static SkStackRegister.RegisterEntry<bool> S17 { get; } | |
+ public static SkStackRegister.RegisterEntry<bool> SA0 { get; } | |
+ public static SkStackRegister.RegisterEntry<bool> SA1 { get; } | |
+ public static SkStackRegister.RegisterEntry<bool> SFB { get; } | |
+ public static SkStackRegister.RegisterEntry<ulong> SFD { get; } | |
+ public static SkStackRegister.RegisterEntry<bool> SFE { get; } | |
+ public static SkStackRegister.RegisterEntry<bool> SFF { get; } | |
+ } | |
+ | |
+ public class SkStackResponse { | |
+ public SkStackResponseStatus Status { get; } | |
+ public ReadOnlyMemory<byte> StatusText { get; } | |
+ public bool Success { get; } | |
+ } | |
+ | |
+ public class SkStackResponseException : InvalidOperationException { | |
+ public SkStackResponseException() {} | |
+ public SkStackResponseException(string message) {} | |
+ public SkStackResponseException(string message, Exception? innerException = null) {} | |
+ } | |
+ | |
+ public class SkStackResponse<TPayload> : SkStackResponse { | |
+ public TPayload Payload { get; } | |
+ } | |
+ | |
+ public class SkStackUartIOException : SkStackErrorResponseException { | |
+ } | |
+ | |
+ public class SkStackUdpSendFailedException : InvalidOperationException { | |
+ public SkStackUdpSendFailedException() {} | |
+ public SkStackUdpSendFailedException(string message) {} | |
+ public SkStackUdpSendFailedException(string message, Exception? innerException = null) {} | |
+ public SkStackUdpSendFailedException(string message, SkStackUdpPortHandle portHandle, IPAddress peerAddress, Exception? innerException = null) {} | |
+ | |
+ public IPAddress? PeerAddress { get; } | |
+ public SkStackUdpPortHandle PortHandle { get; } | |
+ } | |
+ | |
+ public class SkStackUdpSendResultIndeterminateException : InvalidOperationException { | |
+ public SkStackUdpSendResultIndeterminateException() {} | |
+ public SkStackUdpSendResultIndeterminateException(string message) {} | |
+ public SkStackUdpSendResultIndeterminateException(string message, Exception? innerException = null) {} | |
+ } | |
+ | |
+ public readonly struct SkStackChannel : | |
+ IComparable<SkStackChannel>, | |
+ IEquatable<SkStackChannel> | |
+ { | |
+ public static readonly IReadOnlyDictionary<int, SkStackChannel> Channels; // = "System.Collections.Generic.Dictionary`2[System.Int32,Smdn.Net.SkStackIP.SkStackChannel]" | |
+ public static readonly SkStackChannel Empty; // = "0ch (S02=0x00, 0 MHz)" | |
+ | |
+ public static SkStackChannel Channel33 { get; } | |
+ public static SkStackChannel Channel34 { get; } | |
+ public static SkStackChannel Channel35 { get; } | |
+ public static SkStackChannel Channel36 { get; } | |
+ public static SkStackChannel Channel37 { get; } | |
+ public static SkStackChannel Channel38 { get; } | |
+ public static SkStackChannel Channel39 { get; } | |
+ public static SkStackChannel Channel40 { get; } | |
+ public static SkStackChannel Channel41 { get; } | |
+ public static SkStackChannel Channel42 { get; } | |
+ public static SkStackChannel Channel43 { get; } | |
+ public static SkStackChannel Channel44 { get; } | |
+ public static SkStackChannel Channel45 { get; } | |
+ public static SkStackChannel Channel46 { get; } | |
+ public static SkStackChannel Channel47 { get; } | |
+ public static SkStackChannel Channel48 { get; } | |
+ public static SkStackChannel Channel49 { get; } | |
+ public static SkStackChannel Channel50 { get; } | |
+ public static SkStackChannel Channel51 { get; } | |
+ public static SkStackChannel Channel52 { get; } | |
+ public static SkStackChannel Channel53 { get; } | |
+ public static SkStackChannel Channel54 { get; } | |
+ public static SkStackChannel Channel55 { get; } | |
+ public static SkStackChannel Channel56 { get; } | |
+ public static SkStackChannel Channel57 { get; } | |
+ public static SkStackChannel Channel58 { get; } | |
+ public static SkStackChannel Channel59 { get; } | |
+ public static SkStackChannel Channel60 { get; } | |
+ | |
+ public static bool operator == (SkStackChannel x, SkStackChannel y) {} | |
+ public static bool operator != (SkStackChannel x, SkStackChannel y) {} | |
+ | |
+ public int ChannelNumber { get; } | |
+ public decimal FrequencyMHz { get; } | |
+ public bool IsEmpty { get; } | |
+ | |
+ public bool Equals(SkStackChannel other) {} | |
+ public override bool Equals(object? obj) {} | |
+ public override int GetHashCode() {} | |
+ int IComparable<SkStackChannel>.CompareTo(SkStackChannel other) {} | |
+ public override string ToString() {} | |
+ } | |
+ | |
+ public readonly struct SkStackPanDescription { | |
+ public SkStackChannel Channel { get; } | |
+ public int ChannelPage { get; } | |
+ public int Id { get; } | |
+ public PhysicalAddress MacAddress { get; } | |
+ public uint PairingId { get; } | |
+ public decimal Rssi { get; } | |
+ | |
+ public override string ToString() {} | |
+ } | |
+ | |
+ public readonly struct SkStackUdpPort { | |
+ public static readonly SkStackUdpPort Null; // = "0 (#0)" | |
+ | |
+ public SkStackUdpPortHandle Handle { get; } | |
+ public bool IsNull { get; } | |
+ public bool IsUnused { get; } | |
+ public int Port { get; } | |
+ | |
+ public override string ToString() {} | |
+ } | |
+} | |
+ | |
+namespace Smdn.Net.SkStackIP.Protocol { | |
+ public delegate TResult SkStackSequenceParser<TResult>(ISkStackSequenceParserContext context); | |
+ | |
+ public interface ISkStackCommandLineWriter { | |
+ void WriteMaskedToken(ReadOnlySpan<byte> token); | |
+ void WriteToken(ReadOnlySpan<byte> token); | |
+ } | |
+ | |
+ public interface ISkStackSequenceParserContext { | |
+ ReadOnlySequence<byte> UnparsedSequence { get; } | |
+ | |
+ void Complete(); | |
+ void Complete(SequenceReader<byte> consumedReader); | |
+ void Continue(); | |
+ ISkStackSequenceParserContext CreateCopy(); | |
+ virtual SequenceReader<byte> CreateReader() {} | |
+ void Ignore(); | |
+ void SetAsIncomplete(); | |
+ void SetAsIncomplete(SequenceReader<byte> incompleteReader); | |
+ } | |
+ | |
+ public abstract class SkStackProtocolSyntax { | |
+ public static SkStackProtocolSyntax Default { get; } | |
+ | |
+ protected SkStackProtocolSyntax() {} | |
+ | |
+ public abstract ReadOnlySpan<byte> EndOfCommandLine { get; } | |
+ public virtual ReadOnlySpan<byte> EndOfEchobackLine { get; } | |
+ public abstract ReadOnlySpan<byte> EndOfStatusLine { get; } | |
+ public abstract bool ExpectStatusLine { get; } | |
+ } | |
+ | |
+ public static class SkStackTokenParser { | |
+ public static bool Expect<TValue>(ref SequenceReader<byte> reader, int length, Converter<ReadOnlySequence<byte>, TValue> converter, [NotNullWhen(true)] out TValue @value) {} | |
+ public static bool ExpectADDR16(ref SequenceReader<byte> reader, out ushort @value) {} | |
+ public static bool ExpectADDR64(ref SequenceReader<byte> reader, [NotNullWhen(true)] out PhysicalAddress? @value) {} | |
+ public static bool ExpectBinary(ref SequenceReader<byte> reader, out bool @value) {} | |
+ public static bool ExpectCHANNEL(ref SequenceReader<byte> reader, out SkStackChannel @value) {} | |
+ public static bool ExpectCharArray(ref SequenceReader<byte> reader, [NotNullWhen(true)] out string? @value) {} | |
+ public static bool ExpectCharArray(ref SequenceReader<byte> reader, out ReadOnlyMemory<byte> @value) {} | |
+ public static bool ExpectDecimalNumber(ref SequenceReader<byte> reader, int length, out uint @value) {} | |
+ public static bool ExpectDecimalNumber(ref SequenceReader<byte> reader, out uint @value) {} | |
+ public static bool ExpectEndOfLine(ref SequenceReader<byte> reader) {} | |
+ public static bool ExpectIPADDR(ref SequenceReader<byte> reader, [NotNullWhen(true)] out IPAddress? @value) {} | |
+ public static bool ExpectSequence(ref SequenceReader<byte> reader, ReadOnlySpan<byte> expectedSequence) {} | |
+ public static bool ExpectToken(ref SequenceReader<byte> reader, ReadOnlySpan<byte> expectedToken) {} | |
+ public static bool ExpectUINT16(ref SequenceReader<byte> reader, out ushort @value) {} | |
+ public static bool ExpectUINT32(ref SequenceReader<byte> reader, out uint @value) {} | |
+ public static bool ExpectUINT64(ref SequenceReader<byte> reader, out ulong @value) {} | |
+ public static bool ExpectUINT8(ref SequenceReader<byte> reader, out byte @value) {} | |
+ public static void ToByteSequence(ReadOnlySequence<byte> hexTextSequence, int byteSequenceLength, Span<byte> destination) {} | |
+ public static bool TryExpectStatusLine(ref SequenceReader<byte> reader, out SkStackResponseStatus status) {} | |
+ public static OperationStatus TryExpectToken(ref SequenceReader<byte> reader, ReadOnlySpan<byte> expectedToken) {} | |
+ } | |
+ | |
+ public class SkStackUnexpectedResponseException : SkStackResponseException { | |
+ public string? CausedText { get; } | |
+ } | |
+} | |
+// API list generated by Smdn.Reflection.ReverseGenerating.ListApi.MSBuild.Tasks v1.4.0.0. | |
+// Smdn.Reflection.ReverseGenerating.ListApi.Core v1.3.0.0 (https://github.com/smdn/Smdn.Reflection.ReverseGenerating) | |
diff --git a/doc/api-list/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP-net8.0.apilist.cs b/doc/api-list/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP-net8.0.apilist.cs | |
new file mode 100644 | |
index 0000000..d28e48b | |
--- /dev/null | |
+++ b/doc/api-list/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP-net8.0.apilist.cs | |
@@ -0,0 +1,464 @@ | |
+// Smdn.Net.SkStackIP.dll (Smdn.Net.SkStackIP-1.0.0) | |
+// Name: Smdn.Net.SkStackIP | |
+// AssemblyVersion: 1.0.0.0 | |
+// InformationalVersion: 1.0.0+655c155832ea35fece55fe3cd2467b473922319c | |
+// TargetFramework: .NETCoreApp,Version=v8.0 | |
+// Configuration: Release | |
+// Referenced assemblies: | |
+// Microsoft.Extensions.Logging.Abstractions, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60 | |
+// Polly.Core, Version=8.0.0.0, Culture=neutral, PublicKeyToken=c8a3ffc3f8f825cc | |
+// Smdn.Fundamental.ControlPicture, Version=3.0.0.1, Culture=neutral | |
+// Smdn.Fundamental.PrintableEncoding.Hexadecimal, Version=3.0.0.0, Culture=neutral | |
+// System.Collections, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a | |
+// System.ComponentModel.Primitives, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a | |
+// System.IO.Pipelines, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51 | |
+// System.Linq, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a | |
+// System.Memory, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51 | |
+// System.Net.NetworkInformation, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a | |
+// System.Net.Primitives, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a | |
+// System.Runtime, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a | |
+// System.Threading, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a | |
+#nullable enable annotations | |
+ | |
+using System; | |
+using System.Buffers; | |
+using System.Collections.Generic; | |
+using System.ComponentModel; | |
+using System.Diagnostics.CodeAnalysis; | |
+using System.IO; | |
+using System.IO.Pipelines; | |
+using System.Net; | |
+using System.Net.NetworkInformation; | |
+using System.Threading; | |
+using System.Threading.Tasks; | |
+using Microsoft.Extensions.Logging; | |
+using Polly; | |
+using Smdn.Net.SkStackIP; | |
+using Smdn.Net.SkStackIP.Protocol; | |
+ | |
+namespace Smdn.Net.SkStackIP { | |
+ public enum SkStackERXUDPDataFormat : int { | |
+ Binary = 0, | |
+ HexAsciiText = 1, | |
+ } | |
+ | |
+ public enum SkStackErrorCode : int { | |
+ ER01 = 1, | |
+ ER02 = 2, | |
+ ER03 = 3, | |
+ ER04 = 4, | |
+ ER05 = 5, | |
+ ER06 = 6, | |
+ ER07 = 7, | |
+ ER08 = 8, | |
+ ER09 = 9, | |
+ ER10 = 10, | |
+ Undefined = 0, | |
+ } | |
+ | |
+ public enum SkStackEventNumber : byte { | |
+ ActiveScanCompleted = 34, | |
+ BeaconReceived = 32, | |
+ EchoRequestReceived = 5, | |
+ EnergyDetectScanCompleted = 31, | |
+ NeighborAdvertisementReceived = 2, | |
+ NeighborSolicitationReceived = 1, | |
+ PanaSessionEstablishmentCompleted = 37, | |
+ PanaSessionEstablishmentError = 36, | |
+ PanaSessionExpired = 41, | |
+ PanaSessionTerminationCompleted = 39, | |
+ PanaSessionTerminationRequestReceived = 38, | |
+ PanaSessionTerminationTimedOut = 40, | |
+ TransmissionTimeControlLimitationActivated = 50, | |
+ TransmissionTimeControlLimitationDeactivated = 51, | |
+ UdpSendCompleted = 33, | |
+ Undefined = 0, | |
+ WakeupSignalReceived = 192, | |
+ } | |
+ | |
+ public enum SkStackResponseStatus : int { | |
+ Fail = -1, | |
+ Ok = 1, | |
+ Undetermined = 0, | |
+ } | |
+ | |
+ public enum SkStackUdpEncryption : byte { | |
+ EncryptIfAble = 2, | |
+ ForceEncrypt = 1, | |
+ ForcePlainText = 0, | |
+ } | |
+ | |
+ public enum SkStackUdpPortHandle : byte { | |
+ Handle1 = 1, | |
+ Handle2 = 2, | |
+ Handle3 = 3, | |
+ Handle4 = 4, | |
+ Handle5 = 5, | |
+ Handle6 = 6, | |
+ None = 0, | |
+ } | |
+ | |
+ public abstract class SkStackActiveScanOptions : ICloneable { | |
+ public static SkStackActiveScanOptions Default { get; } | |
+ public static SkStackActiveScanOptions Null { get; } | |
+ public static SkStackActiveScanOptions ScanUntilFind { get; } | |
+ | |
+ public static SkStackActiveScanOptions Create(IEnumerable<int> scanDurationGenerator, PhysicalAddress paaMacAddress) {} | |
+ public static SkStackActiveScanOptions Create(IEnumerable<int> scanDurationGenerator, Predicate<SkStackPanDescription>? paaSelector = null) {} | |
+ | |
+ protected SkStackActiveScanOptions() {} | |
+ | |
+ public abstract SkStackActiveScanOptions Clone(); | |
+ object ICloneable.Clone() {} | |
+ } | |
+ | |
+ public class SkStackClient : IDisposable { | |
+ public static readonly TimeSpan SKSCANDefaultDuration; // = "00:00:00.0480000" | |
+ public static readonly TimeSpan SKSCANMaxDuration; // = "00:02:37.2960000" | |
+ public static readonly TimeSpan SKSCANMinDuration; // = "00:00:00.0192000" | |
+ | |
+ public event EventHandler<SkStackPanaSessionEventArgs>? PanaSessionEstablished; | |
+ public event EventHandler<SkStackPanaSessionEventArgs>? PanaSessionExpired; | |
+ public event EventHandler<SkStackPanaSessionEventArgs>? PanaSessionTerminated; | |
+ public event EventHandler<SkStackEventArgs>? Slept; | |
+ public event EventHandler<SkStackEventArgs>? WokeUp; | |
+ | |
+ public SkStackClient(PipeWriter sender, PipeReader receiver, SkStackERXUDPDataFormat erxudpDataFormat = SkStackERXUDPDataFormat.Binary, ILogger? logger = null) {} | |
+ public SkStackClient(Stream stream, bool leaveStreamOpen = true, SkStackERXUDPDataFormat erxudpDataFormat = SkStackERXUDPDataFormat.Binary, ILogger? logger = null) {} | |
+ | |
+ public SkStackERXUDPDataFormat ERXUDPDataFormat { get; protected set; } | |
+ [MemberNotNullWhen(true, "PanaSessionPeerAddress")] | |
+ public bool IsPanaSessionAlive { [MemberNotNullWhen(true, "PanaSessionPeerAddress")] get; } | |
+ protected ILogger? Logger { get; } | |
+ public IPAddress? PanaSessionPeerAddress { get; } | |
+ public TimeSpan ReceiveResponseDelay { get; set; } | |
+ public TimeSpan ReceiveUdpPollingInterval { get; set; } | |
+ public ISynchronizeInvoke? SynchronizingObject { get; set; } | |
+ | |
+ public ValueTask<IReadOnlyList<SkStackPanDescription>> ActiveScanAsync(ReadOnlyMemory<byte> rbid, ReadOnlyMemory<byte> password, SkStackActiveScanOptions? scanOptions = null, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackPanaSessionInfo> AuthenticateAsPanaClientAsync(ReadOnlyMemory<byte> rbid, ReadOnlyMemory<byte> password, IPAddress paaAddress, SkStackChannel channel, int panId, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackPanaSessionInfo> AuthenticateAsPanaClientAsync(ReadOnlyMemory<byte> rbid, ReadOnlyMemory<byte> password, IPAddress paaAddress, int channelNumber, int panId, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackPanaSessionInfo> AuthenticateAsPanaClientAsync(ReadOnlyMemory<byte> rbid, ReadOnlyMemory<byte> password, PhysicalAddress paaMacAddress, SkStackChannel channel, int panId, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackPanaSessionInfo> AuthenticateAsPanaClientAsync(ReadOnlyMemory<byte> rbid, ReadOnlyMemory<byte> password, PhysicalAddress paaMacAddress, int channelNumber, int panId, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackPanaSessionInfo> AuthenticateAsPanaClientAsync(ReadOnlyMemory<byte> rbid, ReadOnlyMemory<byte> password, SkStackActiveScanOptions? scanOptions = null, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackPanaSessionInfo> AuthenticateAsPanaClientAsync(ReadOnlyMemory<byte> rbid, ReadOnlyMemory<byte> password, SkStackPanDescription pan, CancellationToken cancellationToken = default) {} | |
+ public async ValueTask<IPAddress> ConvertToIPv6LinkLocalAddressAsync(PhysicalAddress macAddress, CancellationToken cancellationToken = default) {} | |
+ public ValueTask DisableFlashMemoryAutoLoadAsync(CancellationToken cancellationToken = default) {} | |
+ protected virtual void Dispose(bool disposing) {} | |
+ public void Dispose() {} | |
+ public ValueTask EnableFlashMemoryAutoLoadAsync(CancellationToken cancellationToken = default) {} | |
+ public async ValueTask<IReadOnlyList<IPAddress>> GetAvailableAddressListAsync(CancellationToken cancellationToken = default) {} | |
+ public async ValueTask<IReadOnlyList<SkStackUdpPort>> GetListeningUdpPortListAsync(CancellationToken cancellationToken = default) {} | |
+ public async ValueTask<IReadOnlyDictionary<IPAddress, PhysicalAddress>> GetNeighborCacheListAsync(CancellationToken cancellationToken = default) {} | |
+ public async ValueTask<IReadOnlyList<SkStackUdpPortHandle>> GetUnusedUdpPortHandleListAsync(CancellationToken cancellationToken = default) {} | |
+ public async ValueTask LoadFlashMemoryAsync(CancellationToken cancellationToken = default) {} | |
+ public async ValueTask<SkStackUdpPort> PrepareUdpPortAsync(int port, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<IPAddress> ReceiveUdpAsync(int port, IBufferWriter<byte> buffer, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<IPAddress> ReceiveUdpEchonetLiteAsync(IBufferWriter<byte> buffer, CancellationToken cancellationToken = default) {} | |
+ public ValueTask SaveFlashMemoryAsync(SkStackFlashMemoryWriteRestriction restriction, CancellationToken cancellationToken = default) {} | |
+ internal protected ValueTask<SkStackResponse<TPayload>> SendCommandAsync<TPayload>(ReadOnlyMemory<byte> command, Action<ISkStackCommandLineWriter>? writeArguments, SkStackSequenceParser<TPayload> parseResponsePayload, SkStackProtocolSyntax? syntax = null, bool throwIfErrorStatus = true, CancellationToken cancellationToken = default) {} | |
+ internal protected async ValueTask<SkStackResponse> SendCommandAsync(ReadOnlyMemory<byte> command, Action<ISkStackCommandLineWriter>? writeArguments = null, SkStackProtocolSyntax? syntax = null, bool throwIfErrorStatus = true, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse> SendSKADDNBRAsync(IPAddress ipv6Address, PhysicalAddress macAddress, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse<string>> SendSKAPPVERAsync(CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse> SendSKDSLEEPAsync(bool waitUntilWakeUp = false, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse> SendSKERASEAsync(CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse<(IPAddress LinkLocalAddress, PhysicalAddress MacAddress, SkStackChannel Channel, int PanId, int Addr16)>> SendSKINFOAsync(CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse> SendSKJOINAsync(IPAddress ipv6address, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse<IPAddress>> SendSKLL64Async(PhysicalAddress macAddress, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse> SendSKLOADAsync(CancellationToken cancellationToken = default) {} | |
+ public ValueTask<(SkStackResponse Response, IPAddress Address)> SendSKREJOINAsync(CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse> SendSKRESETAsync(CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse> SendSKSAVEAsync(CancellationToken cancellationToken = default) {} | |
+ public ValueTask<(SkStackResponse Response, IReadOnlyList<SkStackPanDescription> PanDescriptions)> SendSKSCANActiveScanAsync(TimeSpan duration = default, uint channelMask = uint.MaxValue, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<(SkStackResponse Response, IReadOnlyList<SkStackPanDescription> PanDescriptions)> SendSKSCANActiveScanAsync(int durationFactor, uint channelMask = uint.MaxValue, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<(SkStackResponse Response, IReadOnlyList<SkStackPanDescription> PanDescriptions)> SendSKSCANActiveScanPairAsync(TimeSpan duration = default, uint channelMask = uint.MaxValue, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<(SkStackResponse Response, IReadOnlyList<SkStackPanDescription> PanDescriptions)> SendSKSCANActiveScanPairAsync(int durationFactor, uint channelMask = uint.MaxValue, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<(SkStackResponse Response, IReadOnlyDictionary<SkStackChannel, decimal> ScanResult)> SendSKSCANEnergyDetectScanAsync(TimeSpan duration = default, uint channelMask = uint.MaxValue, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<(SkStackResponse Response, IReadOnlyDictionary<SkStackChannel, decimal> ScanResult)> SendSKSCANEnergyDetectScanAsync(int durationFactor, uint channelMask = uint.MaxValue, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<(SkStackResponse Response, bool IsCompletedSuccessfully)> SendSKSENDTOAsync(SkStackUdpPort port, IPAddress destinationAddress, int destinationPort, ReadOnlyMemory<byte> data, SkStackUdpEncryption encryption = SkStackUdpEncryption.EncryptIfAble, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<(SkStackResponse Response, bool IsCompletedSuccessfully)> SendSKSENDTOAsync(SkStackUdpPort port, IPEndPoint destination, ReadOnlyMemory<byte> data, SkStackUdpEncryption encryption = SkStackUdpEncryption.EncryptIfAble, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<(SkStackResponse Response, bool IsCompletedSuccessfully)> SendSKSENDTOAsync(SkStackUdpPortHandle handle, IPAddress destinationAddress, int destinationPort, ReadOnlyMemory<byte> data, SkStackUdpEncryption encryption = SkStackUdpEncryption.EncryptIfAble, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<(SkStackResponse Response, bool IsCompletedSuccessfully)> SendSKSENDTOAsync(SkStackUdpPortHandle handle, IPEndPoint destination, ReadOnlyMemory<byte> data, SkStackUdpEncryption encryption = SkStackUdpEncryption.EncryptIfAble, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse> SendSKSETPWDAsync(ReadOnlyMemory<byte> password, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse> SendSKSETPWDAsync(ReadOnlyMemory<char> password, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse> SendSKSETRBIDAsync(ReadOnlyMemory<byte> id, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse> SendSKSETRBIDAsync(ReadOnlyMemory<char> id, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse<TValue>> SendSKSREGAsync<TValue>(SkStackRegister.RegisterEntry<TValue> register, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse> SendSKSREGAsync<TValue>(SkStackRegister.RegisterEntry<TValue> register, TValue @value, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse<IReadOnlyList<IPAddress>>> SendSKTABLEAvailableAddressListAsync(CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse<IReadOnlyList<SkStackUdpPort>>> SendSKTABLEListeningPortListAsync(CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse<IReadOnlyDictionary<IPAddress, PhysicalAddress>>> SendSKTABLENeighborCacheListAsync(CancellationToken cancellationToken = default) {} | |
+ public async ValueTask<(SkStackResponse Response, bool IsCompletedSuccessfully)> SendSKTERMAsync(CancellationToken cancellationToken = default) {} | |
+ public ValueTask<(SkStackResponse Response, SkStackUdpPort UdpPort)> SendSKUDPPORTAsync(SkStackUdpPortHandle handle, int port, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse> SendSKUDPPORTUnsetAsync(SkStackUdpPortHandle handle, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse<Version>> SendSKVERAsync(CancellationToken cancellationToken = default) {} | |
+ public ValueTask SendUdpEchonetLiteAsync(ReadOnlyMemory<byte> buffer, ResiliencePipeline? resiliencePipeline = null, CancellationToken cancellationToken = default) {} | |
+ protected async ValueTask SetFlashMemoryAutoLoadAsync(bool trueIfEnable, CancellationToken cancellationToken = default) {} | |
+ public void StartCapturingUdpReceiveEvents(int port) {} | |
+ public void StopCapturingUdpReceiveEvents(int port) {} | |
+ public ValueTask<bool> TerminatePanaSessionAsync(CancellationToken cancellationToken = default) {} | |
+ protected void ThrowIfDisposed() {} | |
+ internal protected void ThrowIfPanaSessionAlreadyEstablished() {} | |
+ [MemberNotNull("PanaSessionPeerAddress")] | |
+ internal protected void ThrowIfPanaSessionIsNotEstablished() {} | |
+ } | |
+ | |
+ public class SkStackCommandNotSupportedException : SkStackErrorResponseException { | |
+ } | |
+ | |
+ public class SkStackErrorResponseException : SkStackResponseException { | |
+ public SkStackErrorCode ErrorCode { get; } | |
+ public string ErrorText { get; } | |
+ public SkStackResponse Response { get; } | |
+ } | |
+ | |
+ public class SkStackEventArgs : EventArgs { | |
+ public SkStackEventNumber EventNumber { get; } | |
+ } | |
+ | |
+ public class SkStackFlashMemoryIOException : SkStackErrorResponseException { | |
+ } | |
+ | |
+ public abstract class SkStackFlashMemoryWriteRestriction { | |
+ public static SkStackFlashMemoryWriteRestriction CreateGrantIfElapsed(TimeSpan interval) {} | |
+ public static SkStackFlashMemoryWriteRestriction DangerousCreateAlwaysGrant() {} | |
+ | |
+ protected SkStackFlashMemoryWriteRestriction() {} | |
+ | |
+ internal protected abstract bool IsRestricted(); | |
+ } | |
+ | |
+ public static class SkStackKnownPortNumbers { | |
+ public const int EchonetLite = 3610; | |
+ public const int Pana = 716; | |
+ } | |
+ | |
+ public class SkStackPanaSessionEstablishmentException : SkStackPanaSessionException { | |
+ } | |
+ | |
+ public sealed class SkStackPanaSessionEventArgs : SkStackEventArgs { | |
+ public IPAddress PanaSessionPeerAddress { get; } | |
+ } | |
+ | |
+ public abstract class SkStackPanaSessionException : InvalidOperationException { | |
+ public IPAddress Address { get; } | |
+ public SkStackEventNumber EventNumber { get; } | |
+ } | |
+ | |
+ public sealed class SkStackPanaSessionInfo { | |
+ public SkStackChannel Channel { get; } | |
+ public IPAddress LocalAddress { get; } | |
+ public PhysicalAddress LocalMacAddress { get; } | |
+ public int PanId { get; } | |
+ public IPAddress PeerAddress { get; } | |
+ public PhysicalAddress PeerMacAddress { get; } | |
+ } | |
+ | |
+ public static class SkStackRegister { | |
+ public abstract class RegisterEntry<TValue> { | |
+ private protected delegate bool ExpectValueFunc(ref SequenceReader<byte> reader, out TValue @value); | |
+ | |
+ public bool IsReadable { get; } | |
+ public bool IsWritable { get; } | |
+ public TValue MaxValue { get; } | |
+ public TValue MinValue { get; } | |
+ public string Name { get; } | |
+ } | |
+ | |
+ public static SkStackRegister.RegisterEntry<bool> AcceptIcmpEcho { get; } | |
+ public static SkStackRegister.RegisterEntry<ulong> AccumulatedSendTimeInMilliseconds { get; } | |
+ public static SkStackRegister.RegisterEntry<SkStackChannel> Channel { get; } | |
+ public static SkStackRegister.RegisterEntry<bool> EnableAutoLoad { get; } | |
+ public static SkStackRegister.RegisterEntry<bool> EnableAutoReauthentication { get; } | |
+ public static SkStackRegister.RegisterEntry<bool> EnableEchoback { get; } | |
+ public static SkStackRegister.RegisterEntry<bool> EncryptIPMulticast { get; } | |
+ public static SkStackRegister.RegisterEntry<uint> FrameCounter { get; } | |
+ public static SkStackRegister.RegisterEntry<bool> IsSendingRestricted { get; } | |
+ public static SkStackRegister.RegisterEntry<ReadOnlyMemory<byte>> PairingId { get; } | |
+ public static SkStackRegister.RegisterEntry<ushort> PanId { get; } | |
+ public static SkStackRegister.RegisterEntry<TimeSpan> PanaSessionLifetimeInSeconds { get; } | |
+ public static SkStackRegister.RegisterEntry<bool> RespondBeaconRequest { get; } | |
+ public static SkStackRegister.RegisterEntry<SkStackChannel> S02 { get; } | |
+ public static SkStackRegister.RegisterEntry<ushort> S03 { get; } | |
+ public static SkStackRegister.RegisterEntry<uint> S07 { get; } | |
+ public static SkStackRegister.RegisterEntry<ReadOnlyMemory<byte>> S0A { get; } | |
+ public static SkStackRegister.RegisterEntry<bool> S15 { get; } | |
+ public static SkStackRegister.RegisterEntry<TimeSpan> S16 { get; } | |
+ public static SkStackRegister.RegisterEntry<bool> S17 { get; } | |
+ public static SkStackRegister.RegisterEntry<bool> SA0 { get; } | |
+ public static SkStackRegister.RegisterEntry<bool> SA1 { get; } | |
+ public static SkStackRegister.RegisterEntry<bool> SFB { get; } | |
+ public static SkStackRegister.RegisterEntry<ulong> SFD { get; } | |
+ public static SkStackRegister.RegisterEntry<bool> SFE { get; } | |
+ public static SkStackRegister.RegisterEntry<bool> SFF { get; } | |
+ } | |
+ | |
+ public class SkStackResponse { | |
+ public SkStackResponseStatus Status { get; } | |
+ public ReadOnlyMemory<byte> StatusText { get; } | |
+ public bool Success { get; } | |
+ } | |
+ | |
+ public class SkStackResponseException : InvalidOperationException { | |
+ public SkStackResponseException() {} | |
+ public SkStackResponseException(string message) {} | |
+ public SkStackResponseException(string message, Exception? innerException = null) {} | |
+ } | |
+ | |
+ public class SkStackResponse<TPayload> : SkStackResponse { | |
+ public TPayload Payload { get; } | |
+ } | |
+ | |
+ public class SkStackUartIOException : SkStackErrorResponseException { | |
+ } | |
+ | |
+ public class SkStackUdpSendFailedException : InvalidOperationException { | |
+ public SkStackUdpSendFailedException() {} | |
+ public SkStackUdpSendFailedException(string message) {} | |
+ public SkStackUdpSendFailedException(string message, Exception? innerException = null) {} | |
+ public SkStackUdpSendFailedException(string message, SkStackUdpPortHandle portHandle, IPAddress peerAddress, Exception? innerException = null) {} | |
+ | |
+ public IPAddress? PeerAddress { get; } | |
+ public SkStackUdpPortHandle PortHandle { get; } | |
+ } | |
+ | |
+ public class SkStackUdpSendResultIndeterminateException : InvalidOperationException { | |
+ public SkStackUdpSendResultIndeterminateException() {} | |
+ public SkStackUdpSendResultIndeterminateException(string message) {} | |
+ public SkStackUdpSendResultIndeterminateException(string message, Exception? innerException = null) {} | |
+ } | |
+ | |
+ public readonly struct SkStackChannel : | |
+ IComparable<SkStackChannel>, | |
+ IEquatable<SkStackChannel> | |
+ { | |
+ public static readonly IReadOnlyDictionary<int, SkStackChannel> Channels; // = "System.Collections.Generic.Dictionary`2[System.Int32,Smdn.Net.SkStackIP.SkStackChannel]" | |
+ public static readonly SkStackChannel Empty; // = "0ch (S02=0x00, 0 MHz)" | |
+ | |
+ public static SkStackChannel Channel33 { get; } | |
+ public static SkStackChannel Channel34 { get; } | |
+ public static SkStackChannel Channel35 { get; } | |
+ public static SkStackChannel Channel36 { get; } | |
+ public static SkStackChannel Channel37 { get; } | |
+ public static SkStackChannel Channel38 { get; } | |
+ public static SkStackChannel Channel39 { get; } | |
+ public static SkStackChannel Channel40 { get; } | |
+ public static SkStackChannel Channel41 { get; } | |
+ public static SkStackChannel Channel42 { get; } | |
+ public static SkStackChannel Channel43 { get; } | |
+ public static SkStackChannel Channel44 { get; } | |
+ public static SkStackChannel Channel45 { get; } | |
+ public static SkStackChannel Channel46 { get; } | |
+ public static SkStackChannel Channel47 { get; } | |
+ public static SkStackChannel Channel48 { get; } | |
+ public static SkStackChannel Channel49 { get; } | |
+ public static SkStackChannel Channel50 { get; } | |
+ public static SkStackChannel Channel51 { get; } | |
+ public static SkStackChannel Channel52 { get; } | |
+ public static SkStackChannel Channel53 { get; } | |
+ public static SkStackChannel Channel54 { get; } | |
+ public static SkStackChannel Channel55 { get; } | |
+ public static SkStackChannel Channel56 { get; } | |
+ public static SkStackChannel Channel57 { get; } | |
+ public static SkStackChannel Channel58 { get; } | |
+ public static SkStackChannel Channel59 { get; } | |
+ public static SkStackChannel Channel60 { get; } | |
+ | |
+ public static bool operator == (SkStackChannel x, SkStackChannel y) {} | |
+ public static bool operator != (SkStackChannel x, SkStackChannel y) {} | |
+ | |
+ public int ChannelNumber { get; } | |
+ public decimal FrequencyMHz { get; } | |
+ public bool IsEmpty { get; } | |
+ | |
+ public bool Equals(SkStackChannel other) {} | |
+ public override bool Equals(object? obj) {} | |
+ public override int GetHashCode() {} | |
+ int IComparable<SkStackChannel>.CompareTo(SkStackChannel other) {} | |
+ public override string ToString() {} | |
+ } | |
+ | |
+ public readonly struct SkStackPanDescription { | |
+ public SkStackChannel Channel { get; } | |
+ public int ChannelPage { get; } | |
+ public int Id { get; } | |
+ public PhysicalAddress MacAddress { get; } | |
+ public uint PairingId { get; } | |
+ public decimal Rssi { get; } | |
+ | |
+ public override string ToString() {} | |
+ } | |
+ | |
+ public readonly struct SkStackUdpPort { | |
+ public static readonly SkStackUdpPort Null; // = "0 (#0)" | |
+ | |
+ public SkStackUdpPortHandle Handle { get; } | |
+ public bool IsNull { get; } | |
+ public bool IsUnused { get; } | |
+ public int Port { get; } | |
+ | |
+ public override string ToString() {} | |
+ } | |
+} | |
+ | |
+namespace Smdn.Net.SkStackIP.Protocol { | |
+ public delegate TResult SkStackSequenceParser<TResult>(ISkStackSequenceParserContext context); | |
+ | |
+ public interface ISkStackCommandLineWriter { | |
+ void WriteMaskedToken(ReadOnlySpan<byte> token); | |
+ void WriteToken(ReadOnlySpan<byte> token); | |
+ } | |
+ | |
+ public interface ISkStackSequenceParserContext { | |
+ ReadOnlySequence<byte> UnparsedSequence { get; } | |
+ | |
+ void Complete(); | |
+ void Complete(SequenceReader<byte> consumedReader); | |
+ void Continue(); | |
+ ISkStackSequenceParserContext CreateCopy(); | |
+ virtual SequenceReader<byte> CreateReader() {} | |
+ void Ignore(); | |
+ void SetAsIncomplete(); | |
+ void SetAsIncomplete(SequenceReader<byte> incompleteReader); | |
+ } | |
+ | |
+ public abstract class SkStackProtocolSyntax { | |
+ public static SkStackProtocolSyntax Default { get; } | |
+ | |
+ protected SkStackProtocolSyntax() {} | |
+ | |
+ public abstract ReadOnlySpan<byte> EndOfCommandLine { get; } | |
+ public virtual ReadOnlySpan<byte> EndOfEchobackLine { get; } | |
+ public abstract ReadOnlySpan<byte> EndOfStatusLine { get; } | |
+ public abstract bool ExpectStatusLine { get; } | |
+ } | |
+ | |
+ public static class SkStackTokenParser { | |
+ public static bool Expect<TValue>(ref SequenceReader<byte> reader, int length, Converter<ReadOnlySequence<byte>, TValue> converter, [NotNullWhen(true)] out TValue @value) {} | |
+ public static bool ExpectADDR16(ref SequenceReader<byte> reader, out ushort @value) {} | |
+ public static bool ExpectADDR64(ref SequenceReader<byte> reader, [NotNullWhen(true)] out PhysicalAddress? @value) {} | |
+ public static bool ExpectBinary(ref SequenceReader<byte> reader, out bool @value) {} | |
+ public static bool ExpectCHANNEL(ref SequenceReader<byte> reader, out SkStackChannel @value) {} | |
+ public static bool ExpectCharArray(ref SequenceReader<byte> reader, [NotNullWhen(true)] out string? @value) {} | |
+ public static bool ExpectCharArray(ref SequenceReader<byte> reader, out ReadOnlyMemory<byte> @value) {} | |
+ public static bool ExpectDecimalNumber(ref SequenceReader<byte> reader, int length, out uint @value) {} | |
+ public static bool ExpectDecimalNumber(ref SequenceReader<byte> reader, out uint @value) {} | |
+ public static bool ExpectEndOfLine(ref SequenceReader<byte> reader) {} | |
+ public static bool ExpectIPADDR(ref SequenceReader<byte> reader, [NotNullWhen(true)] out IPAddress? @value) {} | |
+ public static bool ExpectSequence(ref SequenceReader<byte> reader, ReadOnlySpan<byte> expectedSequence) {} | |
+ public static bool ExpectToken(ref SequenceReader<byte> reader, ReadOnlySpan<byte> expectedToken) {} | |
+ public static bool ExpectUINT16(ref SequenceReader<byte> reader, out ushort @value) {} | |
+ public static bool ExpectUINT32(ref SequenceReader<byte> reader, out uint @value) {} | |
+ public static bool ExpectUINT64(ref SequenceReader<byte> reader, out ulong @value) {} | |
+ public static bool ExpectUINT8(ref SequenceReader<byte> reader, out byte @value) {} | |
+ public static void ToByteSequence(ReadOnlySequence<byte> hexTextSequence, int byteSequenceLength, Span<byte> destination) {} | |
+ public static bool TryExpectStatusLine(ref SequenceReader<byte> reader, out SkStackResponseStatus status) {} | |
+ public static OperationStatus TryExpectToken(ref SequenceReader<byte> reader, ReadOnlySpan<byte> expectedToken) {} | |
+ } | |
+ | |
+ public class SkStackUnexpectedResponseException : SkStackResponseException { | |
+ public string? CausedText { get; } | |
+ } | |
+} | |
+// API list generated by Smdn.Reflection.ReverseGenerating.ListApi.MSBuild.Tasks v1.4.0.0. | |
+// Smdn.Reflection.ReverseGenerating.ListApi.Core v1.3.0.0 (https://github.com/smdn/Smdn.Reflection.ReverseGenerating) | |
diff --git a/doc/api-list/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP-netstandard2.1.apilist.cs b/doc/api-list/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP-netstandard2.1.apilist.cs | |
new file mode 100644 | |
index 0000000..39e4fdd | |
--- /dev/null | |
+++ b/doc/api-list/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP-netstandard2.1.apilist.cs | |
@@ -0,0 +1,456 @@ | |
+// Smdn.Net.SkStackIP.dll (Smdn.Net.SkStackIP-1.0.0) | |
+// Name: Smdn.Net.SkStackIP | |
+// AssemblyVersion: 1.0.0.0 | |
+// InformationalVersion: 1.0.0+655c155832ea35fece55fe3cd2467b473922319c | |
+// TargetFramework: .NETStandard,Version=v2.1 | |
+// Configuration: Release | |
+// Referenced assemblies: | |
+// Microsoft.Extensions.Logging.Abstractions, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60 | |
+// Polly.Core, Version=8.0.0.0, Culture=neutral, PublicKeyToken=c8a3ffc3f8f825cc | |
+// Smdn.Fundamental.ControlPicture, Version=3.0.0.1, Culture=neutral | |
+// Smdn.Fundamental.Encoding.Buffer, Version=3.0.0.0, Culture=neutral | |
+// Smdn.Fundamental.PrintableEncoding.Hexadecimal, Version=3.0.0.0, Culture=neutral | |
+// System.IO.Pipelines, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51 | |
+// netstandard, Version=2.1.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51 | |
+#nullable enable annotations | |
+ | |
+using System; | |
+using System.Buffers; | |
+using System.Collections.Generic; | |
+using System.ComponentModel; | |
+using System.Diagnostics.CodeAnalysis; | |
+using System.IO; | |
+using System.IO.Pipelines; | |
+using System.Net; | |
+using System.Net.NetworkInformation; | |
+using System.Threading; | |
+using System.Threading.Tasks; | |
+using Microsoft.Extensions.Logging; | |
+using Polly; | |
+using Smdn.Net.SkStackIP; | |
+using Smdn.Net.SkStackIP.Protocol; | |
+ | |
+namespace Smdn.Net.SkStackIP { | |
+ public enum SkStackERXUDPDataFormat : int { | |
+ Binary = 0, | |
+ HexAsciiText = 1, | |
+ } | |
+ | |
+ public enum SkStackErrorCode : int { | |
+ ER01 = 1, | |
+ ER02 = 2, | |
+ ER03 = 3, | |
+ ER04 = 4, | |
+ ER05 = 5, | |
+ ER06 = 6, | |
+ ER07 = 7, | |
+ ER08 = 8, | |
+ ER09 = 9, | |
+ ER10 = 10, | |
+ Undefined = 0, | |
+ } | |
+ | |
+ public enum SkStackEventNumber : byte { | |
+ ActiveScanCompleted = 34, | |
+ BeaconReceived = 32, | |
+ EchoRequestReceived = 5, | |
+ EnergyDetectScanCompleted = 31, | |
+ NeighborAdvertisementReceived = 2, | |
+ NeighborSolicitationReceived = 1, | |
+ PanaSessionEstablishmentCompleted = 37, | |
+ PanaSessionEstablishmentError = 36, | |
+ PanaSessionExpired = 41, | |
+ PanaSessionTerminationCompleted = 39, | |
+ PanaSessionTerminationRequestReceived = 38, | |
+ PanaSessionTerminationTimedOut = 40, | |
+ TransmissionTimeControlLimitationActivated = 50, | |
+ TransmissionTimeControlLimitationDeactivated = 51, | |
+ UdpSendCompleted = 33, | |
+ Undefined = 0, | |
+ WakeupSignalReceived = 192, | |
+ } | |
+ | |
+ public enum SkStackResponseStatus : int { | |
+ Fail = -1, | |
+ Ok = 1, | |
+ Undetermined = 0, | |
+ } | |
+ | |
+ public enum SkStackUdpEncryption : byte { | |
+ EncryptIfAble = 2, | |
+ ForceEncrypt = 1, | |
+ ForcePlainText = 0, | |
+ } | |
+ | |
+ public enum SkStackUdpPortHandle : byte { | |
+ Handle1 = 1, | |
+ Handle2 = 2, | |
+ Handle3 = 3, | |
+ Handle4 = 4, | |
+ Handle5 = 5, | |
+ Handle6 = 6, | |
+ None = 0, | |
+ } | |
+ | |
+ public abstract class SkStackActiveScanOptions : ICloneable { | |
+ public static SkStackActiveScanOptions Default { get; } | |
+ public static SkStackActiveScanOptions Null { get; } | |
+ public static SkStackActiveScanOptions ScanUntilFind { get; } | |
+ | |
+ public static SkStackActiveScanOptions Create(IEnumerable<int> scanDurationGenerator, PhysicalAddress paaMacAddress) {} | |
+ public static SkStackActiveScanOptions Create(IEnumerable<int> scanDurationGenerator, Predicate<SkStackPanDescription>? paaSelector = null) {} | |
+ | |
+ protected SkStackActiveScanOptions() {} | |
+ | |
+ public abstract SkStackActiveScanOptions Clone(); | |
+ object ICloneable.Clone() {} | |
+ } | |
+ | |
+ public class SkStackClient : IDisposable { | |
+ public static readonly TimeSpan SKSCANDefaultDuration; // = "00:00:00.0480000" | |
+ public static readonly TimeSpan SKSCANMaxDuration; // = "00:02:37.2960000" | |
+ public static readonly TimeSpan SKSCANMinDuration; // = "00:00:00.0192000" | |
+ | |
+ public event EventHandler<SkStackPanaSessionEventArgs>? PanaSessionEstablished; | |
+ public event EventHandler<SkStackPanaSessionEventArgs>? PanaSessionExpired; | |
+ public event EventHandler<SkStackPanaSessionEventArgs>? PanaSessionTerminated; | |
+ public event EventHandler<SkStackEventArgs>? Slept; | |
+ public event EventHandler<SkStackEventArgs>? WokeUp; | |
+ | |
+ public SkStackClient(PipeWriter sender, PipeReader receiver, SkStackERXUDPDataFormat erxudpDataFormat = SkStackERXUDPDataFormat.Binary, ILogger? logger = null) {} | |
+ public SkStackClient(Stream stream, bool leaveStreamOpen = true, SkStackERXUDPDataFormat erxudpDataFormat = SkStackERXUDPDataFormat.Binary, ILogger? logger = null) {} | |
+ | |
+ public SkStackERXUDPDataFormat ERXUDPDataFormat { get; protected set; } | |
+ public bool IsPanaSessionAlive { get; } | |
+ protected ILogger? Logger { get; } | |
+ public IPAddress? PanaSessionPeerAddress { get; } | |
+ public TimeSpan ReceiveResponseDelay { get; set; } | |
+ public TimeSpan ReceiveUdpPollingInterval { get; set; } | |
+ public ISynchronizeInvoke? SynchronizingObject { get; set; } | |
+ | |
+ public ValueTask<IReadOnlyList<SkStackPanDescription>> ActiveScanAsync(ReadOnlyMemory<byte> rbid, ReadOnlyMemory<byte> password, SkStackActiveScanOptions? scanOptions = null, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackPanaSessionInfo> AuthenticateAsPanaClientAsync(ReadOnlyMemory<byte> rbid, ReadOnlyMemory<byte> password, IPAddress paaAddress, SkStackChannel channel, int panId, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackPanaSessionInfo> AuthenticateAsPanaClientAsync(ReadOnlyMemory<byte> rbid, ReadOnlyMemory<byte> password, IPAddress paaAddress, int channelNumber, int panId, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackPanaSessionInfo> AuthenticateAsPanaClientAsync(ReadOnlyMemory<byte> rbid, ReadOnlyMemory<byte> password, PhysicalAddress paaMacAddress, SkStackChannel channel, int panId, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackPanaSessionInfo> AuthenticateAsPanaClientAsync(ReadOnlyMemory<byte> rbid, ReadOnlyMemory<byte> password, PhysicalAddress paaMacAddress, int channelNumber, int panId, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackPanaSessionInfo> AuthenticateAsPanaClientAsync(ReadOnlyMemory<byte> rbid, ReadOnlyMemory<byte> password, SkStackActiveScanOptions? scanOptions = null, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackPanaSessionInfo> AuthenticateAsPanaClientAsync(ReadOnlyMemory<byte> rbid, ReadOnlyMemory<byte> password, SkStackPanDescription pan, CancellationToken cancellationToken = default) {} | |
+ public async ValueTask<IPAddress> ConvertToIPv6LinkLocalAddressAsync(PhysicalAddress macAddress, CancellationToken cancellationToken = default) {} | |
+ public ValueTask DisableFlashMemoryAutoLoadAsync(CancellationToken cancellationToken = default) {} | |
+ protected virtual void Dispose(bool disposing) {} | |
+ public void Dispose() {} | |
+ public ValueTask EnableFlashMemoryAutoLoadAsync(CancellationToken cancellationToken = default) {} | |
+ public async ValueTask<IReadOnlyList<IPAddress>> GetAvailableAddressListAsync(CancellationToken cancellationToken = default) {} | |
+ public async ValueTask<IReadOnlyList<SkStackUdpPort>> GetListeningUdpPortListAsync(CancellationToken cancellationToken = default) {} | |
+ public async ValueTask<IReadOnlyDictionary<IPAddress, PhysicalAddress>> GetNeighborCacheListAsync(CancellationToken cancellationToken = default) {} | |
+ public async ValueTask<IReadOnlyList<SkStackUdpPortHandle>> GetUnusedUdpPortHandleListAsync(CancellationToken cancellationToken = default) {} | |
+ public async ValueTask LoadFlashMemoryAsync(CancellationToken cancellationToken = default) {} | |
+ public async ValueTask<SkStackUdpPort> PrepareUdpPortAsync(int port, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<IPAddress> ReceiveUdpAsync(int port, IBufferWriter<byte> buffer, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<IPAddress> ReceiveUdpEchonetLiteAsync(IBufferWriter<byte> buffer, CancellationToken cancellationToken = default) {} | |
+ public ValueTask SaveFlashMemoryAsync(SkStackFlashMemoryWriteRestriction restriction, CancellationToken cancellationToken = default) {} | |
+ internal protected ValueTask<SkStackResponse<TPayload>> SendCommandAsync<TPayload>(ReadOnlyMemory<byte> command, Action<ISkStackCommandLineWriter>? writeArguments, SkStackSequenceParser<TPayload> parseResponsePayload, SkStackProtocolSyntax? syntax = null, bool throwIfErrorStatus = true, CancellationToken cancellationToken = default) {} | |
+ internal protected async ValueTask<SkStackResponse> SendCommandAsync(ReadOnlyMemory<byte> command, Action<ISkStackCommandLineWriter>? writeArguments = null, SkStackProtocolSyntax? syntax = null, bool throwIfErrorStatus = true, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse> SendSKADDNBRAsync(IPAddress ipv6Address, PhysicalAddress macAddress, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse<string>> SendSKAPPVERAsync(CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse> SendSKDSLEEPAsync(bool waitUntilWakeUp = false, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse> SendSKERASEAsync(CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse<(IPAddress LinkLocalAddress, PhysicalAddress MacAddress, SkStackChannel Channel, int PanId, int Addr16)>> SendSKINFOAsync(CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse> SendSKJOINAsync(IPAddress ipv6address, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse<IPAddress>> SendSKLL64Async(PhysicalAddress macAddress, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse> SendSKLOADAsync(CancellationToken cancellationToken = default) {} | |
+ public ValueTask<(SkStackResponse Response, IPAddress Address)> SendSKREJOINAsync(CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse> SendSKRESETAsync(CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse> SendSKSAVEAsync(CancellationToken cancellationToken = default) {} | |
+ public ValueTask<(SkStackResponse Response, IReadOnlyList<SkStackPanDescription> PanDescriptions)> SendSKSCANActiveScanAsync(TimeSpan duration = default, uint channelMask = uint.MaxValue, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<(SkStackResponse Response, IReadOnlyList<SkStackPanDescription> PanDescriptions)> SendSKSCANActiveScanAsync(int durationFactor, uint channelMask = uint.MaxValue, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<(SkStackResponse Response, IReadOnlyList<SkStackPanDescription> PanDescriptions)> SendSKSCANActiveScanPairAsync(TimeSpan duration = default, uint channelMask = uint.MaxValue, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<(SkStackResponse Response, IReadOnlyList<SkStackPanDescription> PanDescriptions)> SendSKSCANActiveScanPairAsync(int durationFactor, uint channelMask = uint.MaxValue, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<(SkStackResponse Response, IReadOnlyDictionary<SkStackChannel, decimal> ScanResult)> SendSKSCANEnergyDetectScanAsync(TimeSpan duration = default, uint channelMask = uint.MaxValue, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<(SkStackResponse Response, IReadOnlyDictionary<SkStackChannel, decimal> ScanResult)> SendSKSCANEnergyDetectScanAsync(int durationFactor, uint channelMask = uint.MaxValue, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<(SkStackResponse Response, bool IsCompletedSuccessfully)> SendSKSENDTOAsync(SkStackUdpPort port, IPAddress destinationAddress, int destinationPort, ReadOnlyMemory<byte> data, SkStackUdpEncryption encryption = SkStackUdpEncryption.EncryptIfAble, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<(SkStackResponse Response, bool IsCompletedSuccessfully)> SendSKSENDTOAsync(SkStackUdpPort port, IPEndPoint destination, ReadOnlyMemory<byte> data, SkStackUdpEncryption encryption = SkStackUdpEncryption.EncryptIfAble, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<(SkStackResponse Response, bool IsCompletedSuccessfully)> SendSKSENDTOAsync(SkStackUdpPortHandle handle, IPAddress destinationAddress, int destinationPort, ReadOnlyMemory<byte> data, SkStackUdpEncryption encryption = SkStackUdpEncryption.EncryptIfAble, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<(SkStackResponse Response, bool IsCompletedSuccessfully)> SendSKSENDTOAsync(SkStackUdpPortHandle handle, IPEndPoint destination, ReadOnlyMemory<byte> data, SkStackUdpEncryption encryption = SkStackUdpEncryption.EncryptIfAble, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse> SendSKSETPWDAsync(ReadOnlyMemory<byte> password, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse> SendSKSETPWDAsync(ReadOnlyMemory<char> password, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse> SendSKSETRBIDAsync(ReadOnlyMemory<byte> id, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse> SendSKSETRBIDAsync(ReadOnlyMemory<char> id, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse<TValue>> SendSKSREGAsync<TValue>(SkStackRegister.RegisterEntry<TValue> register, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse> SendSKSREGAsync<TValue>(SkStackRegister.RegisterEntry<TValue> register, TValue @value, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse<IReadOnlyList<IPAddress>>> SendSKTABLEAvailableAddressListAsync(CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse<IReadOnlyList<SkStackUdpPort>>> SendSKTABLEListeningPortListAsync(CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse<IReadOnlyDictionary<IPAddress, PhysicalAddress>>> SendSKTABLENeighborCacheListAsync(CancellationToken cancellationToken = default) {} | |
+ public async ValueTask<(SkStackResponse Response, bool IsCompletedSuccessfully)> SendSKTERMAsync(CancellationToken cancellationToken = default) {} | |
+ public ValueTask<(SkStackResponse Response, SkStackUdpPort UdpPort)> SendSKUDPPORTAsync(SkStackUdpPortHandle handle, int port, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse> SendSKUDPPORTUnsetAsync(SkStackUdpPortHandle handle, CancellationToken cancellationToken = default) {} | |
+ public ValueTask<SkStackResponse<Version>> SendSKVERAsync(CancellationToken cancellationToken = default) {} | |
+ public ValueTask SendUdpEchonetLiteAsync(ReadOnlyMemory<byte> buffer, ResiliencePipeline? resiliencePipeline = null, CancellationToken cancellationToken = default) {} | |
+ protected async ValueTask SetFlashMemoryAutoLoadAsync(bool trueIfEnable, CancellationToken cancellationToken = default) {} | |
+ public void StartCapturingUdpReceiveEvents(int port) {} | |
+ public void StopCapturingUdpReceiveEvents(int port) {} | |
+ public ValueTask<bool> TerminatePanaSessionAsync(CancellationToken cancellationToken = default) {} | |
+ protected void ThrowIfDisposed() {} | |
+ internal protected void ThrowIfPanaSessionAlreadyEstablished() {} | |
+ internal protected void ThrowIfPanaSessionIsNotEstablished() {} | |
+ } | |
+ | |
+ public class SkStackCommandNotSupportedException : SkStackErrorResponseException { | |
+ } | |
+ | |
+ public class SkStackErrorResponseException : SkStackResponseException { | |
+ public SkStackErrorCode ErrorCode { get; } | |
+ public string ErrorText { get; } | |
+ public SkStackResponse Response { get; } | |
+ } | |
+ | |
+ public class SkStackEventArgs : EventArgs { | |
+ public SkStackEventNumber EventNumber { get; } | |
+ } | |
+ | |
+ public class SkStackFlashMemoryIOException : SkStackErrorResponseException { | |
+ } | |
+ | |
+ public abstract class SkStackFlashMemoryWriteRestriction { | |
+ public static SkStackFlashMemoryWriteRestriction CreateGrantIfElapsed(TimeSpan interval) {} | |
+ public static SkStackFlashMemoryWriteRestriction DangerousCreateAlwaysGrant() {} | |
+ | |
+ protected SkStackFlashMemoryWriteRestriction() {} | |
+ | |
+ internal protected abstract bool IsRestricted(); | |
+ } | |
+ | |
+ public static class SkStackKnownPortNumbers { | |
+ public const int EchonetLite = 3610; | |
+ public const int Pana = 716; | |
+ } | |
+ | |
+ public class SkStackPanaSessionEstablishmentException : SkStackPanaSessionException { | |
+ } | |
+ | |
+ public sealed class SkStackPanaSessionEventArgs : SkStackEventArgs { | |
+ public IPAddress PanaSessionPeerAddress { get; } | |
+ } | |
+ | |
+ public abstract class SkStackPanaSessionException : InvalidOperationException { | |
+ public IPAddress Address { get; } | |
+ public SkStackEventNumber EventNumber { get; } | |
+ } | |
+ | |
+ public sealed class SkStackPanaSessionInfo { | |
+ public SkStackChannel Channel { get; } | |
+ public IPAddress LocalAddress { get; } | |
+ public PhysicalAddress LocalMacAddress { get; } | |
+ public int PanId { get; } | |
+ public IPAddress PeerAddress { get; } | |
+ public PhysicalAddress PeerMacAddress { get; } | |
+ } | |
+ | |
+ public static class SkStackRegister { | |
+ public abstract class RegisterEntry<TValue> { | |
+ private protected delegate bool ExpectValueFunc(ref SequenceReader<byte> reader, out TValue @value); | |
+ | |
+ public bool IsReadable { get; } | |
+ public bool IsWritable { get; } | |
+ public TValue MaxValue { get; } | |
+ public TValue MinValue { get; } | |
+ public string Name { get; } | |
+ } | |
+ | |
+ public static SkStackRegister.RegisterEntry<bool> AcceptIcmpEcho { get; } | |
+ public static SkStackRegister.RegisterEntry<ulong> AccumulatedSendTimeInMilliseconds { get; } | |
+ public static SkStackRegister.RegisterEntry<SkStackChannel> Channel { get; } | |
+ public static SkStackRegister.RegisterEntry<bool> EnableAutoLoad { get; } | |
+ public static SkStackRegister.RegisterEntry<bool> EnableAutoReauthentication { get; } | |
+ public static SkStackRegister.RegisterEntry<bool> EnableEchoback { get; } | |
+ public static SkStackRegister.RegisterEntry<bool> EncryptIPMulticast { get; } | |
+ public static SkStackRegister.RegisterEntry<uint> FrameCounter { get; } | |
+ public static SkStackRegister.RegisterEntry<bool> IsSendingRestricted { get; } | |
+ public static SkStackRegister.RegisterEntry<ReadOnlyMemory<byte>> PairingId { get; } | |
+ public static SkStackRegister.RegisterEntry<ushort> PanId { get; } | |
+ public static SkStackRegister.RegisterEntry<TimeSpan> PanaSessionLifetimeInSeconds { get; } | |
+ public static SkStackRegister.RegisterEntry<bool> RespondBeaconRequest { get; } | |
+ public static SkStackRegister.RegisterEntry<SkStackChannel> S02 { get; } | |
+ public static SkStackRegister.RegisterEntry<ushort> S03 { get; } | |
+ public static SkStackRegister.RegisterEntry<uint> S07 { get; } | |
+ public static SkStackRegister.RegisterEntry<ReadOnlyMemory<byte>> S0A { get; } | |
+ public static SkStackRegister.RegisterEntry<bool> S15 { get; } | |
+ public static SkStackRegister.RegisterEntry<TimeSpan> S16 { get; } | |
+ public static SkStackRegister.RegisterEntry<bool> S17 { get; } | |
+ public static SkStackRegister.RegisterEntry<bool> SA0 { get; } | |
+ public static SkStackRegister.RegisterEntry<bool> SA1 { get; } | |
+ public static SkStackRegister.RegisterEntry<bool> SFB { get; } | |
+ public static SkStackRegister.RegisterEntry<ulong> SFD { get; } | |
+ public static SkStackRegister.RegisterEntry<bool> SFE { get; } | |
+ public static SkStackRegister.RegisterEntry<bool> SFF { get; } | |
+ } | |
+ | |
+ public class SkStackResponse { | |
+ public SkStackResponseStatus Status { get; } | |
+ public ReadOnlyMemory<byte> StatusText { get; } | |
+ public bool Success { get; } | |
+ } | |
+ | |
+ public class SkStackResponseException : InvalidOperationException { | |
+ public SkStackResponseException() {} | |
+ public SkStackResponseException(string message) {} | |
+ public SkStackResponseException(string message, Exception? innerException = null) {} | |
+ } | |
+ | |
+ public class SkStackResponse<TPayload> : SkStackResponse { | |
+ public TPayload Payload { get; } | |
+ } | |
+ | |
+ public class SkStackUartIOException : SkStackErrorResponseException { | |
+ } | |
+ | |
+ public class SkStackUdpSendFailedException : InvalidOperationException { | |
+ public SkStackUdpSendFailedException() {} | |
+ public SkStackUdpSendFailedException(string message) {} | |
+ public SkStackUdpSendFailedException(string message, Exception? innerException = null) {} | |
+ public SkStackUdpSendFailedException(string message, SkStackUdpPortHandle portHandle, IPAddress peerAddress, Exception? innerException = null) {} | |
+ | |
+ public IPAddress? PeerAddress { get; } | |
+ public SkStackUdpPortHandle PortHandle { get; } | |
+ } | |
+ | |
+ public class SkStackUdpSendResultIndeterminateException : InvalidOperationException { | |
+ public SkStackUdpSendResultIndeterminateException() {} | |
+ public SkStackUdpSendResultIndeterminateException(string message) {} | |
+ public SkStackUdpSendResultIndeterminateException(string message, Exception? innerException = null) {} | |
+ } | |
+ | |
+ public readonly struct SkStackChannel : | |
+ IComparable<SkStackChannel>, | |
+ IEquatable<SkStackChannel> | |
+ { | |
+ public static readonly IReadOnlyDictionary<int, SkStackChannel> Channels; // = "System.Collections.Generic.Dictionary`2[System.Int32,Smdn.Net.SkStackIP.SkStackChannel]" | |
+ public static readonly SkStackChannel Empty; // = "0ch (S02=0x00, 0 MHz)" | |
+ | |
+ public static SkStackChannel Channel33 { get; } | |
+ public static SkStackChannel Channel34 { get; } | |
+ public static SkStackChannel Channel35 { get; } | |
+ public static SkStackChannel Channel36 { get; } | |
+ public static SkStackChannel Channel37 { get; } | |
+ public static SkStackChannel Channel38 { get; } | |
+ public static SkStackChannel Channel39 { get; } | |
+ public static SkStackChannel Channel40 { get; } | |
+ public static SkStackChannel Channel41 { get; } | |
+ public static SkStackChannel Channel42 { get; } | |
+ public static SkStackChannel Channel43 { get; } | |
+ public static SkStackChannel Channel44 { get; } | |
+ public static SkStackChannel Channel45 { get; } | |
+ public static SkStackChannel Channel46 { get; } | |
+ public static SkStackChannel Channel47 { get; } | |
+ public static SkStackChannel Channel48 { get; } | |
+ public static SkStackChannel Channel49 { get; } | |
+ public static SkStackChannel Channel50 { get; } | |
+ public static SkStackChannel Channel51 { get; } | |
+ public static SkStackChannel Channel52 { get; } | |
+ public static SkStackChannel Channel53 { get; } | |
+ public static SkStackChannel Channel54 { get; } | |
+ public static SkStackChannel Channel55 { get; } | |
+ public static SkStackChannel Channel56 { get; } | |
+ public static SkStackChannel Channel57 { get; } | |
+ public static SkStackChannel Channel58 { get; } | |
+ public static SkStackChannel Channel59 { get; } | |
+ public static SkStackChannel Channel60 { get; } | |
+ | |
+ public static bool operator == (SkStackChannel x, SkStackChannel y) {} | |
+ public static bool operator != (SkStackChannel x, SkStackChannel y) {} | |
+ | |
+ public int ChannelNumber { get; } | |
+ public decimal FrequencyMHz { get; } | |
+ public bool IsEmpty { get; } | |
+ | |
+ public bool Equals(SkStackChannel other) {} | |
+ public override bool Equals(object? obj) {} | |
+ public override int GetHashCode() {} | |
+ int IComparable<SkStackChannel>.CompareTo(SkStackChannel other) {} | |
+ public override string ToString() {} | |
+ } | |
+ | |
+ public readonly struct SkStackPanDescription { | |
+ public SkStackChannel Channel { get; } | |
+ public int ChannelPage { get; } | |
+ public int Id { get; } | |
+ public PhysicalAddress MacAddress { get; } | |
+ public uint PairingId { get; } | |
+ public decimal Rssi { get; } | |
+ | |
+ public override string ToString() {} | |
+ } | |
+ | |
+ public readonly struct SkStackUdpPort { | |
+ public static readonly SkStackUdpPort Null; // = "0 (#0)" | |
+ | |
+ public SkStackUdpPortHandle Handle { get; } | |
+ public bool IsNull { get; } | |
+ public bool IsUnused { get; } | |
+ public int Port { get; } | |
+ | |
+ public override string ToString() {} | |
+ } | |
+} | |
+ | |
+namespace Smdn.Net.SkStackIP.Protocol { | |
+ public delegate TResult SkStackSequenceParser<TResult>(ISkStackSequenceParserContext context); | |
+ | |
+ public interface ISkStackCommandLineWriter { | |
+ void WriteMaskedToken(ReadOnlySpan<byte> token); | |
+ void WriteToken(ReadOnlySpan<byte> token); | |
+ } | |
+ | |
+ public interface ISkStackSequenceParserContext { | |
+ ReadOnlySequence<byte> UnparsedSequence { get; } | |
+ | |
+ void Complete(); | |
+ void Complete(SequenceReader<byte> consumedReader); | |
+ void Continue(); | |
+ ISkStackSequenceParserContext CreateCopy(); | |
+ virtual SequenceReader<byte> CreateReader() {} | |
+ void Ignore(); | |
+ void SetAsIncomplete(); | |
+ void SetAsIncomplete(SequenceReader<byte> incompleteReader); | |
+ } | |
+ | |
+ public abstract class SkStackProtocolSyntax { | |
+ public static SkStackProtocolSyntax Default { get; } | |
+ | |
+ protected SkStackProtocolSyntax() {} | |
+ | |
+ public abstract ReadOnlySpan<byte> EndOfCommandLine { get; } | |
+ public virtual ReadOnlySpan<byte> EndOfEchobackLine { get; } | |
+ public abstract ReadOnlySpan<byte> EndOfStatusLine { get; } | |
+ public abstract bool ExpectStatusLine { get; } | |
+ } | |
+ | |
+ public static class SkStackTokenParser { | |
+ public static bool Expect<TValue>(ref SequenceReader<byte> reader, int length, Converter<ReadOnlySequence<byte>, TValue> converter, [NotNullWhen(true)] out TValue @value) {} | |
+ public static bool ExpectADDR16(ref SequenceReader<byte> reader, out ushort @value) {} | |
+ public static bool ExpectADDR64(ref SequenceReader<byte> reader, [NotNullWhen(true)] out PhysicalAddress? @value) {} | |
+ public static bool ExpectBinary(ref SequenceReader<byte> reader, out bool @value) {} | |
+ public static bool ExpectCHANNEL(ref SequenceReader<byte> reader, out SkStackChannel @value) {} | |
+ public static bool ExpectCharArray(ref SequenceReader<byte> reader, [NotNullWhen(true)] out string? @value) {} | |
+ public static bool ExpectCharArray(ref SequenceReader<byte> reader, out ReadOnlyMemory<byte> @value) {} | |
+ public static bool ExpectDecimalNumber(ref SequenceReader<byte> reader, int length, out uint @value) {} | |
+ public static bool ExpectDecimalNumber(ref SequenceReader<byte> reader, out uint @value) {} | |
+ public static bool ExpectEndOfLine(ref SequenceReader<byte> reader) {} | |
+ public static bool ExpectIPADDR(ref SequenceReader<byte> reader, [NotNullWhen(true)] out IPAddress? @value) {} | |
+ public static bool ExpectSequence(ref SequenceReader<byte> reader, ReadOnlySpan<byte> expectedSequence) {} | |
+ public static bool ExpectToken(ref SequenceReader<byte> reader, ReadOnlySpan<byte> expectedToken) {} | |
+ public static bool ExpectUINT16(ref SequenceReader<byte> reader, out ushort @value) {} | |
+ public static bool ExpectUINT32(ref SequenceReader<byte> reader, out uint @value) {} | |
+ public static bool ExpectUINT64(ref SequenceReader<byte> reader, out ulong @value) {} | |
+ public static bool ExpectUINT8(ref SequenceReader<byte> reader, out byte @value) {} | |
+ public static void ToByteSequence(ReadOnlySequence<byte> hexTextSequence, int byteSequenceLength, Span<byte> destination) {} | |
+ public static bool TryExpectStatusLine(ref SequenceReader<byte> reader, out SkStackResponseStatus status) {} | |
+ public static OperationStatus TryExpectToken(ref SequenceReader<byte> reader, ReadOnlySpan<byte> expectedToken) {} | |
+ } | |
+ | |
+ public class SkStackUnexpectedResponseException : SkStackResponseException { | |
+ public string? CausedText { get; } | |
+ } | |
+} | |
+// API list generated by Smdn.Reflection.ReverseGenerating.ListApi.MSBuild.Tasks v1.4.0.0. | |
+// Smdn.Reflection.ReverseGenerating.ListApi.Core v1.3.0.0 (https://github.com/smdn/Smdn.Reflection.ReverseGenerating) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP.Protocol/ISkStackCommandLineWriter.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP.Protocol/ISkStackCommandLineWriter.cs | |
new file mode 100644 | |
index 0000000..5091a0e | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP.Protocol/ISkStackCommandLineWriter.cs | |
@@ -0,0 +1,10 @@ | |
+// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+using System; | |
+ | |
+namespace Smdn.Net.SkStackIP.Protocol; | |
+ | |
+public interface ISkStackCommandLineWriter { | |
+ void WriteToken(ReadOnlySpan<byte> token); | |
+ void WriteMaskedToken(ReadOnlySpan<byte> token); | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP.Protocol/ISkStackCommandLineWriterExtensions.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP.Protocol/ISkStackCommandLineWriterExtensions.cs | |
new file mode 100644 | |
index 0000000..bf5600c | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP.Protocol/ISkStackCommandLineWriterExtensions.cs | |
@@ -0,0 +1,166 @@ | |
+// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+using System; | |
+using System.Buffers; | |
+using System.Net; | |
+using System.Net.NetworkInformation; | |
+using System.Net.Sockets; | |
+#if SYSTEM_TEXT_ASCII | |
+using System.Text; | |
+#endif | |
+ | |
+using Smdn.Formats; | |
+ | |
+namespace Smdn.Net.SkStackIP.Protocol; | |
+ | |
+internal static class ISkStackCommandLineWriterExtensions { | |
+ private const int LengthOfADDR64 = 16; // "0123456789ABCDEF".Length | |
+ private const int LengthOfIPADDR = 39; // "XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX".Length | |
+ | |
+ public static void WriteTokenHex(this ISkStackCommandLineWriter writer, byte value) | |
+ => writer.WriteToken("0123456789ABCDEF"u8.Slice(value, 1)); | |
+ | |
+ public static void WriteTokenBinary(this ISkStackCommandLineWriter writer, bool value) | |
+ => WriteTokenUINT8(writer, value ? (byte)1 : (byte)0, zeroPadding: false); | |
+ | |
+ public static void WriteTokenUINT8(this ISkStackCommandLineWriter writer, byte value, bool zeroPadding = false) | |
+ => WriteTokenUnsignedNumber(writer, value, length: zeroPadding ? 2 : 0); | |
+ | |
+ public static void WriteTokenUINT16(this ISkStackCommandLineWriter writer, ushort value, bool zeroPadding = false) | |
+ => WriteTokenUnsignedNumber(writer, value, length: zeroPadding ? 4 : 0); | |
+ | |
+ public static void WriteTokenUINT32(this ISkStackCommandLineWriter writer, uint value, bool zeroPadding = false) | |
+ => WriteTokenUnsignedNumber(writer, value, length: zeroPadding ? 8 : 0); | |
+ | |
+ public static void WriteTokenUINT64(this ISkStackCommandLineWriter writer, ulong value, bool zeroPadding = false) | |
+ => WriteTokenUnsignedNumber(writer, value, length: zeroPadding ? 16 : 0); | |
+ | |
+ private static void WriteTokenUnsignedNumber(ISkStackCommandLineWriter writer, ulong value, int length) | |
+ { | |
+ if (16 < length) | |
+ throw new NotSupportedException("length too long"); | |
+ | |
+ var formattedNumberBufferLength = length == 0 | |
+ ? 16 // ulong.MaxValue.ToString("X").Length | |
+ : length; | |
+ | |
+#if SYSTEM_IUTF8SPANFORMATTABLE | |
+ Span<byte> bytesSpan = stackalloc byte[formattedNumberBufferLength]; | |
+ | |
+ if (!value.TryFormat( | |
+ bytesSpan, | |
+ out var bytesWritten, | |
+ length == 0 ? "X" : stackalloc char[2] { 'X', (char)('0' + length) }, | |
+ provider: null | |
+ )) { | |
+ throw new InvalidOperationException("unexpected error in conversion"); | |
+ } | |
+#else | |
+ Span<char> charsSpan = stackalloc char[formattedNumberBufferLength]; | |
+ | |
+ if (!value.TryFormat( | |
+ charsSpan, | |
+ out var charsWritten, | |
+ length == 0 ? "X" : stackalloc char[2] { 'X', (char)('0' + length) }, | |
+ provider: null | |
+ )) { | |
+ throw new InvalidOperationException("unexpected error in conversion"); | |
+ } | |
+ | |
+ Span<byte> bytesSpan = stackalloc byte[charsWritten]; | |
+ | |
+ var bytesWritten = SkStack.ToByteSequence(charsSpan.Slice(0, charsWritten), bytesSpan); | |
+#endif | |
+ | |
+ writer.WriteToken(bytesSpan.Slice(0, bytesWritten)); | |
+ } | |
+ | |
+ public static void WriteTokenADDR64(this ISkStackCommandLineWriter writer, PhysicalAddress macAddress) | |
+ { | |
+ if (macAddress is null) | |
+ throw new ArgumentNullException(nameof(macAddress)); | |
+ | |
+ var macAddressBytes = macAddress.GetAddressBytes(); | |
+ | |
+ if (macAddressBytes.Length != 8) | |
+ throw new ArgumentException("address length must be exactly 64 bits", nameof(macAddress)); | |
+ | |
+ Span<byte> addr64 = stackalloc byte[LengthOfADDR64]; | |
+ | |
+ _ = Hexadecimal.TryEncodeUpperCase(macAddressBytes.AsSpan(), addr64, out _); | |
+ | |
+ writer.WriteToken(addr64); | |
+ } | |
+ | |
+ public static void WriteTokenIPADDR(this ISkStackCommandLineWriter writer, IPAddress ipv6address) | |
+ { | |
+ if (ipv6address is null) | |
+ throw new ArgumentNullException(nameof(ipv6address)); | |
+ if (ipv6address.AddressFamily != AddressFamily.InterNetworkV6) | |
+ throw new ArgumentException($"`{nameof(ipv6address)}.{nameof(IPAddress.AddressFamily)}` must be {nameof(AddressFamily.InterNetworkV6)}"); | |
+ | |
+ const int LengthOfIPv6Address = 16; | |
+ | |
+ Span<byte> addressBytes = stackalloc byte[LengthOfIPv6Address]; | |
+ | |
+ if (!ipv6address.TryWriteBytes(addressBytes, out _)) | |
+ throw new InvalidOperationException($"{nameof(IPAddress)}.{nameof(IPAddress.TryWriteBytes)} failed unexpectedly"); | |
+ | |
+ Span<byte> ipaddr = stackalloc byte[LengthOfIPADDR]; | |
+ var bytesWritten = 0; | |
+ | |
+ for (var i = 0; i < LengthOfIPv6Address; i += 2) { | |
+ if (0 < i) | |
+ ipaddr[bytesWritten++] = (byte)':'; | |
+ | |
+ Hexadecimal.TryEncodeUpperCase(addressBytes.Slice(i, 2), ipaddr.Slice(bytesWritten), out var bytesEncoded); | |
+ | |
+ bytesWritten += bytesEncoded; | |
+ } | |
+ | |
+ writer.WriteToken(ipaddr); | |
+ } | |
+ | |
+ public static void WriteToken(this ISkStackCommandLineWriter writer, ReadOnlySpan<char> token) | |
+ => WriteDefaultEncodingToken( | |
+ writer, | |
+ token, | |
+ write: static (t, w) => w.WriteToken(t) | |
+ ); | |
+ | |
+ public static void WriteMaskedToken(this ISkStackCommandLineWriter writer, ReadOnlySpan<char> token) | |
+ => WriteDefaultEncodingToken( | |
+ writer, | |
+ token, | |
+ write: static (t, w) => w.WriteMaskedToken(t) | |
+ ); | |
+ | |
+ private static void WriteDefaultEncodingToken( | |
+ ISkStackCommandLineWriter writer, | |
+ ReadOnlySpan<char> token, | |
+ SpanAction<byte, ISkStackCommandLineWriter> write | |
+ ) | |
+ { | |
+ if (token.IsEmpty) | |
+ throw new ArgumentException("cannot be empty", paramName: nameof(token)); | |
+ | |
+ byte[]? tokenBytes = null; | |
+ | |
+ try { | |
+ tokenBytes = ArrayPool<byte>.Shared.Rent(token.Length); | |
+ | |
+#if SYSTEM_TEXT_ASCII | |
+ if (Ascii.FromUtf16(token, tokenBytes.AsSpan(), out var lengthOfToken) != OperationStatus.Done) | |
+ throw new ArgumentException("token contains non ASCII characters", paramName: nameof(token)); | |
+#else | |
+ var lengthOfToken = SkStack.ToByteSequence(token, tokenBytes.AsSpan()); | |
+#endif | |
+ | |
+ write(tokenBytes.AsSpan(0, lengthOfToken), writer); | |
+ } | |
+ finally { | |
+ if (tokenBytes is not null) | |
+ ArrayPool<byte>.Shared.Return(tokenBytes, clearArray: true); | |
+ } | |
+ } | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP.Protocol/ISkStackSequenceParserContext.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP.Protocol/ISkStackSequenceParserContext.cs | |
new file mode 100644 | |
index 0000000..5ec3756 | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP.Protocol/ISkStackSequenceParserContext.cs | |
@@ -0,0 +1,21 @@ | |
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+#pragma warning disable CA1716 | |
+ | |
+using System.Buffers; | |
+ | |
+namespace Smdn.Net.SkStackIP.Protocol; | |
+ | |
+public interface ISkStackSequenceParserContext { | |
+ ReadOnlySequence<byte> UnparsedSequence { get; } | |
+ | |
+ SequenceReader<byte> CreateReader() => new(UnparsedSequence); | |
+ ISkStackSequenceParserContext CreateCopy(); | |
+ | |
+ void Continue(); | |
+ void Complete(); | |
+ void Complete(SequenceReader<byte> consumedReader); | |
+ void Ignore(); | |
+ void SetAsIncomplete(); | |
+ void SetAsIncomplete(SequenceReader<byte> incompleteReader); | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP.Protocol/SkStack.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP.Protocol/SkStack.cs | |
new file mode 100644 | |
index 0000000..261f9f5 | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP.Protocol/SkStack.cs | |
@@ -0,0 +1,34 @@ | |
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+ | |
+using System; | |
+using System.Buffers; | |
+using System.Text; | |
+ | |
+#if !SYSTEM_TEXT_ENCODINGEXTENSIONS | |
+using Smdn.Text.Encodings; // EncodingReadOnlySequenceExtensions | |
+#endif | |
+ | |
+namespace Smdn.Net.SkStackIP.Protocol; | |
+ | |
+internal static class SkStack { | |
+ private static readonly Encoding DefaultEncoding = Encoding.ASCII; | |
+ | |
+ public static byte[] ToByteSequence(string text) | |
+ => DefaultEncoding.GetBytes(text); | |
+ | |
+#if !SYSTEM_TEXT_ASCII | |
+ public static int ToByteSequence(ReadOnlySpan<char> source, Span<byte> destination) | |
+ => DefaultEncoding.GetBytes(source, destination); | |
+#endif | |
+ | |
+ public static string GetString(ReadOnlySpan<byte> sequence) | |
+ => DefaultEncoding.GetString(sequence); | |
+ | |
+ public static string GetString(ReadOnlySequence<byte> sequence) | |
+ => DefaultEncoding.GetString(sequence); | |
+ | |
+ public static ReadOnlySpan<byte> CRLFSpan => "\r\n"u8; | |
+ | |
+ public const byte SP = 0x20; | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP.Protocol/SkStackCommandLineWriter.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP.Protocol/SkStackCommandLineWriter.cs | |
new file mode 100644 | |
index 0000000..3ae3366 | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP.Protocol/SkStackCommandLineWriter.cs | |
@@ -0,0 +1,70 @@ | |
+// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+using System; | |
+using System.Buffers; | |
+ | |
+namespace Smdn.Net.SkStackIP.Protocol; | |
+ | |
+internal sealed class SkStackCommandLineWriter : ISkStackCommandLineWriter { | |
+ private readonly IBufferWriter<byte> writer; | |
+ private readonly IBufferWriter<byte>? writerForLog; | |
+ | |
+ public SkStackCommandLineWriter( | |
+ IBufferWriter<byte> writer, | |
+ IBufferWriter<byte>? writerForLog | |
+ ) | |
+ { | |
+ this.writer = writer; | |
+ this.writerForLog = writerForLog; | |
+ } | |
+ | |
+ public void Write(ReadOnlySpan<byte> sequence) | |
+ { | |
+ if (sequence.IsEmpty) | |
+ throw new ArgumentException("cannot be empty", paramName: nameof(sequence)); | |
+ | |
+ writer.Write(sequence); | |
+ | |
+ writerForLog?.Write(sequence); | |
+ } | |
+ | |
+ public void WriteToken(ReadOnlySpan<byte> token) | |
+ { | |
+ if (token.IsEmpty) | |
+ throw new ArgumentException("cannot be empty", paramName: nameof(token)); | |
+ | |
+ writer.GetSpan(1)[0] = SkStack.SP; | |
+ writer.Advance(1); | |
+ | |
+ writer.Write(token); | |
+ | |
+ if (writerForLog is not null) { | |
+ writerForLog.GetSpan(1)[0] = SkStack.SP; | |
+ writerForLog.Advance(1); | |
+ | |
+ writerForLog.Write(token); | |
+ } | |
+ } | |
+ | |
+ public void WriteMaskedToken(ReadOnlySpan<byte> token) | |
+ { | |
+ if (token.IsEmpty) | |
+ throw new ArgumentException("cannot be empty", paramName: nameof(token)); | |
+ | |
+ writer.GetSpan(1)[0] = SkStack.SP; | |
+ writer.Advance(1); | |
+ | |
+ writer.Write(token); | |
+ | |
+ if (writerForLog is not null) { | |
+ writerForLog.GetSpan(1)[0] = SkStack.SP; | |
+ writerForLog.Advance(1); | |
+ | |
+ const int MaskLength = 4; | |
+ const byte MaskByte = (byte)'*'; | |
+ | |
+ writerForLog.GetSpan(MaskLength).Slice(0, MaskLength).Fill(MaskByte); | |
+ writerForLog.Advance(MaskLength); | |
+ } | |
+ } | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP.Protocol/SkStackCommandNames.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP.Protocol/SkStackCommandNames.cs | |
new file mode 100644 | |
index 0000000..22f096a | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP.Protocol/SkStackCommandNames.cs | |
@@ -0,0 +1,161 @@ | |
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+ | |
+using System; | |
+ | |
+namespace Smdn.Net.SkStackIP.Protocol; | |
+ | |
+/// <remarks> | |
+/// <para>See 'BP35A1コマンドリファレンス 3. コマンドリファレンス' for detailed specifications.</para> | |
+/// </remarks> | |
+internal class SkStackCommandNames { | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.1. SKSREG' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public static ReadOnlyMemory<byte> SKSREG { get; } = SkStack.ToByteSequence(nameof(SKSREG)); | |
+ | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.2. SKINFO' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public static ReadOnlyMemory<byte> SKINFO { get; } = SkStack.ToByteSequence(nameof(SKINFO)); | |
+ | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.3. SKSTART' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public static ReadOnlyMemory<byte> SKSTART => throw new NotImplementedException(); | |
+ | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.4. SKJOIN' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public static ReadOnlyMemory<byte> SKJOIN { get; } = SkStack.ToByteSequence(nameof(SKJOIN)); | |
+ | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.5. SKREJOIN' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public static ReadOnlyMemory<byte> SKREJOIN { get; } = SkStack.ToByteSequence(nameof(SKREJOIN)); | |
+ | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.6. SKTERM' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public static ReadOnlyMemory<byte> SKTERM { get; } = SkStack.ToByteSequence(nameof(SKTERM)); | |
+ | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.7. SKSENDTO' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public static ReadOnlyMemory<byte> SKSENDTO { get; } = SkStack.ToByteSequence(nameof(SKSENDTO)); | |
+ | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.8. SKPING' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public static ReadOnlyMemory<byte> SKPING => throw new NotImplementedException(); | |
+ | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.9. SKSCAN' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public static ReadOnlyMemory<byte> SKSCAN { get; } = SkStack.ToByteSequence(nameof(SKSCAN)); | |
+ | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.10. SKREGDEV' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public static ReadOnlyMemory<byte> SKREGDEV => throw new NotImplementedException(); | |
+ | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.11. SKRMDEV' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public static ReadOnlyMemory<byte> SKRMDEV => throw new NotImplementedException(); | |
+ | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.12. SKSETKEY' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public static ReadOnlyMemory<byte> SKSETKEY => throw new NotImplementedException(); | |
+ | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.13. SKRMKEY' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public static ReadOnlyMemory<byte> SKRMKEY => throw new NotImplementedException(); | |
+ | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.14. SKSECENABLE' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public static ReadOnlyMemory<byte> SKSECENABLE => throw new NotImplementedException(); | |
+ | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.15. SKSETPSK' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public static ReadOnlyMemory<byte> SKSETPSK => throw new NotImplementedException(); | |
+ | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.16. SKSETPWD' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public static ReadOnlyMemory<byte> SKSETPWD { get; } = SkStack.ToByteSequence(nameof(SKSETPWD)); | |
+ | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.17. SKSETRBID' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public static ReadOnlyMemory<byte> SKSETRBID { get; } = SkStack.ToByteSequence(nameof(SKSETRBID)); | |
+ | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.18. SKADDNBR' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public static ReadOnlyMemory<byte> SKADDNBR { get; } = SkStack.ToByteSequence(nameof(SKADDNBR)); | |
+ | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.19. SKUDPPORT' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public static ReadOnlyMemory<byte> SKUDPPORT { get; } = SkStack.ToByteSequence(nameof(SKUDPPORT)); | |
+ | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.20. SKSAVE' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public static ReadOnlyMemory<byte> SKSAVE { get; } = SkStack.ToByteSequence(nameof(SKSAVE)); | |
+ | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.21. SKLOAD' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public static ReadOnlyMemory<byte> SKLOAD { get; } = SkStack.ToByteSequence(nameof(SKLOAD)); | |
+ | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.22. SKERASE' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public static ReadOnlyMemory<byte> SKERASE { get; } = SkStack.ToByteSequence(nameof(SKERASE)); | |
+ | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.23. SKVER' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public static ReadOnlyMemory<byte> SKVER { get; } = SkStack.ToByteSequence(nameof(SKVER)); | |
+ | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.24. SKAPPVER' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public static ReadOnlyMemory<byte> SKAPPVER { get; } = SkStack.ToByteSequence(nameof(SKAPPVER)); | |
+ | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.25. SKRESET' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public static ReadOnlyMemory<byte> SKRESET { get; } = SkStack.ToByteSequence(nameof(SKRESET)); | |
+ | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.26. SKTABLE' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public static ReadOnlyMemory<byte> SKTABLE { get; } = SkStack.ToByteSequence(nameof(SKTABLE)); | |
+ | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.27. SKDSLEEP' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public static ReadOnlyMemory<byte> SKDSLEEP { get; } = SkStack.ToByteSequence(nameof(SKDSLEEP)); | |
+ | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.28. SKRFLO' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public static ReadOnlyMemory<byte> SKRFLO => throw new NotImplementedException(); | |
+ | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.29. SKLL64' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public static ReadOnlyMemory<byte> SKLL64 { get; } = SkStack.ToByteSequence(nameof(SKLL64)); | |
+ | |
+#if false | |
+ /// <summary>`SKSLEEP` undocumented command.</summary> | |
+ public static ReadOnlyMemory<byte> SKSLEEP => throw new NotImplementedException(); | |
+#endif | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP.Protocol/SkStackErrorCodeNames.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP.Protocol/SkStackErrorCodeNames.cs | |
new file mode 100644 | |
index 0000000..489b0c2 | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP.Protocol/SkStackErrorCodeNames.cs | |
@@ -0,0 +1,45 @@ | |
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+ | |
+using System; | |
+ | |
+namespace Smdn.Net.SkStackIP.Protocol; | |
+ | |
+/// <remarks> | |
+/// <para>See 'BP35A1コマンドリファレンス 7. エラーコード' for detailed specifications.</para> | |
+/// </remarks> | |
+internal static class SkStackErrorCodeNames { | |
+ public static SkStackErrorCode ParseErrorCode(ReadOnlySpan<byte> errorCodeName) | |
+ { | |
+ if (errorCodeName.Length != 4) | |
+ return SkStackErrorCode.Undefined; | |
+ | |
+ if (errorCodeName[0] != (byte)'E') | |
+ return SkStackErrorCode.Undefined; | |
+ | |
+ if (errorCodeName[1] != (byte)'R') | |
+ return SkStackErrorCode.Undefined; | |
+ | |
+ if (errorCodeName[2] == (byte)'0') { | |
+ // ER01-ER09 | |
+ return errorCodeName[3] switch { | |
+ (byte)'1' => SkStackErrorCode.ER01, | |
+ (byte)'2' => SkStackErrorCode.ER02, | |
+ (byte)'3' => SkStackErrorCode.ER03, | |
+ (byte)'4' => SkStackErrorCode.ER04, | |
+ (byte)'5' => SkStackErrorCode.ER05, | |
+ (byte)'6' => SkStackErrorCode.ER06, | |
+ (byte)'7' => SkStackErrorCode.ER07, | |
+ (byte)'8' => SkStackErrorCode.ER08, | |
+ (byte)'9' => SkStackErrorCode.ER09, | |
+ _ => SkStackErrorCode.Undefined, | |
+ }; | |
+ } | |
+ else if (errorCodeName[2] == (byte)'1' && errorCodeName[3] == (byte)'0') { | |
+ // ER10 | |
+ return SkStackErrorCode.ER10; | |
+ } | |
+ | |
+ return SkStackErrorCode.Undefined; | |
+ } | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP.Protocol/SkStackEvent.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP.Protocol/SkStackEvent.cs | |
new file mode 100644 | |
index 0000000..96ef86b | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP.Protocol/SkStackEvent.cs | |
@@ -0,0 +1,61 @@ | |
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+using System; | |
+#if SYSTEM_DIAGNOSTICS_CODEANALYSIS_MEMBERNOTNULLWHENATTRIBUTE | |
+using System.Diagnostics.CodeAnalysis; | |
+#endif | |
+using System.Net; | |
+ | |
+namespace Smdn.Net.SkStackIP.Protocol; | |
+ | |
+/// <remarks> | |
+/// <para>See 'BP35A1コマンドリファレンス 4.8. EVENT' for detailed specifications.</para> | |
+/// </remarks> | |
+internal readonly struct SkStackEvent { | |
+ public static SkStackEvent Create( | |
+ SkStackEventNumber number, | |
+ IPAddress senderAddress, | |
+ int parameter, | |
+ SkStackEventCode expectedSubsequentEventCode | |
+ ) | |
+ => new( | |
+ number, | |
+ senderAddress ?? throw new ArgumentNullException(nameof(senderAddress)), | |
+ parameter, | |
+ expectedSubsequentEventCode | |
+ ); | |
+ | |
+ public static SkStackEvent CreateWakeupSignalReceived() | |
+ => new( | |
+ SkStackEventNumber.WakeupSignalReceived, | |
+ default, | |
+ default, | |
+ default | |
+ ); | |
+ | |
+ public bool HasSenderAddress => Number != SkStackEventNumber.WakeupSignalReceived; | |
+ | |
+ public SkStackEventNumber Number { get; } | |
+ | |
+#if SYSTEM_DIAGNOSTICS_CODEANALYSIS_MEMBERNOTNULLWHENATTRIBUTE | |
+ [MemberNotNullWhen(true, nameof(HasSenderAddress))] | |
+#endif | |
+ public IPAddress? SenderAddress { get; } | |
+ | |
+ public int Parameter { get; } | |
+ | |
+ public SkStackEventCode ExpectedSubsequentEventCode { get; } | |
+ | |
+ private SkStackEvent( | |
+ SkStackEventNumber number, | |
+ IPAddress? senderAddress, | |
+ int parameter, | |
+ SkStackEventCode expectedSubsequentEventCode | |
+ ) | |
+ { | |
+ Number = number; | |
+ SenderAddress = senderAddress; | |
+ Parameter = parameter; | |
+ ExpectedSubsequentEventCode = expectedSubsequentEventCode; | |
+ } | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP.Protocol/SkStackEventCode.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP.Protocol/SkStackEventCode.cs | |
new file mode 100644 | |
index 0000000..5cab3b1 | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP.Protocol/SkStackEventCode.cs | |
@@ -0,0 +1,51 @@ | |
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+ | |
+namespace Smdn.Net.SkStackIP.Protocol; | |
+ | |
+/// <remarks> | |
+/// <para>See 'BP35A1コマンドリファレンス 4. イベント' for detailed specifications.</para> | |
+/// </remarks> | |
+internal enum SkStackEventCode { | |
+ Undefined = 0, | |
+ | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 4.1. ERXUDP' for detailed specifications.</para> | |
+ /// </remarks> | |
+ ERXUDP = 0x41, | |
+ | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 4.2. EPONG' for detailed specifications.</para> | |
+ /// </remarks> | |
+ EPONG = 0x42, | |
+ | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 4.3. EADDR' for detailed specifications.</para> | |
+ /// </remarks> | |
+ EADDR = 0x43, | |
+ | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 4.4. ENEIGHBOR' for detailed specifications.</para> | |
+ /// </remarks> | |
+ ENEIGHBOR = 0x44, | |
+ | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 4.5. EPANDESC' for detailed specifications.</para> | |
+ /// </remarks> | |
+ EPANDESC = 0x45, | |
+ | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 4.6. EEDSCAN' for detailed specifications.</para> | |
+ /// </remarks> | |
+ EEDSCAN = 0x46, | |
+ | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 4.7. EPORT' for detailed specifications.</para> | |
+ /// </remarks> | |
+ EPORT = 0x47, | |
+ | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 4.8. EVENT' for detailed specifications.</para> | |
+ /// </remarks> | |
+ EVENT = 0x48, | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP.Protocol/SkStackEventCodeNames.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP.Protocol/SkStackEventCodeNames.cs | |
new file mode 100644 | |
index 0000000..7e36d5f | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP.Protocol/SkStackEventCodeNames.cs | |
@@ -0,0 +1,17 @@ | |
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+ | |
+using System; | |
+ | |
+namespace Smdn.Net.SkStackIP.Protocol; | |
+ | |
+internal static class SkStackEventCodeNames { | |
+ public static ReadOnlySpan<byte> ERXUDP => "ERXUDP"u8; | |
+ public static ReadOnlySpan<byte> EPONG => "EPONG"u8; | |
+ public static ReadOnlySpan<byte> EADDR => "EADDR"u8; | |
+ public static ReadOnlySpan<byte> ENEIGHBOR => "ENEIGHBOR"u8; | |
+ public static ReadOnlySpan<byte> EPANDESC => "EPANDESC"u8; | |
+ public static ReadOnlySpan<byte> EEDSCAN => "EEDSCAN"u8; | |
+ public static ReadOnlySpan<byte> EPORT => "EPORT"u8; | |
+ public static ReadOnlySpan<byte> EVENT => "EVENT"u8; | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP.Protocol/SkStackEventHandlerBase.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP.Protocol/SkStackEventHandlerBase.cs | |
new file mode 100644 | |
index 0000000..a51f3f5 | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP.Protocol/SkStackEventHandlerBase.cs | |
@@ -0,0 +1,9 @@ | |
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+namespace Smdn.Net.SkStackIP.Protocol; | |
+ | |
+internal abstract class SkStackEventHandlerBase { | |
+ public virtual bool DoContinueHandlingEvents(SkStackResponseStatus status) => status != SkStackResponseStatus.Fail; | |
+ public abstract bool TryProcessEvent(SkStackEvent ev); | |
+ public virtual void ProcessSubsequentEvent(ISkStackSequenceParserContext context) { /*do nothing*/ } | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP.Protocol/SkStackEventParser.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP.Protocol/SkStackEventParser.cs | |
new file mode 100644 | |
index 0000000..1c281dc | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP.Protocol/SkStackEventParser.cs | |
@@ -0,0 +1,428 @@ | |
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+using System; | |
+using System.Buffers; | |
+using System.Collections.Generic; | |
+using System.Collections.ObjectModel; // ReadOnlyDictionary | |
+#if NULL_STATE_STATIC_ANALYSIS_ATTRIBUTES | |
+using System.Diagnostics.CodeAnalysis; | |
+#endif | |
+using System.Net; | |
+using System.Net.NetworkInformation; | |
+ | |
+namespace Smdn.Net.SkStackIP.Protocol; | |
+ | |
+internal static class SkStackEventParser { | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 4.1. ERXUDP' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public static OperationStatus TryExpectERXUDP( | |
+ ISkStackSequenceParserContext context, | |
+ SkStackERXUDPDataFormat erxudpDataFormat, | |
+ out SkStackUdpReceiveEvent erxudp, | |
+ out ReadOnlySequence<byte> erxudpData, | |
+ out int erxudpDataLength | |
+ ) | |
+ { | |
+ erxudp = default; | |
+ erxudpData = default; | |
+ erxudpDataLength = default; | |
+ | |
+ var reader = context.CreateReader(); | |
+ var status = SkStackTokenParser.TryExpectToken(ref reader, SkStackEventCodeNames.ERXUDP); | |
+ | |
+ if (status is OperationStatus.NeedMoreData or OperationStatus.InvalidData) | |
+ return status; | |
+ | |
+ if ( | |
+ SkStackTokenParser.ExpectIPADDR(ref reader, out var sender) && | |
+ SkStackTokenParser.ExpectIPADDR(ref reader, out var dest) && | |
+ SkStackTokenParser.ExpectUINT16(ref reader, out var rport) && | |
+ SkStackTokenParser.ExpectUINT16(ref reader, out var lport) && | |
+ SkStackTokenParser.ExpectADDR64(ref reader, out var senderlla) && | |
+ SkStackTokenParser.ExpectBinary(ref reader, out var secured) && | |
+ SkStackTokenParser.ExpectUINT16(ref reader, out var datalen) | |
+ ) { | |
+ erxudpDataLength = datalen; | |
+ | |
+ var lengthOfDataSequence = erxudpDataFormat switch { | |
+ SkStackERXUDPDataFormat.HexAsciiText => erxudpDataLength * 2, | |
+ _ => erxudpDataLength, | |
+ }; | |
+ | |
+ if (reader.Remaining < lengthOfDataSequence + 2 /*CRLF*/) | |
+ return OperationStatus.NeedMoreData; | |
+ | |
+ var erxudpDataStart = reader.Position; | |
+ | |
+ reader.Advance(lengthOfDataSequence); | |
+ | |
+ var erxudpDataEnd = reader.Position; | |
+ | |
+ if (!SkStackTokenParser.ExpectEndOfLine(ref reader)) | |
+ return OperationStatus.NeedMoreData; | |
+ | |
+ erxudp = new( | |
+ sender: sender, | |
+ dest: dest, | |
+ rport: rport, | |
+ lport: lport, | |
+ senderlla: senderlla, | |
+ secured: secured | |
+ ); | |
+ | |
+ erxudpData = reader.Sequence.Slice(erxudpDataStart, erxudpDataEnd); | |
+ | |
+ context.Complete(reader); | |
+ return OperationStatus.Done; | |
+ } | |
+ | |
+ return OperationStatus.NeedMoreData; | |
+ } | |
+ | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 4.2. EPONG' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public static bool ExpectEPONG( | |
+ ISkStackSequenceParserContext context | |
+ ) | |
+ => throw new NotImplementedException(); | |
+ | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 4.3. EADDR' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public static IReadOnlyList<IPAddress>? ExpectEADDR( | |
+ ISkStackSequenceParserContext context | |
+ ) | |
+ { | |
+ var reader = context.CreateReader(); | |
+ | |
+ if (SkStackTokenParser.TryExpectStatusLine(ref reader, out var status)) { | |
+ // do not consume here | |
+ context.Ignore(); | |
+ return status == SkStackResponseStatus.Ok ? Array.Empty<IPAddress>() : null; | |
+ } | |
+ else if ( | |
+ SkStackTokenParser.ExpectToken(ref reader, SkStackEventCodeNames.EADDR) && | |
+ SkStackTokenParser.ExpectEndOfLine(ref reader) | |
+ ) { | |
+ var list = new List<IPAddress>(capacity: 2); | |
+ | |
+ for (; ; ) { | |
+ var statusLineReader = reader; | |
+ | |
+ if (SkStackTokenParser.TryExpectStatusLine(ref statusLineReader, out var st)) { | |
+ context.Complete(reader); // do not consume status line here | |
+ return st == SkStackResponseStatus.Ok ? list : null; | |
+ } | |
+ else if ( | |
+ SkStackTokenParser.ExpectIPADDR(ref reader, out var address) && | |
+ SkStackTokenParser.ExpectEndOfLine(ref reader) | |
+ ) { | |
+ list.Add(address); | |
+ } | |
+ else { | |
+ context.SetAsIncomplete(); | |
+ return default; | |
+ } | |
+ } | |
+ } | |
+ | |
+ context.SetAsIncomplete(); | |
+ return default; | |
+ } | |
+ | |
+ private static readonly IReadOnlyDictionary<IPAddress, PhysicalAddress> EmptyNeighborCacheList = new ReadOnlyDictionary<IPAddress, PhysicalAddress>( | |
+ new Dictionary<IPAddress, PhysicalAddress>(capacity: 0) | |
+ ); | |
+ | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 4.4. ENEIGHBOR' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public static IReadOnlyDictionary<IPAddress, PhysicalAddress>? ExpectENEIGHBOR( | |
+ ISkStackSequenceParserContext context | |
+ ) | |
+ { | |
+ var statusLineReader = context.CreateReader(); | |
+ var reader = context.CreateReader(); | |
+ | |
+ if (SkStackTokenParser.TryExpectStatusLine(ref statusLineReader, out var status)) { | |
+ // do not consume status line here | |
+ context.Ignore(); | |
+ return status == SkStackResponseStatus.Ok ? EmptyNeighborCacheList : null; | |
+ } | |
+ else if ( | |
+ SkStackTokenParser.ExpectToken(ref reader, SkStackEventCodeNames.ENEIGHBOR) && | |
+ SkStackTokenParser.ExpectEndOfLine(ref reader) | |
+ ) { | |
+ const int NumberOfNeighborCacheEntry = 8; // 3.18. SKADDNBR | |
+ var neighborCache = new Dictionary<IPAddress, PhysicalAddress>(capacity: NumberOfNeighborCacheEntry); | |
+ | |
+ for (; ; ) { | |
+ statusLineReader = reader; | |
+ | |
+ if (SkStackTokenParser.TryExpectStatusLine(ref statusLineReader, out var st)) { | |
+ context.Complete(reader); // do not consume status line here | |
+ return st == SkStackResponseStatus.Ok ? neighborCache : null; | |
+ } | |
+ else if ( | |
+ SkStackTokenParser.ExpectIPADDR(ref reader, out var ipaddr) && | |
+ SkStackTokenParser.ExpectADDR64(ref reader, out var addr64) && | |
+ SkStackTokenParser.ExpectADDR16(ref reader, out _) && | |
+ SkStackTokenParser.ExpectEndOfLine(ref reader) | |
+ ) { | |
+ neighborCache[ipaddr] = addr64; | |
+ } | |
+ else { | |
+ break; | |
+ } | |
+ } | |
+ } | |
+ | |
+ context.SetAsIncomplete(); | |
+ return default; | |
+ } | |
+ | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 4.5. EPANDESC' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public static bool ExpectEPANDESC( | |
+ ISkStackSequenceParserContext context, | |
+ bool expectPairingId, | |
+ out SkStackPanDescription pandesc | |
+ ) | |
+ { | |
+ pandesc = default; | |
+ | |
+ var reader = context.CreateReader(); | |
+ | |
+ if ( | |
+ SkStackTokenParser.ExpectToken(ref reader, SkStackEventCodeNames.EPANDESC) && | |
+ SkStackTokenParser.ExpectEndOfLine(ref reader) | |
+ ) { | |
+ if ( | |
+ SkStackTokenParser.ExpectSequence(ref reader, EPANDESCPrefixChannel) && | |
+ SkStackTokenParser.ExpectUINT8(ref reader, out var channel) && | |
+ SkStackTokenParser.ExpectEndOfLine(ref reader) && | |
+ | |
+ SkStackTokenParser.ExpectSequence(ref reader, EPANDESCPrefixChannelPage) && | |
+ SkStackTokenParser.ExpectUINT8(ref reader, out var channelPage) && | |
+ SkStackTokenParser.ExpectEndOfLine(ref reader) && | |
+ | |
+ SkStackTokenParser.ExpectSequence(ref reader, EPANDESCPrefixPanId) && | |
+ SkStackTokenParser.ExpectUINT16(ref reader, out var panId) && | |
+ SkStackTokenParser.ExpectEndOfLine(ref reader) && | |
+ | |
+ SkStackTokenParser.ExpectSequence(ref reader, EPANDESCPrefixAddress) && | |
+ SkStackTokenParser.ExpectADDR64(ref reader, out var macAddress) && | |
+ SkStackTokenParser.ExpectEndOfLine(ref reader) && | |
+ | |
+ SkStackTokenParser.ExpectSequence(ref reader, EPANDESCPrefixLQI) && | |
+ SkStackTokenParser.ExpectUINT8(ref reader, out var lqi) && | |
+ SkStackTokenParser.ExpectEndOfLine(ref reader) | |
+ ) { | |
+ uint pairingId = default; | |
+ | |
+ if (expectPairingId) { | |
+ if (!( | |
+ SkStackTokenParser.ExpectSequence(ref reader, EPANDESCPrefixPairId) && | |
+ SkStackTokenParser.ExpectUINT32(ref reader, out pairingId) && // instead of CHAR[8] | |
+ SkStackTokenParser.ExpectEndOfLine(ref reader) | |
+ )) { | |
+ context.SetAsIncomplete(); | |
+ return false; | |
+ } | |
+ } | |
+ | |
+ pandesc = new SkStackPanDescription( | |
+ channel: SkStackChannel.FindByChannelNumber(channel, nameof(channel)), | |
+ channelPage: channelPage, | |
+ id: panId, | |
+ macAddress: macAddress, | |
+ rssi: SkStackLQI.ToRSSI(lqi), | |
+ pairingId: expectPairingId ? pairingId : default | |
+ ); | |
+ | |
+ context.Complete(reader); | |
+ return true; | |
+ } | |
+ } | |
+ | |
+ context.SetAsIncomplete(); | |
+ return false; | |
+ } | |
+ | |
+#pragma warning disable IDE0055 | |
+ private static ReadOnlySpan<byte> EPANDESCPrefixChannel => " Channel:"u8; | |
+ private static ReadOnlySpan<byte> EPANDESCPrefixChannelPage => " Channel Page:"u8; | |
+ private static ReadOnlySpan<byte> EPANDESCPrefixPanId => " Pan ID:"u8; | |
+ private static ReadOnlySpan<byte> EPANDESCPrefixAddress => " Addr:"u8; | |
+ private static ReadOnlySpan<byte> EPANDESCPrefixLQI => " LQI:"u8; | |
+ private static ReadOnlySpan<byte> EPANDESCPrefixPairId => " PairID:"u8; | |
+#pragma warning restore IDE0055 | |
+ | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 4.6. EEDSCAN' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public static bool ExpectEEDSCAN( | |
+ ISkStackSequenceParserContext context, | |
+#if NULL_STATE_STATIC_ANALYSIS_ATTRIBUTES | |
+ [NotNullWhen(true)] | |
+#endif | |
+ out IReadOnlyDictionary<SkStackChannel, decimal>? result | |
+ ) | |
+ { | |
+ result = default; | |
+ | |
+ var reader = context.CreateReader(); | |
+ | |
+ if ( | |
+ SkStackTokenParser.ExpectToken(ref reader, SkStackEventCodeNames.EEDSCAN) && | |
+ SkStackTokenParser.ExpectEndOfLine(ref reader) | |
+ ) { | |
+ var ret = new Dictionary<SkStackChannel, decimal>(SkStackChannel.Channels.Count); | |
+ | |
+ result = ret; | |
+ | |
+ for (var i = 0; i < SkStackChannel.Channels.Count; i++) { | |
+ if ( | |
+ SkStackTokenParser.ExpectUINT8(ref reader, out var channel) && | |
+ SkStackTokenParser.ExpectUINT8(ref reader, out var lqi) | |
+ ) { | |
+ ret[SkStackChannel.FindByChannelNumber(channel, nameof(channel))] = SkStackLQI.ToRSSI(lqi); | |
+ } | |
+ else { | |
+ context.SetAsIncomplete(); | |
+ return false; | |
+ } | |
+ } | |
+ | |
+ if (SkStackTokenParser.ExpectEndOfLine(ref reader)) { | |
+ // [VER 1.2.10, APPVER rev26e] EEDSCAN responds extra CRLF | |
+ if (SkStackTokenParser.ExpectSequence(ref reader, SkStack.CRLFSpan)) | |
+ SkStackTokenParser.ExpectEndOfLine(ref reader); | |
+ | |
+ context.Complete(reader); | |
+ return true; | |
+ } | |
+ } | |
+ | |
+ context.SetAsIncomplete(); | |
+ return false; | |
+ } | |
+ | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 4.7. EPORT' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public static IReadOnlyList<SkStackUdpPort>? ExpectEPORT( | |
+ ISkStackSequenceParserContext context | |
+ ) | |
+ { | |
+ var statusLineReader = context.CreateReader(); | |
+ var reader = context.CreateReader(); | |
+ | |
+ if (SkStackTokenParser.TryExpectStatusLine(ref statusLineReader, out var status)) { | |
+ // do not consume status line here | |
+ context.Ignore(); | |
+ return status == SkStackResponseStatus.Ok ? Array.Empty<SkStackUdpPort>() : null; | |
+ } | |
+ else if ( | |
+ SkStackTokenParser.ExpectToken(ref reader, SkStackEventCodeNames.EPORT) && | |
+ SkStackTokenParser.ExpectEndOfLine(ref reader) | |
+ ) { | |
+ var ports = new SkStackUdpPort[SkStackUdpPort.NumberOfPorts]; | |
+ | |
+ for (var i = 0; i < SkStackUdpPort.NumberOfPorts; i++) { | |
+ if ( | |
+ SkStackTokenParser.ExpectDecimalNumber(ref reader, out var port) && | |
+ SkStackTokenParser.ExpectEndOfLine(ref reader) | |
+ ) { | |
+ ports[i] = new SkStackUdpPort( | |
+ handle: (SkStackUdpPortHandle)((int)SkStackUdpPort.HandleMin + i), | |
+ port: (int)port | |
+ ); | |
+ } | |
+ else { | |
+ break; // incomplete | |
+ } | |
+ } | |
+ | |
+ for (; ; ) { | |
+ statusLineReader = reader; | |
+ | |
+ if (SkStackTokenParser.TryExpectStatusLine(ref statusLineReader, out var st)) { | |
+ context.Complete(reader); // do not consume status line here | |
+ return st == SkStackResponseStatus.Ok ? ports : null; | |
+ } | |
+ | |
+ // [VER 1.2.10, APPVER rev26e] EPORT responds extra CRLF and PORT_UDPs? | |
+ // "EPORT␍␊3610␍␊716␍␊0␍␊0␍␊0␍␊0␍␊␍␊3610␍␊0␍␊0␍␊0␍␊OK␍␊" | |
+ | |
+ if (reader.TryReadTo(out ReadOnlySequence<byte> _, SkStack.CRLFSpan, advancePastDelimiter: true)) | |
+ continue; // ignore extra lines | |
+ else | |
+ break; // incomplete | |
+ } | |
+ } | |
+ | |
+ context.SetAsIncomplete(); | |
+ return default; | |
+ } | |
+ | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 4.8. EVENT' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public static OperationStatus TryExpectEVENT( | |
+ ISkStackSequenceParserContext context, | |
+ out SkStackEvent ev | |
+ ) | |
+ { | |
+ ev = default; | |
+ | |
+ var reader = context.CreateReader(); | |
+ var status = SkStackTokenParser.TryExpectToken(ref reader, SkStackEventCodeNames.EVENT); | |
+ | |
+ if (status is OperationStatus.NeedMoreData or OperationStatus.InvalidData) | |
+ return status; | |
+ | |
+ if (SkStackTokenParser.ExpectUINT8(ref reader, out var num)) { | |
+ var number = (SkStackEventNumber)num; | |
+ | |
+ IPAddress? sender = default; | |
+ int parameter = default; | |
+ SkStackEventCode expectedSubsequentEventCode = default; | |
+ | |
+ // C0 does not define <IPADDR> and <PARAM> | |
+ if (number != SkStackEventNumber.WakeupSignalReceived) { | |
+ if (!SkStackTokenParser.ExpectIPADDR(ref reader, out sender)) | |
+ return OperationStatus.NeedMoreData; | |
+ | |
+ switch (number) { | |
+ case SkStackEventNumber.EnergyDetectScanCompleted: | |
+ expectedSubsequentEventCode = SkStackEventCode.EEDSCAN; | |
+ break; | |
+ case SkStackEventNumber.BeaconReceived: | |
+ expectedSubsequentEventCode = SkStackEventCode.EPANDESC; | |
+ break; | |
+ case SkStackEventNumber.UdpSendCompleted: | |
+ if (!SkStackTokenParser.ExpectUINT8(ref reader, out var param)) | |
+ return OperationStatus.NeedMoreData; | |
+ | |
+ parameter = param; | |
+ break; | |
+ } | |
+ } | |
+ | |
+ if (SkStackTokenParser.ExpectEndOfLine(ref reader)) { | |
+ ev = number == SkStackEventNumber.WakeupSignalReceived | |
+ ? SkStackEvent.CreateWakeupSignalReceived() | |
+ : SkStackEvent.Create(number, sender!, parameter, expectedSubsequentEventCode); | |
+ | |
+ context.Complete(reader); | |
+ return OperationStatus.Done; | |
+ } | |
+ } | |
+ | |
+ return OperationStatus.NeedMoreData; | |
+ } | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP.Protocol/SkStackLQI.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP.Protocol/SkStackLQI.cs | |
new file mode 100644 | |
index 0000000..bd8be43 | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP.Protocol/SkStackLQI.cs | |
@@ -0,0 +1,11 @@ | |
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+ | |
+namespace Smdn.Net.SkStackIP.Protocol; | |
+ | |
+internal static class SkStackLQI { | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 4.6. EEDSCAN' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public static decimal ToRSSI(int lqi) => (0.275m * lqi) - 104.27m; | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP.Protocol/SkStackProtocolSyntax.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP.Protocol/SkStackProtocolSyntax.cs | |
new file mode 100644 | |
index 0000000..77558ba | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP.Protocol/SkStackProtocolSyntax.cs | |
@@ -0,0 +1,42 @@ | |
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+ | |
+using System; | |
+ | |
+namespace Smdn.Net.SkStackIP.Protocol; | |
+ | |
+public abstract class SkStackProtocolSyntax { | |
+ public static SkStackProtocolSyntax Default { get; } = new DefaultSyntax(); | |
+ | |
+ private sealed class DefaultSyntax : SkStackProtocolSyntax { | |
+ public override ReadOnlySpan<byte> EndOfCommandLine => SkStack.CRLFSpan; | |
+ public override bool ExpectStatusLine => true; | |
+ public override ReadOnlySpan<byte> EndOfStatusLine => SkStack.CRLFSpan; | |
+ } | |
+ | |
+ internal static SkStackProtocolSyntax SKSENDTO { get; } = new SKSENDTOSyntax(); | |
+ | |
+ private sealed class SKSENDTOSyntax : SkStackProtocolSyntax { | |
+ public override ReadOnlySpan<byte> EndOfCommandLine => ReadOnlySpan<byte>.Empty; | |
+ public override ReadOnlySpan<byte> EndOfEchobackLine => SkStack.CRLFSpan; | |
+ public override bool ExpectStatusLine => true; | |
+ public override ReadOnlySpan<byte> EndOfStatusLine => SkStack.CRLFSpan; | |
+ } | |
+ | |
+ internal static SkStackProtocolSyntax SKLL64 { get; } = new SKLL64Syntax(); | |
+ | |
+ private sealed class SKLL64Syntax : SkStackProtocolSyntax { | |
+ public override ReadOnlySpan<byte> EndOfCommandLine => SkStack.CRLFSpan; | |
+ public override bool ExpectStatusLine => false; | |
+ public override ReadOnlySpan<byte> EndOfStatusLine => SkStack.CRLFSpan; | |
+ } | |
+ | |
+ protected SkStackProtocolSyntax() | |
+ { | |
+ } | |
+ | |
+ public abstract ReadOnlySpan<byte> EndOfCommandLine { get; } | |
+ public virtual ReadOnlySpan<byte> EndOfEchobackLine => EndOfCommandLine; | |
+ public abstract bool ExpectStatusLine { get; } | |
+ public abstract ReadOnlySpan<byte> EndOfStatusLine { get; } | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP.Protocol/SkStackResponseStatusCodes.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP.Protocol/SkStackResponseStatusCodes.cs | |
new file mode 100644 | |
index 0000000..7f357ce | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP.Protocol/SkStackResponseStatusCodes.cs | |
@@ -0,0 +1,12 @@ | |
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+ | |
+using System; | |
+ | |
+namespace Smdn.Net.SkStackIP.Protocol; | |
+ | |
+internal static class SkStackResponseStatusCodes { | |
+ public static ReadOnlySpan<byte> OK => "OK"u8; | |
+ | |
+ public static ReadOnlySpan<byte> FAIL => "FAIL"u8; | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP.Protocol/SkStackSequenceParser.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP.Protocol/SkStackSequenceParser.cs | |
new file mode 100644 | |
index 0000000..7ebd729 | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP.Protocol/SkStackSequenceParser.cs | |
@@ -0,0 +1,8 @@ | |
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+ | |
+namespace Smdn.Net.SkStackIP.Protocol; | |
+ | |
+public delegate TResult SkStackSequenceParser<TResult>( | |
+ ISkStackSequenceParserContext context | |
+); | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP.Protocol/SkStackTokenParser.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP.Protocol/SkStackTokenParser.cs | |
new file mode 100644 | |
index 0000000..bb9a540 | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP.Protocol/SkStackTokenParser.cs | |
@@ -0,0 +1,580 @@ | |
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+ | |
+using System; | |
+using System.Buffers; | |
+using System.Buffers.Binary; | |
+#if NULL_STATE_STATIC_ANALYSIS_ATTRIBUTES | |
+using System.Diagnostics.CodeAnalysis; | |
+#endif | |
+using System.Net; | |
+using System.Net.NetworkInformation; | |
+ | |
+using Smdn.Formats; | |
+using Smdn.Text.Unicode.ControlPictures; | |
+ | |
+namespace Smdn.Net.SkStackIP.Protocol; | |
+ | |
+public static class SkStackTokenParser { | |
+ private delegate (bool Success, TResult Result) TryConvertTokenFunc<TArg, TResult>(ReadOnlySequence<byte> token, TArg arg); | |
+ | |
+ private readonly struct None { } | |
+ | |
+ private static OperationStatus TryExpectToken( | |
+ ref SequenceReader<byte> reader, | |
+ ReadOnlySpan<byte> expectedToken, | |
+ bool throwIfUnexpected | |
+ ) | |
+ => TryExpectOrConvertToken<None, None>( | |
+ reader: ref reader, | |
+ length: | |
+#if DEBUG | |
+ expectedToken.Length == 0 ? throw new InvalidOperationException("expected token must be non-empty string") : expectedToken.Length, | |
+#else | |
+ expectedToken.Length, | |
+#endif | |
+ throwIfUnexpected: throwIfUnexpected, | |
+ expectedToken: expectedToken, | |
+ arg: default, // no argument | |
+ tryConvert: default, // no argument | |
+ out _ // discard | |
+ ); | |
+ | |
+ private static OperationStatus TryConvertToken<TArg, TResult>( | |
+ ref SequenceReader<byte> reader, | |
+ int length, | |
+ bool throwIfUnexpected, | |
+ TArg arg, | |
+ TryConvertTokenFunc<TArg, TResult>? tryConvert, | |
+ out TResult? result | |
+ ) | |
+ => TryExpectOrConvertToken( | |
+ reader: ref reader, | |
+ length: length, | |
+ throwIfUnexpected: throwIfUnexpected, | |
+ expectedToken: default, // no argument | |
+ arg: arg, | |
+ tryConvert: tryConvert, | |
+ out result | |
+ ); | |
+ | |
+ private static OperationStatus TryExpectOrConvertToken<TArg, TResult>( | |
+ ref SequenceReader<byte> reader, | |
+ int length, | |
+ bool throwIfUnexpected, | |
+ ReadOnlySpan<byte> expectedToken, | |
+ TArg arg, | |
+ TryConvertTokenFunc<TArg, TResult>? tryConvert, | |
+ out TResult? result | |
+ ) | |
+ { | |
+ result = default; | |
+ | |
+ // if fixed length | |
+ if (0 < length && reader.Remaining < length) | |
+ return OperationStatus.NeedMoreData; | |
+ | |
+ var readerEOL = reader; | |
+ var readerSP = reader; | |
+ var foundTokenDelimitByEOL = readerEOL.TryReadTo(out ReadOnlySequence<byte> tokenDelimitByEOL, SkStack.CRLFSpan); | |
+ var foundTokenDelimitBySP = readerSP.TryReadTo(out ReadOnlySequence<byte> tokenDelimitBySP, SkStack.SP); | |
+ | |
+ if (foundTokenDelimitByEOL && foundTokenDelimitBySP) { | |
+ // select shorter one | |
+ if (tokenDelimitByEOL.Length < tokenDelimitBySP.Length) { | |
+ foundTokenDelimitByEOL = true; | |
+ foundTokenDelimitBySP = false; | |
+ } | |
+ else { | |
+ foundTokenDelimitBySP = true; | |
+ foundTokenDelimitByEOL = false; | |
+ } | |
+ } | |
+ | |
+ if (!(foundTokenDelimitByEOL || foundTokenDelimitBySP)) | |
+ return OperationStatus.NeedMoreData; | |
+ | |
+ ReadOnlySequence<byte> token; | |
+ var consumedReader = reader; | |
+ | |
+ if (foundTokenDelimitByEOL) { | |
+ token = tokenDelimitByEOL; | |
+ consumedReader.Advance(tokenDelimitByEOL.Length); // keep EOL | |
+ } | |
+ else { | |
+ token = tokenDelimitBySP; | |
+ consumedReader.Advance(tokenDelimitBySP.Length + 1); // consume SP | |
+ } | |
+ | |
+ if (0 < length && token.Length != length) { | |
+ return throwIfUnexpected | |
+ ? throw SkStackUnexpectedResponseException.CreateInvalidToken( | |
+ token, | |
+ $"invalid length of token (expected {length} but was {token.Length})" | |
+ ) | |
+ : OperationStatus.InvalidData; | |
+ } | |
+ | |
+ if (!expectedToken.IsEmpty) { | |
+ if (IsExpectedToken(token, expectedToken, throwIfUnexpected)) { | |
+ reader = consumedReader; | |
+ | |
+ return OperationStatus.Done; | |
+ } | |
+ | |
+ return throwIfUnexpected | |
+ ? throw SkStackUnexpectedResponseException.CreateInvalidToken( | |
+ token: token, | |
+ extraMessage: $"expected: '{expectedToken.ToControlCharsPicturizedString()}'" | |
+ ) | |
+ : OperationStatus.InvalidData; | |
+ } | |
+ | |
+ if (tryConvert is null) | |
+ throw new InvalidOperationException($"It is necessary to specify a valid value for either the parameter {nameof(expectedToken)} or {nameof(tryConvert)}."); | |
+ | |
+ bool converted; | |
+ | |
+ try { | |
+ (converted, result) = tryConvert(token, arg); | |
+ } | |
+ catch (SkStackUnexpectedResponseException) { | |
+ throw; // rethrow | |
+ } | |
+ catch (Exception ex) { | |
+ result = default; | |
+ | |
+ throw SkStackUnexpectedResponseException.CreateInvalidFormat( | |
+ token: token, | |
+ innerException: ex | |
+ ); | |
+ } | |
+ | |
+ if (!converted) | |
+ return OperationStatus.InvalidData; | |
+ | |
+ reader = consumedReader; | |
+ | |
+ return OperationStatus.Done; | |
+ | |
+ static bool IsExpectedToken(ReadOnlySequence<byte> tokenSequence, ReadOnlySpan<byte> expectedToken, bool throwIfUnexpected) | |
+ { | |
+ var reader = new SequenceReader<byte>(tokenSequence); | |
+ | |
+ for (var i = 0; i < expectedToken.Length; i++) { | |
+ if (reader.TryRead(out var b) && b != expectedToken[i]) { | |
+ return throwIfUnexpected | |
+ ? throw SkStackUnexpectedResponseException.CreateInvalidToken(tokenSequence, "unexpected token") | |
+ : false; | |
+ } | |
+ } | |
+ | |
+ return true; | |
+ } | |
+ } | |
+ | |
+ public static bool Expect<TValue>( | |
+ ref SequenceReader<byte> reader, | |
+ int length, | |
+ Converter<ReadOnlySequence<byte>, TValue> converter, | |
+#if NULL_STATE_STATIC_ANALYSIS_ATTRIBUTES | |
+ [NotNullWhen(true)] | |
+#endif | |
+ out TValue? value | |
+ ) | |
+ => OperationStatus.Done == TryConvertToken( | |
+ reader: ref reader, | |
+ length: length, | |
+ throwIfUnexpected: true, | |
+ arg: converter, | |
+ tryConvert: static (token, conv) => (true, conv(token)), | |
+ result: out value | |
+ ); | |
+ | |
+ public static OperationStatus TryExpectToken( | |
+ ref SequenceReader<byte> reader, | |
+ ReadOnlySpan<byte> expectedToken | |
+ ) | |
+ => TryExpectToken( | |
+ reader: ref reader, | |
+ expectedToken: expectedToken, | |
+ throwIfUnexpected: false | |
+ ); | |
+ | |
+ public static bool ExpectToken( | |
+ ref SequenceReader<byte> reader, | |
+ ReadOnlySpan<byte> expectedToken | |
+ ) | |
+ => OperationStatus.Done == TryExpectToken( | |
+ reader: ref reader, | |
+ expectedToken: expectedToken, | |
+ throwIfUnexpected: true | |
+ ); | |
+ | |
+ public static bool ExpectEndOfLine( | |
+ ref SequenceReader<byte> reader | |
+ ) | |
+ => ExpectSequenceCore( | |
+ reader: ref reader, | |
+ expectedSequence: SkStack.CRLFSpan, | |
+ throwIfUnexpected: true, | |
+ createUnexpectedExceptionMessage: static _ => $"expected EOL, but not" | |
+ ); | |
+ | |
+ public static bool ExpectSequence( | |
+ ref SequenceReader<byte> reader, | |
+ ReadOnlySpan<byte> expectedSequence | |
+ ) | |
+ => ExpectSequenceCore( | |
+ reader: ref reader, | |
+ expectedSequence: expectedSequence, | |
+ throwIfUnexpected: true, | |
+ createUnexpectedExceptionMessage: static seq => $"expected sequence '{seq.ToControlCharsPicturizedString()}', but not" | |
+ ); | |
+ | |
+ private delegate string CreateUnexpectedSequenceExceptionMessageFunc(ReadOnlySpan<byte> expectedSequence); | |
+ | |
+ private static bool ExpectSequenceCore( | |
+ ref SequenceReader<byte> reader, | |
+ ReadOnlySpan<byte> expectedSequence, | |
+ bool throwIfUnexpected, | |
+ CreateUnexpectedSequenceExceptionMessageFunc createUnexpectedExceptionMessage | |
+ ) | |
+ { | |
+ if (reader.Remaining < expectedSequence.Length) | |
+ return false; // incomplete | |
+ | |
+ var consumedReader = reader; | |
+ | |
+ if (!consumedReader.IsNext(expectedSequence, advancePast: true)) { | |
+ return throwIfUnexpected | |
+ ? throw SkStackUnexpectedResponseException.CreateInvalidToken( | |
+ consumedReader.GetUnreadSequence().Slice(0, expectedSequence.Length), | |
+ createUnexpectedExceptionMessage(expectedSequence) | |
+ ) | |
+ : false; | |
+ } | |
+ | |
+ reader = consumedReader; | |
+ | |
+ return true; | |
+ } | |
+ | |
+ public static bool ExpectCharArray( | |
+ ref SequenceReader<byte> reader, | |
+ out ReadOnlyMemory<byte> value | |
+ ) | |
+ => Expect( | |
+ reader: ref reader, | |
+ length: 0, | |
+ converter: static seq => seq.ToArray().AsMemory(), | |
+ value: out value | |
+ ); | |
+ | |
+ public static bool ExpectCharArray( | |
+ ref SequenceReader<byte> reader, | |
+#if NULL_STATE_STATIC_ANALYSIS_ATTRIBUTES | |
+ [NotNullWhen(true)] | |
+#endif | |
+ out string? value | |
+ ) | |
+ => Expect( | |
+ reader: ref reader, | |
+ length: 0, | |
+ converter: SkStack.GetString, | |
+ value: out value | |
+ ); | |
+ | |
+ [CLSCompliant(false)] | |
+ public static bool ExpectDecimalNumber( | |
+ ref SequenceReader<byte> reader, | |
+ out uint value | |
+ ) | |
+ => Expect(ref reader, 0, ToDecimalNumber, out value); | |
+ | |
+ [CLSCompliant(false)] | |
+ public static bool ExpectDecimalNumber( | |
+ ref SequenceReader<byte> reader, | |
+ int length, | |
+ out uint value | |
+ ) | |
+ => Expect(ref reader, length, ToDecimalNumber, out value); | |
+ | |
+ public static bool ExpectUINT8( | |
+ ref SequenceReader<byte> reader, | |
+ out byte value | |
+ ) | |
+ => Expect(ref reader, length: 1 * 2, ToUINT8, out value); | |
+ | |
+ [CLSCompliant(false)] | |
+ public static bool ExpectUINT16( | |
+ ref SequenceReader<byte> reader, | |
+ out ushort value | |
+ ) | |
+ => Expect(ref reader, length: 2 * 2, ToUINT16, out value); | |
+ | |
+ [CLSCompliant(false)] | |
+ public static bool ExpectUINT32( | |
+ ref SequenceReader<byte> reader, | |
+ out uint value | |
+ ) | |
+ => Expect(ref reader, length: 4 * 2, ToUINT32, out value); | |
+ | |
+ [CLSCompliant(false)] | |
+ public static bool ExpectUINT64( | |
+ ref SequenceReader<byte> reader, | |
+ out ulong value | |
+ ) | |
+ => Expect(ref reader, length: 8 * 2, ToUINT64, out value); | |
+ | |
+ public static bool ExpectBinary( | |
+ ref SequenceReader<byte> reader, | |
+ out bool value | |
+ ) | |
+ => Expect(ref reader, length: 1, ToBinary, out value); | |
+ | |
+ private static bool ExpectUINT8Array<TValue>( | |
+ ref SequenceReader<byte> reader, | |
+ int length, | |
+ Converter<Memory<byte>, TValue> converter, | |
+#if NULL_STATE_STATIC_ANALYSIS_ATTRIBUTES | |
+ [NotNullWhen(true)] | |
+#endif | |
+ out TValue? value | |
+ ) | |
+ { | |
+ value = default; | |
+ | |
+ byte[]? buffer = null; | |
+ | |
+ try { | |
+ var lengthOfUINT8Array = length * 2; | |
+ | |
+ buffer = ArrayPool<byte>.Shared.Rent(lengthOfUINT8Array); | |
+ | |
+ return OperationStatus.Done == TryConvertToken( | |
+ reader: ref reader, | |
+ length: lengthOfUINT8Array, | |
+ throwIfUnexpected: true, | |
+ arg: (converter, memory: buffer.AsMemory(0, length)), | |
+ tryConvert: static (token, arg) => { | |
+ ToByteSequence(token, arg.memory.Length, arg.memory.Span); | |
+ | |
+ return (true, arg.converter(arg.memory)); | |
+ }, | |
+ result: out value | |
+ ); | |
+ } | |
+ finally { | |
+ if (buffer is not null) | |
+ ArrayPool<byte>.Shared.Return(buffer); | |
+ } | |
+ } | |
+ | |
+ public static bool ExpectIPADDR( | |
+ ref SequenceReader<byte> reader, | |
+#if NULL_STATE_STATIC_ANALYSIS_ATTRIBUTES | |
+ [NotNullWhen(true)] | |
+#endif | |
+ out IPAddress? value | |
+ ) | |
+ => Expect( | |
+ reader: ref reader, | |
+ length: 39, // "XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX".Length | |
+ converter: static seq => IPAddress.Parse(SkStack.GetString(seq)), | |
+ value: out value | |
+ ); | |
+ | |
+ public static bool ExpectADDR64( | |
+ ref SequenceReader<byte> reader, | |
+#if NULL_STATE_STATIC_ANALYSIS_ATTRIBUTES | |
+ [NotNullWhen(true)] | |
+#endif | |
+ out PhysicalAddress? value | |
+ ) | |
+ => ExpectUINT8Array( | |
+ reader: ref reader, | |
+ length: 8, | |
+ converter: static array => new PhysicalAddress(array.ToArray()), // XXX: cannot pass ReadOnlySpan<byte> | |
+ value: out value | |
+ ); | |
+ | |
+ [CLSCompliant(false)] | |
+ public static bool ExpectADDR16( | |
+ ref SequenceReader<byte> reader, | |
+ out ushort value | |
+ ) | |
+ => ExpectUINT16( | |
+ reader: ref reader, | |
+ value: out value | |
+ ); | |
+ | |
+ public static bool ExpectCHANNEL( | |
+ ref SequenceReader<byte> reader, | |
+ out SkStackChannel value | |
+ ) | |
+ { | |
+ value = default; | |
+ | |
+ if (ExpectUINT8(ref reader, out var ch)) { | |
+ value = SkStackChannel.FindByChannelNumber(ch, nameof(ch)); | |
+ return true; | |
+ } | |
+ | |
+ return false; | |
+ } | |
+ | |
+ public static void ToByteSequence(ReadOnlySequence<byte> hexTextSequence, int byteSequenceLength, Span<byte> destination) | |
+ { | |
+ if ((hexTextSequence.Length & 0x1L) != 0L) | |
+ throw SkStackUnexpectedResponseException.CreateInvalidToken(hexTextSequence, "HEX ASCII"); | |
+ if (destination.Length < byteSequenceLength) | |
+ throw new ArgumentException($"buffer too short. expected at least {byteSequenceLength} but was {destination.Length}.", nameof(destination)); | |
+ | |
+ var reader = new SequenceReader<byte>(hexTextSequence); | |
+ | |
+ Span<byte> hexTextOneByte = stackalloc byte[2]; | |
+ | |
+ for (var index = 0; index < byteSequenceLength; index++) { | |
+ reader.TryCopyTo(hexTextOneByte); | |
+ | |
+ if (!Hexadecimal.TryDecode(hexTextOneByte, out var decodedbyte)) | |
+ throw SkStackUnexpectedResponseException.CreateInvalidToken(hexTextOneByte.Slice(0, 1), "HEX ASCII"); | |
+ | |
+ destination[index] = decodedbyte; | |
+ | |
+ reader.Advance(2); | |
+ } | |
+ } | |
+ | |
+ private static byte ToUINT8(ReadOnlySequence<byte> token) | |
+ { | |
+ try { | |
+ Span<byte> uint8 = stackalloc byte[1]; | |
+ | |
+ ToByteSequence(token, 1, uint8); | |
+ | |
+ return uint8[0]; | |
+ } | |
+ catch (SkStackUnexpectedResponseException ex) { | |
+ throw SkStackUnexpectedResponseException.CreateInvalidToken(token, "UINT8", ex); | |
+ } | |
+ } | |
+ | |
+ private static ushort ToUINT16(ReadOnlySequence<byte> token) | |
+ { | |
+ try { | |
+ Span<byte> uint16 = stackalloc byte[2]; | |
+ | |
+ ToByteSequence(token, 2, uint16); | |
+ | |
+ return BinaryPrimitives.ReadUInt16BigEndian(uint16); | |
+ } | |
+ catch (Exception ex) { | |
+ throw SkStackUnexpectedResponseException.CreateInvalidToken(token, "UINT16", ex); | |
+ } | |
+ } | |
+ | |
+ private static uint ToUINT32(ReadOnlySequence<byte> token) | |
+ { | |
+ try { | |
+ Span<byte> uint32 = stackalloc byte[4]; | |
+ | |
+ ToByteSequence(token, 4, uint32); | |
+ | |
+ return BinaryPrimitives.ReadUInt32BigEndian(uint32); | |
+ } | |
+ catch (Exception ex) { | |
+ throw SkStackUnexpectedResponseException.CreateInvalidToken(token, "UINT32", ex); | |
+ } | |
+ } | |
+ | |
+ private static ulong ToUINT64(ReadOnlySequence<byte> token) | |
+ { | |
+ try { | |
+ Span<byte> uint64 = stackalloc byte[8]; | |
+ | |
+ ToByteSequence(token, 8, uint64); | |
+ | |
+ return BinaryPrimitives.ReadUInt64BigEndian(uint64); | |
+ } | |
+ catch (Exception ex) { | |
+ throw SkStackUnexpectedResponseException.CreateInvalidToken(token, "UINT32", ex); | |
+ } | |
+ } | |
+ | |
+ private static uint ToDecimalNumber(ReadOnlySequence<byte> token) | |
+ { | |
+ const int MaxLength = 10; // uint.MaxValue.ToString("D").Length | |
+ | |
+ if (MaxLength < token.Length) | |
+ throw SkStackUnexpectedResponseException.CreateInvalidToken(token, "decimal number with max 10 digit"); | |
+ | |
+ var reader = new SequenceReader<byte>(token); | |
+ | |
+#if SYSTEM_IUTF8SPANPARSABLE | |
+ Span<byte> str = stackalloc byte[(int)reader.Length]; | |
+ | |
+ _ = reader.TryCopyTo(str); | |
+#else | |
+ Span<char> str = stackalloc char[(int)reader.Length]; | |
+ | |
+ for (var i = 0; i < token.Length; i++) { | |
+ reader.TryRead(out var d); | |
+ str[i] = (char)d; | |
+ } | |
+#endif | |
+ | |
+ try { | |
+ return uint.Parse(str, provider: null); | |
+ } | |
+ catch (Exception ex) { | |
+ throw SkStackUnexpectedResponseException.CreateInvalidToken(token, "decimal number", ex); | |
+ } | |
+ } | |
+ | |
+ private static bool ToBinary(ReadOnlySequence<byte> token) | |
+ { | |
+ try { | |
+ return token.FirstSpan[0] switch { | |
+ (byte)'0' => false, | |
+ (byte)'1' => true, | |
+ _ => throw SkStackUnexpectedResponseException.CreateInvalidToken(token, "0 or 1"), | |
+ }; | |
+ } | |
+ catch (SkStackUnexpectedResponseException ex) { | |
+ throw SkStackUnexpectedResponseException.CreateInvalidToken(token, "Binary", ex); | |
+ } | |
+ } | |
+ | |
+ public static bool TryExpectStatusLine( | |
+ ref SequenceReader<byte> reader, | |
+ out SkStackResponseStatus status | |
+ ) | |
+ { | |
+ status = default; | |
+ | |
+ var readerOk = reader; | |
+ var readerFail = reader; | |
+ | |
+ if ( | |
+ readerOk.IsNext(SkStackResponseStatusCodes.OK, advancePast: true) && | |
+ (readerOk.IsNext(SkStack.SP, advancePast: true) || readerOk.IsNext(SkStack.CRLFSpan, advancePast: true)) | |
+ ) { | |
+ reader = readerOk; | |
+ status = SkStackResponseStatus.Ok; | |
+ return true; | |
+ } | |
+ else if ( | |
+ readerFail.IsNext(SkStackResponseStatusCodes.FAIL, advancePast: true) && | |
+ (readerFail.IsNext(SkStack.SP, advancePast: true) || readerFail.IsNext(SkStack.CRLFSpan, advancePast: true)) | |
+ ) { | |
+ reader = readerFail; | |
+ status = SkStackResponseStatus.Fail; | |
+ return true; | |
+ } | |
+ else { | |
+ return false; // is incomplete or is not status line | |
+ } | |
+ } | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP.Protocol/SkStackUdpReceiveEvent.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP.Protocol/SkStackUdpReceiveEvent.cs | |
new file mode 100644 | |
index 0000000..ac760d1 | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP.Protocol/SkStackUdpReceiveEvent.cs | |
@@ -0,0 +1,32 @@ | |
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+ | |
+using System.Net; | |
+using System.Net.NetworkInformation; | |
+ | |
+namespace Smdn.Net.SkStackIP.Protocol; | |
+ | |
+/// <remarks> | |
+/// <para>See 'BP35A1コマンドリファレンス 4.1. ERXUDP' for detailed specifications.</para> | |
+/// </remarks> | |
+internal readonly struct SkStackUdpReceiveEvent { | |
+ public IPEndPoint RemoteEndPoint { get; } | |
+ public IPEndPoint LocalEndPoint { get; } | |
+ public PhysicalAddress RemoteLinkLocalAddress { get; } | |
+ public bool IsSecured { get; } | |
+ | |
+ internal SkStackUdpReceiveEvent( | |
+ IPAddress sender, | |
+ IPAddress dest, | |
+ uint rport, | |
+ uint lport, | |
+ PhysicalAddress senderlla, | |
+ bool secured | |
+ ) | |
+ { | |
+ RemoteEndPoint = new(sender, (int)rport); | |
+ LocalEndPoint = new(dest, (int)lport); | |
+ RemoteLinkLocalAddress = senderlla; | |
+ IsSecured = secured; | |
+ } | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP.Protocol/SkStackUnexpectedResponseException.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP.Protocol/SkStackUnexpectedResponseException.cs | |
new file mode 100644 | |
index 0000000..6a3a8b6 | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP.Protocol/SkStackUnexpectedResponseException.cs | |
@@ -0,0 +1,80 @@ | |
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+#pragma warning disable CA1032 | |
+ | |
+using System; | |
+using System.Buffers; | |
+ | |
+using Smdn.Text.Unicode.ControlPictures; | |
+ | |
+namespace Smdn.Net.SkStackIP.Protocol; | |
+ | |
+/// <summary> | |
+/// The exception that is thrown when the <see cref="SkStackClient"/> received unexpected response. | |
+/// </summary> | |
+/// <seealso cref="SkStackTokenParser"/> | |
+public class SkStackUnexpectedResponseException : SkStackResponseException { | |
+ /// <summary> | |
+ /// Gets the token or text of the response that caused the exception. | |
+ /// </summary> | |
+ public string? CausedText { get; } | |
+ | |
+ private SkStackUnexpectedResponseException(string? causedText, string message, Exception? innerException = null) | |
+ : base(message, innerException) | |
+ { | |
+ CausedText = causedText; | |
+ } | |
+ | |
+ internal static SkStackUnexpectedResponseException CreateLackOfExpectedResponseText(Exception? innerException = null) | |
+ => new( | |
+ causedText: null, | |
+ message: "lack of expected response text", | |
+ innerException: innerException | |
+ ); | |
+ | |
+ internal static SkStackUnexpectedResponseException CreateInvalidFormat( | |
+ ReadOnlySequence<byte> token, | |
+ Exception? innerException = null | |
+ ) | |
+ => new( | |
+ causedText: token.ToControlCharsPicturizedString(), | |
+ message: $"unexpected response format: '{token.ToControlCharsPicturizedString()}'", | |
+ innerException: innerException | |
+ ); | |
+ | |
+ internal static SkStackUnexpectedResponseException CreateInvalidToken( | |
+ ReadOnlySpan<byte> token, | |
+ string extraMessage, | |
+ Exception? innerException = null | |
+ ) | |
+ => new( | |
+ causedText: token.ToControlCharsPicturizedString(), | |
+ message: $"unexpected response token: '{token.ToControlCharsPicturizedString()}' ({extraMessage})", | |
+ innerException: innerException | |
+ ); | |
+ | |
+ internal static SkStackUnexpectedResponseException CreateInvalidToken( | |
+ ReadOnlySequence<byte> token, | |
+ string extraMessage, | |
+ Exception? innerException = null | |
+ ) | |
+ => new( | |
+ causedText: token.ToControlCharsPicturizedString(), | |
+ message: $"unexpected response token: '{token.ToControlCharsPicturizedString()}' ({extraMessage})", | |
+ innerException: innerException | |
+ ); | |
+ | |
+ internal static void ThrowIfUnexpectedSubsequentEventCode( | |
+ SkStackEventCode subsequentEventCode, | |
+ SkStackEventCode expectedEventCode | |
+ ) | |
+ { | |
+ if (subsequentEventCode != expectedEventCode) { | |
+ throw new SkStackUnexpectedResponseException( | |
+ causedText: null, | |
+ message: $"expected subsequent event code is {expectedEventCode}, but was {subsequentEventCode}", | |
+ innerException: null | |
+ ); | |
+ } | |
+ } | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP.csproj b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP.csproj | |
new file mode 100644 | |
index 0000000..c583c86 | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP.csproj | |
@@ -0,0 +1,80 @@ | |
+<!-- | |
+SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+SPDX-License-Identifier: MIT | |
+--> | |
+<Project Sdk="Microsoft.NET.Sdk"> | |
+ <PropertyGroup> | |
+ <TargetFrameworks>net8.0;net6.0;netstandard2.1</TargetFrameworks> | |
+ <VersionPrefix>1.0.0</VersionPrefix> | |
+ <VersionSuffix></VersionSuffix> | |
+ <!-- <PackageValidationBaselineVersion>1.0.0</PackageValidationBaselineVersion> --> | |
+ <Nullable>enable</Nullable> | |
+ <RootNamespace/> <!-- empty the root namespace so that the namespace is determined only by the directory name, for code style rule IDE0030 --> | |
+ <NoWarn>CA1848;$(NoWarn)</NoWarn> <!-- CA1848: For improved performance, use the LoggerMessage delegates instead of calling 'LoggerExtensions.LogXxxxx(...)' --> | |
+ <NoWarn>CS1591;$(NoWarn)</NoWarn> <!-- CS1591: Missing XML comment for publicly visible type or member 'Type_or_Member' --> | |
+ <GenerateDocumentationFile>true</GenerateDocumentationFile> | |
+ </PropertyGroup> | |
+ | |
+ <PropertyGroup Label="metadata"> | |
+ <Description>Provides APIs for operating devices that implement Skyley Networks' SKSTACK IP.</Description> | |
+ <CopyrightYear>2021</CopyrightYear> | |
+ </PropertyGroup> | |
+ | |
+ <PropertyGroup Label="package properties"> | |
+ <PackageTags>SKSTACK,SKSTACK-IP,PANA,Route-B,ECHONET,ECHONET-Lite</PackageTags> | |
+ <GenerateNupkgReadmeFileDependsOnTargets>$(GenerateNupkgReadmeFileDependsOnTargets);GenerateReadmeFileContent</GenerateNupkgReadmeFileDependsOnTargets> | |
+ </PropertyGroup> | |
+ | |
+ <ItemGroup> | |
+ <PackageReference Include="System.IO.Pipelines" Version="8.0.0" /> | |
+ <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" /> | |
+ <PackageReference Include="Polly.Core" Version="8.0.0" /> | |
+ <PackageReference Include="Smdn.Fundamental.ControlPicture" Version="[3.0.0.1,4.0.0)" /> | |
+ <PackageReference Include="Smdn.Fundamental.Encoding.Buffer" Version="[3.0.0,4.0.0)" Condition="$(TargetFramework.StartsWith('netstandard'))" /> | |
+ <PackageReference Include="Smdn.Fundamental.PrintableEncoding.Hexadecimal" Version="[3.0.0,4.0.0)" /> | |
+ </ItemGroup> | |
+ | |
+ <ItemGroup> | |
+ <!-- Third party notice --> | |
+ <None | |
+ Include="$(MSBuildThisFileDirectory)..\..\ThirdPartyNotices.md" | |
+ Pack="true" | |
+ PackagePath="ThirdPartyNotices.md" | |
+ CopyToOutputDirectory="None" | |
+ /> | |
+ </ItemGroup> | |
+ | |
+ <Target Name="GenerateReadmeFileContent" DependsOnTargets="ReadReadmeFileNoticeSectionContent"> | |
+ <PropertyGroup> | |
+ <PackageReadmeFileContent><![CDATA[# $(PackageId) $(PackageVersion) | |
+`$(PackageId)` is a library that provides APIs for operating devices that implement Skyley Networks' SKSTACK IP. | |
+ | |
+This library supports to use any `Stream` or `PipeReader`/`PipeWriter` as the communication channel for the SKSTACK IP protocol, so it has the ability to communicate with devices that use other than serial ports, e.g., pseudo devices. | |
+ | |
+## Getting started | |
+First, add package [$(PackageId)](https://www.nuget.org/packages/$(PackageId)) and [System.IO.Ports](https://www.nuget.org/packages/System.IO.Ports) to the project file. | |
+ | |
+``` | |
+dotnet add package $(PackageId) | |
+dotnet add package System.IO.Ports | |
+``` | |
+ | |
+Next, open the serial port to which the SKSTACK-IP device is connected using with the `SerialPort` class. | |
+ | |
+Then, create a `SkStackClient` instance from the `SerialPort.BaseStream` and call the `SkStackClient`'s method to send the command. | |
+ | |
+```cs | |
+$([System.IO.File]::ReadAllText('$(MSBuildThisFileDirectory)..\..\examples\getting-started\Program.cs').TrimEnd()) | |
+``` | |
+ | |
+More examples can be found on the [GitHub repository]($(RepositoryUrl)/tree/main/examples/), including examples of using library features. | |
+ | |
+## 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)). | |
+ | |
+## Notice | |
+$(ReadmeFileNoticeSectionContent) | |
+]]></PackageReadmeFileContent> | |
+ </PropertyGroup> | |
+ </Target> | |
+</Project> | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackActiveScanOptions.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackActiveScanOptions.cs | |
new file mode 100644 | |
index 0000000..8ffbb8a | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackActiveScanOptions.cs | |
@@ -0,0 +1,148 @@ | |
+// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+ | |
+using System; | |
+using System.Collections.Generic; | |
+using System.Linq; | |
+using System.Net.NetworkInformation; | |
+ | |
+namespace Smdn.Net.SkStackIP; | |
+ | |
+/// <summary> | |
+/// The type for defining the scan intervals (scan duration factors) and the method that selects discovered PANA Authentication Agents (PAA) in the scan for PAA, by the <c>SKSCAN</c> command. | |
+/// </summary> | |
+/// <seealso cref="SkStackClient.ActiveScanAsync" /> | |
+public abstract class SkStackActiveScanOptions : ICloneable { | |
+ /// <summary> | |
+ /// Gets the <see cref="SkStackActiveScanOptions"/> which selects the PAA which found at first during the scan. | |
+ /// The scan starts with a duration factor <c>3</c>. If not found, continue scanning with the following durations factors: <c>4</c>, <c>5</c>, <c>6</c>, <c>6</c>, <c>6</c>. | |
+ /// If any PAA is not found until the last scan, it will stop the scanning. | |
+ /// </summary> | |
+ public static SkStackActiveScanOptions Default { get; } = new DefaultActiveScanOptions(); | |
+ | |
+ private sealed class DefaultActiveScanOptions : SkStackActiveScanOptions { | |
+ public override SkStackActiveScanOptions Clone() => new DefaultActiveScanOptions(); | |
+ | |
+ internal override bool SelectPanaAuthenticationAgent(SkStackPanDescription desc) => true; // select first one | |
+ | |
+ internal override IEnumerable<int> YieldScanDurationFactors() | |
+ { | |
+ yield return 3; | |
+ yield return 4; | |
+ yield return 5; | |
+ yield return 6; | |
+ yield return 6; | |
+ yield return 6; | |
+ } | |
+ } | |
+ | |
+ /// <summary> | |
+ /// Gets the <see cref="SkStackActiveScanOptions"/> which does not select any PAA and does not perform any scanning. | |
+ /// </summary> | |
+ public static SkStackActiveScanOptions Null { get; } = new NullActiveScanOptions(); | |
+ | |
+ private sealed class NullActiveScanOptions : SkStackActiveScanOptions { | |
+ public override SkStackActiveScanOptions Clone() => new NullActiveScanOptions(); | |
+ | |
+ internal override bool SelectPanaAuthenticationAgent(SkStackPanDescription desc) => false; // select nothing | |
+ | |
+ internal override IEnumerable<int> YieldScanDurationFactors() | |
+ => Enumerable.Empty<int>(); | |
+ } | |
+ | |
+ /// <summary> | |
+ /// Gets the <see cref="SkStackActiveScanOptions"/> selects the PAA which found at first during the scan. | |
+ /// The scan starts with a duration factor <c>5</c> and continues scanning infinitely until it finds any PAA. | |
+ /// </summary> | |
+ public static SkStackActiveScanOptions ScanUntilFind { get; } = new ScanUntilFindActiveScanOptions(); | |
+ | |
+ private sealed class ScanUntilFindActiveScanOptions : SkStackActiveScanOptions { | |
+ public override SkStackActiveScanOptions Clone() => new ScanUntilFindActiveScanOptions(); | |
+ | |
+ internal override bool SelectPanaAuthenticationAgent(SkStackPanDescription desc) => true; // select first one | |
+ | |
+ internal override IEnumerable<int> YieldScanDurationFactors() | |
+ { | |
+ for (; ; ) | |
+ yield return 5; | |
+ } | |
+ } | |
+ | |
+ /// <summary> | |
+ /// Creates the <see cref="SkStackActiveScanOptions"/> with the custom selection method and duration factors. | |
+ /// </summary> | |
+ /// <param name="scanDurationGenerator">A collection or iterator that defines the scan durations.</param> | |
+ /// <param name="paaSelector"> | |
+ /// A callback to select the target PAA from the PAAs found during the scan. | |
+ /// If <see langword="null"/>, selects the PAA which found at first during the scan. | |
+ /// </param> | |
+ public static SkStackActiveScanOptions Create( | |
+ IEnumerable<int> scanDurationGenerator, | |
+ Predicate<SkStackPanDescription>? paaSelector = null | |
+ ) | |
+ => new UserDefinedActiveScanOptions( | |
+ paaSelector: paaSelector, | |
+ scanDurationGenerator: scanDurationGenerator | |
+ ); | |
+ | |
+ private sealed class UserDefinedActiveScanOptions : SkStackActiveScanOptions { | |
+ private readonly Predicate<SkStackPanDescription>? paaSelector; | |
+ private readonly IEnumerable<int> scanDurationGenerator; | |
+ | |
+ public UserDefinedActiveScanOptions( | |
+ Predicate<SkStackPanDescription>? paaSelector, | |
+ IEnumerable<int> scanDurationGenerator | |
+ ) | |
+ { | |
+ this.paaSelector = paaSelector; | |
+ this.scanDurationGenerator = scanDurationGenerator ?? throw new ArgumentNullException(nameof(scanDurationGenerator)); | |
+ } | |
+ | |
+ public override SkStackActiveScanOptions Clone() => new UserDefinedActiveScanOptions(paaSelector, scanDurationGenerator.ToArray()); | |
+ internal override bool SelectPanaAuthenticationAgent(SkStackPanDescription desc) => paaSelector?.Invoke(desc) ?? true; | |
+ internal override IEnumerable<int> YieldScanDurationFactors() => scanDurationGenerator; | |
+ } | |
+ | |
+ /// <summary> | |
+ /// Creates the <see cref="SkStackActiveScanOptions"/> with the custom selection method and duration factors. | |
+ /// </summary> | |
+ /// <param name="scanDurationGenerator">A collection or iterator that defines the scan durations.</param> | |
+ /// <param name="paaMacAddress"> | |
+ /// A <see cref="PhysicalAddress"/> of the target PAA. This method selects the first PAA found during the scan that matches this <see cref="PhysicalAddress"/>. | |
+ /// </param> | |
+ public static SkStackActiveScanOptions Create( | |
+ IEnumerable<int> scanDurationGenerator, | |
+ PhysicalAddress paaMacAddress | |
+ ) | |
+ => new FindByMacAddressActiveScanOptions( | |
+ paaMacAddress: paaMacAddress, | |
+ scanDurationGenerator: scanDurationGenerator | |
+ ); | |
+ | |
+ private sealed class FindByMacAddressActiveScanOptions : SkStackActiveScanOptions { | |
+ private readonly PhysicalAddress paaMacAddress; | |
+ private readonly IEnumerable<int> scanDurationGenerator; | |
+ | |
+ public FindByMacAddressActiveScanOptions( | |
+ PhysicalAddress paaMacAddress, | |
+ IEnumerable<int> scanDurationGenerator | |
+ ) | |
+ { | |
+ this.paaMacAddress = paaMacAddress; | |
+ this.scanDurationGenerator = scanDurationGenerator ?? throw new ArgumentNullException(nameof(scanDurationGenerator)); | |
+ } | |
+ | |
+ public override SkStackActiveScanOptions Clone() => new FindByMacAddressActiveScanOptions(paaMacAddress, scanDurationGenerator.ToArray()); | |
+ internal override bool SelectPanaAuthenticationAgent(SkStackPanDescription desc) => desc.MacAddress.Equals(paaMacAddress); | |
+ internal override IEnumerable<int> YieldScanDurationFactors() => scanDurationGenerator; | |
+ } | |
+ | |
+ /* | |
+ * instance members | |
+ */ | |
+ // TODO: public IProgress<int> Progress { get; set; } | |
+ public abstract SkStackActiveScanOptions Clone(); | |
+ object ICloneable.Clone() => Clone(); | |
+ internal abstract bool SelectPanaAuthenticationAgent(SkStackPanDescription desc); | |
+ internal abstract IEnumerable<int> YieldScanDurationFactors(); | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackChannel.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackChannel.cs | |
new file mode 100644 | |
index 0000000..a870f04 | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackChannel.cs | |
@@ -0,0 +1,127 @@ | |
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+#pragma warning disable CA1036 | |
+ | |
+using System; | |
+using System.Collections.Generic; | |
+ | |
+namespace Smdn.Net.SkStackIP; | |
+ | |
+/// <remarks> | |
+/// <para>See 'BP35A1コマンドリファレンス 6. 周波数とチャネル番号' for detailed specifications.</para> | |
+/// </remarks> | |
+public readonly struct SkStackChannel : IEquatable<SkStackChannel>, IComparable<SkStackChannel> { | |
+ public static readonly IReadOnlyDictionary<int, SkStackChannel> Channels = new Dictionary<int, SkStackChannel> { | |
+ { 33, new(channelNumber: 33, frequencyMHz: 922.5m) }, | |
+ { 34, new(channelNumber: 34, frequencyMHz: 922.7m) }, | |
+ { 35, new(channelNumber: 35, frequencyMHz: 922.9m) }, | |
+ { 36, new(channelNumber: 36, frequencyMHz: 923.1m) }, | |
+ { 37, new(channelNumber: 37, frequencyMHz: 923.3m) }, | |
+ { 38, new(channelNumber: 38, frequencyMHz: 923.5m) }, | |
+ { 39, new(channelNumber: 39, frequencyMHz: 923.7m) }, | |
+ { 40, new(channelNumber: 40, frequencyMHz: 923.9m) }, | |
+ { 41, new(channelNumber: 41, frequencyMHz: 924.1m) }, | |
+ { 42, new(channelNumber: 42, frequencyMHz: 924.3m) }, | |
+ { 43, new(channelNumber: 43, frequencyMHz: 924.5m) }, | |
+ { 44, new(channelNumber: 44, frequencyMHz: 924.7m) }, | |
+ { 45, new(channelNumber: 45, frequencyMHz: 924.9m) }, | |
+ { 46, new(channelNumber: 46, frequencyMHz: 925.1m) }, | |
+ { 47, new(channelNumber: 47, frequencyMHz: 925.3m) }, | |
+ { 48, new(channelNumber: 48, frequencyMHz: 925.5m) }, | |
+ { 49, new(channelNumber: 49, frequencyMHz: 925.7m) }, | |
+ { 50, new(channelNumber: 50, frequencyMHz: 925.9m) }, | |
+ { 51, new(channelNumber: 51, frequencyMHz: 926.1m) }, | |
+ { 52, new(channelNumber: 52, frequencyMHz: 926.3m) }, | |
+ { 53, new(channelNumber: 53, frequencyMHz: 926.5m) }, | |
+ { 54, new(channelNumber: 54, frequencyMHz: 926.7m) }, | |
+ { 55, new(channelNumber: 55, frequencyMHz: 926.9m) }, | |
+ { 56, new(channelNumber: 56, frequencyMHz: 927.1m) }, | |
+ { 57, new(channelNumber: 57, frequencyMHz: 927.3m) }, | |
+ { 58, new(channelNumber: 58, frequencyMHz: 927.5m) }, | |
+ { 59, new(channelNumber: 59, frequencyMHz: 927.7m) }, | |
+ { 60, new(channelNumber: 60, frequencyMHz: 927.9m) }, | |
+ }; | |
+ | |
+ public static readonly SkStackChannel Empty; | |
+ | |
+ public static SkStackChannel Channel33 => Channels[33]; | |
+ public static SkStackChannel Channel34 => Channels[34]; | |
+ public static SkStackChannel Channel35 => Channels[35]; | |
+ public static SkStackChannel Channel36 => Channels[36]; | |
+ public static SkStackChannel Channel37 => Channels[37]; | |
+ public static SkStackChannel Channel38 => Channels[38]; | |
+ public static SkStackChannel Channel39 => Channels[39]; | |
+ public static SkStackChannel Channel40 => Channels[40]; | |
+ public static SkStackChannel Channel41 => Channels[41]; | |
+ public static SkStackChannel Channel42 => Channels[42]; | |
+ public static SkStackChannel Channel43 => Channels[43]; | |
+ public static SkStackChannel Channel44 => Channels[44]; | |
+ public static SkStackChannel Channel45 => Channels[45]; | |
+ public static SkStackChannel Channel46 => Channels[46]; | |
+ public static SkStackChannel Channel47 => Channels[47]; | |
+ public static SkStackChannel Channel48 => Channels[48]; | |
+ public static SkStackChannel Channel49 => Channels[49]; | |
+ public static SkStackChannel Channel50 => Channels[50]; | |
+ public static SkStackChannel Channel51 => Channels[51]; | |
+ public static SkStackChannel Channel52 => Channels[52]; | |
+ public static SkStackChannel Channel53 => Channels[53]; | |
+ public static SkStackChannel Channel54 => Channels[54]; | |
+ public static SkStackChannel Channel55 => Channels[55]; | |
+ public static SkStackChannel Channel56 => Channels[56]; | |
+ public static SkStackChannel Channel57 => Channels[57]; | |
+ public static SkStackChannel Channel58 => Channels[58]; | |
+ public static SkStackChannel Channel59 => Channels[59]; | |
+ public static SkStackChannel Channel60 => Channels[60]; | |
+ | |
+ internal static SkStackChannel FindByChannelNumber(int channelNumber, string? paramNameOfChannelNumber = null) | |
+ => Channels.TryGetValue(channelNumber, out var channel) | |
+ ? channel | |
+ : throw new ArgumentOutOfRangeException( | |
+ paramName: paramNameOfChannelNumber ?? nameof(channelNumber), | |
+ actualValue: channelNumber, | |
+ message: "undefined channel" | |
+ ); | |
+ | |
+ /* | |
+ * instance members | |
+ */ | |
+ public int ChannelNumber { get; } | |
+ public decimal FrequencyMHz { get; } | |
+ internal byte RegisterS02Value => (byte)ChannelNumber; | |
+ | |
+ public bool IsEmpty => Equals(Empty); | |
+ | |
+ private SkStackChannel(int channelNumber, decimal frequencyMHz) | |
+ { | |
+ ChannelNumber = channelNumber; | |
+ FrequencyMHz = frequencyMHz; | |
+ } | |
+ | |
+ public override bool Equals(object? obj) | |
+ => obj switch { | |
+ SkStackChannel channel => Equals(channel), | |
+ _ => false, | |
+ }; | |
+ | |
+ public bool Equals(SkStackChannel other) | |
+ => ChannelNumber == other.ChannelNumber; | |
+ | |
+ public static bool operator ==(SkStackChannel x, SkStackChannel y) => x.Equals(y); | |
+ public static bool operator !=(SkStackChannel x, SkStackChannel y) => !x.Equals(y); | |
+ | |
+ int IComparable<SkStackChannel>.CompareTo(SkStackChannel other) | |
+ => ChannelNumber.CompareTo(other.ChannelNumber); | |
+ | |
+#if false | |
+ public static bool operator < (SkStackChannel x, SkStackChannel y) => x.ChannelNumber < y.ChannelNumber; | |
+ public static bool operator <= (SkStackChannel x, SkStackChannel y) => x.ChannelNumber <= y.ChannelNumber; | |
+ public static bool operator > (SkStackChannel x, SkStackChannel y) => x.ChannelNumber > y.ChannelNumber; | |
+ public static bool operator >= (SkStackChannel x, SkStackChannel y) => x.ChannelNumber >= y.ChannelNumber; | |
+#endif | |
+ | |
+ public override int GetHashCode() | |
+ => ChannelNumber.GetHashCode(); | |
+ | |
+ public override string ToString() | |
+ => $"{ChannelNumber}ch ({nameof(SkStackRegister.S02)}=0x{ChannelNumber:X2}, {FrequencyMHz} MHz)"; | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Commands.SKADDNBR.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Commands.SKADDNBR.cs | |
new file mode 100644 | |
index 0000000..8086537 | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Commands.SKADDNBR.cs | |
@@ -0,0 +1,50 @@ | |
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+using System; | |
+using System.Net; | |
+using System.Net.NetworkInformation; | |
+using System.Net.Sockets; | |
+using System.Threading; | |
+using System.Threading.Tasks; | |
+ | |
+using Smdn.Net.SkStackIP.Protocol; | |
+ | |
+namespace Smdn.Net.SkStackIP; | |
+ | |
+#pragma warning disable IDE0040 | |
+partial class SkStackClient { | |
+#pragma warning restore IDE0040 | |
+ /// <summary> | |
+ /// <para>Sends a command <c>SKADDNBR</c>.</para> | |
+ /// </summary> | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.18. SKADDNBR' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public ValueTask<SkStackResponse> SendSKADDNBRAsync( | |
+ IPAddress ipv6Address, | |
+ PhysicalAddress macAddress, | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ { | |
+ const int LengthOfAddr64 = 8; | |
+ | |
+ if (ipv6Address is null) | |
+ throw new ArgumentNullException(nameof(ipv6Address)); | |
+ if (ipv6Address.AddressFamily != AddressFamily.InterNetworkV6) | |
+ throw new ArgumentException($"`{nameof(ipv6Address)}.{nameof(IPAddress.AddressFamily)}` must be {nameof(AddressFamily.InterNetworkV6)}"); | |
+ if (macAddress is null) | |
+ throw new ArgumentNullException(nameof(macAddress)); | |
+ if (macAddress.GetAddressBytes().Length != LengthOfAddr64) | |
+ throw new ArgumentException($"`{nameof(macAddress)}` must be address that is {LengthOfAddr64} bytes length"); | |
+ | |
+ return SendCommandAsync( | |
+ command: SkStackCommandNames.SKADDNBR, | |
+ writeArguments: writer => { | |
+ writer.WriteTokenIPADDR(ipv6Address); | |
+ writer.WriteTokenADDR64(macAddress); | |
+ }, | |
+ throwIfErrorStatus: true, | |
+ cancellationToken: cancellationToken | |
+ ); | |
+ } | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Commands.SKDSLEEP.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Commands.SKDSLEEP.cs | |
new file mode 100644 | |
index 0000000..804706c | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Commands.SKDSLEEP.cs | |
@@ -0,0 +1,60 @@ | |
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+ | |
+using System.Threading; | |
+using System.Threading.Tasks; | |
+ | |
+using Smdn.Net.SkStackIP.Protocol; | |
+ | |
+namespace Smdn.Net.SkStackIP; | |
+ | |
+#pragma warning disable IDE0040 | |
+partial class SkStackClient { | |
+#pragma warning restore IDE0040 | |
+ /// <summary> | |
+ /// <para>Sends a command <c>SKDSLEEP</c>.</para> | |
+ /// </summary> | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.27. SKDSLEEP' for detailed specifications.</para> | |
+ /// </remarks> | |
+ /// <seealso cref="Slept"/> | |
+ /// <seealso cref="WokeUp"/> | |
+ public ValueTask<SkStackResponse> SendSKDSLEEPAsync( | |
+ bool waitUntilWakeUp = false, | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ => SendCommandAsync( | |
+ command: SkStackCommandNames.SKDSLEEP, | |
+ writeArguments: null, | |
+ commandEventHandler: new SKDSLEEPEventHandler(this, waitUntilWakeUp), | |
+ throwIfErrorStatus: true, | |
+ cancellationToken: cancellationToken | |
+ ); | |
+ | |
+ private class SKDSLEEPEventHandler : SkStackEventHandlerBase { | |
+ private readonly SkStackClient owner; | |
+ private readonly bool waitUntilWakeUp; | |
+ | |
+ public SKDSLEEPEventHandler(SkStackClient owner, bool waitUntilWakeUp) | |
+ { | |
+ this.owner = owner; | |
+ this.waitUntilWakeUp = waitUntilWakeUp; | |
+ } | |
+ | |
+ public override bool DoContinueHandlingEvents(SkStackResponseStatus status) | |
+ { | |
+ var sleepStartedSuccessfully = status == SkStackResponseStatus.Ok; | |
+ | |
+ if (sleepStartedSuccessfully) | |
+ owner.RaiseEventSlept(); | |
+ | |
+ if (waitUntilWakeUp) | |
+ return sleepStartedSuccessfully; // do continue handling events if sleep started (wait until `EVENT CO`) | |
+ else | |
+ return false; // do not continue handling events | |
+ } | |
+ | |
+ public override bool TryProcessEvent(SkStackEvent ev) | |
+ => ev.Number == SkStackEventNumber.WakeupSignalReceived; | |
+ } | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Commands.SKINFO.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Commands.SKINFO.cs | |
new file mode 100644 | |
index 0000000..9ca167d | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Commands.SKINFO.cs | |
@@ -0,0 +1,62 @@ | |
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+using System.Net; | |
+using System.Net.NetworkInformation; | |
+using System.Threading; | |
+using System.Threading.Tasks; | |
+ | |
+using Smdn.Net.SkStackIP.Protocol; | |
+ | |
+namespace Smdn.Net.SkStackIP; | |
+ | |
+#pragma warning disable IDE0040 | |
+partial class SkStackClient { | |
+#pragma warning restore IDE0040 | |
+ /// <summary> | |
+ /// <para>Sends a command <c>SKINFO</c>.</para> | |
+ /// </summary> | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.2. SKINFO' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public ValueTask<SkStackResponse<( | |
+ IPAddress LinkLocalAddress, | |
+ PhysicalAddress MacAddress, | |
+ SkStackChannel Channel, | |
+ int PanId, | |
+ int Addr16 | |
+ )>> | |
+ SendSKINFOAsync( | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ => SendCommandAsync( | |
+ command: SkStackCommandNames.SKINFO, | |
+ writeArguments: null, | |
+ parseResponsePayload: static context => { | |
+ var reader = context.CreateReader(); | |
+ | |
+ if ( | |
+ SkStackTokenParser.ExpectToken(ref reader, "EINFO"u8) && | |
+ SkStackTokenParser.ExpectIPADDR(ref reader, out var linkLocalAddress) && | |
+ SkStackTokenParser.ExpectADDR64(ref reader, out var macAddress) && | |
+ SkStackTokenParser.ExpectCHANNEL(ref reader, out var channel) && | |
+ SkStackTokenParser.ExpectUINT16(ref reader, out var panId) && | |
+ SkStackTokenParser.ExpectADDR16(ref reader, out var addr16) && | |
+ SkStackTokenParser.ExpectEndOfLine(ref reader) | |
+ ) { | |
+ context.Complete(reader); | |
+ return ( | |
+ linkLocalAddress, | |
+ macAddress, | |
+ channel, | |
+ (int)panId, | |
+ (int)addr16 | |
+ ); | |
+ } | |
+ | |
+ context.SetAsIncomplete(); | |
+ return default; | |
+ }, | |
+ throwIfErrorStatus: true, | |
+ cancellationToken: cancellationToken | |
+ ); | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Commands.SKJOIN_SKREJOIN.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Commands.SKJOIN_SKREJOIN.cs | |
new file mode 100644 | |
index 0000000..b9fea30 | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Commands.SKJOIN_SKREJOIN.cs | |
@@ -0,0 +1,118 @@ | |
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+using System; | |
+using System.Net; | |
+using System.Net.Sockets; | |
+using System.Threading; | |
+using System.Threading.Tasks; | |
+ | |
+using Smdn.Net.SkStackIP.Protocol; | |
+ | |
+namespace Smdn.Net.SkStackIP; | |
+ | |
+#pragma warning disable IDE0040 | |
+partial class SkStackClient { | |
+#pragma warning restore IDE0040 | |
+ /// <summary> | |
+ /// <para>Sends a command <c>SKJOIN</c>.</para> | |
+ /// </summary> | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.4. SKJOIN' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public ValueTask<SkStackResponse> SendSKJOINAsync( | |
+ IPAddress ipv6address, | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ { | |
+ if (ipv6address is null) | |
+ throw new ArgumentNullException(nameof(ipv6address)); | |
+ if (ipv6address.AddressFamily != AddressFamily.InterNetworkV6) | |
+ throw new ArgumentException($"`{nameof(ipv6address)}.{nameof(IPAddress.AddressFamily)}` must be {nameof(AddressFamily.InterNetworkV6)}"); | |
+ | |
+ return SKJOIN(ipv6address, cancellationToken); | |
+ | |
+ async ValueTask<SkStackResponse> SKJOIN(IPAddress addr, CancellationToken ct) | |
+ { | |
+ var (response, _) = await SKJOIN_SKREJOIN(SkStackCommandNames.SKJOIN, addr, ct).ConfigureAwait(false); | |
+ | |
+ return response; | |
+ } | |
+ } | |
+ | |
+ /// <summary> | |
+ /// <para>Sends a command <c>SKREJOIN</c>.</para> | |
+ /// </summary> | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.5. SKREJOIN' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public ValueTask<( | |
+ SkStackResponse Response, | |
+ IPAddress Address | |
+ )> SendSKREJOINAsync( | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ => SKJOIN_SKREJOIN(SkStackCommandNames.SKREJOIN, ipv6address: null, cancellationToken); | |
+ | |
+ private async ValueTask<( | |
+ SkStackResponse Response, | |
+ IPAddress Address | |
+ )> | |
+ SKJOIN_SKREJOIN( | |
+ ReadOnlyMemory<byte> command, | |
+ IPAddress? ipv6address, | |
+ CancellationToken cancellationToken | |
+ ) | |
+ { | |
+ var eventHandler = new SKJOINEventHandler(); | |
+ var resp = await SendCommandAsync( | |
+ command: command, | |
+ writeArguments: writer => { | |
+ if (ipv6address is not null) | |
+ writer.WriteTokenIPADDR(ipv6address); | |
+ }, | |
+ commandEventHandler: eventHandler, | |
+ throwIfErrorStatus: true, | |
+ cancellationToken: cancellationToken | |
+ ).ConfigureAwait(false); | |
+ | |
+ eventHandler.ThrowIfEstablishmentError(); | |
+ | |
+#if DEBUG | |
+ if (eventHandler.Address is null) | |
+ throw new InvalidOperationException($"{eventHandler.Address} has not been set"); | |
+#endif | |
+ | |
+ return (resp, eventHandler.Address!); | |
+ } | |
+ | |
+ private class SKJOINEventHandler : SkStackEventHandlerBase { | |
+ public bool HasAddressSet { get; private set; } | |
+ public IPAddress? Address { get; private set; } | |
+ | |
+ private SkStackEventNumber eventNumber; | |
+ | |
+ public void ThrowIfEstablishmentError() | |
+ { | |
+ if (eventNumber != SkStackEventNumber.PanaSessionEstablishmentCompleted) | |
+ throw new SkStackPanaSessionEstablishmentException($"PANA session establishment failed. (0x{eventNumber:X})", Address!, eventNumber); | |
+ } | |
+ | |
+ public override bool TryProcessEvent(SkStackEvent ev) | |
+ { | |
+ switch (ev.Number) { | |
+ case SkStackEventNumber.PanaSessionEstablishmentCompleted: | |
+ case SkStackEventNumber.PanaSessionEstablishmentError: | |
+ eventNumber = ev.Number; | |
+#if DEBUG | |
+ if (!ev.HasSenderAddress) | |
+ throw new InvalidOperationException($"{nameof(ev.SenderAddress)} must not be null"); | |
+#endif | |
+ Address = ev.SenderAddress!; | |
+ return true; | |
+ | |
+ default: | |
+ return false; | |
+ } | |
+ } | |
+ } | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Commands.SKSCAN.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Commands.SKSCAN.cs | |
new file mode 100644 | |
index 0000000..fe72208 | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Commands.SKSCAN.cs | |
@@ -0,0 +1,340 @@ | |
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+ | |
+using System; | |
+using System.Collections.Generic; | |
+#if SYSTEM_DIAGNOSTICS_CODEANALYSIS_MEMBERNOTNULLWHENATTRIBUTE | |
+using System.Diagnostics.CodeAnalysis; | |
+#endif | |
+using System.Threading; | |
+using System.Threading.Tasks; | |
+ | |
+using Smdn.Net.SkStackIP.Protocol; | |
+ | |
+namespace Smdn.Net.SkStackIP; | |
+ | |
+#pragma warning disable IDE0040 | |
+partial class SkStackClient { | |
+#pragma warning restore IDE0040 | |
+ private abstract class SKSCANEventHandler<TScanResult> : SkStackEventHandlerBase { | |
+ public bool HasScanResultSet { get; private set; } | |
+ | |
+#if SYSTEM_DIAGNOSTICS_CODEANALYSIS_MEMBERNOTNULLWHENATTRIBUTE | |
+ [MemberNotNullWhen(true, nameof(HasScanResultSet))] | |
+#endif | |
+ public TScanResult? ScanResult { get; private set; } | |
+ | |
+ public abstract override bool TryProcessEvent(SkStackEvent ev); | |
+ public abstract override void ProcessSubsequentEvent(ISkStackSequenceParserContext context); | |
+ | |
+ public void SetScanResult(TScanResult scanResult) | |
+ { | |
+ HasScanResultSet = true; | |
+ ScanResult = scanResult; | |
+ } | |
+ } | |
+ | |
+ /// <summary> | |
+ /// <para>Sends a command <c>SKSCAN 0</c>.</para> | |
+ /// </summary> | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.9. SKSCAN' for detailed specifications.</para> | |
+ /// </remarks> | |
+ [CLSCompliant(false)] | |
+ public ValueTask<( | |
+ SkStackResponse Response, | |
+ IReadOnlyDictionary<SkStackChannel, decimal> ScanResult | |
+ )> SendSKSCANEnergyDetectScanAsync( | |
+ TimeSpan duration = default, | |
+ uint channelMask = SKSCANDefaultChannelMask, | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ => SendSKSCANAsyncCore( | |
+ mode: SKSCANMode.EnergyDetectScan, | |
+ channelMask: channelMask, | |
+ durationFactor: TranslateToSKSCANDurationFactorOrThrowIfOutOfRange(duration == default ? SKSCANDefaultDuration : duration, nameof(duration)), | |
+ commandEventHandler: new SKSCANEnergyDetectScanEventHandler(), | |
+ cancellationToken: cancellationToken | |
+ ); | |
+ | |
+ /// <summary> | |
+ /// <para>Sends a command <c>SKSCAN 0</c>.</para> | |
+ /// </summary> | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.9. SKSCAN' for detailed specifications.</para> | |
+ /// </remarks> | |
+ [CLSCompliant(false)] | |
+ public ValueTask<( | |
+ SkStackResponse Response, | |
+ IReadOnlyDictionary<SkStackChannel, decimal> ScanResult | |
+ )> SendSKSCANEnergyDetectScanAsync( | |
+ int durationFactor, | |
+ uint channelMask = SKSCANDefaultChannelMask, | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ => SendSKSCANAsyncCore( | |
+ mode: SKSCANMode.EnergyDetectScan, | |
+ channelMask: channelMask, | |
+ durationFactor: ThrowIfDurationFactorOutOfRange(durationFactor, nameof(durationFactor)), | |
+ commandEventHandler: new SKSCANEnergyDetectScanEventHandler(), | |
+ cancellationToken: cancellationToken | |
+ ); | |
+ | |
+ private class SKSCANEnergyDetectScanEventHandler : SKSCANEventHandler<IReadOnlyDictionary<SkStackChannel, decimal>> { | |
+ public override bool TryProcessEvent(SkStackEvent ev) | |
+ { | |
+ if (ev.Number == SkStackEventNumber.EnergyDetectScanCompleted) | |
+ return false; // process subsequent event | |
+ | |
+ return false; | |
+ } | |
+ | |
+ public override void ProcessSubsequentEvent(ISkStackSequenceParserContext context) | |
+ { | |
+ var reader = context.CreateReader(); // retain current buffer | |
+ | |
+ if (SkStackEventParser.ExpectEEDSCAN(context, out var result)) { | |
+ SetScanResult(result); | |
+ context.Complete(); | |
+ } | |
+ else { | |
+ context.SetAsIncomplete(reader); // revert buffer | |
+ } | |
+ } | |
+ } | |
+ | |
+ /// <summary> | |
+ /// <para>Sends a command <c>SKSCAN 2</c>.</para> | |
+ /// </summary> | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.9. SKSCAN' for detailed specifications.</para> | |
+ /// </remarks> | |
+ [CLSCompliant(false)] | |
+ public ValueTask<( | |
+ SkStackResponse Response, | |
+ IReadOnlyList<SkStackPanDescription> PanDescriptions | |
+ )> SendSKSCANActiveScanPairAsync( | |
+ TimeSpan duration = default, | |
+ uint channelMask = SKSCANDefaultChannelMask, | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ => SendSKSCANAsyncCore( | |
+ mode: SKSCANMode.ActiveScanPair, | |
+ channelMask: channelMask, | |
+ durationFactor: TranslateToSKSCANDurationFactorOrThrowIfOutOfRange(duration == default ? SKSCANDefaultDuration : duration, nameof(duration)), | |
+ commandEventHandler: new SKSCANActiveScanEventHandler(expectPairingId: true), | |
+ cancellationToken: cancellationToken | |
+ ); | |
+ | |
+ /// <summary> | |
+ /// <para>Sends a command <c>SKSCAN 2</c>.</para> | |
+ /// </summary> | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.9. SKSCAN' for detailed specifications.</para> | |
+ /// </remarks> | |
+ [CLSCompliant(false)] | |
+ public ValueTask<( | |
+ SkStackResponse Response, | |
+ IReadOnlyList<SkStackPanDescription> PanDescriptions | |
+ )> SendSKSCANActiveScanPairAsync( | |
+ int durationFactor, | |
+ uint channelMask = SKSCANDefaultChannelMask, | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ => SendSKSCANAsyncCore( | |
+ mode: SKSCANMode.ActiveScanPair, | |
+ channelMask: channelMask, | |
+ durationFactor: ThrowIfDurationFactorOutOfRange(durationFactor, nameof(durationFactor)), | |
+ commandEventHandler: new SKSCANActiveScanEventHandler(expectPairingId: true), | |
+ cancellationToken: cancellationToken | |
+ ); | |
+ | |
+ /// <summary> | |
+ /// <para>Sends a command <c>SKSCAN 3</c>.</para> | |
+ /// </summary> | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.9. SKSCAN' for detailed specifications.</para> | |
+ /// </remarks> | |
+ [CLSCompliant(false)] | |
+ public ValueTask<( | |
+ SkStackResponse Response, | |
+ IReadOnlyList<SkStackPanDescription> PanDescriptions | |
+ )> | |
+ SendSKSCANActiveScanAsync( | |
+ TimeSpan duration = default, | |
+ uint channelMask = SKSCANDefaultChannelMask, | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ => SendSKSCANAsyncCore( | |
+ mode: SKSCANMode.ActiveScan, | |
+ channelMask: channelMask, | |
+ durationFactor: TranslateToSKSCANDurationFactorOrThrowIfOutOfRange(duration == default ? SKSCANDefaultDuration : duration, nameof(duration)), | |
+ commandEventHandler: new SKSCANActiveScanEventHandler(expectPairingId: false), | |
+ cancellationToken: cancellationToken | |
+ ); | |
+ | |
+ /// <summary> | |
+ /// <para>Sends a command <c>SKSCAN 3</c>.</para> | |
+ /// </summary> | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.9. SKSCAN' for detailed specifications.</para> | |
+ /// </remarks> | |
+ [CLSCompliant(false)] | |
+ public ValueTask<( | |
+ SkStackResponse Response, | |
+ IReadOnlyList<SkStackPanDescription> PanDescriptions | |
+ )> | |
+ SendSKSCANActiveScanAsync( | |
+ int durationFactor, | |
+ uint channelMask = SKSCANDefaultChannelMask, | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ => SendSKSCANAsyncCore( | |
+ mode: SKSCANMode.ActiveScan, | |
+ channelMask: channelMask, | |
+ durationFactor: ThrowIfDurationFactorOutOfRange(durationFactor, nameof(durationFactor)), | |
+ commandEventHandler: new SKSCANActiveScanEventHandler(expectPairingId: false), | |
+ cancellationToken: cancellationToken | |
+ ); | |
+ | |
+ private class SKSCANActiveScanEventHandler : SKSCANEventHandler<IReadOnlyList<SkStackPanDescription>> { | |
+ private readonly bool expectPairingId; | |
+ | |
+ private const int ExpectedMaxPanDescriptionCount = 1; | |
+ private List<SkStackPanDescription>? scanResult = null; | |
+ | |
+ public SKSCANActiveScanEventHandler(bool expectPairingId) | |
+ { | |
+ this.expectPairingId = expectPairingId; | |
+ } | |
+ | |
+ public override bool TryProcessEvent(SkStackEvent ev) | |
+ { | |
+ switch (ev.Number) { | |
+ case SkStackEventNumber.BeaconReceived: | |
+ return false; // process subsequent event | |
+ | |
+ case SkStackEventNumber.ActiveScanCompleted: | |
+ SetScanResult( | |
+ (IReadOnlyList<SkStackPanDescription>?)scanResult ?? Array.Empty<SkStackPanDescription>() | |
+ ); | |
+ return true; // completed | |
+ | |
+ default: | |
+ return false; | |
+ } | |
+ } | |
+ | |
+ public override void ProcessSubsequentEvent(ISkStackSequenceParserContext context) | |
+ { | |
+ var reader = context.CreateReader(); // retain current buffer | |
+ | |
+ if (SkStackEventParser.ExpectEPANDESC(context, expectPairingId, out var pandesc)) { | |
+ scanResult ??= new(capacity: ExpectedMaxPanDescriptionCount); | |
+ scanResult.Add(pandesc); | |
+ context.Continue(); | |
+ } | |
+ else { | |
+ context.SetAsIncomplete(reader); // revert buffer | |
+ } | |
+ } | |
+ } | |
+ | |
+ private enum SKSCANMode : byte { | |
+ EnergyDetectScan = 0, | |
+ ActiveScanPair = 2, | |
+ ActiveScan = 3, | |
+ } | |
+ | |
+ private const uint SKSCANDefaultChannelMask = 0xFFFFFFFF; | |
+ | |
+ private const byte SKSCANMinDurationFactor = 0x0; // 0 | |
+ private const byte SKSCANMaxDurationFactor = 0xE; // 14 | |
+ private const byte SKSCANDefaultDurationFactor = 0x2; // 2 | |
+ | |
+ private static byte ThrowIfDurationFactorOutOfRange(int durationFactor, string paramName) | |
+ => durationFactor is >= SKSCANMinDurationFactor and <= SKSCANMaxDurationFactor | |
+ ? (byte)durationFactor | |
+ : throw new ArgumentOutOfRangeException(paramName, durationFactor, $"must be in range of {SKSCANMinDurationFactor}~{SKSCANMaxDurationFactor}"); | |
+ | |
+ private static TimeSpan ToSKSCANDuration(int factor) | |
+ => TimeSpan.FromMilliseconds(9.6) * (Math.Pow(2.0, factor) + 1); | |
+ | |
+ /// <summary> | |
+ /// The minimum scan duration for each channel in <c>SKSCAN</c> command. | |
+ /// </summary> | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.9. SKSCAN' for detailed specifications.</para> | |
+ /// </remarks> | |
+ /// <seealso cref="SendSKSCANActiveScanAsync(TimeSpan, uint, CancellationToken)"/> | |
+ /// <seealso cref="SendSKSCANActiveScanPairAsync(TimeSpan, uint, CancellationToken)"/> | |
+ /// <seealso cref="SendSKSCANEnergyDetectScanAsync(TimeSpan, uint, CancellationToken)"/> | |
+ public static readonly TimeSpan SKSCANMinDuration = ToSKSCANDuration(SKSCANMinDurationFactor); | |
+ | |
+ /// <summary> | |
+ /// The maximum scan duration for each channel in <c>SKSCAN</c> command. | |
+ /// </summary> | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.9. SKSCAN' for detailed specifications.</para> | |
+ /// </remarks> | |
+ /// <seealso cref="SendSKSCANActiveScanAsync(TimeSpan, uint, CancellationToken)"/> | |
+ /// <seealso cref="SendSKSCANActiveScanPairAsync(TimeSpan, uint, CancellationToken)"/> | |
+ /// <seealso cref="SendSKSCANEnergyDetectScanAsync(TimeSpan, uint, CancellationToken)"/> | |
+ public static readonly TimeSpan SKSCANMaxDuration = ToSKSCANDuration(SKSCANMaxDurationFactor); | |
+ | |
+ /// <summary> | |
+ /// The default scan duration for each channel in <c>SKSCAN</c> command. | |
+ /// </summary> | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.9. SKSCAN' for detailed specifications.</para> | |
+ /// </remarks> | |
+ /// <seealso cref="SendSKSCANActiveScanAsync(TimeSpan, uint, CancellationToken)"/> | |
+ /// <seealso cref="SendSKSCANActiveScanPairAsync(TimeSpan, uint, CancellationToken)"/> | |
+ /// <seealso cref="SendSKSCANEnergyDetectScanAsync(TimeSpan, uint, CancellationToken)"/> | |
+ public static readonly TimeSpan SKSCANDefaultDuration = ToSKSCANDuration(SKSCANDefaultDurationFactor); | |
+ | |
+ private static byte TranslateToSKSCANDurationFactorOrThrowIfOutOfRange(TimeSpan duration, string paramName) | |
+ { | |
+ if (SKSCANMinDuration <= duration && duration <= SKSCANMaxDuration) { | |
+ for (byte durationFactor = SKSCANMinDurationFactor + 1; durationFactor <= SKSCANMaxDurationFactor; durationFactor++) { | |
+ if (duration < ToSKSCANDuration(durationFactor)) | |
+ return (byte)(durationFactor - 1); | |
+ } | |
+ | |
+ return SKSCANMaxDurationFactor; | |
+ } | |
+ | |
+ throw new ArgumentOutOfRangeException(paramName, duration, $"must be in range of {SKSCANMinDuration}~{SKSCANMaxDuration}"); | |
+ } | |
+ | |
+ private async ValueTask<( | |
+ SkStackResponse Response, | |
+ TScanResult ScanResult | |
+ )> SendSKSCANAsyncCore<TScanResult>( | |
+ SKSCANMode mode, | |
+ uint channelMask, | |
+ byte durationFactor, | |
+ SKSCANEventHandler<TScanResult> commandEventHandler, | |
+ CancellationToken cancellationToken | |
+ ) | |
+ { | |
+ var resp = await SendCommandAsync( | |
+ command: SkStackCommandNames.SKSCAN, | |
+ writeArguments: writer => { | |
+ writer.WriteTokenHex((byte)mode); | |
+ writer.WriteTokenUINT32(channelMask, zeroPadding: true); | |
+ writer.WriteTokenHex(durationFactor); | |
+ }, | |
+ commandEventHandler: commandEventHandler, | |
+ throwIfErrorStatus: true, | |
+ cancellationToken: cancellationToken | |
+ ).ConfigureAwait(false); | |
+ | |
+#if DEBUG | |
+ if (!commandEventHandler.HasScanResultSet) | |
+ throw new InvalidOperationException($"{commandEventHandler.ScanResult} has not been set"); | |
+#endif | |
+ | |
+ return (resp, commandEventHandler.ScanResult!); | |
+ } | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Commands.SKSENDTO.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Commands.SKSENDTO.cs | |
new file mode 100644 | |
index 0000000..776d720 | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Commands.SKSENDTO.cs | |
@@ -0,0 +1,164 @@ | |
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+using System; | |
+using System.Net; | |
+using System.Threading; | |
+using System.Threading.Tasks; | |
+ | |
+using Smdn.Net.SkStackIP.Protocol; | |
+ | |
+namespace Smdn.Net.SkStackIP; | |
+ | |
+#pragma warning disable IDE0040 | |
+partial class SkStackClient { | |
+#pragma warning restore IDE0040 | |
+ /// <summary> | |
+ /// <para>Sends a command <c>SKSENDTO</c>.</para> | |
+ /// </summary> | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.7. SKSENDTO' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public ValueTask<( | |
+ SkStackResponse Response, | |
+ bool IsCompletedSuccessfully | |
+ )> SendSKSENDTOAsync( | |
+ SkStackUdpPort port, | |
+ IPEndPoint destination, | |
+ ReadOnlyMemory<byte> data, | |
+ SkStackUdpEncryption encryption = SkStackUdpEncryption.EncryptIfAble, | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ => SendSKSENDTOAsync( | |
+ handle: port.Handle, | |
+ destinationAddress: (destination ?? throw new ArgumentNullException(nameof(destination))).Address, | |
+ destinationPort: destination.Port, | |
+ data: data, | |
+ encryption: encryption, | |
+ cancellationToken: cancellationToken | |
+ ); | |
+ | |
+ /// <summary> | |
+ /// <para>Sends a command <c>SKSENDTO</c>.</para> | |
+ /// </summary> | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.7. SKSENDTO' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public ValueTask<( | |
+ SkStackResponse Response, | |
+ bool IsCompletedSuccessfully | |
+ )> SendSKSENDTOAsync( | |
+ SkStackUdpPort port, | |
+ IPAddress destinationAddress, | |
+ int destinationPort, | |
+ ReadOnlyMemory<byte> data, | |
+ SkStackUdpEncryption encryption = SkStackUdpEncryption.EncryptIfAble, | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ => SendSKSENDTOAsync( | |
+ handle: port.Handle, | |
+ destinationAddress: destinationAddress, | |
+ destinationPort: destinationPort, | |
+ data: data, | |
+ encryption: encryption, | |
+ cancellationToken: cancellationToken | |
+ ); | |
+ | |
+ /// <summary> | |
+ /// <para>Sends a command <c>SKSENDTO</c>.</para> | |
+ /// </summary> | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.7. SKSENDTO' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public ValueTask<( | |
+ SkStackResponse Response, | |
+ bool IsCompletedSuccessfully | |
+ )> SendSKSENDTOAsync( | |
+ SkStackUdpPortHandle handle, | |
+ IPEndPoint destination, | |
+ ReadOnlyMemory<byte> data, | |
+ SkStackUdpEncryption encryption = SkStackUdpEncryption.EncryptIfAble, | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ => SendSKSENDTOAsync( | |
+ handle: handle, | |
+ destinationAddress: (destination ?? throw new ArgumentNullException(nameof(destination))).Address, | |
+ destinationPort: destination.Port, | |
+ data: data, | |
+ encryption: encryption, | |
+ cancellationToken: cancellationToken | |
+ ); | |
+ | |
+ /// <summary> | |
+ /// <para>Sends a command <c>SKSENDTO</c>.</para> | |
+ /// </summary> | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.7. SKSENDTO' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public ValueTask<( | |
+ SkStackResponse Response, | |
+ bool IsCompletedSuccessfully | |
+ )> SendSKSENDTOAsync( | |
+ SkStackUdpPortHandle handle, | |
+ IPAddress destinationAddress, | |
+ int destinationPort, | |
+ ReadOnlyMemory<byte> data, | |
+ SkStackUdpEncryption encryption = SkStackUdpEncryption.EncryptIfAble, | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ { | |
+ const int MinDataLength = 0x0001; | |
+ const int MaxDataLength = 0x04D0; | |
+ | |
+ SkStackUdpPort.ThrowIfPortHandleIsOutOfRange(handle, nameof(handle)); | |
+#if SYSTEM_ENUM_ISDEFINED_OF_TENUM | |
+ if (!Enum.IsDefined(encryption)) | |
+#else | |
+ if (!Enum.IsDefined(typeof(SkStackUdpEncryption), encryption)) | |
+#endif | |
+ throw new ArgumentException($"undefined value of {nameof(SkStackUdpEncryption)}", nameof(encryption)); | |
+ if (destinationAddress is null) | |
+ throw new ArgumentNullException(nameof(destinationAddress)); | |
+ SkStackUdpPort.ThrowIfPortNumberIsOutOfRange(destinationPort, nameof(destinationPort)); | |
+ if (data.IsEmpty) | |
+ throw new ArgumentException("must be non-empty sequence", nameof(data)); | |
+ if (data.Length is not (>= MinDataLength and <= MaxDataLength)) | |
+ throw new ArgumentException($"length of {nameof(data)} must be in range of {MinDataLength}~{MaxDataLength}", nameof(data)); | |
+ | |
+ return SKSENDTO(); | |
+ | |
+ async ValueTask<(SkStackResponse, bool)> SKSENDTO() | |
+ { | |
+ SkStackResponse response; | |
+ bool hasUdpSendResultStored; | |
+ bool isCompletedSuccessfully; | |
+ | |
+ try { | |
+ response = await SendCommandAsync( | |
+ command: SkStackCommandNames.SKSENDTO, | |
+ writeArguments: writer => { | |
+ writer.WriteTokenHex((byte)handle); | |
+ writer.WriteTokenIPADDR(destinationAddress); | |
+ writer.WriteTokenUINT16((ushort)destinationPort, zeroPadding: true); | |
+ writer.WriteTokenHex((byte)encryption); | |
+ writer.WriteTokenUINT16((ushort)data.Length, zeroPadding: true); | |
+ writer.WriteToken(data.Span); | |
+ }, | |
+ syntax: SkStackProtocolSyntax.SKSENDTO, // SKSENDTO must terminate the command line without CRLF | |
+ throwIfErrorStatus: true, | |
+ cancellationToken: cancellationToken | |
+ ).ConfigureAwait(false); | |
+ } | |
+ finally { | |
+ hasUdpSendResultStored = lastUdpSendResult.Remove( | |
+ destinationAddress, | |
+ out isCompletedSuccessfully | |
+ ); | |
+ } | |
+ | |
+ if (!hasUdpSendResultStored) // in case when the 'EVENT 21' was not raised after SKSENDTO | |
+ throw new SkStackUdpSendResultIndeterminateException(); | |
+ | |
+ return (response, isCompletedSuccessfully); | |
+ } | |
+ } | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Commands.SKTABLE.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Commands.SKTABLE.cs | |
new file mode 100644 | |
index 0000000..6c73dca | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Commands.SKTABLE.cs | |
@@ -0,0 +1,81 @@ | |
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+using System.Collections.Generic; | |
+using System.Linq; | |
+using System.Net; | |
+using System.Net.NetworkInformation; | |
+using System.Threading; | |
+using System.Threading.Tasks; | |
+ | |
+using Smdn.Net.SkStackIP.Protocol; | |
+ | |
+namespace Smdn.Net.SkStackIP; | |
+ | |
+#pragma warning disable IDE0040 | |
+partial class SkStackClient { | |
+#pragma warning restore IDE0040 | |
+ /// <summary> | |
+ /// <para>Sends a command <c>SKTABLE 1</c>.</para> | |
+ /// </summary> | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.26. SKTABLE' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public ValueTask<SkStackResponse<IReadOnlyList<IPAddress>>> SendSKTABLEAvailableAddressListAsync( | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ => SendCommandAsync( | |
+ command: SkStackCommandNames.SKTABLE, | |
+ writeArguments: static writer => writer.WriteTokenHex(0x1), | |
+ parseResponsePayload: SkStackEventParser.ExpectEADDR, | |
+ throwIfErrorStatus: true, | |
+ cancellationToken: cancellationToken | |
+ ); | |
+ | |
+ /// <summary> | |
+ /// <para>Sends a command <c>SKTABLE 2</c>.</para> | |
+ /// </summary> | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.26. SKTABLE' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public ValueTask<SkStackResponse<IReadOnlyDictionary<IPAddress, PhysicalAddress>>> SendSKTABLENeighborCacheListAsync( | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ => SendCommandAsync( | |
+ command: SkStackCommandNames.SKTABLE, | |
+ writeArguments: static writer => writer.WriteTokenHex(0x2), | |
+ parseResponsePayload: SkStackEventParser.ExpectENEIGHBOR, | |
+ throwIfErrorStatus: true, | |
+ cancellationToken: cancellationToken | |
+ ); | |
+ | |
+ /// <summary> | |
+ /// <para>Sends a command <c>SKTABLE E</c>.</para> | |
+ /// </summary> | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.26. SKTABLE' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public ValueTask<SkStackResponse<IReadOnlyList<SkStackUdpPort>>> SendSKTABLEListeningPortListAsync( | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ { | |
+ return SendSKTABLEListeningPortListAsyncCore(); | |
+ | |
+ async ValueTask<SkStackResponse<IReadOnlyList<SkStackUdpPort>>> SendSKTABLEListeningPortListAsyncCore() | |
+ { | |
+ var resp = await SendCommandAsync( | |
+ command: SkStackCommandNames.SKTABLE, | |
+ writeArguments: static writer => writer.WriteTokenHex(0xE), | |
+ parseResponsePayload: SkStackEventParser.ExpectEPORT, | |
+ throwIfErrorStatus: true, | |
+ cancellationToken: cancellationToken | |
+ ).ConfigureAwait(false); | |
+ | |
+ var portList = resp.Payload!; | |
+ | |
+ // store or update the port handle for ECHONET Lite each time the EPORT is received | |
+ udpPortHandleForEchonetLite = portList.FirstOrDefault(static p => p.Port == SkStackKnownPortNumbers.EchonetLite).Handle; | |
+ | |
+ return resp; | |
+ } | |
+ } | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Commands.SKTERM.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Commands.SKTERM.cs | |
new file mode 100644 | |
index 0000000..7bda759 | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Commands.SKTERM.cs | |
@@ -0,0 +1,59 @@ | |
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+ | |
+using System.Threading; | |
+using System.Threading.Tasks; | |
+ | |
+using Smdn.Net.SkStackIP.Protocol; | |
+ | |
+namespace Smdn.Net.SkStackIP; | |
+ | |
+#pragma warning disable IDE0040 | |
+partial class SkStackClient { | |
+#pragma warning restore IDE0040 | |
+ /// <summary> | |
+ /// <para>Sends a command <c>SKTERM</c>.</para> | |
+ /// </summary> | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.6. SKTERM' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public async ValueTask<( | |
+ SkStackResponse Response, | |
+ bool IsCompletedSuccessfully | |
+ )> SendSKTERMAsync( | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ { | |
+ var eventHandler = new SKTERMEventHandler(); | |
+ | |
+ var resp = await SendCommandAsync( | |
+ command: SkStackCommandNames.SKTERM, | |
+ writeArguments: null, | |
+ commandEventHandler: eventHandler, | |
+ throwIfErrorStatus: true, | |
+ cancellationToken: cancellationToken | |
+ ).ConfigureAwait(false); | |
+ | |
+ return (resp, eventHandler.IsCompletedSuccessfully); | |
+ } | |
+ | |
+ private class SKTERMEventHandler : SkStackEventHandlerBase { | |
+ public bool IsCompletedSuccessfully { get; private set; } | |
+ | |
+ public override bool TryProcessEvent(SkStackEvent ev) | |
+ { | |
+ switch (ev.Number) { | |
+ case SkStackEventNumber.PanaSessionTerminationCompleted: | |
+ IsCompletedSuccessfully = true; | |
+ return true; | |
+ | |
+ case SkStackEventNumber.PanaSessionTerminationTimedOut: | |
+ IsCompletedSuccessfully = false; | |
+ return true; | |
+ | |
+ default: | |
+ return false; | |
+ } | |
+ } | |
+ } | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Commands.SKUDPPORT.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Commands.SKUDPPORT.cs | |
new file mode 100644 | |
index 0000000..83e7098 | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Commands.SKUDPPORT.cs | |
@@ -0,0 +1,69 @@ | |
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+using System.Threading; | |
+using System.Threading.Tasks; | |
+ | |
+using Smdn.Net.SkStackIP.Protocol; | |
+ | |
+namespace Smdn.Net.SkStackIP; | |
+ | |
+#pragma warning disable IDE0040 | |
+partial class SkStackClient { | |
+#pragma warning restore IDE0040 | |
+ /// <summary> | |
+ /// <para>Sends a command <c>SKUDPPORT</c>.</para> | |
+ /// </summary> | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.19. SKUDPPORT' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public ValueTask<(SkStackResponse Response, SkStackUdpPort UdpPort)> SendSKUDPPORTAsync( | |
+ SkStackUdpPortHandle handle, | |
+ int port, | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ { | |
+ SkStackUdpPort.ThrowIfPortHandleIsOutOfRange(handle, nameof(handle)); | |
+ SkStackUdpPort.ThrowIfPortNumberIsOutOfRangeOrUnused(port, nameof(port)); | |
+ | |
+ return SKUDPPORT(); | |
+ | |
+ async ValueTask<(SkStackResponse Response, SkStackUdpPort UdpPort)> SKUDPPORT() | |
+ { | |
+ var resp = await SendCommandAsync( | |
+ command: SkStackCommandNames.SKUDPPORT, | |
+ writeArguments: writer => { | |
+ writer.WriteTokenHex((byte)handle); | |
+ writer.WriteTokenUINT16((ushort)port, zeroPadding: true); | |
+ }, | |
+ throwIfErrorStatus: true, | |
+ cancellationToken: cancellationToken | |
+ ).ConfigureAwait(false); | |
+ | |
+ return (resp, new SkStackUdpPort(handle, port)); | |
+ } | |
+ } | |
+ | |
+ /// <summary> | |
+ /// <para>Sends a command <c>SKUDPPORT</c>.</para> | |
+ /// </summary> | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.19. SKUDPPORT' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public ValueTask<SkStackResponse> SendSKUDPPORTUnsetAsync( | |
+ SkStackUdpPortHandle handle, | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ { | |
+ SkStackUdpPort.ThrowIfPortHandleIsOutOfRange(handle, nameof(handle)); | |
+ | |
+ return SendCommandAsync( | |
+ command: SkStackCommandNames.SKUDPPORT, | |
+ writeArguments: writer => { | |
+ writer.WriteTokenHex((byte)handle); | |
+ writer.WriteTokenUINT16(SkStackKnownPortNumbers.SetUnused, zeroPadding: true); | |
+ }, | |
+ throwIfErrorStatus: true, | |
+ cancellationToken: cancellationToken | |
+ ); | |
+ } | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Commands.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Commands.cs | |
new file mode 100644 | |
index 0000000..4510743 | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Commands.cs | |
@@ -0,0 +1,374 @@ | |
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+#pragma warning disable CA1506 // TODO: refactor | |
+ | |
+using System; | |
+using System.Net; | |
+using System.Net.NetworkInformation; | |
+#if SYSTEM_TEXT_ASCII | |
+using System.Text; | |
+#endif | |
+using System.Threading; | |
+using System.Threading.Tasks; | |
+ | |
+using Smdn.Net.SkStackIP.Protocol; | |
+ | |
+namespace Smdn.Net.SkStackIP; | |
+ | |
+#pragma warning disable IDE0040 | |
+partial class SkStackClient { | |
+#pragma warning restore IDE0040 | |
+ /// <summary> | |
+ /// <para>Sends a command <c>SKSREG</c>.</para> | |
+ /// <para>Sets the value of the register specified by <paramref name="register"/> to <paramref name="value"/>.</para> | |
+ /// </summary> | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.1. SKSREG' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public ValueTask<SkStackResponse> SendSKSREGAsync<TValue>( | |
+ SkStackRegister.RegisterEntry<TValue> register, | |
+ TValue value, | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ { | |
+ if (register is null) | |
+ throw new ArgumentNullException(nameof(register)); | |
+ if (!register.IsWritable) | |
+ throw new InvalidOperationException($"register {register.Name} is not writable"); | |
+ | |
+ register.ThrowIfValueIsNotInRange(value, nameof(value)); | |
+ | |
+ return SendCommandAsync( | |
+ command: SkStackCommandNames.SKSREG, | |
+ writeArguments: writer => { | |
+ writer.WriteToken(register.SREG.Span); | |
+ register.WriteValueTo(writer, value); | |
+ }, | |
+ throwIfErrorStatus: true, | |
+ cancellationToken: cancellationToken | |
+ ); | |
+ } | |
+ | |
+ /// <summary> | |
+ /// <para>Sends a command <c>SKSREG</c>.</para> | |
+ /// <para>Gets the value of the register specified by <paramref name="register"/>.</para> | |
+ /// </summary> | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.1. SKSREG' for detailed specifications.</para> | |
+ /// </remarks> | |
+ /// <seealso cref="SkStackRegister"/> | |
+ public ValueTask<SkStackResponse<TValue>> SendSKSREGAsync<TValue>( | |
+ SkStackRegister.RegisterEntry<TValue> register, | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ { | |
+ if (register is null) | |
+ throw new ArgumentNullException(nameof(register)); | |
+ if (!register.IsReadable) | |
+ throw new InvalidOperationException($"register {register.Name} is not readable"); | |
+ | |
+ return SendCommandAsync( | |
+ command: SkStackCommandNames.SKSREG, | |
+ writeArguments: writer => writer.WriteToken(register.SREG.Span), | |
+ parseResponsePayload: register.ParseESREG, | |
+ throwIfErrorStatus: true, | |
+ cancellationToken: cancellationToken | |
+ ); | |
+ } | |
+ | |
+#if false | |
+ /// <summary> | |
+ /// <para>Sends a command <c>SKPING</c>.</para> | |
+ /// </summary> | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.8. SKPING' for detailed specifications.</para> | |
+ /// </remarks> | |
+#endif | |
+ | |
+ private const int SKSETPWDMinLength = 1; | |
+ private const int SKSETPWDMaxLength = 32; | |
+ | |
+ /// <summary> | |
+ /// <para>Sends a command <c>SKSETPWD</c>.</para> | |
+ /// </summary> | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.16. SKSETPWD' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public ValueTask<SkStackResponse> SendSKSETPWDAsync( | |
+ ReadOnlyMemory<char> password, | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ { | |
+ if (password.Length is not (>= SKSETPWDMinLength and <= SKSETPWDMaxLength)) | |
+ throw new ArgumentException($"length of `{nameof(password)}` must be in range of {SKSETPWDMinLength}~{SKSETPWDMaxLength}", nameof(password)); | |
+#if SYSTEM_TEXT_ASCII | |
+ if (!Ascii.IsValid(password.Span)) | |
+ throw new ArgumentException($"`{nameof(password)}` contains invalid characters for ASCII sequence", paramName: nameof(password)); | |
+#endif | |
+ | |
+ return SendCommandAsync( | |
+ command: SkStackCommandNames.SKSETPWD, | |
+ writeArguments: writer => { | |
+ writer.WriteTokenUINT8((byte)password.Length, zeroPadding: false); | |
+ writer.WriteMaskedToken(password.Span); | |
+ }, | |
+ throwIfErrorStatus: true, | |
+ cancellationToken: cancellationToken | |
+ ); | |
+ } | |
+ | |
+ /// <summary> | |
+ /// <para>Sends a command <c>SKSETPWD</c>.</para> | |
+ /// </summary> | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.16. SKSETPWD' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public ValueTask<SkStackResponse> SendSKSETPWDAsync( | |
+ ReadOnlyMemory<byte> password, | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ { | |
+ if (password.Length is not (>= SKSETPWDMinLength and <= SKSETPWDMaxLength)) | |
+ throw new ArgumentException($"length of `{nameof(password)}` must be in range of {SKSETPWDMinLength}~{SKSETPWDMaxLength}", nameof(password)); | |
+ | |
+ return SendCommandAsync( | |
+ command: SkStackCommandNames.SKSETPWD, | |
+ writeArguments: writer => { | |
+ writer.WriteTokenUINT8((byte)password.Length, zeroPadding: false); | |
+ writer.WriteMaskedToken(password.Span); | |
+ }, | |
+ throwIfErrorStatus: true, | |
+ cancellationToken: cancellationToken | |
+ ); | |
+ } | |
+ | |
+ private const int SKSETRBIDLengthOfId = 32; | |
+ | |
+ /// <summary> | |
+ /// <para>Sends a command <c>SKSETRBID</c>.</para> | |
+ /// </summary> | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.17. SKSETRBID' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public ValueTask<SkStackResponse> SendSKSETRBIDAsync( | |
+ ReadOnlyMemory<char> id, | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ { | |
+ if (id.Length != SKSETRBIDLengthOfId) | |
+ throw new ArgumentException($"length of `{nameof(id)}` must be exact {SKSETRBIDLengthOfId}", nameof(id)); | |
+#if SYSTEM_TEXT_ASCII | |
+ if (!Ascii.IsValid(id.Span)) | |
+ throw new ArgumentException($"`{nameof(id)}` contains invalid characters for ASCII sequence", paramName: nameof(id)); | |
+#endif | |
+ | |
+ return SendCommandAsync( | |
+ command: SkStackCommandNames.SKSETRBID, | |
+ writeArguments: writer => writer.WriteToken(id.Span), | |
+ throwIfErrorStatus: true, | |
+ cancellationToken: cancellationToken | |
+ ); | |
+ } | |
+ | |
+ /// <summary> | |
+ /// <para>Sends a command <c>SKSETRBID</c>.</para> | |
+ /// </summary> | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.17. SKSETRBID' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public ValueTask<SkStackResponse> SendSKSETRBIDAsync( | |
+ ReadOnlyMemory<byte> id, | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ { | |
+ if (id.Length != SKSETRBIDLengthOfId) | |
+ throw new ArgumentException($"length of `{nameof(id)}` must be exact {SKSETRBIDLengthOfId}", nameof(id)); | |
+ | |
+ return SendCommandAsync( | |
+ command: SkStackCommandNames.SKSETRBID, | |
+ writeArguments: writer => writer.WriteToken(id.Span), | |
+ throwIfErrorStatus: true, | |
+ cancellationToken: cancellationToken | |
+ ); | |
+ } | |
+ | |
+ /// <summary> | |
+ /// <para>Sends a command <c>SKSAVE</c>.</para> | |
+ /// </summary> | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.20. SKSAVE' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public ValueTask<SkStackResponse> SendSKSAVEAsync( | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ => SendFlashMemoryCommand( | |
+ command: SkStackCommandNames.SKSAVE, | |
+ messageForFlashMemoryIOException: "Failed to save the register values to the flash memory.", | |
+ cancellationToken: cancellationToken | |
+ ); | |
+ | |
+ /// <summary> | |
+ /// <para>Sends a command <c>SKLOAD</c>.</para> | |
+ /// </summary> | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.21. SKLOAD' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public ValueTask<SkStackResponse> SendSKLOADAsync( | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ => SendFlashMemoryCommand( | |
+ command: SkStackCommandNames.SKLOAD, | |
+ messageForFlashMemoryIOException: "Failed to load the register values from the flash memory or the register values have not been saved in the flash memory.", | |
+ cancellationToken: cancellationToken | |
+ ); | |
+ | |
+ private async ValueTask<SkStackResponse> SendFlashMemoryCommand( | |
+ ReadOnlyMemory<byte> command, | |
+ string messageForFlashMemoryIOException, | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ { | |
+ var resp = await SendCommandAsync( | |
+ command: command, | |
+ throwIfErrorStatus: false, | |
+ cancellationToken: cancellationToken | |
+ ).ConfigureAwait(false); | |
+ | |
+ resp.ThrowIfErrorStatus( | |
+ (r, code, text) => code == SkStackErrorCode.ER10 | |
+ ? new SkStackFlashMemoryIOException(r, code, text.Span, messageForFlashMemoryIOException) | |
+ : null | |
+ ); | |
+ | |
+ return resp; | |
+ } | |
+ | |
+ /// <summary> | |
+ /// <para>Sends a command <c>SKERASE</c>.</para> | |
+ /// </summary> | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.22. SKERASE' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public ValueTask<SkStackResponse> SendSKERASEAsync( | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ => SendCommandAsync( | |
+ command: SkStackCommandNames.SKERASE, | |
+ throwIfErrorStatus: false, | |
+ cancellationToken: cancellationToken | |
+ ); | |
+ | |
+ /// <summary> | |
+ /// <para>Sends a command <c>SKVER</c>.</para> | |
+ /// </summary> | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.23. SKVER' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public ValueTask<SkStackResponse<Version>> SendSKVERAsync( | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ => SendCommandAsync( | |
+ command: SkStackCommandNames.SKVER, | |
+ writeArguments: null, | |
+ parseResponsePayload: static context => { | |
+ var reader = context.CreateReader(); | |
+ | |
+ if ( | |
+ SkStackTokenParser.ExpectToken(ref reader, "EVER"u8) && | |
+ SkStackTokenParser.ExpectCharArray(ref reader, out string? version) && | |
+ SkStackTokenParser.ExpectEndOfLine(ref reader) | |
+ ) { | |
+ context.Complete(reader); | |
+ return Version.Parse(version); | |
+ } | |
+ | |
+ context.SetAsIncomplete(); | |
+ return default; | |
+ }, | |
+ throwIfErrorStatus: true, | |
+ cancellationToken: cancellationToken | |
+ ); | |
+ | |
+ /// <summary> | |
+ /// <para>Sends a command <c>SKAPPVER</c>.</para> | |
+ /// </summary> | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.24. SKAPPVER' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public ValueTask<SkStackResponse<string>> SendSKAPPVERAsync( | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ => SendCommandAsync( | |
+ command: SkStackCommandNames.SKAPPVER, | |
+ writeArguments: null, | |
+ parseResponsePayload: static context => { | |
+ var reader = context.CreateReader(); | |
+ | |
+ if ( | |
+ SkStackTokenParser.ExpectToken(ref reader, "EAPPVER"u8) && | |
+ SkStackTokenParser.ExpectCharArray(ref reader, out string? appver) && | |
+ SkStackTokenParser.ExpectEndOfLine(ref reader) | |
+ ) { | |
+ context.Complete(reader); | |
+ return appver; | |
+ } | |
+ | |
+ context.SetAsIncomplete(); | |
+ return default; | |
+ }, | |
+ throwIfErrorStatus: true, | |
+ cancellationToken: cancellationToken | |
+ ); | |
+ | |
+ /// <summary> | |
+ /// <para>Sends a command <c>SKRESET</c>.</para> | |
+ /// </summary> | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.25. SKRESET' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public ValueTask<SkStackResponse> SendSKRESETAsync( | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ => SendCommandAsync( | |
+ command: SkStackCommandNames.SKRESET, | |
+ throwIfErrorStatus: true, | |
+ cancellationToken: cancellationToken | |
+ ); | |
+ | |
+ /// <summary> | |
+ /// <para>Sends a command <c>SKLL64</c>.</para> | |
+ /// </summary> | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.29. SKLL64' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public ValueTask<SkStackResponse<IPAddress>> SendSKLL64Async( | |
+ PhysicalAddress macAddress, | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ { | |
+ if (macAddress is null) | |
+ throw new ArgumentNullException(nameof(macAddress)); | |
+ | |
+ return SendCommandAsync( | |
+ command: SkStackCommandNames.SKLL64, | |
+ writeArguments: writer => writer.WriteTokenADDR64(macAddress), | |
+ parseResponsePayload: static context => { | |
+ var reader = context.CreateReader(); | |
+ | |
+ if ( | |
+ SkStackTokenParser.ExpectIPADDR(ref reader, out var linkLocalAddress) && | |
+ SkStackTokenParser.ExpectEndOfLine(ref reader) | |
+ ) { | |
+ context.Complete(reader); | |
+ return linkLocalAddress; | |
+ } | |
+ | |
+ context.SetAsIncomplete(); | |
+ return default; | |
+ }, | |
+ syntax: SkStackProtocolSyntax.SKLL64, // SKLL64 does not define its status | |
+ throwIfErrorStatus: true, | |
+ cancellationToken: cancellationToken | |
+ ); | |
+ } | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Events.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Events.cs | |
new file mode 100644 | |
index 0000000..ab0ef18 | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Events.cs | |
@@ -0,0 +1,289 @@ | |
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+ | |
+using System; | |
+using System.Buffers; | |
+using System.Collections.Generic; | |
+using System.ComponentModel; | |
+using System.Net; | |
+using System.Threading; | |
+using System.Threading.Tasks; | |
+ | |
+using Smdn.Net.SkStackIP.Protocol; | |
+ | |
+namespace Smdn.Net.SkStackIP; | |
+ | |
+#pragma warning disable IDE0040 | |
+partial class SkStackClient { | |
+#pragma warning restore IDE0040 | |
+ | |
+#pragma warning disable CA2012 | |
+ private static readonly ValueTask<bool> TrueResultValueTask = | |
+#if SYSTEM_THREADING_TASKS_VALUETASK_FROMRESULT | |
+ ValueTask.FromResult(true); | |
+#else | |
+ new(result: true); | |
+#endif | |
+ | |
+ private static readonly ValueTask<bool> FalseResultValueTask = | |
+#if SYSTEM_THREADING_TASKS_VALUETASK_FROMRESULT | |
+ ValueTask.FromResult(false); | |
+#else | |
+ new(result: false); | |
+#endif | |
+#pragma warning restore CA2012 | |
+ | |
+ private readonly Dictionary<IPAddress, bool> lastUdpSendResult = new(capacity: 2); | |
+ | |
+#pragma warning disable CA1502 // TODO: refactor | |
+ /// <returns><see langword="true"/> if the first event processed and consumed, otherwise <see langword="false"/>.</returns> | |
+ private ValueTask<bool> ProcessEventsAsync( | |
+ ISkStackSequenceParserContext context, | |
+ SkStackEventHandlerBase? eventHandler, // handles events that are triggered by commands | |
+ CancellationToken cancellationToken | |
+ ) | |
+ { | |
+ var reader = context.CreateReader(); | |
+ | |
+ if (reader.TryRead(out var firstByte)) { | |
+ const byte FirstByteOfEVENTOrERXUDP = (byte)'E'; | |
+ | |
+ if (firstByte != FirstByteOfEVENTOrERXUDP) { | |
+ context.Ignore(); | |
+ return FalseResultValueTask; | |
+ } | |
+ } | |
+ else { | |
+ context.SetAsIncomplete(); | |
+ return FalseResultValueTask; | |
+ } | |
+ | |
+ var statusEVENT = SkStackEventParser.TryExpectEVENT(context, out var ev); | |
+ | |
+ if (statusEVENT == OperationStatus.NeedMoreData) { | |
+ context.SetAsIncomplete(); | |
+ return FalseResultValueTask; | |
+ } | |
+ else if (statusEVENT == OperationStatus.Done) { | |
+ var eventHandlerStatesCompleted = eventHandler is not null && eventHandler.TryProcessEvent(ev); | |
+ | |
+ // log event | |
+ switch (ev.Number) { | |
+ case SkStackEventNumber.NeighborSolicitationReceived: | |
+ case SkStackEventNumber.NeighborAdvertisementReceived: | |
+ case SkStackEventNumber.EchoRequestReceived: | |
+ case SkStackEventNumber.UdpSendCompleted: | |
+ Logger?.LogInfoIPEventReceived(ev); | |
+ break; | |
+ | |
+ case SkStackEventNumber.PanaSessionEstablishmentError: | |
+ case SkStackEventNumber.PanaSessionEstablishmentCompleted: | |
+ case SkStackEventNumber.PanaSessionTerminationRequestReceived: | |
+ case SkStackEventNumber.PanaSessionTerminationCompleted: | |
+ case SkStackEventNumber.PanaSessionTerminationTimedOut: | |
+ case SkStackEventNumber.PanaSessionExpired: | |
+ Logger?.LogInfoPanaEventReceived(ev); | |
+ break; | |
+ | |
+ case SkStackEventNumber.TransmissionTimeControlLimitationActivated: | |
+ case SkStackEventNumber.TransmissionTimeControlLimitationDeactivated: | |
+ Logger?.LogInfoAribStdT108EventReceived(ev); | |
+ break; | |
+ | |
+ case SkStackEventNumber.WakeupSignalReceived: | |
+ // TODO: log event | |
+ break; | |
+ } | |
+ | |
+ // raise event | |
+ switch (ev.Number) { | |
+ case SkStackEventNumber.PanaSessionEstablishmentCompleted: | |
+ PanaSessionPeerAddress = ev.SenderAddress; | |
+ RaiseEventPanaSessionEstablished(ev); | |
+ break; | |
+ | |
+ case SkStackEventNumber.PanaSessionTerminationRequestReceived: | |
+ case SkStackEventNumber.PanaSessionTerminationCompleted: | |
+ case SkStackEventNumber.PanaSessionTerminationTimedOut: | |
+ PanaSessionPeerAddress = null; | |
+ RaiseEventPanaSessionTerminated(ev); | |
+ break; | |
+ | |
+ case SkStackEventNumber.PanaSessionExpired: | |
+ PanaSessionPeerAddress = null; | |
+ RaiseEventPanaSessionExpired(ev); | |
+ break; | |
+ | |
+ case SkStackEventNumber.TransmissionTimeControlLimitationActivated: | |
+ case SkStackEventNumber.TransmissionTimeControlLimitationDeactivated: | |
+ // TODO: raise event | |
+ break; | |
+ | |
+ case SkStackEventNumber.WakeupSignalReceived: | |
+ RaiseEventWokeUp(ev); | |
+ break; | |
+ | |
+ case SkStackEventNumber.BeaconReceived: | |
+ SkStackUnexpectedResponseException.ThrowIfUnexpectedSubsequentEventCode( | |
+ subsequentEventCode: ev.ExpectedSubsequentEventCode, | |
+ expectedEventCode: SkStackEventCode.EPANDESC | |
+ ); | |
+ break; | |
+ | |
+ case SkStackEventNumber.UdpSendCompleted: | |
+#if DEBUG | |
+ if (!ev.HasSenderAddress) | |
+ throw new InvalidOperationException($"{nameof(ev.SenderAddress)} must not be null"); | |
+#endif | |
+ switch (ev.Parameter) { | |
+ case 0: // success | |
+ lastUdpSendResult[ev.SenderAddress!] = true; | |
+ break; | |
+ | |
+ case 1: // failed | |
+ lastUdpSendResult[ev.SenderAddress!] = false; | |
+ break; | |
+ | |
+ case 2: // performed Neighbor Solicitation | |
+ default: | |
+ break; // nothing to do | |
+ } | |
+ | |
+ break; | |
+ | |
+ case SkStackEventNumber.EnergyDetectScanCompleted: | |
+ SkStackUnexpectedResponseException.ThrowIfUnexpectedSubsequentEventCode( | |
+ subsequentEventCode: ev.ExpectedSubsequentEventCode, | |
+ expectedEventCode: SkStackEventCode.EEDSCAN | |
+ ); | |
+ break; | |
+ } | |
+ | |
+ if (eventHandlerStatesCompleted) | |
+ context.Complete(); | |
+ else | |
+ context.Continue(); | |
+ | |
+ return TrueResultValueTask; | |
+ } | |
+ | |
+ var statusERXUDP = SkStackEventParser.TryExpectERXUDP( | |
+ context, | |
+ erxudpDataFormat, | |
+ out var erxudp, | |
+ out var erxudpData, | |
+ out var erxudpDataLength | |
+ ); | |
+ | |
+ if (statusERXUDP == OperationStatus.NeedMoreData) { | |
+ context.SetAsIncomplete(); | |
+ return FalseResultValueTask; | |
+ } | |
+ else if (statusERXUDP == OperationStatus.Done) { | |
+ Logger?.LogInfoIPEventReceived(erxudp, erxudpData); | |
+ | |
+ return ProcessERXUDPAsync(); | |
+ | |
+ async ValueTask<bool> ProcessERXUDPAsync() | |
+ { | |
+ await OnERXUDPAsync( | |
+ localPort: erxudp.LocalEndPoint.Port, | |
+ remoteAddress: erxudp.RemoteEndPoint.Address, | |
+ data: erxudpData, | |
+ dataLength: erxudpDataLength, | |
+ dataFormat: erxudpDataFormat, | |
+ cancellationToken: cancellationToken | |
+ ).ConfigureAwait(false); | |
+ | |
+ context.Continue(); | |
+ return true; | |
+ } | |
+ } | |
+ | |
+ // if (status == OperationStatus.InvalidData) | |
+ context.Ignore(); | |
+ return FalseResultValueTask; | |
+ } | |
+#pragma warning restore CA1502 | |
+ | |
+ /// <summary> | |
+ /// Gets or sets the object used to marshal the event handler calls that are issued when an event received. | |
+ /// </summary> | |
+ public ISynchronizeInvoke? SynchronizingObject { get; set; } | |
+ | |
+ /// <summary> | |
+ /// Occurs when a PANA session is established. | |
+ /// </summary> | |
+ public event EventHandler<SkStackPanaSessionEventArgs>? PanaSessionEstablished; | |
+ | |
+ /// <summary> | |
+ /// Occurs when a PANA session is terminated. | |
+ /// </summary> | |
+ public event EventHandler<SkStackPanaSessionEventArgs>? PanaSessionTerminated; | |
+ | |
+ /// <summary> | |
+ /// Occurs when a PANA session is expired. | |
+ /// </summary> | |
+ public event EventHandler<SkStackPanaSessionEventArgs>? PanaSessionExpired; | |
+ | |
+ internal void RaiseEventPanaSessionEstablished(SkStackEvent baseEvent) => RaiseEventPanaSession(PanaSessionEstablished, baseEvent); | |
+ internal void RaiseEventPanaSessionTerminated(SkStackEvent baseEvent) => RaiseEventPanaSession(PanaSessionTerminated, baseEvent); | |
+ internal void RaiseEventPanaSessionExpired(SkStackEvent baseEvent) => RaiseEventPanaSession(PanaSessionExpired, baseEvent); | |
+ | |
+ private void RaiseEventPanaSession(EventHandler<SkStackPanaSessionEventArgs>? ev, SkStackEvent baseEvent) | |
+ { | |
+ if (ev is null) | |
+ return; // return without creating event args if event hanlder is null | |
+ | |
+ InvokeEvent(SynchronizingObject, ev, this, new SkStackPanaSessionEventArgs(baseEvent)); | |
+ } | |
+ | |
+ /// <summary> | |
+ /// Occurs when a device enters sleep mode. | |
+ /// </summary> | |
+ /// <seealso cref="SendSKDSLEEPAsync"/> | |
+ public event EventHandler<SkStackEventArgs>? Slept; | |
+ | |
+ /// <summary> | |
+ /// Occurs when a device returns from sleep mode. | |
+ /// </summary> | |
+ /// <seealso cref="SendSKDSLEEPAsync"/> | |
+ public event EventHandler<SkStackEventArgs>? WokeUp; | |
+ | |
+ internal void RaiseEventSlept() => RaiseEvent(Slept, default); | |
+ private void RaiseEventWokeUp(SkStackEvent baseEvent) => RaiseEvent(WokeUp, baseEvent); | |
+ | |
+ private void RaiseEvent(EventHandler<SkStackEventArgs>? ev, SkStackEvent baseEvent) | |
+ { | |
+ if (ev is null) | |
+ return; // return without creating event args if event hanlder is null | |
+ | |
+ InvokeEvent(SynchronizingObject, ev, this, new SkStackEventArgs(baseEvent)); | |
+ } | |
+ | |
+ private static void InvokeEvent<TEventArgs>( | |
+ ISynchronizeInvoke? synchronizingObject, | |
+ EventHandler<TEventArgs> ev, | |
+ object sender, | |
+ TEventArgs args | |
+ ) | |
+ where TEventArgs : SkStackEventArgs | |
+ { | |
+ if (synchronizingObject is null || !synchronizingObject.InvokeRequired) { | |
+ try { | |
+ ev(sender, args); | |
+ } | |
+#pragma warning disable CA1031 | |
+ catch { | |
+ // ignore exceptions | |
+ } | |
+#pragma warning restore CA1031 | |
+ } | |
+ else { | |
+ synchronizingObject.BeginInvoke( | |
+ method: ev, | |
+ args: new object[] { sender, args } | |
+ ); | |
+ } | |
+ } | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Functions.EchonetLite.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Functions.EchonetLite.cs | |
new file mode 100644 | |
index 0000000..fa81047 | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Functions.EchonetLite.cs | |
@@ -0,0 +1,100 @@ | |
+// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+ | |
+using System; | |
+using System.Buffers; | |
+using System.Net; | |
+using System.Threading; | |
+using System.Threading.Tasks; | |
+ | |
+using Polly; | |
+ | |
+namespace Smdn.Net.SkStackIP; | |
+ | |
+#pragma warning disable IDE0040 | |
+partial class SkStackClient { | |
+#pragma warning restore IDE0040 | |
+ /// <summary> | |
+ /// An UDP port handle currently assigned to the port for ECHONET Lite. | |
+ /// </summary> | |
+ /// <remarks> | |
+ /// This value will be updated each time an <c>EPORT</c> is received. See implementation of <see cref="SendSKTABLEListeningPortListAsync"/>. | |
+ /// </remarks> | |
+ /// <seealso cref="SkStackKnownPortNumbers.EchonetLite"/> | |
+ /// <seealso cref="SendUdpEchonetLiteAsync"/> | |
+ private SkStackUdpPortHandle udpPortHandleForEchonetLite; | |
+ | |
+ public ValueTask<IPAddress> ReceiveUdpEchonetLiteAsync( | |
+ IBufferWriter<byte> buffer, | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ { | |
+ if (buffer is null) | |
+ throw new ArgumentNullException(nameof(buffer)); | |
+ | |
+ ThrowIfDisposed(); | |
+ | |
+ return ReceiveUdpAsync( | |
+ port: SkStackKnownPortNumbers.EchonetLite, | |
+ buffer: buffer, | |
+ cancellationToken: cancellationToken | |
+ ); | |
+ } | |
+ | |
+ [CLSCompliant(false)] // ResiliencePipeline is not CLS compliant | |
+ public ValueTask SendUdpEchonetLiteAsync( | |
+ ReadOnlyMemory<byte> buffer, | |
+ ResiliencePipeline? resiliencePipeline = null, | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ { | |
+ if (SkStackUdpPort.IsPortHandleIsOutOfRange(udpPortHandleForEchonetLite)) | |
+ throw new InvalidOperationException($"UDP port {SkStackKnownPortNumbers.EchonetLite} is not listening. Call {nameof(PrepareUdpPortAsync)} or {nameof(SendSKUDPPORTAsync)} in advance to listen the port."); | |
+ | |
+ ThrowIfDisposed(); | |
+ ThrowIfPanaSessionIsNotEstablished(); | |
+ | |
+ resiliencePipeline ??= ResiliencePipeline.Empty; | |
+ | |
+ return resiliencePipeline.ExecuteAsync( | |
+ ct => SendUdpEchonetLiteAsyncCore( | |
+ thisClient: this, | |
+ udpPortHandleForEchonetLite: udpPortHandleForEchonetLite, | |
+#if SYSTEM_DIAGNOSTICS_CODEANALYSIS_MEMBERNOTNULLATTRIBUTE | |
+ peerAddress: PanaSessionPeerAddress, | |
+#else | |
+ peerAddress: PanaSessionPeerAddress!, | |
+#endif | |
+ buffer: buffer, | |
+ cancellationToken: ct | |
+ ), | |
+ cancellationToken: cancellationToken | |
+ ); | |
+ | |
+ static async ValueTask SendUdpEchonetLiteAsyncCore( | |
+ SkStackClient thisClient, | |
+ SkStackUdpPortHandle udpPortHandleForEchonetLite, | |
+ IPAddress peerAddress, | |
+ ReadOnlyMemory<byte> buffer, | |
+ CancellationToken cancellationToken | |
+ ) | |
+ { | |
+ var (_, isCompletedSuccessfully) = await thisClient.SendSKSENDTOAsync( | |
+ handle: udpPortHandleForEchonetLite, | |
+ destinationAddress: peerAddress, | |
+ destinationPort: SkStackKnownPortNumbers.EchonetLite, | |
+ data: buffer, | |
+ encryption: SkStackUdpEncryption.ForceEncrypt, | |
+ cancellationToken: cancellationToken | |
+ ).ConfigureAwait(false); | |
+ | |
+ if (!isCompletedSuccessfully) { | |
+ throw new SkStackUdpSendFailedException( | |
+ message: $"Failed to send ECHONET Lite frame. (Handle: {udpPortHandleForEchonetLite}, Peer: {peerAddress})", | |
+ portHandle: udpPortHandleForEchonetLite, | |
+ peerAddress: peerAddress | |
+ ); | |
+ } | |
+ } | |
+ } | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Functions.FlashMemory.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Functions.FlashMemory.cs | |
new file mode 100644 | |
index 0000000..baf9dcf | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Functions.FlashMemory.cs | |
@@ -0,0 +1,60 @@ | |
+// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+using System; | |
+using System.Threading; | |
+using System.Threading.Tasks; | |
+ | |
+namespace Smdn.Net.SkStackIP; | |
+ | |
+#pragma warning disable IDE0040 | |
+partial class SkStackClient { | |
+#pragma warning restore IDE0040 | |
+ public ValueTask SaveFlashMemoryAsync( | |
+ SkStackFlashMemoryWriteRestriction restriction, | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ { | |
+ if (restriction is null) | |
+ throw new ArgumentNullException(nameof(restriction)); | |
+ if (restriction.IsRestricted()) | |
+ throw new InvalidOperationException($"Writing to flash memory is restricted by the {nameof(SkStackFlashMemoryWriteRestriction)}."); | |
+ | |
+ cancellationToken.ThrowIfCancellationRequested(); | |
+ | |
+ return SaveFlashMemoryAsyncCore(); | |
+ | |
+ async ValueTask SaveFlashMemoryAsyncCore() | |
+ => await SendSKSAVEAsync(cancellationToken).ConfigureAwait(false); | |
+ } | |
+ | |
+ public async ValueTask LoadFlashMemoryAsync( | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ => await SendSKLOADAsync(cancellationToken).ConfigureAwait(false); | |
+ | |
+ public ValueTask EnableFlashMemoryAutoLoadAsync( | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ => SetFlashMemoryAutoLoadAsync( | |
+ trueIfEnable: true, | |
+ cancellationToken: cancellationToken | |
+ ); | |
+ | |
+ public ValueTask DisableFlashMemoryAutoLoadAsync( | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ => SetFlashMemoryAutoLoadAsync( | |
+ trueIfEnable: false, | |
+ cancellationToken: cancellationToken | |
+ ); | |
+ | |
+ protected async ValueTask SetFlashMemoryAutoLoadAsync( | |
+ bool trueIfEnable, | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ => await SendSKSREGAsync( | |
+ register: SkStackRegister.EnableAutoLoad, | |
+ value: trueIfEnable, | |
+ cancellationToken: cancellationToken | |
+ ).ConfigureAwait(false); | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Functions.IP.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Functions.IP.cs | |
new file mode 100644 | |
index 0000000..0216a78 | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Functions.IP.cs | |
@@ -0,0 +1,48 @@ | |
+// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+using System.Collections.Generic; | |
+using System.Net; | |
+using System.Net.NetworkInformation; | |
+using System.Threading; | |
+using System.Threading.Tasks; | |
+ | |
+namespace Smdn.Net.SkStackIP; | |
+ | |
+#pragma warning disable IDE0040 | |
+partial class SkStackClient { | |
+#pragma warning restore IDE0040 | |
+ public async ValueTask<IReadOnlyList<IPAddress>> GetAvailableAddressListAsync( | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ { | |
+ var resp = await SendSKTABLEAvailableAddressListAsync( | |
+ cancellationToken: cancellationToken | |
+ ).ConfigureAwait(false); | |
+ | |
+ return resp.Payload!; | |
+ } | |
+ | |
+ public async ValueTask<IPAddress> ConvertToIPv6LinkLocalAddressAsync( | |
+ PhysicalAddress macAddress, | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ { | |
+ var resp = await SendSKLL64Async( | |
+ macAddress: macAddress, | |
+ cancellationToken: cancellationToken | |
+ ).ConfigureAwait(false); | |
+ | |
+ return resp.Payload!; | |
+ } | |
+ | |
+ public async ValueTask<IReadOnlyDictionary<IPAddress, PhysicalAddress>> GetNeighborCacheListAsync( | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ { | |
+ var resp = await SendSKTABLENeighborCacheListAsync( | |
+ cancellationToken: cancellationToken | |
+ ).ConfigureAwait(false); | |
+ | |
+ return resp.Payload!; | |
+ } | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Functions.PANA.ActiveScan.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Functions.PANA.ActiveScan.cs | |
new file mode 100644 | |
index 0000000..9d75be4 | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Functions.PANA.ActiveScan.cs | |
@@ -0,0 +1,87 @@ | |
+// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+using System; | |
+using System.Collections.Generic; | |
+using System.Threading; | |
+using System.Threading.Tasks; | |
+ | |
+namespace Smdn.Net.SkStackIP; | |
+ | |
+#pragma warning disable IDE0040 | |
+partial class SkStackClient { | |
+#pragma warning restore IDE0040 | |
+ public ValueTask<IReadOnlyList<SkStackPanDescription>> ActiveScanAsync( | |
+ ReadOnlyMemory<byte> rbid, | |
+ ReadOnlyMemory<byte> password, | |
+ SkStackActiveScanOptions? scanOptions = null, | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ => ActiveScanAsyncCore( | |
+ rbid: rbid, | |
+ password: password, | |
+ scanDurationFactorGenerator: (scanOptions ?? SkStackActiveScanOptions.Default).YieldScanDurationFactors(), | |
+ cancellationToken: cancellationToken | |
+ ); | |
+ | |
+ private async ValueTask<IReadOnlyList<SkStackPanDescription>> ActiveScanAsyncCore( | |
+ ReadOnlyMemory<byte>? rbid, | |
+ ReadOnlyMemory<byte>? password, | |
+ IEnumerable<int> scanDurationFactorGenerator, | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ { | |
+ if (scanDurationFactorGenerator is null) | |
+ throw new ArgumentNullException(nameof(scanDurationFactorGenerator)); | |
+ | |
+ if (rbid is not null || password is not null) { | |
+ // If RBID or password is supplied, set them before scanning. | |
+ await SetRouteBCredentialAsync( | |
+ rbid: rbid, | |
+ rbidParamName: nameof(rbid), | |
+ password: password, | |
+ passwordParamName: nameof(password), | |
+ cancellationToken: cancellationToken | |
+ ).ConfigureAwait(false); | |
+ } | |
+ | |
+ foreach (var durationFactor in scanDurationFactorGenerator) { | |
+ var (_, result) = await SendSKSCANActiveScanPairAsync( | |
+ durationFactor: durationFactor, | |
+ cancellationToken: cancellationToken | |
+ ).ConfigureAwait(false); | |
+ | |
+ if (0 < result.Count) | |
+ return result; | |
+ | |
+ // no pairs found, try again with next scan duration factor | |
+ } | |
+ | |
+ return Array.Empty<SkStackPanDescription>(); | |
+ } | |
+ | |
+ private async ValueTask<SkStackPanDescription> ActiveScanPanaAuthenticationAgentAsync<TArg>( | |
+ SkStackActiveScanOptions baseScanOptions, | |
+ Func<TArg, SkStackPanDescription, CancellationToken, ValueTask<bool>> selectPanaAuthenticationAgentAsync, | |
+ TArg arg, | |
+ CancellationToken cancellationToken | |
+ ) | |
+ { | |
+ var activeScanResult = await ActiveScanAsyncCore( | |
+ rbid: null, | |
+ password: null, | |
+ scanDurationFactorGenerator: baseScanOptions.YieldScanDurationFactors(), | |
+ cancellationToken: cancellationToken | |
+ ).ConfigureAwait(false); | |
+ | |
+ SkStackPanDescription? ret = null; | |
+ | |
+ foreach (var pan in activeScanResult) { | |
+ if (await selectPanaAuthenticationAgentAsync(arg, pan, cancellationToken).ConfigureAwait(false)) { | |
+ ret = pan; | |
+ break; | |
+ } | |
+ } | |
+ | |
+ return ret ?? throw new InvalidOperationException("No appropriate PAA was found or selected in active scan result."); | |
+ } | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Functions.PANA.AuthenticateAsPaC.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Functions.PANA.AuthenticateAsPaC.cs | |
new file mode 100644 | |
index 0000000..523cc2f | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Functions.PANA.AuthenticateAsPaC.cs | |
@@ -0,0 +1,396 @@ | |
+// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+ | |
+using System; | |
+using System.Buffers; | |
+using System.Net; | |
+using System.Net.NetworkInformation; | |
+using System.Threading; | |
+using System.Threading.Tasks; | |
+ | |
+namespace Smdn.Net.SkStackIP; | |
+ | |
+#pragma warning disable IDE0040 | |
+partial class SkStackClient { | |
+#pragma warning restore IDE0040 | |
+ /// <inheritdoc cref="AuthenticateAsPanaClientAsyncCore"/> | |
+ /// <param name="rbid">A Route-B ID used for PANA authentication.</param> | |
+ /// <param name="password">A password ID used for PANA authentication.</param> | |
+ /// <param name="scanOptions">Options such as scanning behavior when performing active scanning.</param> | |
+ /// <param name="cancellationToken">The <see cref="CancellationToken" /> to monitor for cancellation requests.</param> | |
+ public ValueTask<SkStackPanaSessionInfo> AuthenticateAsPanaClientAsync( | |
+ ReadOnlyMemory<byte> rbid, | |
+ ReadOnlyMemory<byte> password, | |
+ SkStackActiveScanOptions? scanOptions = null, | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ { | |
+ ThrowIfPanaSessionAlreadyEstablished(); | |
+ | |
+ return AuthenticateAsPanaClientAsyncCore( | |
+ rbid: rbid, | |
+ password: password, | |
+ getPaaAddressTask: default, | |
+ channel: null, | |
+ panId: null, | |
+ scanOptions: scanOptions ?? SkStackActiveScanOptions.Default, | |
+ cancellationToken: cancellationToken | |
+ ); | |
+ } | |
+ | |
+ /// <inheritdoc cref="AuthenticateAsPanaClientAsyncCore"/> | |
+ /// <param name="rbid">A Route-B ID used for PANA authentication.</param> | |
+ /// <param name="password">A password ID used for PANA authentication.</param> | |
+ /// <param name="paaAddress">An <see cref="IPAddress"/> representing the IP address of the PANA Authentication Agent (PAA).</param> | |
+ /// <param name="channelNumber">A channel number to be used for PANA session.</param> | |
+ /// <param name="panId">A Personal Area Network (PAN) ID to be used for PANA session.</param> | |
+ /// <param name="cancellationToken">The <see cref="CancellationToken" /> to monitor for cancellation requests.</param> | |
+ public ValueTask<SkStackPanaSessionInfo> AuthenticateAsPanaClientAsync( | |
+ ReadOnlyMemory<byte> rbid, | |
+ ReadOnlyMemory<byte> password, | |
+ IPAddress paaAddress, | |
+ int channelNumber, | |
+ int panId, | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ => AuthenticateAsPanaClientAsync( | |
+ rbid: rbid, | |
+ password: password, | |
+ paaAddress: paaAddress, | |
+ channel: SkStackChannel.FindByChannelNumber(channelNumber, nameof(channelNumber)), | |
+ panId: panId, | |
+ cancellationToken: cancellationToken | |
+ ); | |
+ | |
+ /// <inheritdoc cref="AuthenticateAsPanaClientAsyncCore"/> | |
+ /// <param name="rbid">A Route-B ID used for PANA authentication.</param> | |
+ /// <param name="password">A password ID used for PANA authentication.</param> | |
+ /// <param name="paaAddress">An <see cref="IPAddress"/> representing the IP address of the PANA Authentication Agent (PAA).</param> | |
+ /// <param name="channel">A <see cref="SkStackChannel"/> representing the channel to be used for PANA session.</param> | |
+ /// <param name="panId">A Personal Area Network (PAN) ID to be used for PANA session.</param> | |
+ /// <param name="cancellationToken">The <see cref="CancellationToken" /> to monitor for cancellation requests.</param> | |
+ public ValueTask<SkStackPanaSessionInfo> AuthenticateAsPanaClientAsync( | |
+ ReadOnlyMemory<byte> rbid, | |
+ ReadOnlyMemory<byte> password, | |
+ IPAddress paaAddress, | |
+ SkStackChannel channel, | |
+ int panId, | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ { | |
+ ThrowIfPanaSessionAlreadyEstablished(); | |
+ | |
+ if (channel.IsEmpty) | |
+ throw new ArgumentException(message: "invalid channel (empty channel)", paramName: nameof(channel)); | |
+ | |
+ return AuthenticateAsPanaClientAsyncCore( | |
+ rbid: rbid, | |
+ password: password, | |
+ getPaaAddressTask: new(paaAddress ?? throw new ArgumentNullException(nameof(paaAddress))), | |
+ channel: channel, | |
+ panId: ValidatePanIdAndThrowIfInvalid(panId, nameof(panId)), | |
+ scanOptions: SkStackActiveScanOptions.Null, // scanning will not be performed and therefore this will not be referenced | |
+ cancellationToken: cancellationToken | |
+ ); | |
+ } | |
+ | |
+ /// <inheritdoc cref="AuthenticateAsPanaClientAsyncCore"/> | |
+ /// <param name="rbid">A Route-B ID used for PANA authentication.</param> | |
+ /// <param name="password">A password ID used for PANA authentication.</param> | |
+ /// <param name="pan">A <see cref="SkStackPanDescription"/> representing the address of the PANA Authentication Agent (PAA), PAN ID, and channel used for PANA session.</param> | |
+ /// <param name="cancellationToken">The <see cref="CancellationToken" /> to monitor for cancellation requests.</param> | |
+ public ValueTask<SkStackPanaSessionInfo> AuthenticateAsPanaClientAsync( | |
+ ReadOnlyMemory<byte> rbid, | |
+ ReadOnlyMemory<byte> password, | |
+ SkStackPanDescription pan, | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ => AuthenticateAsPanaClientAsync( | |
+ rbid: rbid, | |
+ password: password, | |
+ paaMacAddress: pan.MacAddress, | |
+ channel: pan.Channel, | |
+ panId: pan.Id, | |
+ cancellationToken: cancellationToken | |
+ ); | |
+ | |
+ /// <inheritdoc cref="AuthenticateAsPanaClientAsyncCore"/> | |
+ /// <param name="rbid">A Route-B ID used for PANA authentication.</param> | |
+ /// <param name="password">A password ID used for PANA authentication.</param> | |
+ /// <param name="paaMacAddress">A <see cref="PhysicalAddress"/> representing the MAC address of the PANA Authentication Agent (PAA).</param> | |
+ /// <param name="channelNumber">A channel number to be used for PANA session.</param> | |
+ /// <param name="panId">A Personal Area Network (PAN) ID to be used for PANA session.</param> | |
+ /// <param name="cancellationToken">The <see cref="CancellationToken" /> to monitor for cancellation requests.</param> | |
+ public ValueTask<SkStackPanaSessionInfo> AuthenticateAsPanaClientAsync( | |
+ ReadOnlyMemory<byte> rbid, | |
+ ReadOnlyMemory<byte> password, | |
+ PhysicalAddress paaMacAddress, | |
+ int channelNumber, | |
+ int panId, | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ => AuthenticateAsPanaClientAsync( | |
+ rbid: rbid, | |
+ password: password, | |
+ paaMacAddress: paaMacAddress, | |
+ channel: SkStackChannel.FindByChannelNumber(channelNumber, nameof(channelNumber)), | |
+ panId: panId, | |
+ cancellationToken: cancellationToken | |
+ ); | |
+ | |
+ /// <inheritdoc cref="AuthenticateAsPanaClientAsyncCore"/> | |
+ /// <param name="rbid">A Route-B ID used for PANA authentication.</param> | |
+ /// <param name="password">A password ID used for PANA authentication.</param> | |
+ /// <param name="paaMacAddress">A <see cref="PhysicalAddress"/> representing the MAC address of the PANA Authentication Agent (PAA).</param> | |
+ /// <param name="channel">A <see cref="SkStackChannel"/> representing the channel to be used for PANA session.</param> | |
+ /// <param name="panId">A Personal Area Network (PAN) ID to be used for PANA session.</param> | |
+ /// <param name="cancellationToken">The <see cref="CancellationToken" /> to monitor for cancellation requests.</param> | |
+ public ValueTask<SkStackPanaSessionInfo> AuthenticateAsPanaClientAsync( | |
+ ReadOnlyMemory<byte> rbid, | |
+ ReadOnlyMemory<byte> password, | |
+ PhysicalAddress paaMacAddress, | |
+ SkStackChannel channel, | |
+ int panId, | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ { | |
+ ThrowIfPanaSessionAlreadyEstablished(); | |
+ | |
+ return AuthenticateAsPanaClientAsyncCore( | |
+ rbid: rbid, | |
+ password: password, | |
+#pragma warning disable CA2012, CS8620 | |
+ getPaaAddressTask: ConvertToIPv6LinkLocalAddressAsync( | |
+ paaMacAddress ?? throw new ArgumentNullException(nameof(paaMacAddress)), | |
+ cancellationToken | |
+ ), | |
+#pragma warning restore CA2012, CS8620 | |
+ channel: channel, | |
+ panId: ValidatePanIdAndThrowIfInvalid(panId, nameof(panId)), | |
+ scanOptions: SkStackActiveScanOptions.Null, // scanning will not be performed and therefore this will not be referenced | |
+ cancellationToken: cancellationToken | |
+ ); | |
+ } | |
+ | |
+ private static int ValidatePanIdAndThrowIfInvalid(int panId, string paramName) | |
+ { | |
+ if (SkStackRegister.PanId.MinValue <= panId && panId <= SkStackRegister.PanId.MaxValue) | |
+ return panId; | |
+ | |
+ throw new ArgumentOutOfRangeException(paramName, panId, $"must be in range of {SkStackRegister.PanId.MinValue}~{SkStackRegister.PanId.MaxValue}"); | |
+ } | |
+ | |
+ /// <summary> | |
+ /// Starts the PANA authentication sequence with the current instance as the PaC. | |
+ /// </summary> | |
+ /// <param name="rbid">A Route-B ID used for PANA authentication.</param> | |
+ /// <param name="password">A password ID used for PANA authentication.</param> | |
+ /// <param name="getPaaAddressTask"> | |
+ /// An <see cref="ValueTask{IPAddress}"/> that returns IP address of the PANA Authentication Agent (PAA). | |
+ /// If returns <see langword="null"/>, an active scan will be performed to discover the PAAs. | |
+ /// </param> | |
+ /// <param name="channel">A <see cref="SkStackChannel"/> representing the channel to be used for PANA session.</param> | |
+ /// <param name="panId">A Personal Area Network (PAN) ID to be used for PANA session.</param> | |
+ /// <param name="scanOptions">Options such as scanning behavior when performing active scanning.</param> | |
+ /// <param name="cancellationToken">The <see cref="CancellationToken" /> to monitor for cancellation requests.</param> | |
+ /// <returns> | |
+ /// A <see cref="ValueTask{SkStackPanaSessionInfo}"/> representing the established PANA session information. | |
+ /// </returns> | |
+ /// <seealso cref="SkStackPanaSessionInfo"/> | |
+ /// <seealso cref="SkStackRegister.Channel"/> | |
+ /// <seealso cref="SkStackRegister.PanId"/> | |
+ /// <seealso cref="PanaSessionPeerAddress"/> | |
+ /// <seealso cref="IsPanaSessionAlive"/> | |
+ /// <seealso cref="SendSKJOINAsync"/> | |
+ /// <seealso cref="SendSKSETRBIDAsync(ReadOnlyMemory{byte}, CancellationToken)"/> | |
+ /// <seealso cref="SendSKSETPWDAsync(ReadOnlyMemory{byte}, CancellationToken)"/> | |
+ private async ValueTask<SkStackPanaSessionInfo> AuthenticateAsPanaClientAsyncCore( | |
+ ReadOnlyMemory<byte> rbid, | |
+ ReadOnlyMemory<byte> password, | |
+ ValueTask<IPAddress?> getPaaAddressTask, | |
+ SkStackChannel? channel, | |
+ int? panId, | |
+ SkStackActiveScanOptions scanOptions, | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ { | |
+ var paaAddress = await getPaaAddressTask.ConfigureAwait(false); | |
+ | |
+ if (paaAddress is not null && !paaAddress.IsIPv6LinkLocal) | |
+ throw new NotSupportedException($"Supplied IP address is not an IPv6 link local address. PAA Address: {paaAddress}"); | |
+ | |
+ await SetRouteBCredentialAsync( | |
+ rbid: rbid, | |
+ rbidParamName: nameof(rbid), | |
+ password: password, | |
+ passwordParamName: nameof(password), | |
+ cancellationToken: cancellationToken | |
+ ).ConfigureAwait(false); | |
+ | |
+ PhysicalAddress? paaMacAddress = null; | |
+ var needToFindPanaAuthenticationAgent = true; | |
+ | |
+ // If PAA address is IPv6 link local, construct MAC address from it and add to the neighbor address table. | |
+ if (paaAddress is not null) { | |
+ byte[]? linkLocalAddressBytes = null; | |
+ | |
+ try { | |
+ linkLocalAddressBytes = ArrayPool<byte>.Shared.Rent(16); | |
+ | |
+ if (paaAddress.TryWriteBytes(linkLocalAddressBytes, out var bytesWritten) && 8 <= bytesWritten) { | |
+ var linkLocalAddressMemory = linkLocalAddressBytes.AsMemory(0, bytesWritten); | |
+ var macAddressMemory = linkLocalAddressMemory.Slice(linkLocalAddressMemory.Length - 8, 8); | |
+ | |
+ macAddressMemory.Span[0] &= 0b_1111_1101; | |
+ | |
+ paaMacAddress = new PhysicalAddress(macAddressMemory.ToArray()); | |
+ | |
+ await SendSKADDNBRAsync( | |
+ ipv6Address: paaAddress, | |
+ macAddress: paaMacAddress, | |
+ cancellationToken: cancellationToken | |
+ ).ConfigureAwait(false); | |
+ | |
+ needToFindPanaAuthenticationAgent = false; | |
+ } | |
+ } | |
+ finally { | |
+ if (linkLocalAddressBytes is not null) | |
+ ArrayPool<byte>.Shared.Return(linkLocalAddressBytes); | |
+ } | |
+ } | |
+ | |
+ // If channel or PAN ID is not supplied, have to scan PAN and retrieve them. | |
+ needToFindPanaAuthenticationAgent |= !channel.HasValue; | |
+ needToFindPanaAuthenticationAgent |= !panId.HasValue; | |
+ | |
+ IPAddress paaAddressNotNull; | |
+ PhysicalAddress paaMacAddressNotNull; | |
+ SkStackChannel channelNotNull; | |
+ int panIdNotNull; | |
+ | |
+ if (needToFindPanaAuthenticationAgent) { | |
+ var peer = await FindPanaAuthenticationAgentAsync( | |
+ paaAddress: paaAddress, | |
+ scanOptions: scanOptions, | |
+ cancellationToken: cancellationToken | |
+ ).ConfigureAwait(false); | |
+ | |
+ channelNotNull = peer.Channel; | |
+ panIdNotNull = peer.Id; | |
+ paaMacAddressNotNull = peer.MacAddress; | |
+ | |
+ if (paaAddress is null) { | |
+ var respSKLL64 = await SendSKLL64Async( | |
+ macAddress: paaMacAddressNotNull, | |
+ cancellationToken: cancellationToken | |
+ ).ConfigureAwait(false); | |
+ | |
+ paaAddressNotNull = respSKLL64.Payload!; | |
+ | |
+ await SendSKADDNBRAsync( | |
+ ipv6Address: paaAddressNotNull, | |
+ macAddress: paaMacAddressNotNull, | |
+ cancellationToken: cancellationToken | |
+ ).ConfigureAwait(false); | |
+ } | |
+ else { | |
+ paaAddressNotNull = paaAddress; | |
+ } | |
+ } | |
+ else { | |
+#if DEBUG | |
+ if (paaAddress is null) | |
+ throw new InvalidOperationException($"{nameof(paaAddress)} is null"); | |
+ if (paaMacAddress is null) | |
+ throw new InvalidOperationException($"{nameof(paaMacAddress)} is null"); | |
+ if (!channel.HasValue) | |
+ throw new InvalidOperationException($"{nameof(channel)} is null"); | |
+ if (!panId.HasValue) | |
+ throw new InvalidOperationException($"{nameof(panId)} is null"); | |
+#endif | |
+ | |
+ paaAddressNotNull = paaAddress!; | |
+ paaMacAddressNotNull = paaMacAddress!; | |
+ | |
+#if !DEBUG | |
+#pragma warning disable CS8629 | |
+#endif | |
+ channelNotNull = channel.Value!; | |
+ panIdNotNull = panId.Value!; | |
+#pragma warning restore CS8629 | |
+ } | |
+ | |
+ // Set channel and PAN ID if needed. | |
+ var resp = await SendSKINFOAsync(cancellationToken: cancellationToken).ConfigureAwait(false); | |
+ var (localAddress, localMacAddress, currentChannel, currentPanId, _) = resp.Payload; | |
+ | |
+ if (!currentChannel.Equals(channelNotNull)) { | |
+ await SendSKSREGAsync( | |
+ SkStackRegister.Channel, | |
+ channelNotNull, | |
+ cancellationToken | |
+ ).ConfigureAwait(false); | |
+ } | |
+ | |
+ if (currentPanId != panIdNotNull) { | |
+ await SendSKSREGAsync( | |
+ SkStackRegister.PanId, | |
+ (ushort)panIdNotNull, | |
+ cancellationToken | |
+ ).ConfigureAwait(false); | |
+ } | |
+ | |
+ // Then attempt to establish the PANA session. | |
+ await SendSKJOINAsync( | |
+ ipv6address: paaAddressNotNull, | |
+ cancellationToken: cancellationToken | |
+ ).ConfigureAwait(false); | |
+ | |
+ return new( | |
+ localAddress: localAddress, | |
+ localMacAddress: localMacAddress, | |
+ peerAddress: paaAddressNotNull, | |
+ peerMacAddress: paaMacAddressNotNull, | |
+ channel: channelNotNull, | |
+ panId: panIdNotNull | |
+ ); | |
+ } | |
+ | |
+ private ValueTask<SkStackPanDescription> FindPanaAuthenticationAgentAsync( | |
+ IPAddress? paaAddress, | |
+ SkStackActiveScanOptions scanOptions, | |
+ CancellationToken cancellationToken | |
+ ) | |
+ { | |
+ if (paaAddress is null) { | |
+#if DEBUG | |
+ if (scanOptions is null) | |
+ throw new ArgumentNullException(nameof(scanOptions)); | |
+#endif | |
+ | |
+ // If PAA address is not supplied, scan and select PAN with the specified selector. | |
+ return ActiveScanPanaAuthenticationAgentAsync( | |
+ baseScanOptions: scanOptions, | |
+ selectPanaAuthenticationAgentAsync: static (options, desc, _) => new(options.SelectPanaAuthenticationAgent(desc)), | |
+ arg: scanOptions, | |
+ cancellationToken: cancellationToken | |
+ ); | |
+ } | |
+ else { | |
+ // If PAA address is supplied, scan and resolve MAC address to collate with the supplied one. | |
+ return ActiveScanPanaAuthenticationAgentAsync( | |
+ baseScanOptions: scanOptions, | |
+ selectPanaAuthenticationAgentAsync: | |
+ async (address, desc, token) => address.Equals( | |
+ await ConvertToIPv6LinkLocalAddressAsync( | |
+ desc.MacAddress, | |
+ token | |
+ ).ConfigureAwait(false) | |
+ ), | |
+ arg: paaAddress, | |
+ cancellationToken: cancellationToken | |
+ ); | |
+ } | |
+ } | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Functions.PANA.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Functions.PANA.cs | |
new file mode 100644 | |
index 0000000..3fedde1 | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Functions.PANA.cs | |
@@ -0,0 +1,94 @@ | |
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+using System; | |
+#if SYSTEM_DIAGNOSTICS_CODEANALYSIS_MEMBERNOTNULLATTRIBUTE || SYSTEM_DIAGNOSTICS_CODEANALYSIS_MEMBERNOTNULLWHENATTRIBUTE | |
+using System.Diagnostics.CodeAnalysis; | |
+#endif | |
+using System.Net; | |
+using System.Threading; | |
+using System.Threading.Tasks; | |
+ | |
+namespace Smdn.Net.SkStackIP; | |
+ | |
+#pragma warning disable IDE0040 | |
+partial class SkStackClient { | |
+#pragma warning restore IDE0040 | |
+ /// <summary>Gets the <see cref="IPAddress"/> of current PANA session peer.</summary> | |
+ /// <value><see langword="null"/> if PANA session has been terminated, expired, or not been established.</value> | |
+ public IPAddress? PanaSessionPeerAddress { get; private set; } | |
+ | |
+ /// <summary>Gets a value indicating whether or not the PANA session is alive.</summary> | |
+ /// <value><see langword="true"/> if PANA session is established and alive, <see langword="false"/> if PANA session has been terminated, expired, or not been established.</value> | |
+#if SYSTEM_DIAGNOSTICS_CODEANALYSIS_MEMBERNOTNULLATTRIBUTE | |
+ [MemberNotNullWhen(true, nameof(PanaSessionPeerAddress))] | |
+#endif | |
+ public bool IsPanaSessionAlive => PanaSessionPeerAddress is not null; | |
+ | |
+#if SYSTEM_DIAGNOSTICS_CODEANALYSIS_MEMBERNOTNULLATTRIBUTE | |
+ [MemberNotNull(nameof(PanaSessionPeerAddress))] | |
+#endif | |
+ protected internal void ThrowIfPanaSessionIsNotEstablished() | |
+ { | |
+ if (PanaSessionPeerAddress is null) | |
+ throw new InvalidOperationException("PANA session has expired or has not been established yet."); | |
+ } | |
+ | |
+ protected internal void ThrowIfPanaSessionAlreadyEstablished() | |
+ { | |
+ if (PanaSessionPeerAddress is not null) | |
+ throw new InvalidOperationException("PANA session has been already established."); | |
+ } | |
+ | |
+ private async ValueTask SetRouteBCredentialAsync( | |
+ ReadOnlyMemory<byte>? rbid, | |
+ string rbidParamName, | |
+ ReadOnlyMemory<byte>? password, | |
+ string passwordParamName, | |
+ CancellationToken cancellationToken | |
+ ) | |
+ { | |
+ if (rbid is not null) { | |
+ if (rbid.Value.IsEmpty) | |
+ throw new ArgumentException("must be non-empty string", rbidParamName ?? nameof(rbid)); | |
+ | |
+ _ = await SendSKSETRBIDAsync( | |
+ id: rbid.Value, | |
+ cancellationToken: cancellationToken | |
+ ).ConfigureAwait(false); | |
+ } | |
+ | |
+ if (password is not null) { | |
+ if (password.Value.IsEmpty) | |
+ throw new ArgumentException("must be non-empty string", passwordParamName ?? nameof(password)); | |
+ | |
+ _ = await SendSKSETPWDAsync( | |
+ password: password.Value, | |
+ cancellationToken: cancellationToken | |
+ ).ConfigureAwait(false); | |
+ } | |
+ } | |
+ | |
+ /// <summary> | |
+ /// Terminates the currently established PANA session by sending <c>SKTERM</c> command. | |
+ /// </summary> | |
+ /// <exception cref="InvalidOperationException">PANA session has already expired or has not been established yet.</exception> | |
+ /// <returns><see langword="true"/> if terminated successfully, otherwise <see langword="false"/> (timed out).</returns> | |
+ public ValueTask<bool> TerminatePanaSessionAsync( | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ { | |
+ ThrowIfDisposed(); | |
+ ThrowIfPanaSessionIsNotEstablished(); | |
+ | |
+ return TerminatePanaSessionAsyncCore(); | |
+ | |
+ async ValueTask<bool> TerminatePanaSessionAsyncCore() | |
+ { | |
+ var (_, isCompletedSuccessfully) = await SendSKTERMAsync( | |
+ cancellationToken: cancellationToken | |
+ ).ConfigureAwait(false); | |
+ | |
+ return isCompletedSuccessfully; | |
+ } | |
+ } | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Functions.UDP.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Functions.UDP.cs | |
new file mode 100644 | |
index 0000000..33c2817 | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Functions.UDP.cs | |
@@ -0,0 +1,358 @@ | |
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+ | |
+using System; | |
+using System.Buffers; | |
+using System.Buffers.Binary; | |
+using System.Collections.Generic; | |
+#if NULL_STATE_STATIC_ANALYSIS_ATTRIBUTES | |
+using System.Diagnostics.CodeAnalysis; | |
+#endif | |
+using System.IO.Pipelines; | |
+using System.Linq; | |
+using System.Net; | |
+using System.Threading; | |
+using System.Threading.Tasks; | |
+ | |
+using Smdn.Net.SkStackIP.Protocol; | |
+ | |
+namespace Smdn.Net.SkStackIP; | |
+ | |
+#pragma warning disable IDE0040 | |
+partial class SkStackClient { | |
+#pragma warning restore IDE0040 | |
+ private static readonly TimeSpan ReceiveUdpPollingIntervalDefault = TimeSpan.FromMilliseconds(10); | |
+ | |
+ private TimeSpan receiveUdpPollingInterval = ReceiveUdpPollingIntervalDefault; | |
+ | |
+ public TimeSpan ReceiveUdpPollingInterval { | |
+ get => receiveUdpPollingInterval; | |
+ set { | |
+ if (value <= TimeSpan.Zero) | |
+ throw new ArgumentOutOfRangeException(message: "must be non-zero positive value", actualValue: value, paramName: nameof(ReceiveUdpPollingInterval)); | |
+ | |
+ receiveUdpPollingInterval = value; | |
+ } | |
+ } | |
+ | |
+ private SkStackERXUDPDataFormat erxudpDataFormat; | |
+ | |
+ /// <summary> | |
+ /// Gets or sets the format of the data part in event <c>ERXUDP</c>. | |
+ /// </summary> | |
+ /// <remarks> | |
+ /// <para>See below for detailed specifications.</para> | |
+ /// <list type="bullet"> | |
+ /// <item><description>'BP35A1コマンドリファレンス 3.30. WOPT (プロダクト設定コマンド)'</description></item> | |
+ /// <item><description>'BP35A1コマンドリファレンス 4.1. ERXUDP'</description></item> | |
+ /// </list> | |
+ /// </remarks> | |
+ /// <seealso cref="SkStackERXUDPDataFormat"/> | |
+ public SkStackERXUDPDataFormat ERXUDPDataFormat { | |
+ get => erxudpDataFormat; | |
+ protected set => erxudpDataFormat = ValidateERXUDPDataFormat(value, nameof(ERXUDPDataFormat)); | |
+ } | |
+ | |
+ private static SkStackERXUDPDataFormat ValidateERXUDPDataFormat( | |
+ SkStackERXUDPDataFormat value, | |
+ string paramNameForValue | |
+ ) | |
+ { | |
+#if SYSTEM_ENUM_ISDEFINED_OF_TENUM | |
+ if (!Enum.IsDefined(value)) | |
+#else | |
+ if (!Enum.IsDefined(typeof(SkStackERXUDPDataFormat), value)) | |
+#endif | |
+ throw new ArgumentException(message: $"undefined value of {nameof(SkStackERXUDPDataFormat)}", paramName: paramNameForValue); | |
+ | |
+ return value; | |
+ } | |
+ | |
+ private readonly Dictionary<int/*port*/, Pipe> udpReceiveEventPipes = new( | |
+ capacity: SkStackUdpPort.NumberOfPorts | |
+ ); | |
+ | |
+ public async ValueTask<IReadOnlyList<SkStackUdpPort>> GetListeningUdpPortListAsync( | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ { | |
+ var resp = await SendSKTABLEListeningPortListAsync( | |
+ cancellationToken: cancellationToken | |
+ ).ConfigureAwait(false); | |
+ | |
+ var portList = resp.Payload!; | |
+ | |
+ return portList.Where(static p => !p.IsUnused).ToArray(); | |
+ } | |
+ | |
+ public async ValueTask<IReadOnlyList<SkStackUdpPortHandle>> GetUnusedUdpPortHandleListAsync( | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ { | |
+ var resp = await SendSKTABLEListeningPortListAsync( | |
+ cancellationToken: cancellationToken | |
+ ).ConfigureAwait(false); | |
+ | |
+ var portList = resp.Payload!; | |
+ | |
+ return portList.Where(static p => p.IsUnused).Select(static p => p.Handle).ToArray(); | |
+ } | |
+ | |
+ public async ValueTask<SkStackUdpPort> PrepareUdpPortAsync( | |
+ int port, | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ { | |
+ static bool TryFindPort( | |
+ IReadOnlyList<SkStackUdpPort> ports, | |
+ Predicate<SkStackUdpPort> predicate, | |
+ out SkStackUdpPort port) | |
+ { | |
+ port = default; | |
+ | |
+ foreach (var p in ports) { | |
+ if (predicate(p)) { | |
+ port = p; | |
+ return true; | |
+ } | |
+ } | |
+ | |
+ return false; | |
+ } | |
+ | |
+ var respSKTABLE = await SendSKTABLEListeningPortListAsync( | |
+ cancellationToken: cancellationToken | |
+ ).ConfigureAwait(false); | |
+ var listeningPortList = respSKTABLE.Payload!; | |
+ | |
+ if (TryFindPort(listeningPortList, p => p.Port == port, out var requestedListeningPort)) | |
+ return requestedListeningPort; | |
+ | |
+ if (TryFindPort(listeningPortList, static p => p.IsUnused, out var unusedPort)) { | |
+ var (resp, newlyListeningPort) = await SendSKUDPPORTAsync( | |
+ handle: unusedPort.Handle, | |
+ port: port, | |
+ cancellationToken: cancellationToken | |
+ ).ConfigureAwait(false); | |
+ | |
+ return newlyListeningPort; | |
+ } | |
+ | |
+ throw new InvalidOperationException("there are no unused port"); | |
+ } | |
+ | |
+ /// <summary> | |
+ /// Starts capturing <c>ERXUDP</c> events for the specified port number and | |
+ /// allocates buffer for reading and writing the received data. | |
+ /// </summary> | |
+ /// <param name="port">The port number to start capturing <c>ERXUDP</c> events.</param> | |
+ /// <seealso cref="ReceiveUdpAsync"/> | |
+ /// <seealso cref="StopCapturingUdpReceiveEvents"/> | |
+ public void StartCapturingUdpReceiveEvents(int port) | |
+ { | |
+ ThrowIfDisposed(); | |
+ | |
+ SkStackUdpPort.ThrowIfPortNumberIsOutOfRangeOrUnused(port, nameof(port)); | |
+ | |
+ lock (udpReceiveEventPipes) { | |
+ udpReceiveEventPipes[port] = new Pipe(new PipeOptions()); // TODO: options | |
+ } | |
+ } | |
+ | |
+ /// <summary> | |
+ /// Stops capturing <c>ERXUDP</c> events for the specified port number. | |
+ /// </summary> | |
+ /// <param name="port">The port number to stop capturing <c>ERXUDP</c> events.</param> | |
+ /// <seealso cref="ReceiveUdpAsync"/> | |
+ public void StopCapturingUdpReceiveEvents(int port) | |
+ { | |
+ ThrowIfDisposed(); | |
+ | |
+ SkStackUdpPort.ThrowIfPortNumberIsOutOfRangeOrUnused(port, nameof(port)); | |
+ | |
+ lock (udpReceiveEventPipes) { | |
+ udpReceiveEventPipes.Remove(port); | |
+ } | |
+ } | |
+ | |
+ private const int UdpReceiveEventLengthOfRemoteAddress = 16; | |
+ private const int UdpReceiveEventLengthOfDataLength = sizeof(ushort); | |
+ | |
+ private ValueTask OnERXUDPAsync( | |
+ int localPort, | |
+ IPAddress remoteAddress, | |
+ ReadOnlySequence<byte> data, | |
+ int dataLength, | |
+ SkStackERXUDPDataFormat dataFormat, | |
+ CancellationToken cancellationToken | |
+ ) | |
+ { | |
+ if (!udpReceiveEventPipes.TryGetValue(localPort, out var pipe)) | |
+ // not capturing | |
+#if SYSTEM_THREADING_TASKS_VALUETASK_COMPLETEDTASK | |
+ return ValueTask.CompletedTask; | |
+#else | |
+ return default; | |
+#endif | |
+ | |
+ return OnERXUDPAsyncCore(pipe.Writer, remoteAddress, data, dataLength, dataFormat, cancellationToken); | |
+ | |
+ static async ValueTask OnERXUDPAsyncCore( | |
+ PipeWriter writer, | |
+ IPAddress remoteAddress, | |
+ ReadOnlySequence<byte> data, | |
+ int dataLength, | |
+ SkStackERXUDPDataFormat dataFormat, | |
+ CancellationToken cancellationToken | |
+ ) | |
+ { | |
+ var packetLength = UdpReceiveEventLengthOfRemoteAddress + UdpReceiveEventLengthOfDataLength + dataLength; | |
+ var memory = writer.GetMemory(dataLength); | |
+ | |
+ // BYTE[16]: remote address | |
+ if (!remoteAddress.TryWriteBytes(memory.Span, out var bytesWritten) && bytesWritten != UdpReceiveEventLengthOfRemoteAddress) | |
+ throw new InvalidOperationException("unexpected format of remote address"); | |
+ | |
+ // UINT16: length of data | |
+ BinaryPrimitives.WriteUInt16LittleEndian(memory.Span.Slice(UdpReceiveEventLengthOfRemoteAddress), (ushort)dataLength); | |
+ | |
+ // BYTE[n]: data | |
+ if (dataFormat == SkStackERXUDPDataFormat.Binary) { | |
+ data.CopyTo(memory.Span.Slice(UdpReceiveEventLengthOfRemoteAddress + UdpReceiveEventLengthOfDataLength)); | |
+ } | |
+ else { | |
+ SkStackTokenParser.ToByteSequence( | |
+ data, | |
+ dataLength, | |
+ memory.Span.Slice(UdpReceiveEventLengthOfRemoteAddress + UdpReceiveEventLengthOfDataLength) | |
+ ); | |
+ } | |
+ | |
+ writer.Advance(packetLength); | |
+ | |
+ var result = await writer.FlushAsync(cancellationToken).ConfigureAwait(false); | |
+ | |
+ if (result.IsCompleted) | |
+ return; | |
+ // throw new InvalidOperationException("writer is completed"); | |
+ } | |
+ } | |
+ | |
+ /// <summary> | |
+ /// Receives UDP data for the port number that has started capturing <c>ERXUDP</c> events. | |
+ /// </summary> | |
+ /// <param name="port">The port number to receive UDP data.</param> | |
+ /// <param name="buffer">The <see cref="IBufferWriter{Byte}"/> that the received UDP data is written to.</param> | |
+ /// <param name="cancellationToken">The <see cref="CancellationToken" /> to monitor for cancellation requests.</param> | |
+ /// <returns> | |
+ /// A <see cref="ValueTask{IPAddress}"/> representing the source address of the received UDP data. | |
+ /// </returns> | |
+ /// <seealso cref="StartCapturingUdpReceiveEvents"/> | |
+ /// <seealso cref="StopCapturingUdpReceiveEvents"/> | |
+ public ValueTask<IPAddress> ReceiveUdpAsync( | |
+ int port, | |
+ IBufferWriter<byte> buffer, | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ { | |
+ if (buffer is null) | |
+ throw new ArgumentNullException(nameof(buffer)); | |
+ | |
+ ThrowIfDisposed(); | |
+ | |
+ SkStackUdpPort.ThrowIfPortNumberIsOutOfRangeOrUnused(port, nameof(port)); | |
+ | |
+ if (!udpReceiveEventPipes.TryGetValue(port, out var pipe)) | |
+ throw new InvalidOperationException($"The port number {port} is not configured to capture receiving events. Call the method `{nameof(StartCapturingUdpReceiveEvents)}` first."); | |
+ | |
+ return ReceiveUdpAsyncCore( | |
+ thisClient: this, | |
+ pipeReader: pipe.Reader, | |
+ bufferWriter: buffer, | |
+ eventPollingInterval: receiveUdpPollingInterval, | |
+ cancellationToken: cancellationToken | |
+ ); | |
+ | |
+ static async ValueTask<IPAddress> ReceiveUdpAsyncCore( | |
+ SkStackClient thisClient, | |
+ PipeReader pipeReader, | |
+ IBufferWriter<byte> bufferWriter, | |
+ TimeSpan eventPollingInterval, | |
+ CancellationToken cancellationToken | |
+ ) | |
+ { | |
+ for (; ; ) { | |
+ if (!pipeReader.TryRead(out var readResult)) { | |
+ var receiveNotificationalEventResult = await thisClient.ReceiveNotificationalEventAsync(cancellationToken).ConfigureAwait(false); | |
+ | |
+ if (!receiveNotificationalEventResult.Received) | |
+ await Task.Delay(eventPollingInterval, cancellationToken).ConfigureAwait(false); | |
+ | |
+ continue; | |
+ } | |
+ | |
+ if (readResult.IsCanceled) | |
+ throw new InvalidOperationException("pending read was cancelled"); | |
+ | |
+ var receivedDataSequence = readResult.Buffer; | |
+ | |
+ if (TryReadReceiveResult(ref receivedDataSequence, bufferWriter, out var remoteAddress)) { | |
+ // advance the buffer to the position where the reading finished | |
+ pipeReader.AdvanceTo(consumed: receivedDataSequence.Start); | |
+ | |
+ return remoteAddress; | |
+ } | |
+ else { | |
+ // mark entire buffer as examined to receive the subsequent data | |
+ pipeReader.AdvanceTo(consumed: readResult.Buffer.Start, examined: readResult.Buffer.End); | |
+ | |
+ // continue; | |
+ } | |
+ } | |
+ } | |
+ | |
+ static bool TryReadReceiveResult( | |
+ ref ReadOnlySequence<byte> receivedDataSequence, | |
+ IBufferWriter<byte> buffer, | |
+#if NULL_STATE_STATIC_ANALYSIS_ATTRIBUTES | |
+ [NotNullWhen(true)] | |
+#endif | |
+ out IPAddress? remoteAddress | |
+ ) | |
+ { | |
+ remoteAddress = default; | |
+ | |
+ var reader = new SequenceReader<byte>(receivedDataSequence); | |
+ | |
+ if (reader.Remaining < UdpReceiveEventLengthOfRemoteAddress + UdpReceiveEventLengthOfDataLength) | |
+ return false; // need more | |
+ | |
+ // BYTE[16]: remote address | |
+ Span<byte> remoteAddressBytes = stackalloc byte[UdpReceiveEventLengthOfRemoteAddress]; | |
+ | |
+ reader.TryCopyTo(remoteAddressBytes); | |
+ reader.Advance(UdpReceiveEventLengthOfRemoteAddress); | |
+ | |
+ remoteAddress = new(remoteAddressBytes); | |
+ | |
+ // UINT16: length of data | |
+ reader.TryReadLittleEndian(out short signedLengthOfData); | |
+ | |
+ var dataLength = unchecked((ushort)signedLengthOfData); | |
+ | |
+ // BYTE[n]: data | |
+ if (reader.Remaining < dataLength) | |
+ return false; // need more | |
+ | |
+ reader.TryCopyTo(buffer.GetSpan(sizeHint: dataLength).Slice(0, dataLength)); | |
+ | |
+ buffer.Advance(dataLength); | |
+ | |
+ reader.Advance(dataLength); | |
+ | |
+ receivedDataSequence = reader.GetUnreadSequence(); | |
+ | |
+ return true; | |
+ } | |
+ } | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Logging.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Logging.cs | |
new file mode 100644 | |
index 0000000..4a4e67d | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Logging.cs | |
@@ -0,0 +1,18 @@ | |
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+ | |
+using Microsoft.Extensions.Logging; | |
+ | |
+namespace Smdn.Net.SkStackIP; | |
+ | |
+#pragma warning disable IDE0040 | |
+partial class SkStackClient { | |
+#pragma warning restore IDE0040 | |
+ internal static readonly EventId EventIdReceivingStatus = new(1, "receiving status"); | |
+ internal static readonly EventId EventIdCommandSequence = new(2, "sent command sequence"); | |
+ internal static readonly EventId EventIdResponseSequence = new(3, "received response sequence"); | |
+ | |
+ internal static readonly EventId EventIdIPEventReceived = new(6, "IP event received"); | |
+ internal static readonly EventId EventIdPanaEventReceived = new(7, "PANA event received"); | |
+ internal static readonly EventId EventIdAribStdT108EventReceived = new(8, "ARIB STD-T108 event received"); | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Receiving.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Receiving.cs | |
new file mode 100644 | |
index 0000000..d409687 | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Receiving.cs | |
@@ -0,0 +1,398 @@ | |
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+ | |
+using System; | |
+using System.Buffers; | |
+using System.Runtime.CompilerServices; | |
+using System.Threading; | |
+using System.Threading.Tasks; | |
+ | |
+using Smdn.Net.SkStackIP.Protocol; | |
+ | |
+namespace Smdn.Net.SkStackIP; | |
+ | |
+#pragma warning disable IDE0040 | |
+partial class SkStackClient { | |
+#pragma warning restore IDE0040 | |
+ private enum ParseSequenceStatus { | |
+ Initial, | |
+ Undetermined, // the parsing finished without declaring its state (invalid state) | |
+ Ignored, // the content of current buffer is a sequence which is not a target for this parser (ex. echoback line) | |
+ Incomplete, // the content of current buffer is a incomplete sequence to complete parsing | |
+ Continueing, // the content of current buffer is a complete sequence but needs more data sequence to return final result | |
+ Completed, // the parsing completed to return final result | |
+ } | |
+ | |
+ private class ParseSequenceContext : ISkStackSequenceParserContext { | |
+ public ReadOnlySequence<byte> UnparsedSequence { get; internal set; } | |
+ public ParseSequenceStatus Status { get; private set; } = ParseSequenceStatus.Initial; | |
+ | |
+ public ParseSequenceContext() | |
+ { | |
+ } | |
+ | |
+ public void Update(ReadOnlySequence<byte> unparsedSequence) | |
+ { | |
+ UnparsedSequence = unparsedSequence; | |
+ Status = ParseSequenceStatus.Undetermined; | |
+ } | |
+ | |
+ public bool IsConsumed(ReadOnlySequence<byte> sequence) | |
+ => !sequence.Start.Equals(UnparsedSequence.Start); | |
+ | |
+ /* | |
+ * ISkStackSequenceParserContext | |
+ */ | |
+ ISkStackSequenceParserContext ISkStackSequenceParserContext.CreateCopy() | |
+ => (ISkStackSequenceParserContext)MemberwiseClone(); | |
+ | |
+ void ISkStackSequenceParserContext.Continue() | |
+ => Status = ParseSequenceStatus.Continueing; | |
+ | |
+ void ISkStackSequenceParserContext.Complete() | |
+ => Status = ParseSequenceStatus.Completed; | |
+ | |
+ void ISkStackSequenceParserContext.Complete(SequenceReader<byte> consumedReader) | |
+ { | |
+ Status = ParseSequenceStatus.Completed; | |
+ UnparsedSequence = consumedReader.GetUnreadSequence(); | |
+ } | |
+ | |
+ void ISkStackSequenceParserContext.Ignore() | |
+ => Status = ParseSequenceStatus.Ignored; | |
+ | |
+ void ISkStackSequenceParserContext.SetAsIncomplete() | |
+ => Status = ParseSequenceStatus.Incomplete; | |
+ | |
+ void ISkStackSequenceParserContext.SetAsIncomplete(SequenceReader<byte> incompleteReader) | |
+ { | |
+ Status = ParseSequenceStatus.Incomplete; | |
+ UnparsedSequence = incompleteReader.GetUnreadSequence(); | |
+ } | |
+ } | |
+ | |
+ private static readonly TimeSpan ReceiveResponseDelayDefault = TimeSpan.FromMilliseconds(10); | |
+ | |
+ private TimeSpan receiveResponseDelay = ReceiveResponseDelayDefault; | |
+ | |
+ /// <summary> | |
+ /// Gets or sets the interval to delay before attempting to receive a subsequent sequence | |
+ /// if the response sequence currently received is incomplete. | |
+ /// </summary> | |
+ public TimeSpan ReceiveResponseDelay { | |
+ get => receiveResponseDelay; | |
+ set { | |
+ if (value <= TimeSpan.Zero) | |
+ throw new ArgumentOutOfRangeException(message: "must be non-zero positive value", actualValue: value, paramName: nameof(ReceiveResponseDelay)); | |
+ | |
+ receiveResponseDelay = value; | |
+ } | |
+ } | |
+ | |
+ private readonly ParseSequenceContext parseSequenceContext; | |
+ private SemaphoreSlim streamReaderSemaphore; | |
+ | |
+#pragma warning disable CA1502 // TODO: refactor | |
+ private async ValueTask<TResult?> ReadAsync<TArg, TResult>( | |
+ Func<ISkStackSequenceParserContext, TArg, TResult> parseSequence, | |
+ TArg arg, | |
+ SkStackEventHandlerBase? eventHandler, | |
+ bool processOnlyERXUDP = false, | |
+ CancellationToken cancellationToken = default, | |
+ [CallerMemberName] string? callerMemberName = default | |
+ ) | |
+ { | |
+#if DEBUG | |
+ if (parseSequence is null) | |
+ throw new ArgumentNullException(nameof(parseSequence)); | |
+#endif | |
+ | |
+ Logger?.LogReceivingStatus($"{callerMemberName} waiting"); | |
+ | |
+ await streamReaderSemaphore.WaitAsync().ConfigureAwait(false); | |
+ | |
+ Logger?.LogReceivingStatus($"{callerMemberName} entered"); | |
+ | |
+ try { | |
+ Logger?.LogReceivingStatus($"{callerMemberName} reading"); | |
+ | |
+ for (; ; ) { | |
+ var reparse = parseSequenceContext.Status switch { | |
+ ParseSequenceStatus.Ignored or ParseSequenceStatus.Continueing => !parseSequenceContext.UnparsedSequence.IsEmpty, | |
+ _ => false, | |
+ }; | |
+ | |
+ ReadOnlySequence<byte> buffer; | |
+ TResult? result = default; | |
+ IDisposable? scopeReadAndParse = null; | |
+ | |
+ try { | |
+ if (reparse) { | |
+ scopeReadAndParse = Logger?.BeginScope($"{callerMemberName} reparse buffered sequence"); | |
+ | |
+ // reparse previous data sequence | |
+ buffer = parseSequenceContext.UnparsedSequence; | |
+ } | |
+ else { | |
+ scopeReadAndParse = Logger?.BeginScope($"{callerMemberName} read sequence from stream"); | |
+ | |
+ Logger?.LogReceivingStatus("buffered: ", parseSequenceContext.UnparsedSequence); | |
+ | |
+ // receive data sequence and parse it | |
+ var readResult = await streamReader.ReadAsync(cancellationToken).ConfigureAwait(false); | |
+ | |
+ if (readResult.IsCanceled) | |
+ throw new OperationCanceledException("canceled"); | |
+ | |
+ buffer = readResult.Buffer; | |
+ } | |
+ | |
+ Logger?.LogReceivingStatus("sequence: ", buffer); | |
+ | |
+ parseSequenceContext.Update(buffer); | |
+ | |
+ try { | |
+ // process events which is received until this point | |
+ var eventProcessed = await ProcessEventsAsync( | |
+ parseSequenceContext, | |
+ eventHandler, | |
+ cancellationToken | |
+ ).ConfigureAwait(false); | |
+ | |
+ Logger?.LogReceivingStatus($"status: {parseSequenceContext.Status}"); | |
+ | |
+ if (eventProcessed) { | |
+ if (processOnlyERXUDP && parseSequenceContext.Status == ParseSequenceStatus.Continueing) | |
+ (parseSequenceContext as ISkStackSequenceParserContext).Complete(); // reset status as Completed to stop reading | |
+ } | |
+ else if (parseSequenceContext.Status != ParseSequenceStatus.Incomplete) { | |
+ // if buffered data sequence does not contain any events, parse it with the specified parser | |
+ Logger?.LogReceivingStatus($"parser: {parseSequence.Method.Name} -- {parseSequence.Method}"); | |
+ | |
+ result = parseSequence(parseSequenceContext, arg); | |
+ | |
+ Logger?.LogReceivingStatus($"parse status: {parseSequenceContext.Status}"); | |
+ } | |
+ } | |
+ catch (SkStackUnexpectedResponseException ex) { | |
+ Logger?.LogReceivingStatus("unexpected response: ", buffer, ex); | |
+ | |
+ throw; | |
+ } | |
+ } | |
+ finally { | |
+ scopeReadAndParse?.Dispose(); | |
+ } | |
+ | |
+ var (markAsExamined, advanceIfConsumed, returnResult, delay) = parseSequenceContext.Status switch { | |
+ ParseSequenceStatus.Completed => (markAsExamined: true, advanceIfConsumed: true, returnResult: true, delay: default), | |
+ ParseSequenceStatus.Ignored => (markAsExamined: false, advanceIfConsumed: false, returnResult: true, delay: default), | |
+ ParseSequenceStatus.Incomplete => (markAsExamined: true, advanceIfConsumed: false, returnResult: false, delay: true), | |
+ ParseSequenceStatus.Continueing => (markAsExamined: true, advanceIfConsumed: true, returnResult: false, delay: false), | |
+ ParseSequenceStatus.Undetermined or _ => throw new InvalidOperationException("final status is invalid or remains undetermined"), | |
+ }; | |
+ | |
+ if (advanceIfConsumed && parseSequenceContext.IsConsumed(buffer)) { | |
+ // advance the buffer to the position where parsing finished | |
+ Logger?.LogDebugResponse(buffer.Slice(0, parseSequenceContext.UnparsedSequence.Start), result); | |
+ streamReader.AdvanceTo(consumed: parseSequenceContext.UnparsedSequence.Start); | |
+ } | |
+ else if (markAsExamined) { | |
+ // mark entire buffer as examined to receive the subsequent data | |
+ streamReader.AdvanceTo(consumed: buffer.Start, examined: buffer.End); | |
+ } | |
+ | |
+ if (returnResult) | |
+ return result; | |
+ | |
+ if (delay) | |
+ await Task.Delay(receiveResponseDelay).ConfigureAwait(false); | |
+ | |
+ Logger?.LogReceivingStatus($"{callerMemberName} continue reading"); | |
+ } // for infinite | |
+ } | |
+ finally { | |
+ Logger?.LogReceivingStatus($"{callerMemberName} exited"); | |
+ streamReaderSemaphore.Release(); | |
+ } | |
+ } | |
+#pragma warning restore CA1502 | |
+ | |
+ private async ValueTask<SkStackResponse<TPayload>> ReceiveResponseAsync<TPayload>( | |
+ ReadOnlyMemory<byte> command, | |
+ SkStackSequenceParser<TPayload?>? parseResponsePayload, | |
+ SkStackEventHandlerBase? commandEventHandler, | |
+ SkStackProtocolSyntax syntax, | |
+ CancellationToken cancellationToken | |
+ ) | |
+ { | |
+ Logger?.LogReceivingStatus($"{nameof(ReceiveResponseAsync)} ", command); | |
+ | |
+ // try read and parse echoback | |
+ await ReadAsync( | |
+ parseSequence: ParseEchobackLine, | |
+ arg: (command, syntax), | |
+ eventHandler: null, | |
+ cancellationToken: cancellationToken | |
+ ).ConfigureAwait(false); | |
+ | |
+ var response = new SkStackResponse<TPayload>(); | |
+ | |
+ // read and parse response payload | |
+ if (parseResponsePayload is not null) { | |
+ response.Payload = await ReadAsync( | |
+ parseSequence: static (context, parser) => parser(context), | |
+ arg: parseResponsePayload, // TODO: syntax | |
+ eventHandler: null, | |
+ cancellationToken: cancellationToken | |
+ ).ConfigureAwait(false); | |
+ } | |
+ | |
+ // read and parse response status line | |
+ if (syntax.ExpectStatusLine) { | |
+ (response.Status, response.StatusText) = await ReadAsync( | |
+ parseSequence: ParseStatusLine, | |
+ arg: (command, syntax), | |
+ eventHandler: null, | |
+ cancellationToken: cancellationToken | |
+ ).ConfigureAwait(false); | |
+ } | |
+ else { | |
+ // (SKLL64) if the command which is not defined its status, always treat it as success | |
+ response.Status = SkStackResponseStatus.Ok; | |
+ } | |
+ | |
+ if (commandEventHandler is not null && commandEventHandler.DoContinueHandlingEvents(response.Status)) { | |
+ const int ParseSequenceEmptyResult = default; | |
+ | |
+ Logger?.LogReceivingStatus($"{nameof(ReceiveResponseAsync)} {commandEventHandler.GetType().Name}"); | |
+ | |
+ await ReadAsync( | |
+ parseSequence: static (context, handler) => { handler.ProcessSubsequentEvent(context); return ParseSequenceEmptyResult; }, | |
+ arg: commandEventHandler, | |
+ eventHandler: commandEventHandler, | |
+ cancellationToken: cancellationToken | |
+ ).ConfigureAwait(false); | |
+ } | |
+ | |
+ return response; | |
+ } | |
+ | |
+ internal readonly struct ReceiveNotificationalEventResult { | |
+ public static readonly ReceiveNotificationalEventResult NotReceivedResult = new(~default(int)); | |
+ public static readonly ReceiveNotificationalEventResult ReceivedResult = default; | |
+ | |
+ public bool Received => value == ReceivedResult.value; | |
+ | |
+ private readonly int value; | |
+ | |
+ private ReceiveNotificationalEventResult(int value) | |
+ { | |
+ this.value = value; | |
+ } | |
+ } | |
+ | |
+ internal ValueTask<ReceiveNotificationalEventResult> ReceiveNotificationalEventAsync( | |
+ CancellationToken cancellationToken | |
+ ) | |
+ => ReadAsync( | |
+ parseSequence: static (context, _) => ReceiveNotificationalEventResult.NotReceivedResult, | |
+ arg: default(int), | |
+ eventHandler: null, | |
+ processOnlyERXUDP: true, | |
+ cancellationToken: cancellationToken | |
+ ); | |
+ | |
+ private static object? ParseEchobackLine( | |
+ ISkStackSequenceParserContext context, | |
+ (ReadOnlyMemory<byte> Command, SkStackProtocolSyntax Syntax) args | |
+ ) | |
+ { | |
+ var (command, syntax) = args; | |
+ | |
+ // SKSENDTO occasionally echoes back the line with only CRLF even if the register SFE is set to 0 (???) | |
+ if (syntax == SkStackProtocolSyntax.SKSENDTO) { | |
+ var sksendtoEchobackLineReader = context.CreateReader(); | |
+ | |
+ if (sksendtoEchobackLineReader.IsNext(syntax.EndOfEchobackLine, advancePast: true)) { | |
+ context.Complete(sksendtoEchobackLineReader); | |
+ return SkStackClientLoggerExtensions.EchobackLineMarker; | |
+ } | |
+ } | |
+ | |
+ var comm = command.Span; | |
+ var reader = context.CreateReader(); | |
+ | |
+ if (comm.Length <= reader.Length && !reader.IsNext(comm, advancePast: false)) { | |
+ context.Ignore(); // echoback line does not start with the command | |
+ return null; | |
+ } | |
+ | |
+ var echobackLineReader = reader; | |
+ | |
+ if (!reader.TryReadTo(out ReadOnlySequence<byte> echobackLine, delimiter: syntax.EndOfEchobackLine)) { | |
+ context.SetAsIncomplete(); // end of echoback line is not found | |
+ return default; | |
+ } | |
+ | |
+ if (echobackLine.Length < comm.Length) { | |
+ context.Ignore(); | |
+ return null; | |
+ } | |
+ | |
+ echobackLineReader.Advance(comm.Length); // advance to position right after the command | |
+ | |
+ if (echobackLineReader.IsNext(SkStack.SP) || echobackLineReader.IsNext(syntax.EndOfEchobackLine)) { | |
+ context.Complete(reader); | |
+ return SkStackClientLoggerExtensions.EchobackLineMarker; | |
+ } | |
+ else { | |
+ context.Ignore(); | |
+ return null; | |
+ } | |
+ } | |
+ | |
+ private static ( | |
+ SkStackResponseStatus Status, | |
+ ReadOnlyMemory<byte> StatusText | |
+ ) | |
+ ParseStatusLine( | |
+ ISkStackSequenceParserContext context, | |
+ (ReadOnlyMemory<byte> Command, SkStackProtocolSyntax Syntax) args | |
+ ) | |
+ { | |
+ SkStackResponseStatus status = default; | |
+ ReadOnlyMemory<byte> statusText = default; | |
+ | |
+ var (_, syntax) = args; | |
+ | |
+ var reader = context.CreateReader(); | |
+ | |
+ if (!reader.TryReadTo(out ReadOnlySequence<byte> statusLine, delimiter: syntax.EndOfStatusLine, advancePastDelimiter: true)) { | |
+ context.SetAsIncomplete(); | |
+ return default; | |
+ } | |
+ | |
+ var statusLineReader = new SequenceReader<byte>(statusLine); | |
+ | |
+ if (statusLineReader.IsNext(SkStackResponseStatusCodes.OK, advancePast: true)) | |
+ status = SkStackResponseStatus.Ok; | |
+ else if (statusLineReader.IsNext(SkStackResponseStatusCodes.FAIL, advancePast: true)) | |
+ status = SkStackResponseStatus.Fail; | |
+ | |
+ if (status == default) { | |
+ // if the line starts with unknown status code, mark entire buffer as consumed to discard buffer | |
+ // context.Ignore(); | |
+ context.Complete(reader); | |
+ return default; | |
+ } | |
+ else { | |
+ if (statusLineReader.IsNext(SkStack.SP, advancePast: true)) | |
+ statusText = statusLineReader.GetUnreadSequence().ToArray(); | |
+ | |
+ context.Complete(reader); | |
+ | |
+ return (status, statusText); | |
+ } | |
+ } | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Sending.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Sending.cs | |
new file mode 100644 | |
index 0000000..9415be3 | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.Sending.cs | |
@@ -0,0 +1,194 @@ | |
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+ | |
+using System; | |
+using System.Threading; | |
+using System.Threading.Tasks; | |
+ | |
+using Smdn.Net.SkStackIP.Protocol; | |
+ | |
+namespace Smdn.Net.SkStackIP; | |
+ | |
+#pragma warning disable IDE0040 | |
+partial class SkStackClient { | |
+#pragma warning restore IDE0040 | |
+ /// <summary>Sends a command.</summary> | |
+ /// <param name="command">The command to be sent.</param> | |
+ /// <param name="writeArguments">The <see cref="Action{ISkStackCommandLineWriter}"/> for write command arguments to the buffer. Can be <see langword="null"/> if command has no arguments.</param> | |
+ /// <param name="syntax">The <see cref="SkStackProtocolSyntax"/> that describes the command syntax.</param> | |
+ /// <param name="throwIfErrorStatus">The <see langword="bool"/> value that specifies whether throw exception if the response status is error.</param> | |
+ /// <param name="cancellationToken">The <see cref="CancellationToken" /> to monitor for cancellation requests.</param> | |
+ protected internal async ValueTask<SkStackResponse> SendCommandAsync( | |
+ ReadOnlyMemory<byte> command, | |
+ Action<ISkStackCommandLineWriter>? writeArguments = null, | |
+ SkStackProtocolSyntax? syntax = null, | |
+ bool throwIfErrorStatus = true, | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ { | |
+ var resp = await SendCommandAsyncCore<SkStackResponse.NullPayload>( | |
+ command: command, | |
+ writeArguments: writeArguments, | |
+ parseResponsePayload: null, | |
+ commandEventHandler: null, | |
+ syntax: syntax ?? SkStackProtocolSyntax.Default, | |
+ throwIfErrorStatus: throwIfErrorStatus, | |
+ cancellationToken: cancellationToken | |
+ ).ConfigureAwait(false); | |
+ | |
+ return resp; | |
+ } | |
+ | |
+ /// <summary>Sends a command.</summary> | |
+ /// <typeparam name="TPayload">The type of response payload. See <paramref name="parseResponsePayload"/>.</typeparam> | |
+ /// <param name="command">The command to be sent.</param> | |
+ /// <param name="writeArguments">The <see cref="Action{ISkStackCommandLineWriter}"/> for write command arguments to the buffer. Can be <see langword="null"/> if command has no arguments.</param> | |
+ /// <param name="parseResponsePayload">The delegate for parsing the response payload. If <see langword="null"/>, parsing response payload will not be attempted.</param> | |
+ /// <param name="syntax">The <see cref="SkStackProtocolSyntax"/> that describes the command syntax.</param> | |
+ /// <param name="throwIfErrorStatus">The <see langword="bool"/> value that specifies whether throw exception if the response status is error.</param> | |
+ /// <param name="cancellationToken">The <see cref="CancellationToken" /> to monitor for cancellation requests.</param> | |
+ protected internal ValueTask<SkStackResponse<TPayload>> SendCommandAsync<TPayload>( | |
+ ReadOnlyMemory<byte> command, | |
+ Action<ISkStackCommandLineWriter>? writeArguments, | |
+ SkStackSequenceParser<TPayload?> parseResponsePayload, | |
+ SkStackProtocolSyntax? syntax = null, | |
+ bool throwIfErrorStatus = true, | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ => SendCommandAsyncCore( | |
+ command: command, | |
+ writeArguments: writeArguments, | |
+ parseResponsePayload: parseResponsePayload ?? throw new ArgumentNullException(nameof(parseResponsePayload)), | |
+ commandEventHandler: null, | |
+ syntax: syntax ?? SkStackProtocolSyntax.Default, | |
+ throwIfErrorStatus: throwIfErrorStatus, | |
+ cancellationToken: cancellationToken | |
+ ); | |
+ | |
+ /// <summary>Sends a command.</summary> | |
+ /// <param name="command">The command to be sent.</param> | |
+ /// <param name="writeArguments">The <see cref="Action{ISkStackCommandLineWriter}"/> for write command arguments to the buffer. Can be <see langword="null"/> if command has no arguments.</param> | |
+ /// <param name="commandEventHandler">The <see cref="SkStackEventHandlerBase" /> that handles the events that will occur until the response is received.</param> | |
+ /// <param name="syntax">The <see cref="SkStackProtocolSyntax"/> that describes the command syntax.</param> | |
+ /// <param name="throwIfErrorStatus">The <see langword="bool"/> value that specifies whether throw exception if the response status is error.</param> | |
+ /// <param name="cancellationToken">The <see cref="CancellationToken" /> to monitor for cancellation requests.</param> | |
+ internal async ValueTask<SkStackResponse> SendCommandAsync( | |
+ ReadOnlyMemory<byte> command, | |
+ Action<ISkStackCommandLineWriter>? writeArguments, | |
+ SkStackEventHandlerBase? commandEventHandler, | |
+ SkStackProtocolSyntax? syntax = null, | |
+ bool throwIfErrorStatus = true, | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ { | |
+ var resp = await SendCommandAsyncCore<SkStackResponse.NullPayload>( | |
+ command: command, | |
+ writeArguments: writeArguments, | |
+ parseResponsePayload: null, | |
+ commandEventHandler: commandEventHandler, | |
+ syntax: syntax ?? SkStackProtocolSyntax.Default, | |
+ throwIfErrorStatus: throwIfErrorStatus, | |
+ cancellationToken: cancellationToken | |
+ ).ConfigureAwait(false); | |
+ | |
+ return resp; | |
+ } | |
+ | |
+ /// <summary>Sends a command.</summary> | |
+ /// <typeparam name="TPayload">The type of response payload. See <paramref name="parseResponsePayload"/>.</typeparam> | |
+ /// <param name="command">The command to be sent.</param> | |
+ /// <param name="writeArguments">The <see cref="Action{ISkStackCommandLineWriter}"/> for write command arguments to the buffer. Can be <see langword="null"/> if command has no arguments.</param> | |
+ /// <param name="parseResponsePayload">The delegate for parsing the response payload. If <see langword="null"/>, parsing response payload will not be attempted.</param> | |
+ /// <param name="commandEventHandler">The <see cref="SkStackEventHandlerBase" /> that handles the events that will occur until the response is received.</param> | |
+ /// <param name="syntax">The <see cref="SkStackProtocolSyntax"/> that describes the command syntax.</param> | |
+ /// <param name="throwIfErrorStatus">The <see langword="bool"/> value that specifies whether throw exception if the response status is error.</param> | |
+ /// <param name="cancellationToken">The <see cref="CancellationToken" /> to monitor for cancellation requests.</param> | |
+ private ValueTask<SkStackResponse<TPayload>> SendCommandAsyncCore<TPayload>( | |
+ ReadOnlyMemory<byte> command, | |
+ Action<ISkStackCommandLineWriter>? writeArguments, | |
+ SkStackSequenceParser<TPayload?>? parseResponsePayload, | |
+ SkStackEventHandlerBase? commandEventHandler, | |
+ SkStackProtocolSyntax syntax, | |
+ bool throwIfErrorStatus, | |
+ CancellationToken cancellationToken | |
+ ) | |
+ { | |
+ ThrowIfDisposed(); | |
+ | |
+ syntax ??= SkStackProtocolSyntax.Default; | |
+ | |
+ // write command line | |
+ WriteCommandLine( | |
+ command, | |
+ writeArguments, | |
+ syntax | |
+ ); | |
+ | |
+ // flush command and receive response | |
+ return FlushAndReceive( | |
+ command, | |
+ parseResponsePayload, | |
+ commandEventHandler, | |
+ syntax, | |
+ throwIfErrorStatus, | |
+ cancellationToken | |
+ ); | |
+ } | |
+ | |
+ private void WriteCommandLine( | |
+ ReadOnlyMemory<byte> command, | |
+ Action<ISkStackCommandLineWriter>? writeArguments, | |
+ SkStackProtocolSyntax syntax | |
+ ) | |
+ { | |
+ if (command.IsEmpty) | |
+ throw new ArgumentException("must be non-empty byte sequence", nameof(command)); | |
+ | |
+ // write command | |
+ commandLineWriter.Write(command.Span); | |
+ | |
+ // write arguments | |
+ if (writeArguments is not null) | |
+ writeArguments(commandLineWriter); | |
+ | |
+ // write end of command line | |
+ if (!syntax.EndOfCommandLine.IsEmpty) | |
+ // must terminate the SKSENDTO command line without CRLF | |
+ // ROHM product setting commands line must be terminated with CR instead of CRLF | |
+ commandLineWriter.Write(syntax.EndOfCommandLine); | |
+ | |
+ // write command to logger | |
+ if (Logger is not null && logWriter is not null) { | |
+ Logger.LogDebugCommand(logWriter.WrittenMemory); | |
+ logWriter.Clear(); | |
+ } | |
+ } | |
+ | |
+ private async ValueTask<SkStackResponse<TPayload>> FlushAndReceive<TPayload>( | |
+ ReadOnlyMemory<byte> command, | |
+ SkStackSequenceParser<TPayload?>? parseResponsePayload, | |
+ SkStackEventHandlerBase? commandEventHandler, | |
+ SkStackProtocolSyntax syntax, | |
+ bool throwIfErrorStatus, | |
+ CancellationToken cancellationToken | |
+ ) | |
+ { | |
+ var writerResult = await streamWriter.FlushAsync(cancellationToken).ConfigureAwait(false); | |
+ | |
+ if (writerResult.IsCompleted) | |
+ throw new InvalidOperationException("writer is completed"); | |
+ | |
+ var response = await ReceiveResponseAsync( | |
+ command, | |
+ parseResponsePayload, | |
+ commandEventHandler, | |
+ syntax, | |
+ cancellationToken | |
+ ).ConfigureAwait(false); | |
+ | |
+ if (throwIfErrorStatus) | |
+ response.ThrowIfErrorStatus(translateException: null); | |
+ | |
+ return response; | |
+ } | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.cs | |
new file mode 100644 | |
index 0000000..ad42e56 | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClient.cs | |
@@ -0,0 +1,137 @@ | |
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+using System; | |
+using System.Buffers; | |
+using System.IO; | |
+using System.IO.Pipelines; | |
+ | |
+using Microsoft.Extensions.Logging; | |
+ | |
+using Smdn.Net.SkStackIP.Protocol; | |
+ | |
+namespace Smdn.Net.SkStackIP; | |
+ | |
+/// <summary> | |
+/// Provides a client implementation that sends SKSTACK-IP commands and receives responses and handles events. | |
+/// </summary> | |
+public partial class SkStackClient : IDisposable { | |
+ private PipeWriter streamWriter; | |
+ private readonly SkStackCommandLineWriter commandLineWriter; | |
+ private PipeReader streamReader; | |
+ | |
+ protected ILogger? Logger { get; } | |
+ | |
+ private readonly ArrayBufferWriter<byte>? logWriter; | |
+ | |
+ /// <summary> | |
+ /// Initializes a new instance of the <see cref="SkStackClient"/> class with specifying the <see cref="Stream"/> for transmitting SKSTACK-IP protocol. | |
+ /// </summary> | |
+ /// <param name="stream"> | |
+ /// The data stream for transmitting SKSTACK-IP protocol. | |
+ /// </param> | |
+ /// <param name="leaveStreamOpen"> | |
+ /// A <see langworkd="bool"/> value specifying whether the <paramref name="stream"/> should be left open or not when disposing instance. | |
+ /// </param> | |
+ /// <param name="erxudpDataFormat"> | |
+ /// A value that specifies the format of the data part received in the event <c>ERXUDP</c>. See <see cref="SkStackERXUDPDataFormat"/>. | |
+ /// </param> | |
+ /// <param name="logger">The <see cref="ILogger"/> to report the situation.</param> | |
+ public SkStackClient( | |
+ Stream stream, | |
+ bool leaveStreamOpen = true, | |
+ SkStackERXUDPDataFormat erxudpDataFormat = default, | |
+ ILogger? logger = null | |
+ ) | |
+ : this( | |
+ sender: PipeWriter.Create( | |
+ ValidateStream(stream, nameof(stream)), | |
+ new(leaveOpen: leaveStreamOpen, minimumBufferSize: 64) | |
+ ), | |
+ receiver: PipeReader.Create( | |
+ stream, | |
+ new(leaveOpen: leaveStreamOpen, bufferSize: 1024, minimumReadSize: 256) | |
+ ), | |
+ erxudpDataFormat: erxudpDataFormat, | |
+ logger: logger | |
+ ) | |
+ { | |
+ } | |
+ | |
+ private static Stream ValidateStream(Stream stream, string paramNameOfStream) | |
+ { | |
+ if (stream is null) | |
+ throw new ArgumentNullException(paramName: paramNameOfStream); | |
+ if (!stream.CanRead) | |
+ throw new ArgumentException(message: $"{nameof(stream)} must be readable stream", paramName: paramNameOfStream); | |
+ if (!stream.CanWrite) | |
+ throw new ArgumentException(message: $"{nameof(stream)} must be writable stream", paramName: paramNameOfStream); | |
+ | |
+ return stream; | |
+ } | |
+ | |
+ /// <summary> | |
+ /// Initializes a new instance of the <see cref="SkStackClient"/> class with specifying the <see cref="PipeWriter"/> and <see cref="PipeReader"/>. | |
+ /// </summary> | |
+ /// <param name="sender"> | |
+ /// A <see cref="PipeWriter"/> for sending SKSTACK-IP protocol commands. | |
+ /// </param> | |
+ /// <param name="receiver"> | |
+ /// A <see cref="PipeReader"/> for receiving SKSTACK-IP protocol responses. | |
+ /// </param> | |
+ /// <param name="erxudpDataFormat"> | |
+ /// A value that specifies the format of the data part received in the event <c>ERXUDP</c>. See <see cref="SkStackERXUDPDataFormat"/>. | |
+ /// </param> | |
+ /// <param name="logger">The <see cref="ILogger"/> to report the situation.</param> | |
+ public SkStackClient( | |
+ PipeWriter sender, | |
+ PipeReader receiver, | |
+ SkStackERXUDPDataFormat erxudpDataFormat = default, | |
+ ILogger? logger = null | |
+ ) | |
+ { | |
+ streamReader = receiver ?? throw new ArgumentNullException(nameof(receiver)); | |
+ streamWriter = sender ?? throw new ArgumentNullException(nameof(sender)); | |
+ this.erxudpDataFormat = ValidateERXUDPDataFormat(erxudpDataFormat, nameof(erxudpDataFormat)); | |
+ Logger = logger; | |
+ | |
+ if (Logger is not null && Logger.IsCommandLoggingEnabled()) { | |
+ logWriter = new ArrayBufferWriter<byte>(initialCapacity: 64); | |
+ | |
+ commandLineWriter = new(streamWriter, logWriter); | |
+ } | |
+ else { | |
+ commandLineWriter = new(streamWriter, null); | |
+ } | |
+ | |
+ parseSequenceContext = new ParseSequenceContext(); | |
+ streamReaderSemaphore = new(initialCount: 1, maxCount: 1); | |
+ | |
+ StartCapturingUdpReceiveEvents(SkStackKnownPortNumbers.EchonetLite); | |
+ } | |
+ | |
+ protected void ThrowIfDisposed() | |
+ { | |
+ if (streamWriter is null) | |
+ throw new ObjectDisposedException(GetType().FullName); | |
+ } | |
+ | |
+ public void Dispose() | |
+ { | |
+ Dispose(true); | |
+ GC.SuppressFinalize(this); | |
+ } | |
+ | |
+ protected virtual void Dispose(bool disposing) | |
+ { | |
+ if (disposing) { | |
+ streamWriter?.Complete(); | |
+ streamWriter = null!; | |
+ | |
+ streamReader?.Complete(); | |
+ streamReader = null!; | |
+ | |
+ streamReaderSemaphore?.Dispose(); | |
+ streamReaderSemaphore = null!; | |
+ } | |
+ } | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClientLoggerExtensions.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClientLoggerExtensions.cs | |
new file mode 100644 | |
index 0000000..33dfb30 | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackClientLoggerExtensions.cs | |
@@ -0,0 +1,200 @@ | |
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+using System; | |
+using System.Buffers; | |
+ | |
+using Microsoft.Extensions.Logging; | |
+ | |
+using Smdn.Net.SkStackIP.Protocol; | |
+using Smdn.Text.Unicode.ControlPictures; | |
+ | |
+namespace Smdn.Net.SkStackIP; | |
+ | |
+internal static class SkStackClientLoggerExtensions { | |
+ private const string PrefixCommand = "↦ "; | |
+ private const string PrefixResponse = "↤ "; | |
+ private const string PrefixEchoback = "↩ "; | |
+ | |
+ private const LogLevel LogLevelReceivingStatusDefault = LogLevel.Trace; | |
+ | |
+ public static void LogReceivingStatus(this ILogger logger, string prefix, ReadOnlyMemory<byte> command, Exception? exception = null) | |
+ { | |
+ var level = exception is null ? LogLevelReceivingStatusDefault : LogLevel.Error; | |
+ | |
+ if (!logger.IsEnabled(level)) | |
+ return; | |
+ | |
+ logger.Log( | |
+ level, | |
+ SkStackClient.EventIdReceivingStatus, | |
+ exception, | |
+ "{Prefix}{Command}", | |
+ prefix, | |
+ command.Span.ToControlCharsPicturizedString() | |
+ ); | |
+ } | |
+ | |
+ public static void LogReceivingStatus(this ILogger logger, string prefix, ReadOnlySequence<byte> sequence, Exception? exception = null) | |
+ { | |
+ var level = exception is null ? LogLevelReceivingStatusDefault : LogLevel.Error; | |
+ | |
+ if (!logger.IsEnabled(level)) | |
+ return; | |
+ | |
+ logger.Log( | |
+ level, | |
+ SkStackClient.EventIdReceivingStatus, | |
+ exception, | |
+ "{Prefix}{Sequence}", | |
+ prefix, | |
+ sequence.ToControlCharsPicturizedString() | |
+ ); | |
+ } | |
+ | |
+ public static void LogReceivingStatus(this ILogger logger, string message, Exception? exception = null) | |
+ { | |
+ var level = exception is null ? LogLevelReceivingStatusDefault : LogLevel.Error; | |
+ | |
+ if (!logger.IsEnabled(level)) | |
+ return; | |
+ | |
+ logger.Log( | |
+ level, | |
+ SkStackClient.EventIdReceivingStatus, | |
+ exception, | |
+ "{Message}", | |
+ message | |
+ ); | |
+ } | |
+ | |
+ private const LogLevel LogLevelCommand = LogLevel.Debug; | |
+ | |
+ public static bool IsCommandLoggingEnabled(this ILogger logger) | |
+ => logger.IsEnabled(LogLevelCommand); | |
+ | |
+ public static void LogDebugCommand(this ILogger logger, ReadOnlyMemory<byte> sequence) | |
+ { | |
+ if (!logger.IsEnabled(LogLevelCommand)) | |
+ return; | |
+ | |
+ logger.Log( | |
+ LogLevelCommand, | |
+ SkStackClient.EventIdCommandSequence, | |
+ "{Prefix}{Sequence}", | |
+ PrefixCommand, | |
+ sequence.Span.ToControlCharsPicturizedString() | |
+ ); | |
+ } | |
+ | |
+ public static readonly object EchobackLineMarker = new(); | |
+ | |
+ private const LogLevel LogLevelResponse = LogLevel.Debug; | |
+ | |
+ public static void LogDebugResponse(this ILogger logger, ReadOnlySequence<byte> sequence, object? marker) | |
+ { | |
+ if (!logger.IsEnabled(LogLevelResponse)) | |
+ return; | |
+ | |
+ logger.Log( | |
+ LogLevelResponse, | |
+ SkStackClient.EventIdResponseSequence, | |
+ "{Prefix}{Sequence}", | |
+ ReferenceEquals(marker, EchobackLineMarker) ? PrefixEchoback : PrefixResponse, | |
+ sequence.ToControlCharsPicturizedString() | |
+ ); | |
+ } | |
+ | |
+ public static void LogInfoIPEventReceived(this ILogger logger, SkStackEvent ev) | |
+ { | |
+ const LogLevel Level = LogLevel.Information; | |
+ | |
+ if (!logger.IsEnabled(Level)) | |
+ return; | |
+ | |
+ if (ev.Number == SkStackEventNumber.UdpSendCompleted) { | |
+ logger.Log( | |
+ Level, | |
+ SkStackClient.EventIdIPEventReceived, | |
+ "IPv6: {Number} - {Parameter} (EVENT {NumberInHex:X2}, PARAM {Parameter}, {SenderAddress})", | |
+ ev.Number, | |
+ ev.Parameter switch { | |
+ 0 => "Successful", | |
+ 1 => "Failed", | |
+ 2 => "Neighbor Solicitation", | |
+ _ => "Unknown", | |
+ }, | |
+ (byte)ev.Number, | |
+ ev.Parameter, | |
+ ev.SenderAddress | |
+ ); | |
+ } | |
+ else { | |
+ logger.Log( | |
+ Level, | |
+ SkStackClient.EventIdIPEventReceived, | |
+ "IPv6: {Number} (EVENT {NumberInHex:X2}, {SenderAddress})", | |
+ ev.Number, | |
+ (byte)ev.Number, | |
+ ev.SenderAddress | |
+ ); | |
+ } | |
+ } | |
+ | |
+ public static void LogInfoIPEventReceived(this ILogger logger, SkStackUdpReceiveEvent erxudp, ReadOnlySequence<byte> erxudpData) | |
+ { | |
+ const LogLevel Level = LogLevel.Information; | |
+ | |
+ if (!logger.IsEnabled(Level)) | |
+ return; | |
+ | |
+ logger.Log( | |
+ Level, | |
+ SkStackClient.EventIdIPEventReceived, | |
+ "{Prefix}: {LocalEndPoint}←{RemoteEndPoint} {RemoteLinkLocalAddress} (secured: {IsSecured}, length: {Length})", | |
+ erxudp.LocalEndPoint.Port switch { | |
+ SkStackKnownPortNumbers.EchonetLite => "ECHONET Lite/IPv6", | |
+ SkStackKnownPortNumbers.Pana => "PANA/IPv6", | |
+ _ => "IPv6", | |
+ }, | |
+ erxudp.LocalEndPoint, | |
+ erxudp.RemoteEndPoint, | |
+ erxudp.RemoteLinkLocalAddress, | |
+ erxudp.IsSecured, | |
+ erxudpData.Length | |
+ ); | |
+ } | |
+ | |
+ public static void LogInfoPanaEventReceived(this ILogger logger, SkStackEvent ev) | |
+ { | |
+ const LogLevel Level = LogLevel.Information; | |
+ | |
+ if (!logger.IsEnabled(Level)) | |
+ return; | |
+ | |
+ logger.Log( | |
+ Level, | |
+ SkStackClient.EventIdPanaEventReceived, | |
+ "PANA: {Number} (EVENT {NumberInHex:X2}, {SenderAddress})", | |
+ ev.Number, | |
+ (byte)ev.Number, | |
+ ev.SenderAddress | |
+ ); | |
+ } | |
+ | |
+ public static void LogInfoAribStdT108EventReceived(this ILogger logger, SkStackEvent ev) | |
+ { | |
+ const LogLevel Level = LogLevel.Information; | |
+ | |
+ if (!logger.IsEnabled(Level)) | |
+ return; | |
+ | |
+ logger.Log( | |
+ Level, | |
+ SkStackClient.EventIdAribStdT108EventReceived, | |
+ "ARIB STD-T108: {Number} (EVENT {NumberInHex:X2}, {SenderAddress})", | |
+ ev.Number, | |
+ (byte)ev.Number, | |
+ ev.SenderAddress | |
+ ); | |
+ } | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackCommandNotSupportedException.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackCommandNotSupportedException.cs | |
new file mode 100644 | |
index 0000000..2d2cc02 | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackCommandNotSupportedException.cs | |
@@ -0,0 +1,28 @@ | |
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+#pragma warning disable CA1032 | |
+ | |
+using System; | |
+ | |
+namespace Smdn.Net.SkStackIP; | |
+ | |
+/// <summary>Describes the error code <c>ER04</c>.</summary> | |
+/// <remarks> | |
+/// <para>See 'BP35A1コマンドリファレンス 7. エラーコード' for detailed specifications.</para> | |
+/// </remarks> | |
+public class SkStackCommandNotSupportedException : SkStackErrorResponseException { | |
+ internal SkStackCommandNotSupportedException( | |
+ SkStackResponse response, | |
+ SkStackErrorCode errorCode, | |
+ ReadOnlySpan<byte> errorText, | |
+ string message | |
+ ) | |
+ : base( | |
+ response: response, | |
+ errorCode: errorCode, | |
+ errorText: errorText, | |
+ message: message | |
+ ) | |
+ { | |
+ } | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackERXUDPDataFormat.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackERXUDPDataFormat.cs | |
new file mode 100644 | |
index 0000000..d2cd140 | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackERXUDPDataFormat.cs | |
@@ -0,0 +1,15 @@ | |
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+ | |
+namespace Smdn.Net.SkStackIP; | |
+ | |
+/// <remarks> | |
+/// <para>See 'BP35A1コマンドリファレンス 3.30. WOPT (プロダクト設定コマンド)' for detailed specifications.</para> | |
+/// </remarks> | |
+public enum SkStackERXUDPDataFormat { | |
+ /// <summary>The data part of <c>ERXUDP</c> is displayed in binary format.</summary> | |
+ Binary = 0, | |
+ | |
+ /// <summary>The data part of <c>ERXUDP</c> is displayed in hex ASCII format.</summary> | |
+ HexAsciiText = 1, | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackErrorCode.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackErrorCode.cs | |
new file mode 100644 | |
index 0000000..7384310 | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackErrorCode.cs | |
@@ -0,0 +1,22 @@ | |
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+ | |
+namespace Smdn.Net.SkStackIP; | |
+ | |
+/// <remarks> | |
+/// <para>See 'BP35A1コマンドリファレンス 7. エラーコード' for detailed specifications.</para> | |
+/// </remarks> | |
+public enum SkStackErrorCode { | |
+ Undefined = 0, | |
+ | |
+ ER01 = 1, | |
+ ER02 = 2, | |
+ ER03 = 3, | |
+ ER04 = 4, | |
+ ER05 = 5, | |
+ ER06 = 6, | |
+ ER07 = 7, | |
+ ER08 = 8, | |
+ ER09 = 9, | |
+ ER10 = 10, | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackErrorResponseException.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackErrorResponseException.cs | |
new file mode 100644 | |
index 0000000..08ebfe6 | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackErrorResponseException.cs | |
@@ -0,0 +1,48 @@ | |
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+#pragma warning disable CA1032 | |
+ | |
+using System; | |
+ | |
+using Smdn.Net.SkStackIP.Protocol; | |
+ | |
+namespace Smdn.Net.SkStackIP; | |
+ | |
+/// <summary> | |
+/// The exception that is thrown when the <see cref="SkStackClient"/> received an error response. | |
+/// </summary> | |
+public class SkStackErrorResponseException : SkStackResponseException { | |
+ /// <summary> | |
+ /// Gets the <see cref="SkStackResponse"/> that caused the exception. | |
+ /// </summary> | |
+ public SkStackResponse Response { get; } | |
+ | |
+ /// <summary> | |
+ /// Gets the <see cref="SkStackErrorCode"/> that caused the exception. | |
+ /// </summary> | |
+ public SkStackErrorCode ErrorCode { get; } | |
+ | |
+ /// <summary> | |
+ /// Gets the <see langword="string"/> that describes the reason of the error. | |
+ /// </summary> | |
+ public string ErrorText { get; } | |
+ | |
+ internal SkStackErrorResponseException( | |
+ SkStackResponse response, | |
+ SkStackErrorCode errorCode, | |
+ ReadOnlySpan<byte> errorText, | |
+ string message, | |
+ Exception? innerException = null | |
+ ) | |
+ : base( | |
+ message: errorText.IsEmpty | |
+ ? $"{message} [{errorCode}]" | |
+ : $"{message} [{errorCode}] \"{SkStack.GetString(errorText)}\"", | |
+ innerException: innerException | |
+ ) | |
+ { | |
+ Response = response; | |
+ ErrorCode = errorCode; | |
+ ErrorText = SkStack.GetString(errorText); | |
+ } | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackEventArgs.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackEventArgs.cs | |
new file mode 100644 | |
index 0000000..78686fe | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackEventArgs.cs | |
@@ -0,0 +1,31 @@ | |
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+ | |
+using System; | |
+using System.Net; | |
+ | |
+using Smdn.Net.SkStackIP.Protocol; | |
+ | |
+namespace Smdn.Net.SkStackIP; | |
+ | |
+/// <summary> | |
+/// Provides data for the events <see cref="SkStackClient.WokeUp"/> and <see cref="SkStackClient.Slept"/>. | |
+/// </summary> | |
+public class SkStackEventArgs : EventArgs { | |
+ private protected IPAddress? SenderAddress { get; } | |
+ | |
+ /// <summary> | |
+ /// Gets the <see cref="SkStackEventNumber"/> that represents the event that occurred. | |
+ /// </summary> | |
+ public SkStackEventNumber EventNumber { get; } | |
+ | |
+ internal SkStackEventArgs(SkStackEvent baseEvent) | |
+ { | |
+ EventNumber = baseEvent.Number; | |
+ SenderAddress = baseEvent.Number switch { | |
+ SkStackEventNumber.Undefined => null, | |
+ SkStackEventNumber.WakeupSignalReceived => null, | |
+ _ => baseEvent.SenderAddress ?? throw new InvalidOperationException($"{nameof(baseEvent.SenderAddress)} must not be null"), | |
+ }; | |
+ } | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackEventNumber.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackEventNumber.cs | |
new file mode 100644 | |
index 0000000..6a90170 | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackEventNumber.cs | |
@@ -0,0 +1,37 @@ | |
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+namespace Smdn.Net.SkStackIP; | |
+ | |
+/// <remarks> | |
+/// <para>See 'BP35A1コマンドリファレンス 4.8. EVENT' for detailed specifications.</para> | |
+/// </remarks> | |
+public enum SkStackEventNumber : byte { | |
+ Undefined = 0x00, | |
+ | |
+ NeighborSolicitationReceived = 0x01, | |
+ NeighborAdvertisementReceived = 0x02, | |
+ EchoRequestReceived = 0x05, | |
+ EnergyDetectScanCompleted = 0x1F, | |
+ BeaconReceived = 0x20, | |
+ UdpSendCompleted = 0x21, | |
+ ActiveScanCompleted = 0x22, | |
+ | |
+ PanaSessionEstablishmentError = 0x24, | |
+ PanaSessionEstablishmentCompleted = 0x25, | |
+ PanaSessionTerminationRequestReceived = 0x26, | |
+ PanaSessionTerminationCompleted = 0x27, | |
+ PanaSessionTerminationTimedOut = 0x28, | |
+ PanaSessionExpired = 0x29, | |
+ | |
+ /// <seealso href="https://www.arib.or.jp/kikaku/kikaku_tushin/desc/std-t108.html">[ARIB STD-T108] 920MHz帯テレメータ用、テレコントロール用及びデータ伝送用無線設備</seealso> | |
+ /// <seealso href="http://www.arib.or.jp/english/html/overview/doc/5-STD-T108v1_3-E1.pdf">[ARIB STD-T108] 920MHz帯テレメータ用、テレコントロール用及びデータ伝送用無線設備 (PDF)</seealso> | |
+ TransmissionTimeControlLimitationActivated = 0x32, | |
+ | |
+ /// <seealso href="https://www.arib.or.jp/kikaku/kikaku_tushin/desc/std-t108.html">[ARIB STD-T108] 920MHz帯テレメータ用、テレコントロール用及びデータ伝送用無線設備</seealso> | |
+ /// <seealso href="http://www.arib.or.jp/english/html/overview/doc/5-STD-T108v1_3-E1.pdf">[ARIB STD-T108] 920MHz帯テレメータ用、テレコントロール用及びデータ伝送用無線設備 (PDF)</seealso> | |
+ TransmissionTimeControlLimitationDeactivated = 0x33, | |
+ | |
+ /// <summary><c>SKDSLEEP</c>; Wake-up signal received.</summary> | |
+ /// <remarks>This event is not clearly documented.</remarks> | |
+ WakeupSignalReceived = 0xC0, | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackFlashMemoryIOException.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackFlashMemoryIOException.cs | |
new file mode 100644 | |
index 0000000..46ea975 | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackFlashMemoryIOException.cs | |
@@ -0,0 +1,35 @@ | |
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+#pragma warning disable CA1032 | |
+ | |
+using System; | |
+ | |
+namespace Smdn.Net.SkStackIP; | |
+ | |
+/// <summary>Describes the error code <c>ER10</c> of <c>SKSAVE</c> or <c>SKLOAD</c> response.</summary> | |
+/// <remarks> | |
+/// <para>See below for detailed specifications.</para> | |
+/// <list type="bullet"> | |
+/// <item><description>'BP35A1コマンドリファレンス 3.20. SKSAVE'</description></item> | |
+/// <item><description>'BP35A1コマンドリファレンス 3.21. SKLOAD'</description></item> | |
+/// <item><description>'BP35A1コマンドリファレンス 7. エラーコード'</description></item> | |
+/// </list> | |
+/// </remarks> | |
+/// <seealso cref="SkStackClient.SendSKSAVEAsync"/> | |
+/// <seealso cref="SkStackClient.SendSKLOADAsync"/> | |
+public class SkStackFlashMemoryIOException : SkStackErrorResponseException { | |
+ internal SkStackFlashMemoryIOException( | |
+ SkStackResponse response, | |
+ SkStackErrorCode errorCode, | |
+ ReadOnlySpan<byte> errorText, | |
+ string message | |
+ ) | |
+ : base( | |
+ response: response, | |
+ errorCode: errorCode, | |
+ errorText: errorText, | |
+ message: message | |
+ ) | |
+ { | |
+ } | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackFlashMemoryWriteRestriction.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackFlashMemoryWriteRestriction.cs | |
new file mode 100644 | |
index 0000000..7ba3ec6 | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackFlashMemoryWriteRestriction.cs | |
@@ -0,0 +1,75 @@ | |
+// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+using System; | |
+using System.Diagnostics; | |
+ | |
+namespace Smdn.Net.SkStackIP; | |
+ | |
+/// <summary> | |
+/// Provides a restriction to write to device's flash memory. | |
+/// </summary> | |
+/// <remarks> | |
+/// For devices such as the <see href="https://www.rohm.co.jp/products/wireless-communication/specified-low-power-radio-modules/bp35a1-product">ROHM BP35A1</see>, | |
+/// the number of writes to flash memory is limited up to approximately 10,000. | |
+/// Be careful not to write unnecessarily to prevent damage to the flash memory. | |
+/// </remarks> | |
+/// <see cref="SkStackClient.SaveFlashMemoryAsync"/> | |
+public abstract class SkStackFlashMemoryWriteRestriction { | |
+ /// <summary> | |
+ /// Create an <see cref="SkStackFlashMemoryWriteRestriction"/> instance that always grants write permission. | |
+ /// </summary> | |
+ /// <remarks> | |
+ /// Be careful not to exceed the write limit, as the instance returned by this method will grant all write requests. | |
+ /// </remarks> | |
+ public static SkStackFlashMemoryWriteRestriction DangerousCreateAlwaysGrant() | |
+ => new AlwaysGrantSkStackFlashMemoryWriteRestriction(); | |
+ | |
+ private sealed class AlwaysGrantSkStackFlashMemoryWriteRestriction : SkStackFlashMemoryWriteRestriction { | |
+ protected internal override bool IsRestricted() => false; | |
+ } | |
+ | |
+ /// <summary> | |
+ /// Create an <see cref="SkStackFlashMemoryWriteRestriction"/> instance that grants write permission only if a certain amount of time has elapsed. | |
+ /// </summary> | |
+ public static SkStackFlashMemoryWriteRestriction CreateGrantIfElapsed(TimeSpan interval) | |
+ { | |
+ if (interval <= TimeSpan.Zero) | |
+ throw new ArgumentOutOfRangeException(message: "must be non zero positive value", paramName: nameof(interval), actualValue: interval); | |
+ | |
+ return new GrantIfElapsedSkStackFlashMemoryWriteRestriction(interval); | |
+ } | |
+ | |
+ private sealed class GrantIfElapsedSkStackFlashMemoryWriteRestriction : SkStackFlashMemoryWriteRestriction { | |
+ private readonly TimeSpan interval; | |
+ private Stopwatch? stopwatch; | |
+ | |
+ public GrantIfElapsedSkStackFlashMemoryWriteRestriction(TimeSpan interval) | |
+ { | |
+ this.interval = interval; | |
+ } | |
+ | |
+ protected internal override bool IsRestricted() | |
+ { | |
+ const bool Permit = false; | |
+ | |
+ if (stopwatch is null) { | |
+ stopwatch = Stopwatch.StartNew(); | |
+ | |
+ return Permit; // permit the initial write | |
+ } | |
+ | |
+ if (interval <= stopwatch.Elapsed) { | |
+ stopwatch.Restart(); | |
+ | |
+ return Permit; // permit if specific interval has elapsed | |
+ } | |
+ | |
+ return !Permit; | |
+ } | |
+ } | |
+ | |
+ /* | |
+ * instance members | |
+ */ | |
+ protected internal abstract bool IsRestricted(); | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackKnownPortNumbers.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackKnownPortNumbers.cs | |
new file mode 100644 | |
index 0000000..9adf0b7 | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackKnownPortNumbers.cs | |
@@ -0,0 +1,28 @@ | |
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+ | |
+namespace Smdn.Net.SkStackIP; | |
+ | |
+public static class SkStackKnownPortNumbers { | |
+ /// <summary>Represents the port number <c>3610</c>, assigned to ECHONET Lite.</summary> | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 5.1. UDP ポート' for detailed specifications.</para> | |
+ /// </remarks> | |
+ public const int EchonetLite = 3610; | |
+ | |
+ /// <summary>Represents the port number <c>716</c>, assigned to PANA.</summary> | |
+ /// <remarks> | |
+ /// <para>See below for detailed specifications.</para> | |
+ /// <list type="bullet"> | |
+ /// <item><description>'BP35A1コマンドリファレンス 5.1. UDP ポート'</description></item> | |
+ /// <item><description><see href="https://datatracker.ietf.org/doc/html/rfc5191">[RFC5191] Protocol for Carrying Authentication for Network Access (PANA) 6.1. IP and UDP Headers</see></description></item> | |
+ /// </list> | |
+ /// </remarks> | |
+ public const int Pana = 716; | |
+ | |
+ /// <summary>Represents the port number <c>0</c>, to set to be unused port.</summary> | |
+ /// <remarks> | |
+ /// <para>See 'BP35A1コマンドリファレンス 3.19. SKUDPPORT' for detailed specifications.</para> | |
+ /// </remarks> | |
+ internal const int SetUnused = 0; | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackPanDescription.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackPanDescription.cs | |
new file mode 100644 | |
index 0000000..3e6b3d5 | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackPanDescription.cs | |
@@ -0,0 +1,36 @@ | |
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+ | |
+using System; | |
+using System.Net.NetworkInformation; | |
+ | |
+namespace Smdn.Net.SkStackIP; | |
+ | |
+public readonly struct SkStackPanDescription { | |
+ public SkStackChannel Channel { get; } | |
+ public int ChannelPage { get; } | |
+ public int Id { get; } | |
+ public PhysicalAddress MacAddress { get; } | |
+ public decimal Rssi { get; } | |
+ [CLSCompliant(false)] public uint PairingId { get; } | |
+ | |
+ internal SkStackPanDescription( | |
+ SkStackChannel channel, | |
+ int channelPage, | |
+ int id, | |
+ PhysicalAddress macAddress, | |
+ decimal rssi, | |
+ uint pairingId | |
+ ) | |
+ { | |
+ Channel = channel; | |
+ ChannelPage = channelPage; | |
+ Id = id; | |
+ MacAddress = macAddress; | |
+ Rssi = rssi; | |
+ PairingId = pairingId; | |
+ } | |
+ | |
+ public override string ToString() | |
+ => $"{Channel}, Channel page: {ChannelPage}, PAN ID: 0x{Id:X4}, MAC address: {MacAddress}, Pairing ID: 0x{PairingId:X8}, RSSI: {Rssi:N2} dB"; | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackPanaSessionEstablishmentException.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackPanaSessionEstablishmentException.cs | |
new file mode 100644 | |
index 0000000..30a95f0 | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackPanaSessionEstablishmentException.cs | |
@@ -0,0 +1,29 @@ | |
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+#pragma warning disable CA1032 | |
+ | |
+using System; | |
+using System.Net; | |
+ | |
+namespace Smdn.Net.SkStackIP; | |
+ | |
+/// <summary> | |
+/// The exception that represents an error on the establishment of a PANA session. | |
+/// </summary> | |
+/// <seealso cref="SkStackClient.SendSKJOINAsync"/> | |
+public class SkStackPanaSessionEstablishmentException : SkStackPanaSessionException { | |
+ internal SkStackPanaSessionEstablishmentException( | |
+ string message, | |
+ IPAddress address, | |
+ SkStackEventNumber eventNumber, | |
+ Exception? innerException = null | |
+ ) | |
+ : base( | |
+ message: message, | |
+ address: address, | |
+ eventNumber: eventNumber, | |
+ innerException: innerException | |
+ ) | |
+ { | |
+ } | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackPanaSessionEventArgs.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackPanaSessionEventArgs.cs | |
new file mode 100644 | |
index 0000000..b8792b8 | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackPanaSessionEventArgs.cs | |
@@ -0,0 +1,28 @@ | |
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+ | |
+using System.Net; | |
+ | |
+using Smdn.Net.SkStackIP.Protocol; | |
+ | |
+namespace Smdn.Net.SkStackIP; | |
+ | |
+/// <summary> | |
+/// <para>Provides data for the following events.</para> | |
+/// <list type="bullet"> | |
+/// <item><description><see cref="SkStackClient.PanaSessionEstablished"/></description></item> | |
+/// <item><description><see cref="SkStackClient.PanaSessionTerminated"/></description></item> | |
+/// <item><description><see cref="SkStackClient.PanaSessionExpired"/></description></item> | |
+/// </list> | |
+/// </summary> | |
+public sealed class SkStackPanaSessionEventArgs : SkStackEventArgs { | |
+ /// <summary> | |
+ /// Gets the peer address of the PANA session to which the event occurred. | |
+ /// </summary> | |
+ public IPAddress PanaSessionPeerAddress => SenderAddress!; | |
+ | |
+ internal SkStackPanaSessionEventArgs(SkStackEvent baseEvent) | |
+ : base(baseEvent) | |
+ { | |
+ } | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackPanaSessionException.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackPanaSessionException.cs | |
new file mode 100644 | |
index 0000000..1c92420 | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackPanaSessionException.cs | |
@@ -0,0 +1,31 @@ | |
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+#pragma warning disable CA1032 | |
+ | |
+using System; | |
+using System.Net; | |
+ | |
+namespace Smdn.Net.SkStackIP; | |
+ | |
+/// <summary> | |
+/// The exception that represents an error whitin a PANA session. | |
+/// </summary> | |
+public abstract class SkStackPanaSessionException : InvalidOperationException { | |
+ public IPAddress Address { get; } | |
+ public SkStackEventNumber EventNumber { get; } | |
+ | |
+ private protected SkStackPanaSessionException( | |
+ string message, | |
+ IPAddress address, | |
+ SkStackEventNumber eventNumber, | |
+ Exception? innerException = null | |
+ ) | |
+ : base( | |
+ message: message, | |
+ innerException: innerException | |
+ ) | |
+ { | |
+ Address = address; | |
+ EventNumber = eventNumber; | |
+ } | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackPanaSessionInfo.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackPanaSessionInfo.cs | |
new file mode 100644 | |
index 0000000..fbfec0a | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackPanaSessionInfo.cs | |
@@ -0,0 +1,60 @@ | |
+// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+ | |
+using System.Net; | |
+using System.Net.NetworkInformation; | |
+ | |
+namespace Smdn.Net.SkStackIP; | |
+ | |
+/// <summary> | |
+/// A class representing information about an established PANA session. | |
+/// </summary> | |
+public sealed class SkStackPanaSessionInfo { | |
+ /// <summary> | |
+ /// Gets the <see cref="IPAddress"/> representing the local IP address of the PANA session. | |
+ /// </summary> | |
+ public IPAddress LocalAddress { get; } | |
+ | |
+ /// <summary> | |
+ /// Gets the <see cref="PhysicalAddress"/> representing the local MAC address of the PANA session. | |
+ /// </summary> | |
+ public PhysicalAddress LocalMacAddress { get; } | |
+ | |
+ /// <summary> | |
+ /// Gets the <see cref="IPAddress"/> representing the peer IP address of the PANA session. | |
+ /// </summary> | |
+ /// <seealso cref="SkStackClient.PanaSessionPeerAddress"/> | |
+ public IPAddress PeerAddress { get; } | |
+ | |
+ /// <summary> | |
+ /// Gets the <see cref="PhysicalAddress"/> representing the peer MAC address of the PANA session. | |
+ /// </summary> | |
+ public PhysicalAddress PeerMacAddress { get; } | |
+ | |
+ /// <summary> | |
+ /// Gets the <see cref="SkStackChannel"/> representing the logical channel number used in the PANA session. | |
+ /// </summary> | |
+ public SkStackChannel Channel { get; } | |
+ | |
+ /// <summary> | |
+ /// Gets the value representing the ID for the Personal Area Network (PAN) used in the PANA session. | |
+ /// </summary> | |
+ public int PanId { get; } | |
+ | |
+ internal SkStackPanaSessionInfo( | |
+ IPAddress localAddress, | |
+ PhysicalAddress localMacAddress, | |
+ IPAddress peerAddress, | |
+ PhysicalAddress peerMacAddress, | |
+ SkStackChannel channel, | |
+ int panId | |
+ ) | |
+ { | |
+ LocalAddress = localAddress; | |
+ LocalMacAddress = localMacAddress; | |
+ PeerAddress = peerAddress; | |
+ PeerMacAddress = peerMacAddress; | |
+ Channel = channel; | |
+ PanId = panId; | |
+ } | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackRegister.RegisterEntry.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackRegister.RegisterEntry.cs | |
new file mode 100644 | |
index 0000000..29c1128 | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackRegister.RegisterEntry.cs | |
@@ -0,0 +1,279 @@ | |
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+#pragma warning disable SA1316 | |
+ | |
+using System; | |
+using System.Buffers; | |
+ | |
+using Smdn.Net.SkStackIP.Protocol; | |
+ | |
+namespace Smdn.Net.SkStackIP; | |
+ | |
+#pragma warning disable IDE0040 | |
+partial class SkStackRegister { | |
+#pragma warning restore IDE0040 | |
+ public abstract class RegisterEntry<TValue> { | |
+ public string Name { get; } | |
+ internal ReadOnlyMemory<byte> SREG { get; } | |
+ public bool IsReadable { get; } | |
+ public bool IsWritable { get; } | |
+ public TValue MinValue { get; } | |
+ public TValue MaxValue { get; } | |
+ | |
+ internal delegate void WriteSKSREGArgumentFunc(ISkStackCommandLineWriter writer, TValue value); | |
+ private WriteSKSREGArgumentFunc WriteSKSREGArgument { get; } | |
+ | |
+ private protected delegate bool ExpectValueFunc(ref SequenceReader<byte> reader, out TValue value); | |
+ private ExpectValueFunc ExpectValue { get; } | |
+ | |
+ private protected RegisterEntry( | |
+ string name, | |
+ (bool isReadable, bool isWritable) readWrite, | |
+ (TValue minValue, TValue maxValue) valueRange, | |
+ WriteSKSREGArgumentFunc writeSKSREGArgument, | |
+ ExpectValueFunc expectValue | |
+ ) | |
+ { | |
+ if (readWrite.isWritable && writeSKSREGArgument is null) | |
+ throw new ArgumentNullException(nameof(writeSKSREGArgument)); | |
+ if (readWrite.isReadable && expectValue is null) | |
+ throw new ArgumentNullException(nameof(expectValue)); | |
+ | |
+ Name = name; | |
+ SREG = SkStack.ToByteSequence(name); | |
+ IsReadable = readWrite.isReadable; | |
+ IsWritable = readWrite.isWritable; | |
+ MinValue = valueRange.minValue; | |
+ MaxValue = valueRange.maxValue; | |
+ WriteSKSREGArgument = writeSKSREGArgument; | |
+ ExpectValue = expectValue; | |
+ } | |
+ | |
+ internal virtual void ThrowIfValueIsNotInRange(TValue value, string paramName) | |
+ { | |
+ if (!IsInRange(value)) | |
+ throw new ArgumentOutOfRangeException(paramName, value, $"must be in range of {MinValue}~{MaxValue}"); | |
+ } | |
+ | |
+ private protected abstract bool IsInRange(TValue value); | |
+ | |
+ internal TValue? ParseESREG( | |
+ ISkStackSequenceParserContext context | |
+ ) | |
+ { | |
+ var reader = context.CreateReader(); | |
+ | |
+ if ( | |
+ SkStackTokenParser.ExpectToken(ref reader, "ESREG"u8) && | |
+ ExpectValue(ref reader, out var result) && | |
+ SkStackTokenParser.ExpectEndOfLine(ref reader) | |
+ ) { | |
+ context.Complete(reader); | |
+ return result; | |
+ } | |
+ | |
+ context.SetAsIncomplete(); | |
+ return default; | |
+ } | |
+ | |
+ internal void WriteValueTo(ISkStackCommandLineWriter writer, TValue value) | |
+ => WriteSKSREGArgument(writer, value); | |
+ } | |
+ | |
+ private abstract class ComparableValueRegisterEntry<TValue> : | |
+ RegisterEntry<TValue> | |
+ where TValue : IComparable<TValue> { | |
+ private protected ComparableValueRegisterEntry( | |
+ string name, | |
+ (bool isReadable, bool isWritable) readWrite, | |
+ (TValue minValue, TValue maxValue) valueRange, | |
+ WriteSKSREGArgumentFunc writeSKSREGArgument, | |
+ ExpectValueFunc expectValue | |
+ ) | |
+ : base( | |
+ name: name, | |
+ readWrite: readWrite, | |
+ valueRange: valueRange, | |
+ writeSKSREGArgument: writeSKSREGArgument, | |
+ expectValue: expectValue | |
+ ) | |
+ { | |
+ } | |
+ | |
+ private protected override bool IsInRange(TValue value) | |
+ => MinValue.CompareTo(value) <= 0 && 0 <= MaxValue.CompareTo(value); | |
+ } | |
+ | |
+ private sealed class RegisterBinaryEntry : ComparableValueRegisterEntry<bool> { | |
+ public RegisterBinaryEntry( | |
+ string name, | |
+ (bool isReadable, bool isWritable) readWrite | |
+ ) | |
+ : base( | |
+ name: name, | |
+ readWrite: readWrite, | |
+ valueRange: (minValue: false, maxValue: true), | |
+ writeSKSREGArgument: static (writer, value) => writer.WriteTokenBinary(value), | |
+ expectValue: SkStackTokenParser.ExpectBinary | |
+ ) | |
+ { | |
+ } | |
+ | |
+ private protected override bool IsInRange(bool value) => true; | |
+ } | |
+ | |
+#if false // unused | |
+ private sealed class RegisterUINT8Entry : ComparableValueRegisterEntry<byte> { | |
+ public RegisterUINT8Entry( | |
+ string name, | |
+ (bool isReadable, bool isWritable) readWrite, | |
+ (byte minValue, byte maxValue) valueRange | |
+ ) | |
+ : base( | |
+ name: name, | |
+ readWrite: readWrite, | |
+ valueRange: valueRange, | |
+ writeSKSREGArgument: static (writer, value) => writer.WriteTokenUINT8(value), | |
+ expectValue: SkStackTokenParser.ExpectUINT8 | |
+ ) | |
+ { | |
+ } | |
+ } | |
+#endif | |
+ | |
+ private sealed class RegisterChannelEntry : ComparableValueRegisterEntry<SkStackChannel> { | |
+ public RegisterChannelEntry( | |
+ string name, | |
+ (bool isReadable, bool isWritable) readWrite, | |
+ (SkStackChannel minValue, SkStackChannel maxValue) valueRange | |
+ ) | |
+ : base( | |
+ name: name, | |
+ readWrite: readWrite, | |
+ valueRange: valueRange, | |
+ writeSKSREGArgument: static (writer, value) => writer.WriteTokenUINT8(value.RegisterS02Value), | |
+ expectValue: SkStackTokenParser.ExpectCHANNEL | |
+ ) | |
+ { | |
+ } | |
+ } | |
+ | |
+ private sealed class RegisterUINT16Entry : ComparableValueRegisterEntry<ushort> { | |
+ public RegisterUINT16Entry( | |
+ string name, | |
+ (bool isReadable, bool isWritable) readWrite, | |
+ (ushort minValue, ushort maxValue) valueRange | |
+ ) | |
+ : base( | |
+ name: name, | |
+ readWrite: readWrite, | |
+ valueRange: valueRange, | |
+ writeSKSREGArgument: static (writer, value) => writer.WriteTokenUINT16(value), | |
+ expectValue: SkStackTokenParser.ExpectUINT16 | |
+ ) | |
+ { | |
+ } | |
+ } | |
+ | |
+ private sealed class RegisterUINT32Entry : ComparableValueRegisterEntry<uint> { | |
+ public RegisterUINT32Entry( | |
+ string name, | |
+ (bool isReadable, bool isWritable) readWrite, | |
+ (uint minValue, uint maxValue) valueRange | |
+ ) | |
+ : base( | |
+ name: name, | |
+ readWrite: readWrite, | |
+ valueRange: valueRange, | |
+ writeSKSREGArgument: static (writer, value) => writer.WriteTokenUINT32(value), | |
+ expectValue: SkStackTokenParser.ExpectUINT32 | |
+ ) | |
+ { | |
+ } | |
+ } | |
+ | |
+ private sealed class RegisterUINT32SecondsTimeSpanEntry : ComparableValueRegisterEntry<TimeSpan> { | |
+ public RegisterUINT32SecondsTimeSpanEntry( | |
+ string name, | |
+ (bool isReadable, bool isWritable) readWrite, | |
+ (uint minValue, uint maxValue) valueRange | |
+ ) | |
+ : base( | |
+ name: name, | |
+ readWrite: readWrite, | |
+ valueRange: ( | |
+ minValue: TimeSpan.FromSeconds(valueRange.minValue), | |
+ maxValue: TimeSpan.FromSeconds(valueRange.maxValue) | |
+ ), | |
+ writeSKSREGArgument: static (writer, value) => writer.WriteTokenUINT32((uint)value.TotalSeconds), | |
+ expectValue: ExpectValue | |
+ ) | |
+ { | |
+ } | |
+ | |
+ private static bool ExpectValue( | |
+ ref SequenceReader<byte> reader, | |
+ out TimeSpan value | |
+ ) | |
+ { | |
+ value = default; | |
+ | |
+ if (SkStackTokenParser.ExpectUINT32(ref reader, out var seconds)) { | |
+ value = TimeSpan.FromSeconds(seconds); | |
+ return true; | |
+ } | |
+ | |
+ return false; | |
+ } | |
+ } | |
+ | |
+ private sealed class RegisterUINT64Entry : ComparableValueRegisterEntry<ulong> { | |
+ public RegisterUINT64Entry( | |
+ string name, | |
+ (bool isReadable, bool isWritable) readWrite, | |
+ (uint minValue, uint maxValue) valueRange | |
+ ) | |
+ : base( | |
+ name: name, | |
+ readWrite: readWrite, | |
+ valueRange: valueRange, | |
+ writeSKSREGArgument: static (writer, value) => writer.WriteTokenUINT64(value), | |
+ expectValue: SkStackTokenParser.ExpectUINT64 | |
+ ) | |
+ { | |
+ } | |
+ } | |
+ | |
+ private sealed class RegisterCHARArrayEntry : RegisterEntry<ReadOnlyMemory<byte>> { | |
+ private readonly int minLength; | |
+ private readonly int maxLength; | |
+ | |
+ public RegisterCHARArrayEntry( | |
+ string name, | |
+ (bool isReadable, bool isWritable) readWrite, | |
+ int minLength, | |
+ int maxLength | |
+ ) | |
+ : base( | |
+ name: name, | |
+ readWrite: readWrite, | |
+ valueRange: default, | |
+ writeSKSREGArgument: static (writer, value) => writer.WriteToken(value.Span), | |
+ expectValue: SkStackTokenParser.ExpectCharArray | |
+ ) | |
+ { | |
+ this.minLength = minLength; | |
+ this.maxLength = maxLength; | |
+ } | |
+ | |
+ private protected override bool IsInRange(ReadOnlyMemory<byte> value) => throw new NotImplementedException(); | |
+ | |
+ internal override void ThrowIfValueIsNotInRange(ReadOnlyMemory<byte> value, string paramName) | |
+ { | |
+ if (value.IsEmpty) | |
+ throw new ArgumentException("must be non-empty value", paramName); | |
+ if (!(minLength <= value.Length && value.Length <= maxLength)) | |
+ throw new ArgumentOutOfRangeException(paramName, value, $"length of {paramName} must be in range of {minLength}~{maxLength}"); | |
+ } | |
+ } | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackRegister.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackRegister.cs | |
new file mode 100644 | |
index 0000000..efe3d73 | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackRegister.cs | |
@@ -0,0 +1,75 @@ | |
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+ | |
+using System; | |
+ | |
+namespace Smdn.Net.SkStackIP; | |
+ | |
+/// <remarks> | |
+/// <para>See 'BP35A1コマンドリファレンス 3.1. SKSREG' for detailed specifications.</para> | |
+/// </remarks> | |
+public static partial class SkStackRegister { | |
+ private static readonly (bool IsReadable, bool IsWritable) RW = (IsReadable: true, IsWritable: true); | |
+ private static readonly (bool IsReadable, bool IsWritable) R = (IsReadable: true, IsWritable: false); | |
+ | |
+ public static RegisterEntry<SkStackChannel> S02 { get; } = new RegisterChannelEntry(name: nameof(S02), readWrite: RW, valueRange: (minValue: SkStackChannel.Channel33, maxValue: SkStackChannel.Channel60)); | |
+ [CLSCompliant(false)] | |
+ public static RegisterEntry<ushort> S03 { get; } = new RegisterUINT16Entry(name: nameof(S03), readWrite: RW, valueRange: (minValue: 0x0000, maxValue: 0xFFFF)); | |
+ [CLSCompliant(false)] | |
+ public static RegisterEntry<uint> S07 { get; } = new RegisterUINT32Entry(name: nameof(S07), readWrite: R, valueRange: default); | |
+ public static RegisterEntry<ReadOnlyMemory<byte>> S0A { get; } = new RegisterCHARArrayEntry(name: nameof(S0A), readWrite: RW, minLength: 8, maxLength: 8); | |
+ public static RegisterEntry<bool> S15 { get; } = new RegisterBinaryEntry(name: nameof(S15), readWrite: RW); | |
+ [CLSCompliant(false)] | |
+ public static RegisterEntry<TimeSpan> S16 { get; } = new RegisterUINT32SecondsTimeSpanEntry(name: nameof(S16), readWrite: RW, valueRange: (minValue: 0x_0000_003C, maxValue: 0x_FFFF_FFFF)); | |
+ public static RegisterEntry<bool> S17 { get; } = new RegisterBinaryEntry(name: nameof(S17), readWrite: RW); | |
+ public static RegisterEntry<bool> SA0 { get; } = new RegisterBinaryEntry(name: nameof(SA0), readWrite: RW); | |
+ public static RegisterEntry<bool> SA1 { get; } = new RegisterBinaryEntry(name: nameof(SA1), readWrite: RW); | |
+ public static RegisterEntry<bool> SFB { get; } = new RegisterBinaryEntry(name: nameof(SFB), readWrite: R); | |
+ [CLSCompliant(false)] | |
+ public static RegisterEntry<ulong> SFD { get; } = new RegisterUINT64Entry(name: nameof(SFD), readWrite: R, valueRange: default); | |
+ public static RegisterEntry<bool> SFE { get; } = new RegisterBinaryEntry(name: nameof(SFE), readWrite: RW); | |
+ public static RegisterEntry<bool> SFF { get; } = new RegisterBinaryEntry(name: nameof(SFF), readWrite: RW); | |
+ | |
+ /* | |
+ * alias of SXX | |
+ */ | |
+ | |
+ /// <remarks>This property is an alias for the register number <see cref="S02"/>.</remarks> | |
+ public static RegisterEntry<SkStackChannel> Channel => S02; | |
+ | |
+ /// <remarks>This property is an alias for the register number <see cref="S03"/>.</remarks> | |
+ [CLSCompliant(false)] public static RegisterEntry<ushort> PanId => S03; | |
+ | |
+ /// <remarks>This property is an alias for the register number <see cref="S07"/>.</remarks> | |
+ [CLSCompliant(false)] public static RegisterEntry<uint> FrameCounter => S07; | |
+ | |
+ /// <remarks>This property is an alias for the register number <see cref="S0A"/>.</remarks> | |
+ public static RegisterEntry<ReadOnlyMemory<byte>> PairingId => S0A; | |
+ | |
+ /// <remarks>This property is an alias for the register number <see cref="S15"/>.</remarks> | |
+ public static RegisterEntry<bool> RespondBeaconRequest => S15; | |
+ | |
+ /// <remarks>This property is an alias for the register number <see cref="S16"/>.</remarks> | |
+ [CLSCompliant(false)] public static RegisterEntry<TimeSpan> PanaSessionLifetimeInSeconds => S16; | |
+ | |
+ /// <remarks>This property is an alias for the register number <see cref="S17"/>.</remarks> | |
+ public static RegisterEntry<bool> EnableAutoReauthentication => S17; | |
+ | |
+ /// <remarks>This property is an alias for the register number <see cref="SA0"/>.</remarks> | |
+ public static RegisterEntry<bool> EncryptIPMulticast => SA0; | |
+ | |
+ /// <remarks>This property is an alias for the register number <see cref="SA1"/>.</remarks> | |
+ public static RegisterEntry<bool> AcceptIcmpEcho => SA1; | |
+ | |
+ /// <remarks>This property is an alias for the register number <see cref="SFB"/>.</remarks> | |
+ public static RegisterEntry<bool> IsSendingRestricted => SFB; | |
+ | |
+ /// <remarks>This property is an alias for the register number <see cref="SFD"/>.</remarks> | |
+ [CLSCompliant(false)] public static RegisterEntry<ulong> AccumulatedSendTimeInMilliseconds => SFD; | |
+ | |
+ /// <remarks>This property is an alias for the register number <see cref="SFE"/>.</remarks> | |
+ public static RegisterEntry<bool> EnableEchoback => SFE; | |
+ | |
+ /// <remarks>This property is an alias for the register number <see cref="SFF"/>.</remarks> | |
+ public static RegisterEntry<bool> EnableAutoLoad => SFF; | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackResponse.OfTPayload.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackResponse.OfTPayload.cs | |
new file mode 100644 | |
index 0000000..7b4fff0 | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackResponse.OfTPayload.cs | |
@@ -0,0 +1,12 @@ | |
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+namespace Smdn.Net.SkStackIP; | |
+ | |
+public class SkStackResponse<TPayload> : SkStackResponse { | |
+ public TPayload? Payload { get; internal set; } | |
+ | |
+ internal SkStackResponse() | |
+ : base() | |
+ { | |
+ } | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackResponse.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackResponse.cs | |
new file mode 100644 | |
index 0000000..3e667fe | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackResponse.cs | |
@@ -0,0 +1,104 @@ | |
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+ | |
+using System; | |
+#if NULL_STATE_STATIC_ANALYSIS_ATTRIBUTES | |
+using System.Diagnostics.CodeAnalysis; | |
+#endif | |
+ | |
+using Smdn.Net.SkStackIP.Protocol; | |
+ | |
+namespace Smdn.Net.SkStackIP; | |
+ | |
+public class SkStackResponse { | |
+ internal readonly struct NullPayload { } | |
+ | |
+ public bool Success => Status == SkStackResponseStatus.Ok; | |
+ public SkStackResponseStatus Status { get; internal set; } = SkStackResponseStatus.Undetermined; | |
+ public ReadOnlyMemory<byte> StatusText { get; internal set; } | |
+ | |
+ internal SkStackResponse() | |
+ { | |
+ } | |
+ | |
+ private bool TryParseErrorStatus( | |
+ out SkStackErrorCode errorCode, | |
+ out ReadOnlyMemory<byte> errorText, | |
+#if NULL_STATE_STATIC_ANALYSIS_ATTRIBUTES | |
+ [NotNullWhen(true)] | |
+#endif | |
+ out string? errorMessage | |
+ ) | |
+ { | |
+ errorCode = default; | |
+ errorText = default; | |
+ errorMessage = default; | |
+ | |
+ if (Status is SkStackResponseStatus.Ok or SkStackResponseStatus.Undetermined) | |
+ return false; // not error status | |
+ | |
+ ReadOnlySpan<byte> errorCodeName; | |
+ | |
+ if (5 <= StatusText.Length && StatusText.Span[4] == SkStack.SP) { | |
+ errorCodeName = StatusText.Span.Slice(0, 4); | |
+ errorText = StatusText.Slice(5); | |
+ } | |
+ else { | |
+ errorCodeName = StatusText.Span; | |
+ errorText = default; | |
+ } | |
+ | |
+ errorCode = SkStackErrorCodeNames.ParseErrorCode(errorCodeName); | |
+ | |
+ errorMessage = errorCode switch { | |
+ SkStackErrorCode.ER01 => "Reserved error code", | |
+ SkStackErrorCode.ER02 => "Reserved error code", | |
+ SkStackErrorCode.ER03 => "Reserved error code", | |
+ SkStackErrorCode.ER04 => "Unsupported command", | |
+ SkStackErrorCode.ER05 => "Invalid number of arguments", | |
+ SkStackErrorCode.ER06 => "Argument out-of-range or invalid format", | |
+ SkStackErrorCode.ER07 => "Reserved error code", | |
+ SkStackErrorCode.ER08 => "Reserved error code", | |
+ SkStackErrorCode.ER09 => "UART input error", | |
+ SkStackErrorCode.ER10 => "Command completed unsuccessfully", | |
+ _ => "unknown or undefined error code", | |
+ }; | |
+ | |
+ return true; | |
+ } | |
+ | |
+ internal void ThrowIfErrorStatus( | |
+ Func<SkStackResponse, SkStackErrorCode, ReadOnlyMemory<byte>, Exception?>? translateException | |
+ ) | |
+ { | |
+ if (!TryParseErrorStatus(out var errorCode, out var errorText, out var errorMessage)) | |
+ return; | |
+ | |
+ var translatedException = | |
+ translateException?.Invoke(this, errorCode, errorText) | |
+ ?? errorCode switch { | |
+ SkStackErrorCode.ER04 => new SkStackCommandNotSupportedException( | |
+ response: this, | |
+ errorCode: errorCode, | |
+ errorText: errorText.Span, | |
+ message: errorMessage | |
+ ), | |
+ | |
+ SkStackErrorCode.ER09 => new SkStackUartIOException( | |
+ response: this, | |
+ errorCode: errorCode, | |
+ errorText: errorText.Span, | |
+ message: errorMessage | |
+ ), | |
+ | |
+ _ => new SkStackErrorResponseException( | |
+ response: this, | |
+ errorCode: errorCode, | |
+ errorText: errorText.Span, | |
+ message: errorMessage | |
+ ), | |
+ }; | |
+ | |
+ throw translatedException; | |
+ } | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackResponseException.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackResponseException.cs | |
new file mode 100644 | |
index 0000000..5766d77 | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackResponseException.cs | |
@@ -0,0 +1,25 @@ | |
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+using System; | |
+ | |
+namespace Smdn.Net.SkStackIP; | |
+ | |
+/// <summary> | |
+/// The exception that is thrown when the <see cref="SkStackClient"/> received an invalid or an unexpected response. | |
+/// </summary> | |
+public class SkStackResponseException : InvalidOperationException { | |
+ public SkStackResponseException() | |
+ : base() | |
+ { | |
+ } | |
+ | |
+ public SkStackResponseException(string message) | |
+ : base(message: message) | |
+ { | |
+ } | |
+ | |
+ public SkStackResponseException(string message, Exception? innerException = null) | |
+ : base(message: message, innerException: innerException) | |
+ { | |
+ } | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackResponseStatus.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackResponseStatus.cs | |
new file mode 100644 | |
index 0000000..25da5a7 | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackResponseStatus.cs | |
@@ -0,0 +1,10 @@ | |
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+ | |
+namespace Smdn.Net.SkStackIP; | |
+ | |
+public enum SkStackResponseStatus { | |
+ Undetermined = 0, // used as default(SkStackResponseStatus) | |
+ Ok = +1, | |
+ Fail = -1, | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackUartIOException.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackUartIOException.cs | |
new file mode 100644 | |
index 0000000..4049caa | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackUartIOException.cs | |
@@ -0,0 +1,28 @@ | |
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+#pragma warning disable CA1032 | |
+ | |
+using System; | |
+ | |
+namespace Smdn.Net.SkStackIP; | |
+ | |
+/// <summary>Describes the error code <c>ER09</c>.</summary> | |
+/// <remarks> | |
+/// <para>See 'BP35A1コマンドリファレンス 7. エラーコード' for detailed specifications.</para> | |
+/// </remarks> | |
+public class SkStackUartIOException : SkStackErrorResponseException { | |
+ internal SkStackUartIOException( | |
+ SkStackResponse response, | |
+ SkStackErrorCode errorCode, | |
+ ReadOnlySpan<byte> errorText, | |
+ string message | |
+ ) | |
+ : base( | |
+ response: response, | |
+ errorCode: errorCode, | |
+ errorText: errorText, | |
+ message: message | |
+ ) | |
+ { | |
+ } | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackUdpEncryption.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackUdpEncryption.cs | |
new file mode 100644 | |
index 0000000..e949cf1 | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackUdpEncryption.cs | |
@@ -0,0 +1,13 @@ | |
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+ | |
+namespace Smdn.Net.SkStackIP; | |
+ | |
+/// <remarks> | |
+/// <para>See 'BP35A1コマンドリファレンス 3.7. SKSENDTO' for detailed specifications.</para> | |
+/// </remarks> | |
+public enum SkStackUdpEncryption : byte { | |
+ ForcePlainText = 0x00, | |
+ ForceEncrypt = 0x01, | |
+ EncryptIfAble = 0x02, | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackUdpPort.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackUdpPort.cs | |
new file mode 100644 | |
index 0000000..a66a6d5 | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackUdpPort.cs | |
@@ -0,0 +1,55 @@ | |
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+ | |
+using System; | |
+ | |
+namespace Smdn.Net.SkStackIP; | |
+ | |
+/// <remarks> | |
+/// <para>See 'BP35A1コマンドリファレンス 5. 待ち受けポート番号' for detailed specifications.</para> | |
+/// </remarks> | |
+public readonly struct SkStackUdpPort { | |
+ internal const int NumberOfPorts = 6; | |
+ internal static readonly SkStackUdpPortHandle HandleMin = SkStackUdpPortHandle.Handle1; | |
+ internal static readonly SkStackUdpPortHandle HandleMax = SkStackUdpPortHandle.Handle6; | |
+ | |
+ public static readonly SkStackUdpPort Null = default; // Null.Handle will be invalid handle | |
+ | |
+ public SkStackUdpPortHandle Handle { get; } | |
+ public int Port { get; } | |
+ | |
+ public bool IsNull => Handle == Null.Handle; | |
+ public bool IsUnused => Port == 0; | |
+ | |
+ internal SkStackUdpPort(SkStackUdpPortHandle handle, int port) | |
+ { | |
+ Handle = handle; | |
+ Port = port; | |
+ } | |
+ | |
+ public override string ToString() | |
+ => $"{Port} (#{(byte)Handle})"; | |
+ | |
+ internal static bool IsPortHandleIsOutOfRange(SkStackUdpPortHandle handle) | |
+ => handle is < SkStackUdpPortHandle.Handle1 or > SkStackUdpPortHandle.Handle6; | |
+ | |
+ internal static void ThrowIfPortHandleIsOutOfRange(SkStackUdpPortHandle handle, string paramName) | |
+ { | |
+ if (IsPortHandleIsOutOfRange(handle)) | |
+ throw new ArgumentOutOfRangeException(paramName: paramName, actualValue: handle, message: $"invalid value of {nameof(SkStackUdpPortHandle)}"); | |
+ } | |
+ | |
+ internal static void ThrowIfPortNumberIsOutOfRange(int portNumber, string paramName) | |
+ { | |
+ if (portNumber is not (>= ushort.MinValue and <= ushort.MaxValue)) // UINT16 | |
+ throw new ArgumentOutOfRangeException(paramName, portNumber, $"must be in range of {ushort.MinValue}~{ushort.MaxValue}"); | |
+ } | |
+ | |
+ internal static void ThrowIfPortNumberIsOutOfRangeOrUnused(int portNumber, string paramName) | |
+ { | |
+ if (portNumber == SkStackKnownPortNumbers.SetUnused) | |
+ throw new ArgumentOutOfRangeException(paramName, portNumber, $"can not use port number {SkStackKnownPortNumbers.SetUnused}"); | |
+ | |
+ ThrowIfPortNumberIsOutOfRange(portNumber, paramName); | |
+ } | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackUdpPortHandle.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackUdpPortHandle.cs | |
new file mode 100644 | |
index 0000000..56f9d56 | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackUdpPortHandle.cs | |
@@ -0,0 +1,22 @@ | |
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+namespace Smdn.Net.SkStackIP; | |
+ | |
+/// <remarks> | |
+/// <para>See below for detailed specifications.</para> | |
+/// <list type="bullet"> | |
+/// <item><description>'BP35A1コマンドリファレンス 3.7. SKSENDTO'</description></item> | |
+/// <item><description>'BP35A1コマンドリファレンス 3.19. SKUDPPORT'</description></item> | |
+/// <item><description>'BP35A1コマンドリファレンス 4.7. EPORT'</description></item> | |
+/// <item><description>'BP35A1コマンドリファレンス 5. 待ち受けポート番号'</description></item> | |
+/// </list> | |
+/// </remarks> | |
+public enum SkStackUdpPortHandle : byte { | |
+ None = 0, | |
+ Handle1 = 1, | |
+ Handle2 = 2, | |
+ Handle3 = 3, | |
+ Handle4 = 4, | |
+ Handle5 = 5, | |
+ Handle6 = 6, | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackUdpSendFailedException.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackUdpSendFailedException.cs | |
new file mode 100644 | |
index 0000000..ce5bfa8 | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackUdpSendFailedException.cs | |
@@ -0,0 +1,43 @@ | |
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+using System; | |
+using System.Net; | |
+ | |
+namespace Smdn.Net.SkStackIP; | |
+ | |
+/// <summary> | |
+/// The exception that is thrown when the <see cref="SkStackClient"/> attempted to perform <c>SKSENDTO</c> and raised <c>EVENT 21</c> with <c>PARAM 1</c> ('Failed to send UDP'). | |
+/// </summary> | |
+/// <seealso cref="SkStackEventNumber.UdpSendCompleted"/> | |
+/// <seealso cref="SkStackClient.SendUdpEchonetLiteAsync"/> | |
+public class SkStackUdpSendFailedException : InvalidOperationException { | |
+ public SkStackUdpPortHandle PortHandle { get; } | |
+ public IPAddress? PeerAddress { get; } | |
+ | |
+ public SkStackUdpSendFailedException() | |
+ : base() | |
+ { | |
+ } | |
+ | |
+ public SkStackUdpSendFailedException(string message) | |
+ : base(message: message) | |
+ { | |
+ } | |
+ | |
+ public SkStackUdpSendFailedException(string message, Exception? innerException = null) | |
+ : base(message: message, innerException: innerException) | |
+ { | |
+ } | |
+ | |
+ public SkStackUdpSendFailedException( | |
+ string message, | |
+ SkStackUdpPortHandle portHandle, | |
+ IPAddress peerAddress, | |
+ Exception? innerException = null | |
+ ) | |
+ : base(message: message, innerException: innerException) | |
+ { | |
+ PortHandle = portHandle; | |
+ PeerAddress = peerAddress; | |
+ } | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackUdpSendResultIndeterminateException.cs b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackUdpSendResultIndeterminateException.cs | |
new file mode 100644 | |
index 0000000..0657e9c | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/Smdn.Net.SkStackIP/SkStackUdpSendResultIndeterminateException.cs | |
@@ -0,0 +1,30 @@ | |
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+using System; | |
+ | |
+namespace Smdn.Net.SkStackIP; | |
+ | |
+#pragma warning disable CS0419 | |
+/// <summary> | |
+/// The exception that is thrown when the <c>EVENT 21</c> was not raised or received after performing <c>SKSENDTO</c>. | |
+/// </summary> | |
+/// <seealso cref="SkStackClient.SendSKSENDTOAsync"/> | |
+#pragma warning restore CS0419 | |
+public class SkStackUdpSendResultIndeterminateException : InvalidOperationException { | |
+ private const string DefaultMessage = "Unable to confirm the send results since the EVENT 21 was not raised or received after performing SKSENDTO."; | |
+ | |
+ public SkStackUdpSendResultIndeterminateException() | |
+ : base(message: DefaultMessage) | |
+ { | |
+ } | |
+ | |
+ public SkStackUdpSendResultIndeterminateException(string message) | |
+ : base(message: message) | |
+ { | |
+ } | |
+ | |
+ public SkStackUdpSendResultIndeterminateException(string message, Exception? innerException = null) | |
+ : base(message: message, innerException: innerException) | |
+ { | |
+ } | |
+} | |
diff --git a/src/Smdn.Net.SkStackIP/System.Buffers/SequenceReaderExtensions.cs b/src/Smdn.Net.SkStackIP/System.Buffers/SequenceReaderExtensions.cs | |
new file mode 100644 | |
index 0000000..0cb12f2 | |
--- /dev/null | |
+++ b/src/Smdn.Net.SkStackIP/System.Buffers/SequenceReaderExtensions.cs | |
@@ -0,0 +1,16 @@ | |
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+ | |
+using System.Runtime.CompilerServices; | |
+ | |
+namespace System.Buffers; | |
+ | |
+internal static class SequenceReaderExtensions { | |
+ [MethodImpl(MethodImplOptions.AggressiveInlining)] | |
+ public static ReadOnlySequence<T> GetUnreadSequence<T>(this SequenceReader<T> sequenceReader) where T : unmanaged, IEquatable<T> | |
+#if SYSTEM_BUFFERS_SEQUENCEREADER_UNREADSEQUENCE | |
+ => sequenceReader.UnreadSequence; | |
+#else | |
+ => sequenceReader.Sequence.Slice(sequenceReader.Position); | |
+#endif | |
+} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment