Files
natsnet/dotnet/src/ZB.MOM.NatsNet.Server/Monitor/MonitorHelpers.cs
T
Joseph Doherty 4e63820e7a feat(batch42): implement foundation helpers — msgtrace, monitor helpers, scheduler
Group A: Create MsgTrace.cs with TraceCompressionType, MsgTraceState (factory
methods, pipeline event helpers, sendEvent/sendEventFromJetStream, trace header
injection) and MsgTraceHelper (sample, genHeaderMapIfTraceHeadersPresent,
initAndSendIngressErrEvent, isMsgTraceEnabled, msgTraceSupport). Adds Trace field
to PublishArgument and Trace accessor to ParseContext.

Group C: Create MonitorHelpers.cs with GatewayzOptions, Gatewayz, RemoteGatewayz,
AccountGatewayz, ExtImport, ExtServiceLatency types; plus 25 standalone helper
functions (newSubsDetailList, newSubsList, createProxyInfo, makePeerCerts,
decodeBool, decodeUint64, decodeInt, decodeState, decodeSubs, newSubDetail,
newClientSubDetail, myUptime, tlsCertNotAfter, urlsToStrings, getPinnedCertsAsSlice,
getMonitorGWOptions, createOutboundRemoteGatewayz, createOutboundAccountsGatewayz,
createAccountOutboundGatewayz, createInboundAccountsGatewayz,
createInboundAccountGatewayz, ResponseHandler, handleResponse, newExtServiceLatency,
newExtImport).

Group D: Implement GetScheduledMessages in MsgScheduling; add Seq field to InMsg
for out-of-band scheduling sort. Group B (GatewayInterestMode.String) already complete.
2026-03-01 08:42:50 -05:00

803 lines
31 KiB
C#

// Copyright 2013-2026 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Adapted from server/monitor.go in the NATS server Go source.
using System.Net;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Text.Json.Serialization;
using ZB.MOM.NatsNet.Server.Internal;
namespace ZB.MOM.NatsNet.Server;
// ============================================================================
// GatewayzOptions — query options for the Gatewayz endpoint
// Mirrors Go <c>GatewayzOptions</c> struct in server/monitor.go.
// ============================================================================
/// <summary>
/// Options that control the output of a <c>Gatewayz</c> monitoring query.
/// Mirrors Go <c>GatewayzOptions</c> struct in server/monitor.go.
/// </summary>
public sealed class GatewayzOptions
{
/// <summary>When non-empty, limits output to the gateway with this name. Mirrors Go <c>Name</c>.</summary>
[JsonPropertyName("name")]
public string Name { get; set; } = string.Empty;
/// <summary>When true, includes accounts with their interest. Mirrors Go <c>Accounts</c>.</summary>
[JsonPropertyName("accounts")]
public bool Accounts { get; set; }
/// <summary>Limits accounts to this specific name (implies <see cref="Accounts"/>). Mirrors Go <c>AccountName</c>.</summary>
[JsonPropertyName("account_name")]
public string AccountName { get; set; } = string.Empty;
/// <summary>When true, subscription subjects are included in account results. Mirrors Go <c>AccountSubscriptions</c>.</summary>
[JsonPropertyName("subscriptions")]
public bool AccountSubscriptions { get; set; }
/// <summary>When true, verbose subscription details are included. Mirrors Go <c>AccountSubscriptionsDetail</c>.</summary>
[JsonPropertyName("subscriptions_detail")]
public bool AccountSubscriptionsDetail { get; set; }
}
// ============================================================================
// Gatewayz — top-level gateway monitoring response
// Mirrors Go <c>Gatewayz</c> struct in server/monitor.go.
// ============================================================================
/// <summary>
/// Top-level response type for the <c>/gatewayz</c> monitoring endpoint.
/// Mirrors Go <c>Gatewayz</c> struct in server/monitor.go.
/// </summary>
public sealed class Gatewayz
{
[JsonPropertyName("server_id")]
public string Id { get; set; } = string.Empty;
[JsonPropertyName("now")]
public DateTime Now { get; set; }
[JsonPropertyName("name")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Name { get; set; }
[JsonPropertyName("host")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Host { get; set; }
[JsonPropertyName("port")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public int Port { get; set; }
[JsonPropertyName("outbound_gateways")]
public Dictionary<string, RemoteGatewayz> OutboundGateways { get; set; } = new();
[JsonPropertyName("inbound_gateways")]
public Dictionary<string, List<RemoteGatewayz>> InboundGateways { get; set; } = new();
}
// ============================================================================
// RemoteGatewayz — information about a single remote gateway connection
// Mirrors Go <c>RemoteGatewayz</c> struct in server/monitor.go.
// ============================================================================
/// <summary>
/// Information about a single outbound or inbound gateway connection.
/// Mirrors Go <c>RemoteGatewayz</c> struct in server/monitor.go.
/// </summary>
public sealed class RemoteGatewayz
{
/// <summary>True if the gateway was explicitly configured (not implicit). Mirrors Go <c>IsConfigured</c>.</summary>
[JsonPropertyName("configured")]
public bool IsConfigured { get; set; }
/// <summary>Connection details. Mirrors Go <c>Connection *ConnInfo</c>.</summary>
[JsonPropertyName("connection")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public ConnInfo? Connection { get; set; }
/// <summary>Per-account interest information. Mirrors Go <c>Accounts []*AccountGatewayz</c>.</summary>
[JsonPropertyName("accounts")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public List<AccountGatewayz>? Accounts { get; set; }
}
// ============================================================================
// AccountGatewayz — per-account interest mode on a gateway
// Mirrors Go <c>AccountGatewayz</c> struct in server/monitor.go.
// ============================================================================
/// <summary>
/// Per-account interest mode information for a gateway connection.
/// Mirrors Go <c>AccountGatewayz</c> struct in server/monitor.go.
/// </summary>
public sealed class AccountGatewayz
{
[JsonPropertyName("name")]
public string Name { get; set; } = string.Empty;
[JsonPropertyName("interest_mode")]
public string InterestMode { get; set; } = string.Empty;
[JsonPropertyName("no_interest_count")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public int NoInterestCount { get; set; }
[JsonPropertyName("interest_only_threshold")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public int InterestOnlyThreshold { get; set; }
[JsonPropertyName("num_subs")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public int TotalSubscriptions { get; set; }
[JsonPropertyName("num_queue_subs")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public int NumQueueSubscriptions { get; set; }
[JsonPropertyName("subscriptions_list")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public List<string>? Subs { get; set; }
[JsonPropertyName("subscriptions_list_detail")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public List<SubDetail>? SubsDetail { get; set; }
}
// ============================================================================
// ExtImport — external account import detail for /accountz
// Mirrors Go <c>ExtImport</c> struct in server/monitor.go.
// ============================================================================
/// <summary>
/// External view of a service import entry, as returned by the <c>/accountz</c> endpoint.
/// Mirrors Go <c>ExtImport</c> struct in server/monitor.go.
/// Note: The JWT <c>Import</c> embedded struct fields are inlined here since the
/// nats.io/jwt library is not yet ported.
/// </summary>
public sealed class ExtImport
{
/// <summary>Whether this import is invalid. Mirrors Go <c>Invalid bool</c>.</summary>
[JsonPropertyName("invalid")]
public bool Invalid { get; set; }
/// <summary>Whether the requestor's client info is shared. Mirrors Go <c>Share bool</c>.</summary>
[JsonPropertyName("share")]
public bool Share { get; set; }
/// <summary>Whether latency tracking is enabled. Mirrors Go <c>Tracking bool</c>.</summary>
[JsonPropertyName("tracking")]
public bool Tracking { get; set; }
/// <summary>Headers used when latency is triggered by a header. Mirrors Go <c>TrackingHdr http.Header</c>.</summary>
[JsonPropertyName("tracking_header")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public Dictionary<string, string[]>? TrackingHeader { get; set; }
/// <summary>
/// Latency configuration from the exporting account's JWT claim.
/// Mirrors Go <c>Latency *jwt.ServiceLatency</c>.
/// Sampling and subject are stored directly since jwt lib is not ported.
/// </summary>
[JsonPropertyName("latency")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public ExtServiceLatency? Latency { get; set; }
/// <summary>First-leg latency measurement. Mirrors Go <c>M1 *ServiceLatency</c>.</summary>
[JsonPropertyName("m1")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public ServiceLatency? M1 { get; set; }
// Inlined jwt.Import fields.
/// <summary>Subject of the imported service. Mirrors Go <c>jwt.Import.Subject</c>.</summary>
[JsonPropertyName("subject")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Subject { get; set; }
/// <summary>Account that exports the service. Mirrors Go <c>jwt.Import.Account</c>.</summary>
[JsonPropertyName("account")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Account { get; set; }
/// <summary>Local subject used on the importing account. Mirrors Go <c>jwt.Import.LocalSubject</c>.</summary>
[JsonPropertyName("local_subject")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? LocalSubject { get; set; }
}
// ============================================================================
// ExtServiceLatency — external representation of service latency config
// Used by ExtImport in place of jwt.ServiceLatency.
// ============================================================================
/// <summary>
/// External representation of service latency configuration, used in <see cref="ExtImport"/>.
/// Mirrors Go <c>jwt.ServiceLatency</c> from nats.io/jwt/v2.
/// </summary>
public sealed class ExtServiceLatency
{
[JsonPropertyName("sampling")]
public int Sampling { get; set; }
[JsonPropertyName("results")]
public string Results { get; set; } = string.Empty;
}
// ============================================================================
// MonitorHelpers — standalone helper functions
// Mirrors standalone functions in server/monitor.go.
// ============================================================================
/// <summary>
/// Standalone helper functions used by the monitoring subsystem.
/// Mirrors package-level functions from <c>server/monitor.go</c>.
/// </summary>
internal static class MonitorHelpers
{
// -------------------------------------------------------------------------
// newSubsDetailList
// -------------------------------------------------------------------------
/// <summary>
/// Builds a verbose subscription detail list for a client connection.
/// Client must be locked by caller.
/// Mirrors Go <c>newSubsDetailList</c>.
/// </summary>
internal static List<SubDetail> NewSubsDetailList(ClientConnection client)
{
var result = new List<SubDetail>(client.Subs?.Count ?? 0);
if (client.Subs == null)
return result;
foreach (var sub in client.Subs.Values)
result.Add(NewClientSubDetail(sub, client.Cid));
return result;
}
// -------------------------------------------------------------------------
// newSubsList
// -------------------------------------------------------------------------
/// <summary>
/// Builds a plain subscription subject list for a client connection.
/// Client must be locked by caller.
/// Mirrors Go <c>newSubsList</c>.
/// </summary>
internal static List<string> NewSubsList(ClientConnection client)
{
var result = new List<string>(client.Subs?.Count ?? 0);
if (client.Subs == null)
return result;
foreach (var sub in client.Subs.Values)
result.Add(Encoding.UTF8.GetString(sub.Subject));
return result;
}
// -------------------------------------------------------------------------
// createProxyInfo
// -------------------------------------------------------------------------
/// <summary>
/// Returns a <see cref="ProxyInfo"/> if the connection has a proxy key set, or <c>null</c>.
/// Client lock must be held on entry.
/// Mirrors Go <c>createProxyInfo</c>.
/// </summary>
internal static ProxyInfo? CreateProxyInfo(ClientConnection c)
{
if (string.IsNullOrEmpty(c.ProxyKey))
return null;
return new ProxyInfo { Key = c.ProxyKey };
}
// -------------------------------------------------------------------------
// makePeerCerts
// -------------------------------------------------------------------------
/// <summary>
/// Converts a list of X.509 peer certificates into <see cref="TlsPeerCert"/> summary records.
/// Each record contains subject string, SPKI SHA-256 hex, and certificate SHA-256 hex.
/// Mirrors Go <c>makePeerCerts</c>.
/// </summary>
internal static List<TlsPeerCert> MakePeerCerts(IReadOnlyList<X509Certificate2> peerCerts)
{
var result = new List<TlsPeerCert>(peerCerts.Count);
foreach (var cert in peerCerts)
{
var spkiHash = SHA256.HashData(cert.PublicKey.ExportSubjectPublicKeyInfo());
var certHash = SHA256.HashData(cert.RawData);
result.Add(new TlsPeerCert
{
Subject = cert.Subject,
SubjectPkiSha256 = Convert.ToHexString(spkiHash).ToLowerInvariant(),
CertSha256 = Convert.ToHexString(certHash).ToLowerInvariant(),
});
}
return result;
}
// -------------------------------------------------------------------------
// decodeBool
// -------------------------------------------------------------------------
/// <summary>
/// Parses a boolean query-string parameter from an HTTP listener request.
/// Writes a 400 status and returns an error if the value cannot be parsed.
/// Mirrors Go <c>decodeBool</c>.
/// </summary>
internal static (bool Value, Exception? Error) DecodeBool(
HttpListenerResponse response,
System.Collections.Specialized.NameValueCollection query,
string param)
{
var str = query[param] ?? string.Empty;
if (str.Length == 0)
return (false, null);
if (bool.TryParse(str, out var val))
return (val, null);
if (str == "1") return (true, null);
if (str == "0") return (false, null);
var err = new FormatException($"Error decoding boolean for '{param}': {str}");
response.StatusCode = 400;
return (false, err);
}
// -------------------------------------------------------------------------
// decodeUint64
// -------------------------------------------------------------------------
/// <summary>
/// Parses a uint64 query-string parameter from an HTTP listener request.
/// Mirrors Go <c>decodeUint64</c>.
/// </summary>
internal static (ulong Value, Exception? Error) DecodeUint64(
HttpListenerResponse response,
System.Collections.Specialized.NameValueCollection query,
string param)
{
var str = query[param] ?? string.Empty;
if (str.Length == 0)
return (0, null);
if (ulong.TryParse(str, out var val))
return (val, null);
var err = new FormatException($"Error decoding uint64 for '{param}': {str}");
response.StatusCode = 400;
return (0, err);
}
// -------------------------------------------------------------------------
// decodeInt
// -------------------------------------------------------------------------
/// <summary>
/// Parses an int query-string parameter from an HTTP listener request.
/// Mirrors Go <c>decodeInt</c>.
/// </summary>
internal static (int Value, Exception? Error) DecodeInt(
HttpListenerResponse response,
System.Collections.Specialized.NameValueCollection query,
string param)
{
var str = query[param] ?? string.Empty;
if (str.Length == 0)
return (0, null);
if (int.TryParse(str, out var val))
return (val, null);
var err = new FormatException($"Error decoding int for '{param}': {str}");
response.StatusCode = 400;
return (0, err);
}
// -------------------------------------------------------------------------
// decodeState
// -------------------------------------------------------------------------
/// <summary>
/// Parses the connection-state filter query parameter.
/// Mirrors Go <c>decodeState</c>.
/// </summary>
internal static (ConnState Value, Exception? Error) DecodeState(
HttpListenerResponse response,
System.Collections.Specialized.NameValueCollection query)
{
var str = query["state"] ?? string.Empty;
if (str.Length == 0)
return (ConnState.ConnOpen, null);
switch (str.ToLowerInvariant())
{
case "open": return (ConnState.ConnOpen, null);
case "closed": return (ConnState.ConnClosed, null);
case "any":
case "all": return (ConnState.ConnAll, null);
}
var err = new FormatException($"Error decoding state for {str}");
response.StatusCode = 400;
return (default, err);
}
// -------------------------------------------------------------------------
// decodeSubs
// -------------------------------------------------------------------------
/// <summary>
/// Parses the <c>subs</c> query parameter into <c>subs</c> and <c>subsDet</c> flags.
/// Mirrors Go <c>decodeSubs</c>.
/// </summary>
internal static (bool Subs, bool SubsDetail, Exception? Error) DecodeSubs(
HttpListenerResponse response,
System.Collections.Specialized.NameValueCollection query)
{
var raw = query["subs"] ?? string.Empty;
if (raw.Equals("detail", StringComparison.OrdinalIgnoreCase))
return (false, true, null);
var (subs, err) = DecodeBool(response, query, "subs");
return (subs, false, err);
}
// -------------------------------------------------------------------------
// newSubDetail
// -------------------------------------------------------------------------
/// <summary>
/// Creates a <see cref="SubDetail"/> including account name from the owning client.
/// Client must be locked on entry.
/// Mirrors Go <c>newSubDetail</c>.
/// </summary>
internal static SubDetail NewSubDetail(Internal.Subscription sub, ClientConnection client)
{
var sd = NewClientSubDetail(sub, client.Cid);
var acc = client.Account() as Account;
sd.Account = acc?.GetName();
sd.AccountTag = acc?.GetNameTag();
return sd;
}
// -------------------------------------------------------------------------
// newClientSubDetail
// -------------------------------------------------------------------------
/// <summary>
/// Creates a <see cref="SubDetail"/> from a subscription (no account name).
/// Mirrors Go <c>newClientSubDetail</c>.
/// </summary>
internal static SubDetail NewClientSubDetail(Internal.Subscription sub, ulong cid)
{
return new SubDetail
{
Subject = Encoding.UTF8.GetString(sub.Subject),
Queue = sub.Queue is { Length: > 0 }
? Encoding.UTF8.GetString(sub.Queue)
: null,
Sid = sub.Sid is { Length: > 0 }
? Encoding.UTF8.GetString(sub.Sid)
: string.Empty,
Cid = cid,
};
}
// -------------------------------------------------------------------------
// myUptime
// -------------------------------------------------------------------------
/// <summary>
/// Formats a <see cref="TimeSpan"/> as a human-readable uptime string
/// (e.g. <c>"2d3h14m5s"</c>, <c>"45m30s"</c>).
/// Mirrors Go <c>myUptime</c>.
/// </summary>
internal static string MyUptime(TimeSpan d)
{
var tsecs = (long)d.TotalSeconds;
var tmins = tsecs / 60;
var thrs = tmins / 60;
var tdays = thrs / 24;
var tyrs = tdays / 365;
if (tyrs > 0)
return $"{tyrs}y{tdays % 365}d{thrs % 24}h{tmins % 60}m{tsecs % 60}s";
if (tdays > 0)
return $"{tdays}d{thrs % 24}h{tmins % 60}m{tsecs % 60}s";
if (thrs > 0)
return $"{thrs}h{tmins % 60}m{tsecs % 60}s";
if (tmins > 0)
return $"{tmins}m{tsecs % 60}s";
return $"{tsecs}s";
}
// -------------------------------------------------------------------------
// tlsCertNotAfter
// -------------------------------------------------------------------------
/// <summary>
/// Returns the expiry date of the first certificate in the given collection,
/// or <see cref="DateTime.MinValue"/> if the collection is empty.
/// Mirrors Go <c>tlsCertNotAfter</c>.
/// </summary>
internal static DateTime TlsCertNotAfter(X509CertificateCollection? certs)
{
if (certs == null || certs.Count == 0)
return DateTime.MinValue;
if (certs[0] is X509Certificate2 cert2)
return cert2.NotAfter.ToUniversalTime();
try
{
var parsed = new X509Certificate2(certs[0]);
return parsed.NotAfter.ToUniversalTime();
}
catch
{
return DateTime.MinValue;
}
}
// -------------------------------------------------------------------------
// urlsToStrings
// -------------------------------------------------------------------------
/// <summary>
/// Converts a list of <see cref="Uri"/> objects to their <c>Host:Port</c> string form.
/// Mirrors Go <c>urlsToStrings</c>.
/// </summary>
internal static string[] UrlsToStrings(IReadOnlyList<Uri> urls)
{
var result = new string[urls.Count];
for (int i = 0; i < urls.Count; i++)
result[i] = urls[i].Authority; // "host:port"
return result;
}
// -------------------------------------------------------------------------
// getPinnedCertsAsSlice
// -------------------------------------------------------------------------
/// <summary>
/// Converts a <see cref="PinnedCertSet"/> to a plain string array.
/// Returns <c>null</c> if the set is empty.
/// Mirrors Go <c>getPinnedCertsAsSlice</c>.
/// </summary>
internal static string[]? GetPinnedCertsAsSlice(PinnedCertSet? certs)
{
if (certs == null || certs.Count == 0)
return null;
var result = new string[certs.Count];
certs.CopyTo(result);
return result;
}
// -------------------------------------------------------------------------
// getMonitorGWOptions
// -------------------------------------------------------------------------
/// <summary>
/// Extracts gateway name filter and accounts flag from <see cref="GatewayzOptions"/>.
/// When <c>AccountName</c> is set but <c>Accounts</c> is false, the accounts flag is
/// implicitly promoted to true.
/// Mirrors Go <c>getMonitorGWOptions</c>.
/// </summary>
internal static (string Name, bool Accounts) GetMonitorGWOptions(GatewayzOptions? opts)
{
if (opts == null)
return (string.Empty, false);
var name = opts.Name;
var accs = opts.Accounts;
if (!accs && !string.IsNullOrEmpty(opts.AccountName))
accs = true;
return (name, accs);
}
// -------------------------------------------------------------------------
// createOutboundRemoteGatewayz
// -------------------------------------------------------------------------
/// <summary>
/// Builds a <see cref="RemoteGatewayz"/> from an outbound gateway client connection.
/// Client lock is acquired internally.
/// Mirrors Go <c>createOutboundRemoteGatewayz</c>.
/// Note: Per-account interest detail (outsim) requires a running gateway layer;
/// account lists are empty in the current partial port.
/// </summary>
internal static (string Name, RemoteGatewayz? Rgw) CreateOutboundRemoteGatewayz(
ClientConnection c,
GatewayzOptions? opts,
DateTime now,
bool doAccs)
{
lock (c)
{
var name = c.Gateway?.Name;
if (string.IsNullOrEmpty(name))
return (string.Empty, null);
var isConfigured = c.Gateway?.Cfg != null && !c.Gateway.Cfg.IsImplicit();
var rgw = new RemoteGatewayz
{
IsConfigured = isConfigured,
Connection = new ConnInfo(),
Accounts = doAccs ? [] : null,
};
return (name, rgw);
}
}
// -------------------------------------------------------------------------
// createOutboundAccountsGatewayz
// -------------------------------------------------------------------------
/// <summary>
/// Returns the per-account interest list for an outbound gateway connection.
/// Mirrors Go <c>createOutboundAccountsGatewayz</c>.
/// Note: Requires fully-ported gateway outsim state; returns empty list in current port.
/// </summary>
internal static List<AccountGatewayz> CreateOutboundAccountsGatewayz(
GatewayzOptions? opts,
ClientConnection c)
{
// outsim not yet ported — return empty.
return [];
}
// -------------------------------------------------------------------------
// createAccountOutboundGatewayz
// -------------------------------------------------------------------------
/// <summary>
/// Creates an <see cref="AccountGatewayz"/> entry for a named outbound account.
/// Mirrors Go <c>createAccountOutboundGatewayz</c>.
/// Note: Requires fully-ported outsie state; returns Optimistic defaults.
/// </summary>
internal static AccountGatewayz CreateAccountOutboundGatewayz(
GatewayzOptions? opts,
string name,
object? outsie)
{
// outsie not yet ported — return Optimistic defaults.
return new AccountGatewayz
{
Name = name,
InterestMode = GatewayInterestMode.Optimistic.String(),
};
}
// -------------------------------------------------------------------------
// createInboundAccountsGatewayz
// -------------------------------------------------------------------------
/// <summary>
/// Returns the per-account interest list for an inbound gateway connection.
/// Mirrors Go <c>createInboundAccountsGatewayz</c>.
/// Note: Requires fully-ported gateway insim state; returns empty list.
/// </summary>
internal static List<AccountGatewayz> CreateInboundAccountsGatewayz(
GatewayzOptions? opts,
ClientConnection c)
{
// insim not yet ported — return empty.
return [];
}
// -------------------------------------------------------------------------
// createInboundAccountGatewayz
// -------------------------------------------------------------------------
/// <summary>
/// Creates an <see cref="AccountGatewayz"/> entry for a named inbound account.
/// Mirrors Go <c>createInboundAccountGatewayz</c>.
/// Note: Requires fully-ported insie state; returns Optimistic defaults.
/// </summary>
internal static AccountGatewayz CreateInboundAccountGatewayz(string name, object? insie)
{
// insie not yet ported — return Optimistic defaults.
return new AccountGatewayz
{
Name = name,
InterestMode = GatewayInterestMode.Optimistic.String(),
};
}
// -------------------------------------------------------------------------
// ResponseHandler / handleResponse
// -------------------------------------------------------------------------
/// <summary>
/// Writes a JSON (or JSONP) HTTP monitoring response with <c>200 OK</c>.
/// Mirrors Go <c>ResponseHandler</c>.
/// </summary>
internal static void ResponseHandler(
HttpListenerResponse response,
HttpListenerRequest request,
byte[] data)
{
HandleResponse(200, response, request, data);
}
/// <summary>
/// Writes a JSON (or JSONP if the <c>callback</c> query param is set) HTTP monitoring response.
/// Mirrors Go <c>handleResponse</c>.
/// </summary>
internal static void HandleResponse(
int statusCode,
HttpListenerResponse response,
HttpListenerRequest request,
byte[] data)
{
var callback = request.QueryString["callback"] ?? string.Empty;
response.StatusCode = statusCode;
if (callback.Length > 0)
{
response.ContentType = "application/javascript";
var prefix = Encoding.UTF8.GetBytes($"{callback}(");
var suffix = Encoding.UTF8.GetBytes(")");
response.OutputStream.Write(prefix);
response.OutputStream.Write(data);
response.OutputStream.Write(suffix);
}
else
{
response.ContentType = "application/json";
response.Headers["Access-Control-Allow-Origin"] = "*";
response.OutputStream.Write(data);
}
}
// -------------------------------------------------------------------------
// newExtServiceLatency
// -------------------------------------------------------------------------
/// <summary>
/// Converts an <see cref="InternalServiceLatency"/> to an <see cref="ExtServiceLatency"/>
/// for the <c>/accountz</c> response. Returns <c>null</c> if input is <c>null</c>.
/// Mirrors Go <c>newExtServiceLatency</c>.
/// </summary>
internal static ExtServiceLatency? NewExtServiceLatency(InternalServiceLatency? l)
{
if (l == null)
return null;
return new ExtServiceLatency
{
Sampling = l.Sampling,
Results = l.Subject,
};
}
// -------------------------------------------------------------------------
// newExtImport
// -------------------------------------------------------------------------
/// <summary>
/// Converts a <see cref="ServiceImportEntry"/> to an <see cref="ExtImport"/>
/// for the <c>/accountz</c> response.
/// Mirrors Go <c>newExtImport</c>.
/// </summary>
internal static ExtImport NewExtImport(ServiceImportEntry? v)
{
if (v == null)
return new ExtImport { Invalid = true };
return new ExtImport
{
Invalid = v.Invalid,
Share = v.Share,
Tracking = v.Tracking,
TrackingHeader = v.TrackingHeader,
Latency = NewExtServiceLatency(v.Latency),
M1 = v.M1,
Subject = v.To,
Account = v.Account?.Name,
LocalSubject = v.From,
};
}
}