Merge branch 'worktree-agent-a4679340'

# Conflicts:
#	dotnet/src/ZB.MOM.NatsNet.Server/Events/EventHelpers.cs
#	dotnet/src/ZB.MOM.NatsNet.Server/Events/EventTypes.cs
#	dotnet/src/ZB.MOM.NatsNet.Server/NatsServer.Events.cs
#	dotnet/src/ZB.MOM.NatsNet.Server/NatsServerTypes.cs
#	reports/current.md
This commit is contained in:
Joseph Doherty
2026-03-01 09:53:22 -05:00
6 changed files with 2280 additions and 568 deletions

View File

@@ -4508,6 +4508,28 @@ public sealed partial class Account : INatsAccount
return string.Empty;
}
/// <summary>
/// Clears the connection-heartbeat timer. Caller must hold the account lock.
/// Mirrors Go <c>(a *Account) clearConnectionTimer()</c> in server/events.go.
/// </summary>
internal void ClearConnectionHeartbeatTimer()
{
ClearTimerLocked(ref _ctmr);
}
/// <summary>
/// Starts or resets the connection-heartbeat timer.
/// Caller must hold the account lock.
/// Mirrors Go inline timer setup in <c>sendAccConnsUpdate()</c>.
/// </summary>
internal void SetConnectionHeartbeatTimer(long delayMs, Action callback)
{
if (_ctmr == null)
_ctmr = new Timer(_ => callback(), null, delayMs, Timeout.Infinite);
else
_ctmr.Change(delayMs, Timeout.Infinite);
}
/// <summary>
/// Stops and nulls out a timer. Lock must be held by the caller.
/// Mirrors Go <c>clearTimer(t **time.Timer)</c>.

View File

@@ -259,6 +259,22 @@ internal static class EventHelpers
return text[start..end].Trim();
}
/// <summary>
/// Sends a JSON response for an account claims update request.
/// Mirrors Go <c>respondToUpdate</c> in server/events.go.
/// </summary>
internal static void RespondToUpdate(NatsServer server, string reply, string pubKey, string message, Exception? err)
{
if (string.IsNullOrEmpty(reply)) return;
var response = new ServerApiResponse();
if (err != null)
{
response.Error = new ServerApiError { Code = 500, Description = err.Message };
}
response.Data = new { account = pubKey, message, error = err?.Message };
server.SendInternalResponse(reply, response);
}
}
// =========================================================================

View File

@@ -151,13 +151,212 @@ public static class EventIntervals
/// </summary>
public delegate void SysMsgHandler(
Subscription sub,
NatsClient client,
ClientConnection client,
Account acc,
string subject,
string reply,
byte[] hdr,
byte[] msg);
// ============================================================================
// MsgHandler — subscription message callback delegate
// Mirrors Go <c>msgHandler</c> func type in server/events.go.
// ============================================================================
/// <summary>
/// Callback for a subscription message. Identical signature to
/// <see cref="SysMsgHandler"/>; the distinction exists in Go but is
/// collapsed here since both carry the same parameters.
/// Mirrors Go <c>msgHandler</c> in server/events.go.
/// </summary>
public delegate void MsgHandler(
Subscription sub,
ClientConnection client,
Account acc,
string subject,
string reply,
byte[] hdr,
byte[] msg);
// ============================================================================
// ServerApiError — error payload for server API responses
// Mirrors Go <c>ApiError</c> used in server/events.go responses.
// ============================================================================
/// <summary>
/// Error payload returned in <see cref="ServerApiResponse"/> when a
/// monitoring z-endpoint request fails.
/// Mirrors Go <c>ApiError</c> struct used by server API responses.
/// </summary>
public sealed class ServerApiError
{
[System.Text.Json.Serialization.JsonPropertyName("code")]
public int Code { get; set; }
[System.Text.Json.Serialization.JsonPropertyName("description")]
public string Description { get; set; } = string.Empty;
}
// ============================================================================
// ServerApiResponse — wrapper for server API (z-endpoint) responses
// Mirrors Go <c>ServerAPIResponse</c> in server/events.go.
// ============================================================================
/// <summary>
/// Standard envelope returned by server monitoring API (varz, connz, etc.)
/// published via the internal system bus.
/// Mirrors Go <c>ServerAPIResponse</c> in server/events.go.
/// </summary>
public sealed class ServerApiResponse
{
[System.Text.Json.Serialization.JsonPropertyName("server")]
public ServerInfo? Server { get; set; }
[System.Text.Json.Serialization.JsonPropertyName("data")]
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)]
public object? Data { get; set; }
[System.Text.Json.Serialization.JsonPropertyName("error")]
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)]
public ServerApiError? Error { get; set; }
}
// ============================================================================
// EventFilterOptions — server filter options for z-endpoint requests
// Mirrors Go <c>EventFilterOptions</c> in server/events.go.
// ============================================================================
/// <summary>
/// Filter parameters sent in monitoring z-endpoint request messages that
/// allow targeting a specific server, cluster, or set of tags.
/// Mirrors Go <c>EventFilterOptions</c> in server/events.go.
/// </summary>
public class EventFilterOptions
{
[System.Text.Json.Serialization.JsonPropertyName("server_name")]
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)]
public string? Name { get; set; }
[System.Text.Json.Serialization.JsonPropertyName("cluster")]
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)]
public string? Cluster { get; set; }
[System.Text.Json.Serialization.JsonPropertyName("host")]
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)]
public string? Host { get; set; }
[System.Text.Json.Serialization.JsonPropertyName("domain")]
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)]
public string? Domain { get; set; }
[System.Text.Json.Serialization.JsonPropertyName("tags")]
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)]
public List<string>? Tags { get; set; }
/// <summary>
/// When true, name/cluster/host must match exactly; when false, substring
/// matching is used.
/// Mirrors Go <c>EventFilterOptions.ExactMatch</c>.
/// </summary>
[System.Text.Json.Serialization.JsonPropertyName("exact")]
public bool ExactMatch { get; set; }
}
// ============================================================================
// UserInfo — user info response payload for $SYS.REQ.USER.INFO
// Mirrors Go <c>UserInfo</c> struct in server/events.go.
// ============================================================================
/// <summary>
/// Response payload returned by the <c>$SYS.REQ.USER.INFO</c> endpoint.
/// Contains the authenticated user's identity, account, permissions, and
/// claim expiry.
/// Mirrors Go <c>UserInfo</c> struct in server/events.go.
/// </summary>
public sealed class UserInfo
{
[System.Text.Json.Serialization.JsonPropertyName("userId")]
public string UserId { get; set; } = string.Empty;
[System.Text.Json.Serialization.JsonPropertyName("account")]
public string Account { get; set; } = string.Empty;
[System.Text.Json.Serialization.JsonPropertyName("permissions")]
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)]
public object? Permissions { get; set; }
[System.Text.Json.Serialization.JsonPropertyName("expires")]
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)]
public DateTime Expires { get; set; }
}
// ============================================================================
// KickClientReq / LdmClientReq — client control request payloads
// Mirrors Go anonymous structs used in server/events.go.
// ============================================================================
/// <summary>
/// Request payload for <c>$SYS.REQ.SERVER.{id}.KICK</c>, which asks this
/// server to forcefully disconnect the client with the given CID.
/// Mirrors the anonymous struct used in <c>Server.kickClient()</c>.
/// </summary>
internal sealed class KickClientReq
{
[System.Text.Json.Serialization.JsonPropertyName("cid")]
public ulong Cid { get; set; }
}
/// <summary>
/// Request payload for <c>$SYS.REQ.SERVER.{id}.LDM</c>, which asks this
/// server to put the client with the given CID into lame-duck mode.
/// Mirrors the anonymous struct used in <c>Server.ldmClient()</c>.
/// </summary>
internal sealed class LdmClientReq
{
[System.Text.Json.Serialization.JsonPropertyName("cid")]
public ulong Cid { get; set; }
}
// ============================================================================
// StatszEventOptions — options for statsz z-endpoint requests
// Mirrors Go <c>StatszEventOptions</c> in server/events.go.
// ============================================================================
/// <summary>
/// Options embedded in z-endpoint request messages that allow filtering and
/// configuring the statsz response.
/// Mirrors Go <c>StatszEventOptions</c> in server/events.go.
/// </summary>
public sealed class StatszEventOptions : EventFilterOptions
{
}
// ============================================================================
// AccNumSubsReq — request payload for subscription count queries
// Mirrors Go <c>accNumSubsReq</c> struct in server/events.go.
// ============================================================================
/// <summary>
/// Payload for <c>$SYS.REQ.ACCOUNT.NSUBS</c> requests, which ask a remote
/// server how many local subscriptions exist for a given account + subject.
/// Mirrors Go <c>accNumSubsReq</c> in server/events.go.
/// </summary>
internal sealed class AccNumSubsReq
{
[System.Text.Json.Serialization.JsonPropertyName("server")]
public ServerInfo Server { get; set; } = new();
[System.Text.Json.Serialization.JsonPropertyName("acc")]
public string Account { get; set; } = string.Empty;
[System.Text.Json.Serialization.JsonPropertyName("subject")]
public string Subject { get; set; } = string.Empty;
[System.Text.Json.Serialization.JsonPropertyName("queue")]
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)]
public string? Queue { get; set; }
}
// ============================================================================
// InSysMsg — queued internal system message
// Mirrors Go <c>inSysMsg</c> struct in server/events.go.
@@ -170,9 +369,9 @@ public delegate void SysMsgHandler(
/// </summary>
internal sealed class InSysMsg
{
public Subscription? Sub { get; set; }
public NatsClient? Client { get; set; }
public Account? Acc { get; set; }
public Subscription? Sub { get; set; }
public ClientConnection? Client { get; set; }
public Account? Acc { get; set; }
public string Subject { get; set; } = string.Empty;
public string Reply { get; set; } = string.Empty;
public byte[]? Hdr { get; set; }
@@ -195,10 +394,10 @@ internal sealed class InSysMsg
internal sealed class InternalState
{
// ---- identity / sequencing ----
public Account? Account { get; set; }
public ClientConnection? Client { get; set; }
public long Seq; // accessed via Interlocked.Increment
public int Sid { get; set; }
public Account? Account { get; set; }
public ClientConnection? Client { get; set; }
public long Seq; // accessed via Interlocked.Increment
public int Sid { get; set; }
// ---- remote server tracking ----
/// <summary>Map of server ID → serverUpdate. Mirrors Go <c>servers map[string]*serverUpdate</c>.</summary>
@@ -216,7 +415,7 @@ internal sealed class InternalState
/// Pending reply subject → handler map.
/// Mirrors Go <c>replies map[string]msgHandler</c>.
/// </summary>
public Dictionary<string, Action<Subscription, NatsClient, Account, string, string, byte[], byte[]>> Replies { get; set; } = new();
public Dictionary<string, Action<Subscription, ClientConnection, Account, string, string, byte[], byte[]>> Replies { get; set; } = new();
// ---- queues ----
/// <summary>Outbound message send queue. Mirrors Go <c>sendq *ipQueue[*pubMsg]</c>.</summary>
@@ -290,7 +489,7 @@ internal sealed class ServerUpdate
internal sealed class PubMsg
{
public ClientConnection? Client { get; set; }
public string Subject { get; set; } = string.Empty;
public string Subject { get; set; } = string.Empty;
public string Reply { get; set; } = string.Empty;
public ServerInfo? Si { get; set; }
public byte[]? Hdr { get; set; }
@@ -302,11 +501,6 @@ internal sealed class PubMsg
public bool Echo { get; set; }
public bool Last { get; set; }
/// <summary>
/// Clears all fields and returns this instance to the pool held in
/// <see cref="EventHelpers.NewPubMsg"/>.
/// Mirrors Go <c>(pm *pubMsg) returnToPool()</c> in server/events.go.
/// </summary>
internal void ReturnToPool()
{
Client = null;
@@ -318,7 +512,6 @@ internal sealed class PubMsg
Oct = 0;
Echo = false;
Last = false;
EventHelpers.ReturnPubMsg(this);
}
}

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -1,6 +1,6 @@
# NATS .NET Porting Status Report
Generated: 2026-03-01 14:18:03 UTC
Generated: 2026-03-01 14:53:22 UTC
## Modules (12 total)
@@ -13,10 +13,10 @@ Generated: 2026-03-01 14:18:03 UTC
| Status | Count |
|--------|-------|
| complete | 22 |
| deferred | 363 |
| deferred | 284 |
| n_a | 24 |
| stub | 1 |
| verified | 3263 |
| verified | 3342 |
## Unit Tests (3257 total)
@@ -35,4 +35,4 @@ Generated: 2026-03-01 14:18:03 UTC
## Overall Progress
**5694/6942 items complete (82.0%)**
**5773/6942 items complete (83.2%)**