Released package
Release notes
The full release notes are available at gist.
Change log
Change log in this release:
- 2024-11-03 update assembly version
- 2024-11-03 require .NET SDK version 8.0 or later
- 2024-11-03 add support for 'graph_total' attribute
- 2024-11-03 improve LocalNode.Create to be able to accept the list of addresses allowed to access LocalNode
- 2024-11-03 introduce IPluginGraphAttributes to make configuring graph attributes more flexible
- 2024-10-31 introduce IAccessRule to allow access to NodeBase to be configured using with IServiceCollection
- 2024-10-28 make LocalNode abstract
- 2024-10-28 make NodeBase.HostName abstract
API changes
API changes in this release:
diff --git a/doc/api-list/Smdn.Net.MuninNode/Smdn.Net.MuninNode-net6.0.apilist.cs b/doc/api-list/Smdn.Net.MuninNode/Smdn.Net.MuninNode-net6.0.apilist.cs
index 130febd..62a4f2b 100644
--- a/doc/api-list/Smdn.Net.MuninNode/Smdn.Net.MuninNode-net6.0.apilist.cs
+++ b/doc/api-list/Smdn.Net.MuninNode/Smdn.Net.MuninNode-net6.0.apilist.cs
@@ -1,215 +1,221 @@
-// Smdn.Net.MuninNode.dll (Smdn.Net.MuninNode-1.3.0)
+// Smdn.Net.MuninNode.dll (Smdn.Net.MuninNode-2.0.0)
// Name: Smdn.Net.MuninNode
-// AssemblyVersion: 1.3.0.0
-// InformationalVersion: 1.3.0+191d215fe57392cb544e2ffea221644a1007cfc0
+// AssemblyVersion: 2.0.0.0
+// InformationalVersion: 2.0.0+0c4121c0bc87932e6486c3b38a123cb59460ac02
// TargetFramework: .NETCoreApp,Version=v6.0
// Configuration: Release
// Referenced assemblies:
// Microsoft.Extensions.DependencyInjection.Abstractions, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60
// Microsoft.Extensions.Logging.Abstractions, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60
// Smdn.Fundamental.Exception, Version=3.0.0.0, Culture=neutral
// System.Collections, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
// System.ComponentModel, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
// System.IO.Pipelines, Version=6.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.Primitives, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
// System.Net.Sockets, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
// System.Runtime, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
// System.Security.Cryptography.Algorithms, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
// System.Text.RegularExpressions, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
#nullable enable annotations
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Smdn.Net.MuninNode;
using Smdn.Net.MuninPlugin;
namespace Smdn.Net.MuninNode {
- public class LocalNode : NodeBase {
- public LocalNode(IPluginProvider pluginProvider, string hostName, int port, ILogger? logger = null) {}
- public LocalNode(IPluginProvider pluginProvider, string hostName, int port, IServiceProvider? serviceProvider = null) {}
- public LocalNode(IReadOnlyCollection<IPlugin> plugins, int port, IServiceProvider? serviceProvider = null) {}
- public LocalNode(IReadOnlyCollection<IPlugin> plugins, string hostName, int port, IServiceProvider? serviceProvider = null) {}
+ public interface IAccessRule {
+ bool IsAcceptable(IPEndPoint remoteEndPoint);
+ }
+
+ public static class IAccessRuleServiceCollectionExtensions {
+ public static IServiceCollection AddMuninNodeAccessRule(this IServiceCollection services, IAccessRule accessRule) {}
+ public static IServiceCollection AddMuninNodeAccessRule(this IServiceCollection services, IReadOnlyList<IPAddress> addressListAllowFrom) {}
+ }
- public IPEndPoint LocalEndPoint { get; }
+ public abstract class LocalNode : NodeBase {
+ public static LocalNode Create(IPluginProvider pluginProvider, int port, string? hostName = null, IReadOnlyList<IPAddress>? addressListAllowFrom = null, IServiceProvider? serviceProvider = null) {}
+ public static LocalNode Create(IReadOnlyCollection<IPlugin> plugins, int port, string? hostName = null, IReadOnlyList<IPAddress>? addressListAllowFrom = null, IServiceProvider? serviceProvider = null) {}
+
+ protected LocalNode(IAccessRule? accessRule, ILogger? logger = null) {}
protected override Socket CreateServerSocket() {}
- protected override bool IsClientAcceptable(IPEndPoint remoteEndPoint) {}
+ protected virtual EndPoint GetLocalEndPointToBind() {}
}
public abstract class NodeBase :
IAsyncDisposable,
IDisposable
{
- private protected class PluginProvider : IPluginProvider {
- public PluginProvider(IReadOnlyCollection<IPlugin> plugins) {}
-
- public IReadOnlyCollection<IPlugin> Plugins { get; }
- public INodeSessionCallback? SessionCallback { get; }
- }
-
- protected NodeBase(IPluginProvider pluginProvider, string hostName, ILogger? logger) {}
- protected NodeBase(IReadOnlyCollection<IPlugin> plugins, string hostName, ILogger? logger) {}
+ protected NodeBase(IAccessRule? accessRule, ILogger? logger) {}
public virtual Encoding Encoding { get; }
- public string HostName { get; }
+ public abstract string HostName { get; }
+ public EndPoint LocalEndPoint { get; }
protected ILogger? Logger { get; }
public virtual Version NodeVersion { get; }
- [Obsolete("This member will be deprecated in future version.")]
- public IReadOnlyCollection<IPlugin> Plugins { get; }
+ public abstract IPluginProvider PluginProvider { get; }
public async ValueTask AcceptAsync(bool throwIfCancellationRequested, CancellationToken cancellationToken) {}
public async ValueTask AcceptSingleSessionAsync(CancellationToken cancellationToken = default) {}
protected abstract Socket CreateServerSocket();
protected virtual void Dispose(bool disposing) {}
public void Dispose() {}
public async ValueTask DisposeAsync() {}
protected virtual async ValueTask DisposeAsyncCore() {}
- protected abstract bool IsClientAcceptable(IPEndPoint remoteEndPoint);
public void Start() {}
+ protected void ThrowIfPluginProviderIsNull() {}
}
}
namespace Smdn.Net.MuninPlugin {
public interface INodeSessionCallback {
ValueTask ReportSessionClosedAsync(string sessionId, CancellationToken cancellationToken);
ValueTask ReportSessionStartedAsync(string sessionId, CancellationToken cancellationToken);
}
public interface IPlugin {
IPluginDataSource DataSource { get; }
- PluginGraphAttributes GraphAttributes { get; }
+ IPluginGraphAttributes GraphAttributes { get; }
string Name { get; }
INodeSessionCallback? SessionCallback { get; }
}
public interface IPluginDataSource {
IReadOnlyCollection<IPluginField> Fields { get; }
}
public interface IPluginField {
PluginFieldAttributes Attributes { get; }
string Name { get; }
ValueTask<string> GetFormattedValueStringAsync(CancellationToken cancellationToken);
}
+ public interface IPluginGraphAttributes {
+ IEnumerable<string> EnumerateAttributes();
+ }
+
public interface IPluginProvider {
IReadOnlyCollection<IPlugin> Plugins { get; }
INodeSessionCallback? SessionCallback { get; }
}
public enum PluginFieldGraphStyle : int {
Area = 1,
AreaStack = 3,
Default = 0,
Line = 100,
LineStack = 200,
LineStackWidth1 = 201,
LineStackWidth2 = 202,
LineStackWidth3 = 203,
LineWidth1 = 101,
LineWidth2 = 102,
LineWidth3 = 103,
Stack = 2,
}
public class Plugin :
INodeSessionCallback,
IPlugin,
IPluginDataSource
{
public Plugin(string name, PluginGraphAttributes graphAttributes, IReadOnlyCollection<IPluginField> fields) {}
public IReadOnlyCollection<IPluginField> Fields { get; }
public PluginGraphAttributes GraphAttributes { get; }
public string Name { get; }
IPluginDataSource IPlugin.DataSource { get; }
+ IPluginGraphAttributes IPlugin.GraphAttributes { get; }
INodeSessionCallback? IPlugin.SessionCallback { get; }
IReadOnlyCollection<IPluginField> IPluginDataSource.Fields { get; }
protected virtual ValueTask ReportSessionClosedAsync(string sessionId, CancellationToken cancellationToken) {}
protected virtual ValueTask ReportSessionStartedAsync(string sessionId, CancellationToken cancellationToken) {}
ValueTask INodeSessionCallback.ReportSessionClosedAsync(string sessionId, CancellationToken cancellationToken) {}
ValueTask INodeSessionCallback.ReportSessionStartedAsync(string sessionId, CancellationToken cancellationToken) {}
}
public static class PluginFactory {
public static IPluginField CreateField(string label, Func<double?> fetchValue) {}
public static IPluginField CreateField(string label, PluginFieldGraphStyle graphStyle, Func<double?> fetchValue) {}
public static IPluginField CreateField(string label, PluginFieldGraphStyle graphStyle, PluginFieldNormalValueRange normalRangeForWarning, PluginFieldNormalValueRange normalRangeForCritical, Func<double?> fetchValue) {}
public static IPluginField CreateField(string name, string label, PluginFieldGraphStyle graphStyle, PluginFieldNormalValueRange normalRangeForWarning, PluginFieldNormalValueRange normalRangeForCritical, Func<double?> fetchValue) {}
public static IPluginField CreateField(string name, string label, PluginFieldGraphStyle graphStyle, PluginFieldNormalValueRange normalRangeForWarning, PluginFieldNormalValueRange normalRangeForCritical, string? negativeFieldName, Func<double?> fetchValue) {}
public static IPlugin CreatePlugin(string name, PluginGraphAttributes graphAttributes, IReadOnlyCollection<IPluginField> fields) {}
public static IPlugin CreatePlugin(string name, PluginGraphAttributes graphAttributes, IReadOnlyCollection<PluginFieldBase> fields) {}
public static IPlugin CreatePlugin(string name, PluginGraphAttributes graphAttributes, PluginFieldBase field) {}
public static IPlugin CreatePlugin(string name, string fieldLabel, Func<double?> fetchFieldValue, PluginGraphAttributes graphAttributes) {}
public static IPlugin CreatePlugin(string name, string fieldLabel, PluginFieldGraphStyle fieldGraphStyle, Func<double?> fetchFieldValue, PluginGraphAttributes graphAttributes) {}
}
public abstract class PluginFieldBase : IPluginField {
protected PluginFieldBase(string label, string? name, PluginFieldGraphStyle graphStyle = PluginFieldGraphStyle.Default, PluginFieldNormalValueRange normalRangeForWarning = default, PluginFieldNormalValueRange normalRangeForCritical = default) {}
protected PluginFieldBase(string label, string? name, PluginFieldGraphStyle graphStyle, PluginFieldNormalValueRange normalRangeForWarning, PluginFieldNormalValueRange normalRangeForCritical, string? negativeFieldName) {}
public PluginFieldGraphStyle GraphStyle { get; }
public string Label { get; }
public string Name { get; }
public string? NegativeFieldName { get; }
public PluginFieldNormalValueRange NormalRangeForCritical { get; }
public PluginFieldNormalValueRange NormalRangeForWarning { get; }
PluginFieldAttributes IPluginField.Attributes { get; }
protected abstract ValueTask<double?> FetchValueAsync(CancellationToken cancellationToken);
async ValueTask<string> IPluginField.GetFormattedValueStringAsync(CancellationToken cancellationToken) {}
}
- public sealed class PluginGraphAttributes {
- [Obsolete("This member will be deprecated in future version.")]
- public PluginGraphAttributes(string title, string category, string verticalLabel, bool scale, string arguments, TimeSpan updateRate, int? width = null, int? height = null) {}
- public PluginGraphAttributes(string title, string category, string verticalLabel, bool scale, string arguments, TimeSpan? updateRate = null, int? width = null, int? height = null) {}
- public PluginGraphAttributes(string title, string category, string verticalLabel, bool scale, string arguments, TimeSpan? updateRate, int? width, int? height, IEnumerable<string>? order) {}
+ public sealed class PluginGraphAttributes : IPluginGraphAttributes {
+ public PluginGraphAttributes(string title, string category, string verticalLabel, bool scale, string arguments) {}
+ public PluginGraphAttributes(string title, string category, string verticalLabel, bool scale, string arguments, TimeSpan? updateRate, int? width, int? height, IEnumerable<string>? order, string? totalValueLabel) {}
public string Arguments { get; }
public string Category { get; }
public int? Height { get; }
public string? Order { get; }
public bool Scale { get; }
public string Title { get; }
+ public string? TotalValueLabel { get; }
public TimeSpan? UpdateRate { get; }
public string VerticalLabel { get; }
public int? Width { get; }
+
+ public IEnumerable<string> EnumerateAttributes() {}
}
public readonly struct PluginFieldAttributes {
public PluginFieldAttributes(string label, PluginFieldGraphStyle graphStyle = PluginFieldGraphStyle.Default) {}
public PluginFieldAttributes(string label, PluginFieldGraphStyle graphStyle = PluginFieldGraphStyle.Default, PluginFieldNormalValueRange normalRangeForWarning = default, PluginFieldNormalValueRange normalRangeForCritical = default) {}
public PluginFieldAttributes(string label, PluginFieldGraphStyle graphStyle, PluginFieldNormalValueRange normalRangeForWarning, PluginFieldNormalValueRange normalRangeForCritical, string? negativeFieldName) {}
public PluginFieldGraphStyle GraphStyle { get; }
public string Label { get; }
public string? NegativeFieldName { get; }
public PluginFieldNormalValueRange NormalRangeForCritical { get; }
public PluginFieldNormalValueRange NormalRangeForWarning { get; }
}
public readonly struct PluginFieldNormalValueRange {
public static readonly PluginFieldNormalValueRange None; // = "Smdn.Net.MuninPlugin.PluginFieldNormalValueRange"
public static PluginFieldNormalValueRange CreateMax(double max) {}
public static PluginFieldNormalValueRange CreateMin(double min) {}
public static PluginFieldNormalValueRange CreateRange(double min, double max) {}
public bool HasValue { get; }
public double? Max { get; }
public double? Min { get; }
}
}
// API list generated by Smdn.Reflection.ReverseGenerating.ListApi.MSBuild.Tasks v1.4.1.0.
// Smdn.Reflection.ReverseGenerating.ListApi.Core v1.3.1.0 (https://github.com/smdn/Smdn.Reflection.ReverseGenerating)
diff --git a/doc/api-list/Smdn.Net.MuninNode/Smdn.Net.MuninNode-net8.0.apilist.cs b/doc/api-list/Smdn.Net.MuninNode/Smdn.Net.MuninNode-net8.0.apilist.cs
index 73b3402..d282525 100644
--- a/doc/api-list/Smdn.Net.MuninNode/Smdn.Net.MuninNode-net8.0.apilist.cs
+++ b/doc/api-list/Smdn.Net.MuninNode/Smdn.Net.MuninNode-net8.0.apilist.cs
@@ -1,215 +1,221 @@
-// Smdn.Net.MuninNode.dll (Smdn.Net.MuninNode-1.3.0)
+// Smdn.Net.MuninNode.dll (Smdn.Net.MuninNode-2.0.0)
// Name: Smdn.Net.MuninNode
-// AssemblyVersion: 1.3.0.0
-// InformationalVersion: 1.3.0+191d215fe57392cb544e2ffea221644a1007cfc0
+// AssemblyVersion: 2.0.0.0
+// InformationalVersion: 2.0.0+0c4121c0bc87932e6486c3b38a123cb59460ac02
// TargetFramework: .NETCoreApp,Version=v8.0
// Configuration: Release
// Referenced assemblies:
// Microsoft.Extensions.DependencyInjection.Abstractions, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60
// Microsoft.Extensions.Logging.Abstractions, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60
// Smdn.Fundamental.Exception, Version=3.0.0.0, Culture=neutral
// System.Collections, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
// System.ComponentModel, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
// System.IO.Pipelines, Version=6.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.Primitives, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
// System.Net.Sockets, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
// System.Runtime, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
// System.Security.Cryptography, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
// System.Text.RegularExpressions, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
#nullable enable annotations
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Smdn.Net.MuninNode;
using Smdn.Net.MuninPlugin;
namespace Smdn.Net.MuninNode {
- public class LocalNode : NodeBase {
- public LocalNode(IPluginProvider pluginProvider, string hostName, int port, ILogger? logger = null) {}
- public LocalNode(IPluginProvider pluginProvider, string hostName, int port, IServiceProvider? serviceProvider = null) {}
- public LocalNode(IReadOnlyCollection<IPlugin> plugins, int port, IServiceProvider? serviceProvider = null) {}
- public LocalNode(IReadOnlyCollection<IPlugin> plugins, string hostName, int port, IServiceProvider? serviceProvider = null) {}
+ public interface IAccessRule {
+ bool IsAcceptable(IPEndPoint remoteEndPoint);
+ }
+
+ public static class IAccessRuleServiceCollectionExtensions {
+ public static IServiceCollection AddMuninNodeAccessRule(this IServiceCollection services, IAccessRule accessRule) {}
+ public static IServiceCollection AddMuninNodeAccessRule(this IServiceCollection services, IReadOnlyList<IPAddress> addressListAllowFrom) {}
+ }
- public IPEndPoint LocalEndPoint { get; }
+ public abstract class LocalNode : NodeBase {
+ public static LocalNode Create(IPluginProvider pluginProvider, int port, string? hostName = null, IReadOnlyList<IPAddress>? addressListAllowFrom = null, IServiceProvider? serviceProvider = null) {}
+ public static LocalNode Create(IReadOnlyCollection<IPlugin> plugins, int port, string? hostName = null, IReadOnlyList<IPAddress>? addressListAllowFrom = null, IServiceProvider? serviceProvider = null) {}
+
+ protected LocalNode(IAccessRule? accessRule, ILogger? logger = null) {}
protected override Socket CreateServerSocket() {}
- protected override bool IsClientAcceptable(IPEndPoint remoteEndPoint) {}
+ protected virtual EndPoint GetLocalEndPointToBind() {}
}
public abstract class NodeBase :
IAsyncDisposable,
IDisposable
{
- private protected class PluginProvider : IPluginProvider {
- public PluginProvider(IReadOnlyCollection<IPlugin> plugins) {}
-
- public IReadOnlyCollection<IPlugin> Plugins { get; }
- public INodeSessionCallback? SessionCallback { get; }
- }
-
- protected NodeBase(IPluginProvider pluginProvider, string hostName, ILogger? logger) {}
- protected NodeBase(IReadOnlyCollection<IPlugin> plugins, string hostName, ILogger? logger) {}
+ protected NodeBase(IAccessRule? accessRule, ILogger? logger) {}
public virtual Encoding Encoding { get; }
- public string HostName { get; }
+ public abstract string HostName { get; }
+ public EndPoint LocalEndPoint { get; }
protected ILogger? Logger { get; }
public virtual Version NodeVersion { get; }
- [Obsolete("This member will be deprecated in future version.")]
- public IReadOnlyCollection<IPlugin> Plugins { get; }
+ public abstract IPluginProvider PluginProvider { get; }
public async ValueTask AcceptAsync(bool throwIfCancellationRequested, CancellationToken cancellationToken) {}
public async ValueTask AcceptSingleSessionAsync(CancellationToken cancellationToken = default) {}
protected abstract Socket CreateServerSocket();
protected virtual void Dispose(bool disposing) {}
public void Dispose() {}
public async ValueTask DisposeAsync() {}
protected virtual async ValueTask DisposeAsyncCore() {}
- protected abstract bool IsClientAcceptable(IPEndPoint remoteEndPoint);
public void Start() {}
+ protected void ThrowIfPluginProviderIsNull() {}
}
}
namespace Smdn.Net.MuninPlugin {
public interface INodeSessionCallback {
ValueTask ReportSessionClosedAsync(string sessionId, CancellationToken cancellationToken);
ValueTask ReportSessionStartedAsync(string sessionId, CancellationToken cancellationToken);
}
public interface IPlugin {
IPluginDataSource DataSource { get; }
- PluginGraphAttributes GraphAttributes { get; }
+ IPluginGraphAttributes GraphAttributes { get; }
string Name { get; }
INodeSessionCallback? SessionCallback { get; }
}
public interface IPluginDataSource {
IReadOnlyCollection<IPluginField> Fields { get; }
}
public interface IPluginField {
PluginFieldAttributes Attributes { get; }
string Name { get; }
ValueTask<string> GetFormattedValueStringAsync(CancellationToken cancellationToken);
}
+ public interface IPluginGraphAttributes {
+ IEnumerable<string> EnumerateAttributes();
+ }
+
public interface IPluginProvider {
IReadOnlyCollection<IPlugin> Plugins { get; }
INodeSessionCallback? SessionCallback { get; }
}
public enum PluginFieldGraphStyle : int {
Area = 1,
AreaStack = 3,
Default = 0,
Line = 100,
LineStack = 200,
LineStackWidth1 = 201,
LineStackWidth2 = 202,
LineStackWidth3 = 203,
LineWidth1 = 101,
LineWidth2 = 102,
LineWidth3 = 103,
Stack = 2,
}
public class Plugin :
INodeSessionCallback,
IPlugin,
IPluginDataSource
{
public Plugin(string name, PluginGraphAttributes graphAttributes, IReadOnlyCollection<IPluginField> fields) {}
public IReadOnlyCollection<IPluginField> Fields { get; }
public PluginGraphAttributes GraphAttributes { get; }
public string Name { get; }
IPluginDataSource IPlugin.DataSource { get; }
+ IPluginGraphAttributes IPlugin.GraphAttributes { get; }
INodeSessionCallback? IPlugin.SessionCallback { get; }
IReadOnlyCollection<IPluginField> IPluginDataSource.Fields { get; }
protected virtual ValueTask ReportSessionClosedAsync(string sessionId, CancellationToken cancellationToken) {}
protected virtual ValueTask ReportSessionStartedAsync(string sessionId, CancellationToken cancellationToken) {}
ValueTask INodeSessionCallback.ReportSessionClosedAsync(string sessionId, CancellationToken cancellationToken) {}
ValueTask INodeSessionCallback.ReportSessionStartedAsync(string sessionId, CancellationToken cancellationToken) {}
}
public static class PluginFactory {
public static IPluginField CreateField(string label, Func<double?> fetchValue) {}
public static IPluginField CreateField(string label, PluginFieldGraphStyle graphStyle, Func<double?> fetchValue) {}
public static IPluginField CreateField(string label, PluginFieldGraphStyle graphStyle, PluginFieldNormalValueRange normalRangeForWarning, PluginFieldNormalValueRange normalRangeForCritical, Func<double?> fetchValue) {}
public static IPluginField CreateField(string name, string label, PluginFieldGraphStyle graphStyle, PluginFieldNormalValueRange normalRangeForWarning, PluginFieldNormalValueRange normalRangeForCritical, Func<double?> fetchValue) {}
public static IPluginField CreateField(string name, string label, PluginFieldGraphStyle graphStyle, PluginFieldNormalValueRange normalRangeForWarning, PluginFieldNormalValueRange normalRangeForCritical, string? negativeFieldName, Func<double?> fetchValue) {}
public static IPlugin CreatePlugin(string name, PluginGraphAttributes graphAttributes, IReadOnlyCollection<IPluginField> fields) {}
public static IPlugin CreatePlugin(string name, PluginGraphAttributes graphAttributes, IReadOnlyCollection<PluginFieldBase> fields) {}
public static IPlugin CreatePlugin(string name, PluginGraphAttributes graphAttributes, PluginFieldBase field) {}
public static IPlugin CreatePlugin(string name, string fieldLabel, Func<double?> fetchFieldValue, PluginGraphAttributes graphAttributes) {}
public static IPlugin CreatePlugin(string name, string fieldLabel, PluginFieldGraphStyle fieldGraphStyle, Func<double?> fetchFieldValue, PluginGraphAttributes graphAttributes) {}
}
public abstract class PluginFieldBase : IPluginField {
protected PluginFieldBase(string label, string? name, PluginFieldGraphStyle graphStyle = PluginFieldGraphStyle.Default, PluginFieldNormalValueRange normalRangeForWarning = default, PluginFieldNormalValueRange normalRangeForCritical = default) {}
protected PluginFieldBase(string label, string? name, PluginFieldGraphStyle graphStyle, PluginFieldNormalValueRange normalRangeForWarning, PluginFieldNormalValueRange normalRangeForCritical, string? negativeFieldName) {}
public PluginFieldGraphStyle GraphStyle { get; }
public string Label { get; }
public string Name { get; }
public string? NegativeFieldName { get; }
public PluginFieldNormalValueRange NormalRangeForCritical { get; }
public PluginFieldNormalValueRange NormalRangeForWarning { get; }
PluginFieldAttributes IPluginField.Attributes { get; }
protected abstract ValueTask<double?> FetchValueAsync(CancellationToken cancellationToken);
async ValueTask<string> IPluginField.GetFormattedValueStringAsync(CancellationToken cancellationToken) {}
}
- public sealed class PluginGraphAttributes {
- [Obsolete("This member will be deprecated in future version.")]
- public PluginGraphAttributes(string title, string category, string verticalLabel, bool scale, string arguments, TimeSpan updateRate, int? width = null, int? height = null) {}
- public PluginGraphAttributes(string title, string category, string verticalLabel, bool scale, string arguments, TimeSpan? updateRate = null, int? width = null, int? height = null) {}
- public PluginGraphAttributes(string title, string category, string verticalLabel, bool scale, string arguments, TimeSpan? updateRate, int? width, int? height, IEnumerable<string>? order) {}
+ public sealed class PluginGraphAttributes : IPluginGraphAttributes {
+ public PluginGraphAttributes(string title, string category, string verticalLabel, bool scale, string arguments) {}
+ public PluginGraphAttributes(string title, string category, string verticalLabel, bool scale, string arguments, TimeSpan? updateRate, int? width, int? height, IEnumerable<string>? order, string? totalValueLabel) {}
public string Arguments { get; }
public string Category { get; }
public int? Height { get; }
public string? Order { get; }
public bool Scale { get; }
public string Title { get; }
+ public string? TotalValueLabel { get; }
public TimeSpan? UpdateRate { get; }
public string VerticalLabel { get; }
public int? Width { get; }
+
+ public IEnumerable<string> EnumerateAttributes() {}
}
public readonly struct PluginFieldAttributes {
public PluginFieldAttributes(string label, PluginFieldGraphStyle graphStyle = PluginFieldGraphStyle.Default) {}
public PluginFieldAttributes(string label, PluginFieldGraphStyle graphStyle = PluginFieldGraphStyle.Default, PluginFieldNormalValueRange normalRangeForWarning = default, PluginFieldNormalValueRange normalRangeForCritical = default) {}
public PluginFieldAttributes(string label, PluginFieldGraphStyle graphStyle, PluginFieldNormalValueRange normalRangeForWarning, PluginFieldNormalValueRange normalRangeForCritical, string? negativeFieldName) {}
public PluginFieldGraphStyle GraphStyle { get; }
public string Label { get; }
public string? NegativeFieldName { get; }
public PluginFieldNormalValueRange NormalRangeForCritical { get; }
public PluginFieldNormalValueRange NormalRangeForWarning { get; }
}
public readonly struct PluginFieldNormalValueRange {
public static readonly PluginFieldNormalValueRange None; // = "Smdn.Net.MuninPlugin.PluginFieldNormalValueRange"
public static PluginFieldNormalValueRange CreateMax(double max) {}
public static PluginFieldNormalValueRange CreateMin(double min) {}
public static PluginFieldNormalValueRange CreateRange(double min, double max) {}
public bool HasValue { get; }
public double? Max { get; }
public double? Min { get; }
}
}
// API list generated by Smdn.Reflection.ReverseGenerating.ListApi.MSBuild.Tasks v1.4.1.0.
// Smdn.Reflection.ReverseGenerating.ListApi.Core v1.3.1.0 (https://github.com/smdn/Smdn.Reflection.ReverseGenerating)
diff --git a/doc/api-list/Smdn.Net.MuninNode/Smdn.Net.MuninNode-netstandard2.1.apilist.cs b/doc/api-list/Smdn.Net.MuninNode/Smdn.Net.MuninNode-netstandard2.1.apilist.cs
index 48ee340..db8e8bb 100644
--- a/doc/api-list/Smdn.Net.MuninNode/Smdn.Net.MuninNode-netstandard2.1.apilist.cs
+++ b/doc/api-list/Smdn.Net.MuninNode/Smdn.Net.MuninNode-netstandard2.1.apilist.cs
@@ -1,208 +1,214 @@
-// Smdn.Net.MuninNode.dll (Smdn.Net.MuninNode-1.3.0)
+// Smdn.Net.MuninNode.dll (Smdn.Net.MuninNode-2.0.0)
// Name: Smdn.Net.MuninNode
-// AssemblyVersion: 1.3.0.0
-// InformationalVersion: 1.3.0+191d215fe57392cb544e2ffea221644a1007cfc0
+// AssemblyVersion: 2.0.0.0
+// InformationalVersion: 2.0.0+0c4121c0bc87932e6486c3b38a123cb59460ac02
// TargetFramework: .NETStandard,Version=v2.1
// Configuration: Release
// Referenced assemblies:
// Microsoft.Extensions.DependencyInjection.Abstractions, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60
// Microsoft.Extensions.Logging.Abstractions, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60
// Smdn.Fundamental.Encoding.Buffer, Version=3.0.0.0, Culture=neutral
// Smdn.Fundamental.Exception, Version=3.0.0.0, Culture=neutral
// System.IO.Pipelines, Version=6.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51
// netstandard, Version=2.1.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51
#nullable enable annotations
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Smdn.Net.MuninNode;
using Smdn.Net.MuninPlugin;
namespace Smdn.Net.MuninNode {
- public class LocalNode : NodeBase {
- public LocalNode(IPluginProvider pluginProvider, string hostName, int port, ILogger? logger = null) {}
- public LocalNode(IPluginProvider pluginProvider, string hostName, int port, IServiceProvider? serviceProvider = null) {}
- public LocalNode(IReadOnlyCollection<IPlugin> plugins, int port, IServiceProvider? serviceProvider = null) {}
- public LocalNode(IReadOnlyCollection<IPlugin> plugins, string hostName, int port, IServiceProvider? serviceProvider = null) {}
+ public interface IAccessRule {
+ bool IsAcceptable(IPEndPoint remoteEndPoint);
+ }
+
+ public static class IAccessRuleServiceCollectionExtensions {
+ public static IServiceCollection AddMuninNodeAccessRule(this IServiceCollection services, IAccessRule accessRule) {}
+ public static IServiceCollection AddMuninNodeAccessRule(this IServiceCollection services, IReadOnlyList<IPAddress> addressListAllowFrom) {}
+ }
- public IPEndPoint LocalEndPoint { get; }
+ public abstract class LocalNode : NodeBase {
+ public static LocalNode Create(IPluginProvider pluginProvider, int port, string? hostName = null, IReadOnlyList<IPAddress>? addressListAllowFrom = null, IServiceProvider? serviceProvider = null) {}
+ public static LocalNode Create(IReadOnlyCollection<IPlugin> plugins, int port, string? hostName = null, IReadOnlyList<IPAddress>? addressListAllowFrom = null, IServiceProvider? serviceProvider = null) {}
+
+ protected LocalNode(IAccessRule? accessRule, ILogger? logger = null) {}
protected override Socket CreateServerSocket() {}
- protected override bool IsClientAcceptable(IPEndPoint remoteEndPoint) {}
+ protected virtual EndPoint GetLocalEndPointToBind() {}
}
public abstract class NodeBase :
IAsyncDisposable,
IDisposable
{
- private protected class PluginProvider : IPluginProvider {
- public PluginProvider(IReadOnlyCollection<IPlugin> plugins) {}
-
- public IReadOnlyCollection<IPlugin> Plugins { get; }
- public INodeSessionCallback? SessionCallback { get; }
- }
-
- protected NodeBase(IPluginProvider pluginProvider, string hostName, ILogger? logger) {}
- protected NodeBase(IReadOnlyCollection<IPlugin> plugins, string hostName, ILogger? logger) {}
+ protected NodeBase(IAccessRule? accessRule, ILogger? logger) {}
public virtual Encoding Encoding { get; }
- public string HostName { get; }
+ public abstract string HostName { get; }
+ public EndPoint LocalEndPoint { get; }
protected ILogger? Logger { get; }
public virtual Version NodeVersion { get; }
- [Obsolete("This member will be deprecated in future version.")]
- public IReadOnlyCollection<IPlugin> Plugins { get; }
+ public abstract IPluginProvider PluginProvider { get; }
public async ValueTask AcceptAsync(bool throwIfCancellationRequested, CancellationToken cancellationToken) {}
public async ValueTask AcceptSingleSessionAsync(CancellationToken cancellationToken = default) {}
protected abstract Socket CreateServerSocket();
protected virtual void Dispose(bool disposing) {}
public void Dispose() {}
public async ValueTask DisposeAsync() {}
protected virtual ValueTask DisposeAsyncCore() {}
- protected abstract bool IsClientAcceptable(IPEndPoint remoteEndPoint);
public void Start() {}
+ protected void ThrowIfPluginProviderIsNull() {}
}
}
namespace Smdn.Net.MuninPlugin {
public interface INodeSessionCallback {
ValueTask ReportSessionClosedAsync(string sessionId, CancellationToken cancellationToken);
ValueTask ReportSessionStartedAsync(string sessionId, CancellationToken cancellationToken);
}
public interface IPlugin {
IPluginDataSource DataSource { get; }
- PluginGraphAttributes GraphAttributes { get; }
+ IPluginGraphAttributes GraphAttributes { get; }
string Name { get; }
INodeSessionCallback? SessionCallback { get; }
}
public interface IPluginDataSource {
IReadOnlyCollection<IPluginField> Fields { get; }
}
public interface IPluginField {
PluginFieldAttributes Attributes { get; }
string Name { get; }
ValueTask<string> GetFormattedValueStringAsync(CancellationToken cancellationToken);
}
+ public interface IPluginGraphAttributes {
+ IEnumerable<string> EnumerateAttributes();
+ }
+
public interface IPluginProvider {
IReadOnlyCollection<IPlugin> Plugins { get; }
INodeSessionCallback? SessionCallback { get; }
}
public enum PluginFieldGraphStyle : int {
Area = 1,
AreaStack = 3,
Default = 0,
Line = 100,
LineStack = 200,
LineStackWidth1 = 201,
LineStackWidth2 = 202,
LineStackWidth3 = 203,
LineWidth1 = 101,
LineWidth2 = 102,
LineWidth3 = 103,
Stack = 2,
}
public class Plugin :
INodeSessionCallback,
IPlugin,
IPluginDataSource
{
public Plugin(string name, PluginGraphAttributes graphAttributes, IReadOnlyCollection<IPluginField> fields) {}
public IReadOnlyCollection<IPluginField> Fields { get; }
public PluginGraphAttributes GraphAttributes { get; }
public string Name { get; }
IPluginDataSource IPlugin.DataSource { get; }
+ IPluginGraphAttributes IPlugin.GraphAttributes { get; }
INodeSessionCallback? IPlugin.SessionCallback { get; }
IReadOnlyCollection<IPluginField> IPluginDataSource.Fields { get; }
protected virtual ValueTask ReportSessionClosedAsync(string sessionId, CancellationToken cancellationToken) {}
protected virtual ValueTask ReportSessionStartedAsync(string sessionId, CancellationToken cancellationToken) {}
ValueTask INodeSessionCallback.ReportSessionClosedAsync(string sessionId, CancellationToken cancellationToken) {}
ValueTask INodeSessionCallback.ReportSessionStartedAsync(string sessionId, CancellationToken cancellationToken) {}
}
public static class PluginFactory {
public static IPluginField CreateField(string label, Func<double?> fetchValue) {}
public static IPluginField CreateField(string label, PluginFieldGraphStyle graphStyle, Func<double?> fetchValue) {}
public static IPluginField CreateField(string label, PluginFieldGraphStyle graphStyle, PluginFieldNormalValueRange normalRangeForWarning, PluginFieldNormalValueRange normalRangeForCritical, Func<double?> fetchValue) {}
public static IPluginField CreateField(string name, string label, PluginFieldGraphStyle graphStyle, PluginFieldNormalValueRange normalRangeForWarning, PluginFieldNormalValueRange normalRangeForCritical, Func<double?> fetchValue) {}
public static IPluginField CreateField(string name, string label, PluginFieldGraphStyle graphStyle, PluginFieldNormalValueRange normalRangeForWarning, PluginFieldNormalValueRange normalRangeForCritical, string? negativeFieldName, Func<double?> fetchValue) {}
public static IPlugin CreatePlugin(string name, PluginGraphAttributes graphAttributes, IReadOnlyCollection<IPluginField> fields) {}
public static IPlugin CreatePlugin(string name, PluginGraphAttributes graphAttributes, IReadOnlyCollection<PluginFieldBase> fields) {}
public static IPlugin CreatePlugin(string name, PluginGraphAttributes graphAttributes, PluginFieldBase field) {}
public static IPlugin CreatePlugin(string name, string fieldLabel, Func<double?> fetchFieldValue, PluginGraphAttributes graphAttributes) {}
public static IPlugin CreatePlugin(string name, string fieldLabel, PluginFieldGraphStyle fieldGraphStyle, Func<double?> fetchFieldValue, PluginGraphAttributes graphAttributes) {}
}
public abstract class PluginFieldBase : IPluginField {
protected PluginFieldBase(string label, string? name, PluginFieldGraphStyle graphStyle = PluginFieldGraphStyle.Default, PluginFieldNormalValueRange normalRangeForWarning = default, PluginFieldNormalValueRange normalRangeForCritical = default) {}
protected PluginFieldBase(string label, string? name, PluginFieldGraphStyle graphStyle, PluginFieldNormalValueRange normalRangeForWarning, PluginFieldNormalValueRange normalRangeForCritical, string? negativeFieldName) {}
public PluginFieldGraphStyle GraphStyle { get; }
public string Label { get; }
public string Name { get; }
public string? NegativeFieldName { get; }
public PluginFieldNormalValueRange NormalRangeForCritical { get; }
public PluginFieldNormalValueRange NormalRangeForWarning { get; }
PluginFieldAttributes IPluginField.Attributes { get; }
protected abstract ValueTask<double?> FetchValueAsync(CancellationToken cancellationToken);
async ValueTask<string> IPluginField.GetFormattedValueStringAsync(CancellationToken cancellationToken) {}
}
- public sealed class PluginGraphAttributes {
- [Obsolete("This member will be deprecated in future version.")]
- public PluginGraphAttributes(string title, string category, string verticalLabel, bool scale, string arguments, TimeSpan updateRate, int? width = null, int? height = null) {}
- public PluginGraphAttributes(string title, string category, string verticalLabel, bool scale, string arguments, TimeSpan? updateRate = null, int? width = null, int? height = null) {}
- public PluginGraphAttributes(string title, string category, string verticalLabel, bool scale, string arguments, TimeSpan? updateRate, int? width, int? height, IEnumerable<string>? order) {}
+ public sealed class PluginGraphAttributes : IPluginGraphAttributes {
+ public PluginGraphAttributes(string title, string category, string verticalLabel, bool scale, string arguments) {}
+ public PluginGraphAttributes(string title, string category, string verticalLabel, bool scale, string arguments, TimeSpan? updateRate, int? width, int? height, IEnumerable<string>? order, string? totalValueLabel) {}
public string Arguments { get; }
public string Category { get; }
public int? Height { get; }
public string? Order { get; }
public bool Scale { get; }
public string Title { get; }
+ public string? TotalValueLabel { get; }
public TimeSpan? UpdateRate { get; }
public string VerticalLabel { get; }
public int? Width { get; }
+
+ public IEnumerable<string> EnumerateAttributes() {}
}
public readonly struct PluginFieldAttributes {
public PluginFieldAttributes(string label, PluginFieldGraphStyle graphStyle = PluginFieldGraphStyle.Default) {}
public PluginFieldAttributes(string label, PluginFieldGraphStyle graphStyle = PluginFieldGraphStyle.Default, PluginFieldNormalValueRange normalRangeForWarning = default, PluginFieldNormalValueRange normalRangeForCritical = default) {}
public PluginFieldAttributes(string label, PluginFieldGraphStyle graphStyle, PluginFieldNormalValueRange normalRangeForWarning, PluginFieldNormalValueRange normalRangeForCritical, string? negativeFieldName) {}
public PluginFieldGraphStyle GraphStyle { get; }
public string Label { get; }
public string? NegativeFieldName { get; }
public PluginFieldNormalValueRange NormalRangeForCritical { get; }
public PluginFieldNormalValueRange NormalRangeForWarning { get; }
}
public readonly struct PluginFieldNormalValueRange {
public static readonly PluginFieldNormalValueRange None; // = "Smdn.Net.MuninPlugin.PluginFieldNormalValueRange"
public static PluginFieldNormalValueRange CreateMax(double max) {}
public static PluginFieldNormalValueRange CreateMin(double min) {}
public static PluginFieldNormalValueRange CreateRange(double min, double max) {}
public bool HasValue { get; }
public double? Max { get; }
public double? Min { get; }
}
}
// API list generated by Smdn.Reflection.ReverseGenerating.ListApi.MSBuild.Tasks v1.4.1.0.
// Smdn.Reflection.ReverseGenerating.ListApi.Core v1.3.1.0 (https://github.com/smdn/Smdn.Reflection.ReverseGenerating)
Full changes
Full changes in this release:
diff --git a/src/Smdn.Net.MuninNode/Smdn.Net.MuninNode.csproj b/src/Smdn.Net.MuninNode/Smdn.Net.MuninNode.csproj
index 6384660..338df69 100644
--- a/src/Smdn.Net.MuninNode/Smdn.Net.MuninNode.csproj
+++ b/src/Smdn.Net.MuninNode/Smdn.Net.MuninNode.csproj
@@ -5,14 +5,11 @@ SPDX-License-Identifier: MIT
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.1;net6.0;net8.0</TargetFrameworks>
- <VersionPrefix>1.3.0</VersionPrefix>
+ <VersionPrefix>2.0.0</VersionPrefix>
<VersionSuffix></VersionSuffix>
- <PackageValidationBaselineVersion>1.2.0</PackageValidationBaselineVersion>
+ <!-- <PackageValidationBaselineVersion>2.0.0</PackageValidationBaselineVersion> -->
<RootNamespace/> <!-- empty the root namespace so that the namespace is determined only by the directory name, for code style rule IDE0030 -->
<Nullable>enable</Nullable>
- <DefineConstants
- Condition="$([MSBuild]::VersionGreaterThanOrEquals('$(NETCoreSdkVersion)', '7.0.0'))"
- >$(DefineConstants);LANG_VERSION_11_OR_GREATER</DefineConstants> <!-- required to use the UTF-8 string literals in C# 11 -->
<NoWarn>CS1591;$(NoWarn)</NoWarn> <!-- CS1591: Missing XML comment for publicly visible type or member 'Type_or_Member' -->
</PropertyGroup>
diff --git a/src/Smdn.Net.MuninNode/Smdn.Net.MuninNode/AddressListAccessRule.cs b/src/Smdn.Net.MuninNode/Smdn.Net.MuninNode/AddressListAccessRule.cs
new file mode 100644
index 0000000..9723a86
--- /dev/null
+++ b/src/Smdn.Net.MuninNode/Smdn.Net.MuninNode/AddressListAccessRule.cs
@@ -0,0 +1,38 @@
+// SPDX-FileCopyrightText: 2024 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Sockets;
+
+namespace Smdn.Net.MuninNode;
+
+internal sealed class AddressListAccessRule : IAccessRule {
+ private readonly IReadOnlyList<IPAddress> addressListAllowFrom;
+
+ public AddressListAccessRule(IReadOnlyList<IPAddress> addressListAllowFrom)
+ {
+ this.addressListAllowFrom = addressListAllowFrom ?? throw new ArgumentNullException(nameof(addressListAllowFrom));
+ }
+
+ public bool IsAcceptable(IPEndPoint remoteEndPoint)
+ {
+ if (remoteEndPoint is null)
+ throw new ArgumentNullException(nameof(remoteEndPoint));
+
+ var remoteAddress = remoteEndPoint.Address;
+
+ foreach (var addressAllowFrom in addressListAllowFrom) {
+ if (addressAllowFrom.AddressFamily == AddressFamily.InterNetwork) {
+ // test for client acceptability by IPv4 address
+ if (remoteAddress.IsIPv4MappedToIPv6)
+ remoteAddress = remoteAddress.MapToIPv4();
+ }
+
+ if (addressAllowFrom.Equals(remoteAddress))
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/src/Smdn.Net.MuninNode/Smdn.Net.MuninNode/IAccessRule.cs b/src/Smdn.Net.MuninNode/Smdn.Net.MuninNode/IAccessRule.cs
new file mode 100644
index 0000000..b87f0eb
--- /dev/null
+++ b/src/Smdn.Net.MuninNode/Smdn.Net.MuninNode/IAccessRule.cs
@@ -0,0 +1,9 @@
+// SPDX-FileCopyrightText: 2024 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+using System.Net;
+
+namespace Smdn.Net.MuninNode;
+
+public interface IAccessRule {
+ bool IsAcceptable(IPEndPoint remoteEndPoint);
+}
diff --git a/src/Smdn.Net.MuninNode/Smdn.Net.MuninNode/IAccessRuleServiceCollectionExtensions.cs b/src/Smdn.Net.MuninNode/Smdn.Net.MuninNode/IAccessRuleServiceCollectionExtensions.cs
new file mode 100644
index 0000000..96cf384
--- /dev/null
+++ b/src/Smdn.Net.MuninNode/Smdn.Net.MuninNode/IAccessRuleServiceCollectionExtensions.cs
@@ -0,0 +1,46 @@
+// SPDX-FileCopyrightText: 2024 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+using System;
+using System.Collections.Generic;
+using System.Net;
+
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+
+namespace Smdn.Net.MuninNode;
+
+public static class IAccessRuleServiceCollectionExtensions {
+ /// <param name="services">The <see cref="IServiceCollection"/> to add services to.</param>
+ /// <param name="addressListAllowFrom">The <see cref="IReadOnlyList{IPAddress}"/> indicates the read-only list of addresses allowed to access <see cref="NodeBase"/>.</param>
+ public static IServiceCollection AddMuninNodeAccessRule(
+ this IServiceCollection services,
+ IReadOnlyList<IPAddress> addressListAllowFrom
+ )
+ => AddMuninNodeAccessRule(
+ services: services ?? throw new ArgumentNullException(nameof(services)),
+ accessRule: new AddressListAccessRule(
+ addressListAllowFrom: addressListAllowFrom ?? throw new ArgumentNullException(nameof(addressListAllowFrom))
+ )
+ );
+
+ /// <param name="services">The <see cref="IServiceCollection"/> to add services to.</param>
+ /// <param name="accessRule">The <see cref="IAccessRule"/> which defines access rules to <see cref="NodeBase"/>.</param>
+ public static IServiceCollection AddMuninNodeAccessRule(
+ this IServiceCollection services,
+ IAccessRule accessRule
+ )
+ {
+#pragma warning disable CA1510
+ if (services is null)
+ throw new ArgumentNullException(nameof(services));
+ if (accessRule is null)
+ throw new ArgumentNullException(nameof(accessRule));
+#pragma warning restore CA1510
+
+ services.TryAdd(
+ ServiceDescriptor.Singleton(typeof(IAccessRule), accessRule)
+ );
+
+ return services;
+ }
+}
diff --git a/src/Smdn.Net.MuninNode/Smdn.Net.MuninNode/LocalNode.Create.cs b/src/Smdn.Net.MuninNode/Smdn.Net.MuninNode/LocalNode.Create.cs
new file mode 100644
index 0000000..28dc002
--- /dev/null
+++ b/src/Smdn.Net.MuninNode/Smdn.Net.MuninNode/LocalNode.Create.cs
@@ -0,0 +1,134 @@
+// SPDX-FileCopyrightText: 2024 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+using System;
+using System.Collections.Generic;
+using System.Net;
+
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+using Smdn.Net.MuninPlugin;
+
+namespace Smdn.Net.MuninNode;
+
+#pragma warning disable IDE0040
+partial class LocalNode {
+#pragma warning restore IDE0040
+ private class ReadOnlyCollectionPluginProvider : IPluginProvider {
+ public IReadOnlyCollection<IPlugin> Plugins { get; }
+ public INodeSessionCallback? SessionCallback => null;
+
+ public ReadOnlyCollectionPluginProvider(IReadOnlyCollection<IPlugin> plugins)
+ {
+ Plugins = plugins;
+ }
+ }
+
+ private sealed class ConcreteLocalNode : LocalNode {
+ public override IPluginProvider PluginProvider { get; }
+ public override string HostName { get; }
+
+ private readonly int port;
+
+ public ConcreteLocalNode(
+ IPluginProvider pluginProvider,
+ string hostName,
+ int port,
+ IAccessRule? accessRule,
+ IServiceProvider? serviceProvider = null
+ )
+ : base(
+ accessRule: accessRule ?? serviceProvider?.GetService<IAccessRule>(),
+ logger: serviceProvider?.GetService<ILoggerFactory>()?.CreateLogger<LocalNode>()
+ )
+ {
+ PluginProvider = pluginProvider;
+ HostName = hostName;
+ this.port = port;
+ }
+
+ protected override EndPoint GetLocalEndPointToBind()
+ => new IPEndPoint(
+ address: ((IPEndPoint)base.GetLocalEndPointToBind()).Address,
+ port: port
+ );
+ }
+
+ /// <summary>
+ /// Creates a new instance of the <see cref="LocalNode"/> class.
+ /// </summary>
+ /// <param name="plugins">
+ /// The readolny collection of <see cref="IPlugin"/>s provided by this node.
+ /// </param>
+ /// <param name="port">
+ /// The port number on which this node accepts connections.
+ /// </param>
+ /// <param name="hostName">
+ /// The hostname advertised by this node. This value is used as the display name for HTML generated by Munin.
+ /// If <see langword="null"/> or empty, the default hostname is used.
+ /// </param>
+ /// <param name="addressListAllowFrom">
+ /// The <see cref="IReadOnlyList{IPAddress}"/> indicates the read-only list of addresses allowed to access <see cref="LocalNode"/>.
+ /// </param>
+ /// <param name="serviceProvider">
+ /// The <see cref="IServiceProvider"/>.
+ /// This overload attempts to get a service of <see cref="ILoggerFactory"/>, to create an <see cref="ILogger"/>.
+ /// Also attempts to get a service of <see cref="IAccessRule"/> if <paramref name="addressListAllowFrom"/> is <see langword="null"/>.
+ /// </param>
+ /// <remarks>
+ /// Most Munin-Node uses port 4949 by default, but it is recommended to use other port numbers to avoid conflicts with other nodes.
+ /// </remarks>
+ public static LocalNode Create(
+ IReadOnlyCollection<IPlugin> plugins,
+ int port,
+ string? hostName = null,
+ IReadOnlyList<IPAddress>? addressListAllowFrom = null,
+ IServiceProvider? serviceProvider = null
+ )
+ => Create(
+ pluginProvider: new ReadOnlyCollectionPluginProvider(plugins ?? throw new ArgumentNullException(nameof(plugins))),
+ hostName: string.IsNullOrEmpty(hostName) ? DefaultHostName : hostName,
+ port: port,
+ addressListAllowFrom: addressListAllowFrom,
+ serviceProvider: serviceProvider
+ );
+
+ /// <summary>
+ /// Creates a new instance of the <see cref="LocalNode"/> class.
+ /// </summary>
+ /// <param name="pluginProvider">
+ /// The <see cref="IPluginProvider"/> that provides <see cref="IPlugin"/>s for this node.
+ /// </param>
+ /// <param name="port">
+ /// The port number on which this node accepts connections.
+ /// </param>
+ /// <param name="hostName">
+ /// The hostname advertised by this node. This value is used as the display name for HTML generated by Munin.
+ /// If <see langword="null"/> or empty, the default hostname is used.
+ /// </param>
+ /// <param name="addressListAllowFrom">
+ /// The <see cref="IReadOnlyList{IPAddress}"/> indicates the read-only list of addresses allowed to access <see cref="LocalNode"/>.
+ /// </param>
+ /// <param name="serviceProvider">
+ /// The <see cref="IServiceProvider"/>.
+ /// This overload attempts to get a service of <see cref="ILoggerFactory"/>, to create an <see cref="ILogger"/>.
+ /// Also attempts to get a service of <see cref="IAccessRule"/> if <paramref name="addressListAllowFrom"/> is <see langword="null"/>.
+ /// </param>
+ /// <remarks>
+ /// Most Munin-Node uses port 4949 by default, but it is recommended to use other port numbers to avoid conflicts with other nodes.
+ /// </remarks>
+ public static LocalNode Create(
+ IPluginProvider pluginProvider,
+ int port,
+ string? hostName = null,
+ IReadOnlyList<IPAddress>? addressListAllowFrom = null,
+ IServiceProvider? serviceProvider = null
+ )
+ => new ConcreteLocalNode(
+ pluginProvider: pluginProvider ?? throw new ArgumentNullException(nameof(pluginProvider)),
+ hostName: string.IsNullOrEmpty(hostName) ? DefaultHostName : hostName,
+ port: port,
+ accessRule: addressListAllowFrom is null ? null : new AddressListAccessRule(addressListAllowFrom),
+ serviceProvider: serviceProvider
+ );
+}
diff --git a/src/Smdn.Net.MuninNode/Smdn.Net.MuninNode/LocalNode.cs b/src/Smdn.Net.MuninNode/Smdn.Net.MuninNode/LocalNode.cs
index 9810630..dae7c3c 100644
--- a/src/Smdn.Net.MuninNode/Smdn.Net.MuninNode/LocalNode.cs
+++ b/src/Smdn.Net.MuninNode/Smdn.Net.MuninNode/LocalNode.cs
@@ -1,167 +1,66 @@
// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
// SPDX-License-Identifier: MIT
using System;
-using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
-using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
-using Smdn.Net.MuninPlugin;
-
namespace Smdn.Net.MuninNode;
/// <summary>
/// Implement a <c>Munin-Node</c> that acts as a node on the localhost and only accepts connections from the local loopback address (127.0.0.1, ::1).
/// </summary>
-public class LocalNode : NodeBase {
+public abstract partial class LocalNode : NodeBase {
private const string DefaultHostName = "munin-node.localhost";
- private static readonly int MaxClients = 1;
-
- public IPEndPoint LocalEndPoint { get; }
-
- /// <inheritdoc cref="LocalNode(IReadOnlyCollection{IPlugin}, string, int, IServiceProvider)"/>
- public LocalNode(
- IReadOnlyCollection<IPlugin> plugins,
- int port,
- IServiceProvider? serviceProvider = null
- )
- : this(
- plugins: plugins,
- hostName: DefaultHostName,
- port: port,
- serviceProvider: serviceProvider
- )
- {
- }
-
- /// <summary>
- /// Initializes a new instance of the <see cref="LocalNode"/> class.
- /// </summary>
- /// <param name="plugins">
- /// The collection of plugins provided by this node.
- /// </param>
- /// <param name="hostName">
- /// The hostname advertised by this node. This value is used as the display name for HTML generated by Munin.
- /// </param>
- /// <param name="port">
- /// The port number on which this node accepts connections.
- /// </param>
- /// <param name="serviceProvider">
- /// The <see cref="IServiceProvider"/>.
- /// This constructor overload attempts to get a service of <see cref="ILoggerFactory"/>, to create an <see cref="ILogger"/>.
- /// </param>
- /// <remarks>
- /// Most Munin-Node uses port 4949 by default, but it is recommended to use other port numbers to avoid conflicts with other nodes.
- /// </remarks>
- public LocalNode(
- IReadOnlyCollection<IPlugin> plugins,
- string hostName,
- int port,
- IServiceProvider? serviceProvider = null
- )
- : this(
- pluginProvider: new PluginProvider(plugins ?? throw new ArgumentNullException(nameof(plugins))),
- hostName: hostName,
- port: port,
- serviceProvider: serviceProvider
- )
- {
- }
-
- /// <summary>
- /// Initializes a new instance of the <see cref="LocalNode"/> class.
- /// </summary>
- /// <param name="pluginProvider">
- /// The <see cref="IPluginProvider"/> that provides <see cref="IPlugin"/>s for this node.
- /// </param>
- /// <param name="hostName">
- /// The hostname advertised by this node. This value is used as the display name for HTML generated by Munin.
- /// </param>
- /// <param name="port">
- /// The port number on which this node accepts connections.
- /// </param>
- /// <param name="serviceProvider">
- /// The <see cref="IServiceProvider"/>.
- /// This constructor overload attempts to get a service of <see cref="ILoggerFactory"/>, to create an <see cref="ILogger"/>.
- /// </param>
- /// <remarks>
- /// Most Munin-Node uses port 4949 by default, but it is recommended to use other port numbers to avoid conflicts with other nodes.
- /// </remarks>
- public LocalNode(
- IPluginProvider pluginProvider,
- string hostName,
- int port,
- IServiceProvider? serviceProvider = null
- )
- : this(
- pluginProvider: pluginProvider ?? throw new ArgumentNullException(nameof(pluginProvider)),
- hostName: hostName,
- port: port,
- logger: serviceProvider?.GetService<ILoggerFactory>()?.CreateLogger<LocalNode>()
- )
- {
- }
/// <summary>
/// Initializes a new instance of the <see cref="LocalNode"/> class.
/// </summary>
- /// <param name="pluginProvider">
- /// The <see cref="IPluginProvider"/> that provides <see cref="IPlugin"/>s for this node.
- /// </param>
- /// <param name="hostName">
- /// The hostname advertised by this node. This value is used as the display name for HTML generated by Munin.
- /// </param>
- /// <param name="port">
- /// The port number on which this node accepts connections.
+ /// <param name="accessRule">
+ /// The <see cref="IAccessRule"/> to determine whether to accept or reject a remote host that connects to <see cref="LocalNode"/>.
/// </param>
/// <param name="logger">
/// The <see cref="ILogger"/> to report the situation.
/// </param>
- /// <remarks>
- /// Most Munin-Node uses port 4949 by default, but it is recommended to use other port numbers to avoid conflicts with other nodes.
- /// </remarks>
- public LocalNode(
- IPluginProvider pluginProvider,
- string hostName,
- int port,
+ protected LocalNode(
+ IAccessRule? accessRule,
ILogger? logger = null
)
: base(
- pluginProvider: pluginProvider,
- hostName: hostName,
+ accessRule: accessRule,
logger: logger
)
{
- if (Socket.OSSupportsIPv6) {
- LocalEndPoint = new IPEndPoint(
- address: IPAddress.IPv6Loopback,
- port: port
- );
}
-#pragma warning disable IDE0045
- else if (Socket.OSSupportsIPv4) {
-#pragma warning restore IDE0045
- LocalEndPoint = new IPEndPoint(
- address: IPAddress.Loopback,
- port: port
+
+ /// <summary>
+ /// Gets the <see cref="EndPoint"/> to be bound as the <c>Munin-Node</c>'s endpoint.
+ /// </summary>
+ /// <returns>
+ /// An <see cref="EndPoint"/>.
+ /// The default implementation returns an <see cref="IPEndPoint"/> with the port number <c>0</c>
+ /// and <see cref="IPAddress.IPv6Loopback"/>/<see cref="IPAddress.Loopback"/>.
+ /// </returns>
+ protected virtual EndPoint GetLocalEndPointToBind()
+ => new IPEndPoint(
+ address:
+ Socket.OSSupportsIPv6
+ ? IPAddress.IPv6Loopback
+ : Socket.OSSupportsIPv4
+ ? IPAddress.Loopback
+ : throw new NotSupportedException(),
+ port: 0
);
- }
- else {
- throw new NotSupportedException();
- }
- }
protected override Socket CreateServerSocket()
{
+ const int MaxClients = 1;
+
Socket? server = null;
try {
- var endPoint = new IPEndPoint(
- address: Socket.OSSupportsIPv6 ? IPAddress.IPv6Any : IPAddress.Any,
- port: LocalEndPoint.Port
- );
+ var endPoint = GetLocalEndPointToBind();
server = new Socket(
endPoint.AddressFamily,
@@ -179,15 +78,8 @@ public class LocalNode : NodeBase {
return server;
}
catch {
-#pragma warning disable CA1508
server?.Dispose();
-#pragma warning restore CA1508
throw;
}
}
-
- protected override bool IsClientAcceptable(IPEndPoint remoteEndPoint)
- => IPAddress.IsLoopback(
- (remoteEndPoint ?? throw new ArgumentNullException(nameof(remoteEndPoint))).Address
- );
}
diff --git a/src/Smdn.Net.MuninNode/Smdn.Net.MuninNode/NodeBase.cs b/src/Smdn.Net.MuninNode/Smdn.Net.MuninNode/NodeBase.cs
index df4b7a1..53eca82 100644
--- a/src/Smdn.Net.MuninNode/Smdn.Net.MuninNode/NodeBase.cs
+++ b/src/Smdn.Net.MuninNode/Smdn.Net.MuninNode/NodeBase.cs
@@ -32,58 +32,26 @@ namespace Smdn.Net.MuninNode;
public abstract class NodeBase : IDisposable, IAsyncDisposable {
private static readonly Version DefaultNodeVersion = new(1, 0, 0, 0);
- [Obsolete("This member will be deprecated in future version.")]
- public IReadOnlyCollection<IPlugin> Plugins => pluginProvider.Plugins;
-
- public string HostName { get; }
+ public abstract IPluginProvider PluginProvider { get; }
+ public abstract string HostName { get; }
public virtual Version NodeVersion => DefaultNodeVersion;
public virtual Encoding Encoding => Encoding.Default;
- private readonly IPluginProvider pluginProvider;
-
protected ILogger? Logger { get; }
- private Socket? server;
-
- protected NodeBase(
- IReadOnlyCollection<IPlugin> plugins,
- string hostName,
- ILogger? logger
- )
- : this(
- pluginProvider: new PluginProvider(plugins ?? throw new ArgumentNullException(nameof(plugins))),
- hostName: hostName,
- logger: logger
- )
- {
- }
+ private readonly IAccessRule? accessRule;
- private protected class PluginProvider : IPluginProvider {
- public IReadOnlyCollection<IPlugin> Plugins { get; }
- public INodeSessionCallback? SessionCallback => null;
+ private Socket? server;
- public PluginProvider(IReadOnlyCollection<IPlugin> plugins)
- {
- Plugins = plugins;
- }
- }
+ public EndPoint LocalEndPoint => server?.LocalEndPoint ?? throw new InvalidOperationException("not yet bound or already disposed");
protected NodeBase(
- IPluginProvider pluginProvider,
- string hostName,
+ IAccessRule? accessRule,
ILogger? logger
)
{
- this.pluginProvider = pluginProvider ?? throw new ArgumentNullException(nameof(pluginProvider));
-
- if (hostName == null)
- throw new ArgumentNullException(nameof(hostName));
- if (hostName.Length == 0)
- throw ExceptionUtils.CreateArgumentMustBeNonEmptyString(nameof(hostName));
-
- HostName = hostName;
-
+ this.accessRule = accessRule;
Logger = logger;
}
@@ -147,6 +115,12 @@ public abstract class NodeBase : IDisposable, IAsyncDisposable {
server = null!;
}
+ protected void ThrowIfPluginProviderIsNull()
+ {
+ if (PluginProvider is null)
+ throw new InvalidOperationException($"{nameof(PluginProvider)} cannot be null");
+ }
+
protected abstract Socket CreateServerSocket();
public void Start()
@@ -161,8 +135,6 @@ public abstract class NodeBase : IDisposable, IAsyncDisposable {
Logger?.LogInformation("started (end point: {LocalEndPoint})", server.LocalEndPoint);
}
- protected abstract bool IsClientAcceptable(IPEndPoint remoteEndPoint);
-
/// <summary>
/// Starts accepting multiple sessions.
/// The <see cref="ValueTask" /> this method returns will never complete unless the cancellation requested by the <paramref name="cancellationToken" />.
@@ -213,6 +185,8 @@ public abstract class NodeBase : IDisposable, IAsyncDisposable {
if (server is null)
throw new InvalidOperationException("not started or already closed");
+ ThrowIfPluginProviderIsNull();
+
Logger?.LogInformation("accepting...");
var client = await server
@@ -239,7 +213,7 @@ public abstract class NodeBase : IDisposable, IAsyncDisposable {
return;
}
- if (!IsClientAcceptable(remoteEndPoint)) {
+ if (accessRule is not null && !accessRule.IsAcceptable(remoteEndPoint)) {
Logger?.LogWarning("access refused: {RemoteEndPoint}", remoteEndPoint);
return;
}
@@ -289,10 +263,10 @@ public abstract class NodeBase : IDisposable, IAsyncDisposable {
Logger?.LogInformation("[{RemoteEndPoint}] session started; ID={SessionId}", remoteEndPoint, sessionId);
try {
- if (pluginProvider.SessionCallback is not null)
- await pluginProvider.SessionCallback.ReportSessionStartedAsync(sessionId, cancellationToken).ConfigureAwait(false);
+ if (PluginProvider.SessionCallback is not null)
+ await PluginProvider.SessionCallback.ReportSessionStartedAsync(sessionId, cancellationToken).ConfigureAwait(false);
- foreach (var plugin in pluginProvider.Plugins) {
+ foreach (var plugin in PluginProvider.Plugins) {
if (plugin.SessionCallback is not null)
await plugin.SessionCallback.ReportSessionStartedAsync(sessionId, cancellationToken).ConfigureAwait(false);
}
@@ -308,13 +282,13 @@ public abstract class NodeBase : IDisposable, IAsyncDisposable {
Logger?.LogInformation("[{RemoteEndPoint}] session closed; ID={SessionId}", remoteEndPoint, sessionId);
}
finally {
- foreach (var plugin in pluginProvider.Plugins) {
+ foreach (var plugin in PluginProvider.Plugins) {
if (plugin.SessionCallback is not null)
await plugin.SessionCallback.ReportSessionClosedAsync(sessionId, cancellationToken).ConfigureAwait(false);
}
- if (pluginProvider.SessionCallback is not null)
- await pluginProvider.SessionCallback.ReportSessionClosedAsync(sessionId, cancellationToken).ConfigureAwait(false);
+ if (PluginProvider.SessionCallback is not null)
+ await PluginProvider.SessionCallback.ReportSessionClosedAsync(sessionId, cancellationToken).ConfigureAwait(false);
}
}
finally {
@@ -483,20 +457,13 @@ public abstract class NodeBase : IDisposable, IAsyncDisposable {
var reader = new SequenceReader<byte>(buffer);
const byte LF = (byte)'\n';
-#pragma warning disable SA1003
if (
-#if LANG_VERSION_11_OR_GREATER
- !reader.TryReadTo(out line, delimiter: "\r\n"u8, advancePastDelimiter: true)
-#else
- !reader.TryReadTo(out line, delimiter: CRLF.Span, advancePastDelimiter: true)
-#endif
- &&
+ !reader.TryReadTo(out line, delimiter: "\r\n"u8, advancePastDelimiter: true) &&
!reader.TryReadTo(out line, delimiter: LF, advancePastDelimiter: true)
) {
line = default;
return false;
}
-#pragma warning restore SA1003
#if SYSTEM_BUFFERS_SEQUENCEREADER_UNREADSEQUENCE
buffer = reader.UnreadSequence;
@@ -508,10 +475,6 @@ public abstract class NodeBase : IDisposable, IAsyncDisposable {
}
}
-#if !LANG_VERSION_11_OR_GREATER
- private static readonly ReadOnlyMemory<byte> CRLF = Encoding.ASCII.GetBytes("\r\n");
-#endif
-
private static bool ExpectCommand(
ReadOnlySequence<byte> commandLine,
ReadOnlySpan<byte> expectedCommand,
@@ -547,7 +510,6 @@ public abstract class NodeBase : IDisposable, IAsyncDisposable {
private static readonly byte CommandQuitShort = (byte)'.';
-#if LANG_VERSION_11_OR_GREATER
private ValueTask RespondToCommandAsync(
Socket client,
ReadOnlySequence<byte> commandLine,
@@ -592,62 +554,6 @@ public abstract class NodeBase : IDisposable, IAsyncDisposable {
);
}
}
-#else
- private static readonly ReadOnlyMemory<byte> commandFetch = Encoding.ASCII.GetBytes("fetch");
- private static readonly ReadOnlyMemory<byte> commandNodes = Encoding.ASCII.GetBytes("nodes");
- private static readonly ReadOnlyMemory<byte> commandList = Encoding.ASCII.GetBytes("list");
- private static readonly ReadOnlyMemory<byte> commandConfig = Encoding.ASCII.GetBytes("config");
- private static readonly ReadOnlyMemory<byte> commandQuit = Encoding.ASCII.GetBytes("quit");
- private static readonly ReadOnlyMemory<byte> commandCap = Encoding.ASCII.GetBytes("cap");
- private static readonly ReadOnlyMemory<byte> commandVersion = Encoding.ASCII.GetBytes("version");
-
- private ValueTask RespondToCommandAsync(
- Socket client,
- ReadOnlySequence<byte> commandLine,
- CancellationToken cancellationToken
- )
- {
- cancellationToken.ThrowIfCancellationRequested();
-
- if (ExpectCommand(commandLine, commandFetch.Span, out var fetchArguments)) {
- return ProcessCommandFetchAsync(client, fetchArguments, cancellationToken);
- }
- else if (ExpectCommand(commandLine, commandNodes.Span, out _)) {
- return ProcessCommandNodesAsync(client, cancellationToken);
- }
- else if (ExpectCommand(commandLine, commandList.Span, out var listArguments)) {
- return ProcessCommandListAsync(client, listArguments, cancellationToken);
- }
- else if (ExpectCommand(commandLine, commandConfig.Span, out var configArguments)) {
- return ProcessCommandConfigAsync(client, configArguments, cancellationToken);
- }
- else if (
- ExpectCommand(commandLine, commandQuit.Span, out _) ||
- (commandLine.Length == 1 && commandLine.FirstSpan[0] == commandQuitShort)
- ) {
- client.Close();
-#if SYSTEM_THREADING_TASKS_VALUETASK_COMPLETEDTASK
- return ValueTask.CompletedTask;
-#else
- return default;
-#endif
- }
- else if (ExpectCommand(commandLine, commandCap.Span, out var capArguments)) {
- return ProcessCommandCapAsync(client, capArguments, cancellationToken);
- }
- else if (ExpectCommand(commandLine, commandVersion.Span, out _)) {
- return ProcessCommandVersionAsync(client, cancellationToken);
- }
- else {
- return SendResponseAsync(
- client: client,
- encoding: Encoding,
- responseLine: "# Unknown command. Try cap, list, nodes, config, fetch, version or quit",
- cancellationToken: cancellationToken
- );
- }
- }
-#endif
#pragma warning disable IDE0230
private static readonly ReadOnlyMemory<byte> EndOfLine = new[] { (byte)'\n' };
@@ -756,11 +662,13 @@ public abstract class NodeBase : IDisposable, IAsyncDisposable {
CancellationToken cancellationToken
)
{
+ ThrowIfPluginProviderIsNull();
+
// XXX: ignore [node] arguments
return SendResponseAsync(
client: client,
encoding: Encoding,
- responseLine: string.Join(" ", pluginProvider.Plugins.Select(static plugin => plugin.Name)),
+ responseLine: string.Join(" ", PluginProvider.Plugins.Select(static plugin => plugin.Name)),
cancellationToken: cancellationToken
);
}
@@ -771,7 +679,9 @@ public abstract class NodeBase : IDisposable, IAsyncDisposable {
CancellationToken cancellationToken
)
{
- var plugin = pluginProvider.Plugins.FirstOrDefault(
+ ThrowIfPluginProviderIsNull();
+
+ var plugin = PluginProvider.Plugins.FirstOrDefault(
plugin => string.Equals(Encoding.GetString(arguments), plugin.Name, StringComparison.Ordinal)
);
@@ -829,7 +739,9 @@ public abstract class NodeBase : IDisposable, IAsyncDisposable {
CancellationToken cancellationToken
)
{
- var plugin = pluginProvider.Plugins.FirstOrDefault(
+ ThrowIfPluginProviderIsNull();
+
+ var plugin = PluginProvider.Plugins.FirstOrDefault(
plugin => string.Equals(Encoding.GetString(arguments), plugin.Name, StringComparison.Ordinal)
);
@@ -842,24 +754,11 @@ public abstract class NodeBase : IDisposable, IAsyncDisposable {
);
}
- var graphAttrs = plugin.GraphAttributes;
-
- var responseLines = new List<string>() {
- $"graph_title {graphAttrs.Title}",
- $"graph_category {graphAttrs.Category}",
- $"graph_args {graphAttrs.Arguments}",
- $"graph_scale {(graphAttrs.Scale ? "yes" : "no")}",
- $"graph_vlabel {graphAttrs.VerticalLabel}",
- };
+ var responseLines = new List<string>(capacity: 20);
- if (graphAttrs.UpdateRate.HasValue)
- responseLines.Add($"update_rate {(int)graphAttrs.UpdateRate.Value.TotalSeconds}");
- if (graphAttrs.Width.HasValue)
- responseLines.Add($"graph_width {graphAttrs.Width.Value}");
- if (graphAttrs.Height.HasValue)
- responseLines.Add($"graph_height {graphAttrs.Height.Value}");
- if (!string.IsNullOrEmpty(graphAttrs.Order))
- responseLines.Add($"graph_order {graphAttrs.Order}");
+ responseLines.AddRange(
+ plugin.GraphAttributes.EnumerateAttributes()
+ );
// The fields referenced by {fieldname}.negative must be defined ahread of others,
// and thus lists the negative field settings first.
diff --git a/src/Smdn.Net.MuninNode/Smdn.Net.MuninPlugin/IPlugin.cs b/src/Smdn.Net.MuninNode/Smdn.Net.MuninPlugin/IPlugin.cs
index cf725ac..783a667 100644
--- a/src/Smdn.Net.MuninNode/Smdn.Net.MuninPlugin/IPlugin.cs
+++ b/src/Smdn.Net.MuninNode/Smdn.Net.MuninPlugin/IPlugin.cs
@@ -12,9 +12,10 @@ public interface IPlugin {
/// <remarks>This value is used as the plugin name returned by the 'list' argument, or the plugin name specified by the 'fetch' argument.</remarks>
string Name { get; }
- /// <summary>Gets a <see cref="PluginGraphAttributes"/> that represents the graph attributes when the field values (<see cref="IPluginField"/>) are drawn as a graph.</summary>
+ /// <summary>Gets a <see cref="IPluginGraphAttributes"/> that represents the graph attributes when the field values (<see cref="IPluginField"/>) are drawn as a graph.</summary>
+ /// <seealso cref="IPluginGraphAttributes"/>
/// <seealso cref="PluginGraphAttributes"/>
- PluginGraphAttributes GraphAttributes { get; }
+ IPluginGraphAttributes GraphAttributes { get; }
/// <summary>Gets a <see cref="IPluginDataSource"/> that serves as the data source for the plugin.</summary>
/// <seealso cref="IPluginDataSource"/>
diff --git a/src/Smdn.Net.MuninNode/Smdn.Net.MuninPlugin/IPluginGraphAttributes.cs b/src/Smdn.Net.MuninNode/Smdn.Net.MuninPlugin/IPluginGraphAttributes.cs
new file mode 100644
index 0000000..b659b61
--- /dev/null
+++ b/src/Smdn.Net.MuninNode/Smdn.Net.MuninPlugin/IPluginGraphAttributes.cs
@@ -0,0 +1,17 @@
+// SPDX-FileCopyrightText: 2024 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+using System.Collections.Generic;
+
+namespace Smdn.Net.MuninPlugin;
+
+/// <summary>
+/// Provides an interface that abstracts the plugin graph attributes, related to the drawing of a single graph.
+/// </summary>
+/// <seealso href="https://guide.munin-monitoring.org/en/latest/reference/plugin.html#global-attributes">Plugin reference - Global attributes</seealso>
+public interface IPluginGraphAttributes {
+ /// <summary>
+ /// Enumerates plugin graph attributes defined by types that implement this interface.
+ /// </summary>
+ /// <returns><see cref="IEnumerable{String}"/> that enumerates graph attributes.</returns>
+ IEnumerable<string> EnumerateAttributes();
+}
diff --git a/src/Smdn.Net.MuninNode/Smdn.Net.MuninPlugin/Plugin.cs b/src/Smdn.Net.MuninNode/Smdn.Net.MuninPlugin/Plugin.cs
index 8a599bf..16f7d37 100644
--- a/src/Smdn.Net.MuninNode/Smdn.Net.MuninPlugin/Plugin.cs
+++ b/src/Smdn.Net.MuninNode/Smdn.Net.MuninPlugin/Plugin.cs
@@ -15,6 +15,8 @@ public class Plugin : IPlugin, IPluginDataSource, INodeSessionCallback {
public IReadOnlyCollection<IPluginField> Fields { get; }
#pragma warning disable CA1033
+ IPluginGraphAttributes IPlugin.GraphAttributes => GraphAttributes;
+
IPluginDataSource IPlugin.DataSource => this;
IReadOnlyCollection<IPluginField> IPluginDataSource.Fields => Fields;
diff --git a/src/Smdn.Net.MuninNode/Smdn.Net.MuninPlugin/PluginGraphAttributes.cs b/src/Smdn.Net.MuninNode/Smdn.Net.MuninPlugin/PluginGraphAttributes.cs
index 3e76d32..8358320 100644
--- a/src/Smdn.Net.MuninNode/Smdn.Net.MuninPlugin/PluginGraphAttributes.cs
+++ b/src/Smdn.Net.MuninNode/Smdn.Net.MuninPlugin/PluginGraphAttributes.cs
@@ -11,8 +11,9 @@ namespace Smdn.Net.MuninPlugin;
/// Defines graph attributes that should be returned when the plugin is called with the 'config' argument.
/// This type represents the collection of 'field name attributes'.
/// </summary>
+/// <seealso cref="IPluginGraphAttributes"/>
/// <seealso href="https://guide.munin-monitoring.org/en/latest/reference/plugin.html#global-attributes">Plugin reference - Global attributes</seealso>
-public sealed class PluginGraphAttributes {
+public sealed class PluginGraphAttributes : IPluginGraphAttributes {
/// <summary>Gets a value for the <c>graph_title</c>.</summary>
/// <seealso href="https://guide.munin-monitoring.org/en/latest/reference/plugin.html#graph-title">Plugin reference - Global attributes - graph_title</seealso>
public string Title { get; }
@@ -51,40 +52,16 @@ public sealed class PluginGraphAttributes {
/// <seealso href="https://guide.munin-monitoring.org/en/latest/reference/plugin.html#graph-order">Plugin reference - Global attributes - graph_order</seealso>
public string? Order { get; }
- public PluginGraphAttributes(
- string title,
- string category,
- string verticalLabel,
- bool scale,
- string arguments,
- TimeSpan? updateRate = null,
- int? width = null,
- int? height = null
- )
- : this(
- title: title,
- category: category,
- verticalLabel: verticalLabel,
- scale: scale,
- arguments: arguments,
- updateRate: updateRate,
- width: width,
- height: height,
- order: null
- )
- {
- }
+ /// <summary>Gets a value for the <c>graph_total</c>.</summary>
+ /// <seealso href="https://guide.munin-monitoring.org/en/latest/reference/plugin.html#graph-total">Plugin reference - Global attributes - graph_total</seealso>
+ public string? TotalValueLabel { get; }
- [Obsolete("This member will be deprecated in future version.")]
public PluginGraphAttributes(
string title,
string category,
string verticalLabel,
bool scale,
- string arguments,
- TimeSpan updateRate,
- int? width = null,
- int? height = null
+ string arguments
)
: this(
title: title,
@@ -92,10 +69,11 @@ public sealed class PluginGraphAttributes {
verticalLabel: verticalLabel,
scale: scale,
arguments: arguments,
- updateRate: updateRate,
- width: width,
- height: height,
- order: null
+ updateRate: null,
+ width: null,
+ height: null,
+ order: null,
+ totalValueLabel: null
)
{
}
@@ -109,7 +87,8 @@ public sealed class PluginGraphAttributes {
TimeSpan? updateRate,
int? width,
int? height,
- IEnumerable<string>? order
+ IEnumerable<string>? order,
+ string? totalValueLabel
)
{
if (title == null)
@@ -141,10 +120,31 @@ public sealed class PluginGraphAttributes {
Width = width;
Height = height;
Order = order is null ? null : string.Join(" ", order);
+ TotalValueLabel = totalValueLabel;
if (updateRate.HasValue && updateRate.Value < TimeSpan.FromSeconds(1.0))
throw new ArgumentOutOfRangeException(nameof(updateRate), updateRate, "must be at least 1 seconds");
UpdateRate = updateRate;
}
+
+ public IEnumerable<string> EnumerateAttributes()
+ {
+ yield return $"graph_title {Title}";
+ yield return $"graph_category {Category}";
+ yield return $"graph_args {Arguments}";
+ yield return $"graph_scale {(Scale ? "yes" : "no")}";
+ yield return $"graph_vlabel {VerticalLabel}";
+
+ if (UpdateRate.HasValue)
+ yield return $"update_rate {(int)UpdateRate.Value.TotalSeconds}";
+ if (Width.HasValue)
+ yield return $"graph_width {Width.Value}";
+ if (Height.HasValue)
+ yield return $"graph_height {Height.Value}";
+ if (!string.IsNullOrEmpty(Order))
+ yield return $"graph_order {Order}";
+ if (!string.IsNullOrEmpty(TotalValueLabel))
+ yield return $"graph_total {TotalValueLabel}";
+ }
}
Notes
Full Changelog: releases/Smdn.Net.MuninNode-1.3.0...releases/Smdn.Net.MuninNode-2.0.0