# Subscriptions Overview

The subscription system maps published subjects to interested subscribers. It consists of four classes: `Subscription` (the subscription model), `SubListResult` (a match result container), `SubjectMatch` (subject validation and wildcard matching), and `SubList` (the trie that routes messages to subscribers).

Go reference: `golang/nats-server/server/sublist.go`

---

## Subjects

A NATS subject is a dot-separated string of tokens: `foo.bar.baz`. Each token is a non-empty sequence of non-whitespace characters. The token separator is `.`.

### Wildcards

Two wildcard tokens are supported:

| Token | Name | Matches |
|-------|------|---------|
| `*` | Partial wildcard (Pwc) | Exactly one token at that position |
| `>` | Full wildcard (Fwc) | One or more remaining tokens |

Rules:
- `foo.*` matches `foo.bar` but not `foo.bar.baz` (only one token after `foo`).
- `foo.>` matches `foo.bar` and `foo.bar.baz` (one or more tokens after `foo`).
- `>` must be the last token in a subject. `foo.>.bar` is invalid.
- Publishers cannot use wildcards — only subscribers can.

### Queue Groups

Subscribers with the same queue name form a queue group. When a message matches a queue group, exactly one member of that group receives the message. The server selects a member per published message, which distributes load across the group.

Queue subscriptions are independent of plain subscriptions. If a subject has both plain subscribers and queue group subscribers, all plain subscribers receive the message and one member from each queue group receives it.

---

## The `Subscription` Class

A `Subscription` represents one subscriber's interest in a subject.

```csharp
public sealed class Subscription
{
    public required string Subject { get; init; }
    public string? Queue { get; init; }
    public required string Sid { get; init; }
    public long MessageCount;  // Interlocked
    public long MaxMessages;   // 0 = unlimited
    public NatsClient? Client { get; set; }
}
```

- `Subject` — the subject pattern, which may contain wildcards.
- `Queue` — the queue group name. `null` means this is a plain subscription.
- `Sid` — the subscription ID string assigned by the client. Used to identify the subscription in `UNSUB` commands.
- `MessageCount` — updated with `Interlocked.Increment` on each delivered message. Compared against `MaxMessages` to implement `UNSUB maxMessages`.
- `MaxMessages` — the auto-unsubscribe limit. `0` means unlimited.
- `Client` — back-reference to the `NatsClient` that owns this subscription. Used during message delivery to write `MSG` frames to the connection.

---

## The `SubListResult` Class

`SubListResult` is the return type of `SubList.Match()`. It separates plain subscribers from queue group subscribers.

```csharp
public sealed class SubListResult
{
    public static readonly SubListResult Empty = new([], []);

    public Subscription[] PlainSubs { get; }
    public Subscription[][] QueueSubs { get; } // outer = groups, inner = members

    public SubListResult(Subscription[] plainSubs, Subscription[][] queueSubs)
    {
        PlainSubs = plainSubs;
        QueueSubs = queueSubs;
    }
}
```

- `PlainSubs` — all plain subscribers whose subject pattern matches the published subject.
- `QueueSubs` — one entry per queue group. Each inner array contains all members of that group. The message router picks one member per group.
- `Empty` — a static singleton returned when no subscriptions match. Avoids allocation on the common case of no interest.

---

## The `SubjectMatch` Class

`SubjectMatch` is a static utility class for subject validation and wildcard pattern matching. It defines the three fundamental constants used throughout the subscription system:

```csharp
public static class SubjectMatch
{
    public const char Pwc = '*';   // partial wildcard
    public const char Fwc = '>';   // full wildcard
    public const char Sep = '.';   // token separator
    ...
}
```

### Validation methods

**`IsValidSubject(string subject)`** — returns `true` if the subject is a well-formed NATS subject. Rejects:
- Empty strings.
- Empty tokens (consecutive separators, or leading/trailing separators).
- Tokens containing whitespace characters.
- Any token after `>` (full wildcard must be last).

**`IsLiteral(string subject)`** — returns `true` if the subject contains no wildcards. A character is treated as a wildcard only when it appears as a complete token (preceded and followed by `.` or the string boundary).

**`IsValidPublishSubject(string subject)`** — returns `true` if the subject is both valid and literal. Publishers must use literal subjects; this is the combined check applied before inserting a message into the routing path.

### Pattern matching

**`MatchLiteral(string literal, string pattern)`** — tests whether a literal subject matches a pattern that may contain wildcards. Used by the cache invalidation logic to determine which cached results are affected when a wildcard subscription is added or removed.

```csharp
public static bool MatchLiteral(string literal, string pattern)
{
    int li = 0, pi = 0;

    while (pi < pattern.Length)
    {
        // Get next pattern token
        int pTokenStart = pi;
        while (pi < pattern.Length && pattern[pi] != Sep)
            pi++;
        int pTokenLen = pi - pTokenStart;
        if (pi < pattern.Length)
            pi++; // skip separator

        // Full wildcard -- matches everything remaining
        if (pTokenLen == 1 && pattern[pTokenStart] == Fwc)
            return li < literal.Length; // must have at least one token left

        // Get next literal token
        if (li >= literal.Length)
            return false;
        int lTokenStart = li;
        while (li < literal.Length && literal[li] != Sep)
            li++;
        int lTokenLen = li - lTokenStart;
        if (li < literal.Length)
            li++; // skip separator

        // Partial wildcard -- matches any single token
        if (pTokenLen == 1 && pattern[pTokenStart] == Pwc)
            continue;

        // Literal comparison
        if (pTokenLen != lTokenLen)
            return false;
        if (string.Compare(literal, lTokenStart, pattern, pTokenStart, pTokenLen, StringComparison.Ordinal) != 0)
            return false;
    }

    return li >= literal.Length; // both exhausted
}
```

The algorithm walks both strings token by token without allocating. `>` short-circuits immediately, returning `true` as long as at least one literal token remains. `*` skips the corresponding literal token without comparison. Literal tokens are compared with `StringComparison.Ordinal`.

---

## Related Documentation

- [SubList Trie](../Subscriptions/SubList.md)

<!-- Last verified against codebase: 2026-02-22 -->
