namespace DodoSSH.Domain;
///
/// A relay ticket that has been redeemed.
///
///
///
/// The replay guard, and the only state the relay shares between nodes — see ADR 0004. A ticket is
/// verified without the database; what the database adds is that it is verified once, by a
/// unique insert on that a second redemption loses whichever node it reaches.
///
///
/// A row is worth keeping only while its ticket could still verify, so is
/// stored to let redemption sweep the rows that can no longer matter. Nothing else reads this table,
/// and it carries nothing about the target: the audit record is .
///
///
public sealed class RelayTicketUse
{
/// The ticket's own id. Primary key, and the whole of the guard.
public Guid TicketId { get; set; }
/// When the ticket stops verifying, after which this row guards nothing.
public DateTimeOffset ExpiresAtUtc { get; set; }
/// When it was redeemed.
public DateTimeOffset UsedAtUtc { get; set; }
}
///
/// The audit record of one relay session.
///
///
///
/// What ADR 0004 says remains auditable in relay mode, and nothing more: who, to where, for how long,
/// how many bytes each way and why it ended. The relay forwards SSH ciphertext, so there is no content
/// to record and no column that pretends otherwise.
///
///
/// Written twice — once when the target answers, once when the session ends — each time from a scope
/// opened for that write alone. A session lasts hours and a scope held across it is a pooled database
/// connection held across it, which is the failure ADR 0004 records.
///
///
/// No foreign keys to the host or the vault, deliberately. An audit row has to outlive what it is
/// about: a host deleted a minute after a session is exactly the host an operator will want the
/// session for.
///
///
public sealed class RelaySession
{
/// The session id, minted with the ticket and returned to the client. Primary key.
public Guid Id { get; set; }
/// Who opened it.
public Guid UserId { get; set; }
/// The host it reached.
public Guid HostId { get; set; }
/// The vault the host was in when the ticket was issued.
public Guid VaultId { get; set; }
/// The hostname as the host row carried it at issue.
public string TargetHost { get; set; } = string.Empty;
/// The address actually dialled, which is the one that answered.
/// Null only for a session whose target never answered.
public string? TargetAddress { get; set; }
/// The port dialled.
public int TargetPort { get; set; }
/// The client's address as this node saw it, for the audit ADR 0004 promises.
public string? ClientAddress { get; set; }
/// When the socket was accepted.
public DateTimeOffset StartedAtUtc { get; set; }
/// When it closed, or null while it is open — or if the node died under it.
public DateTimeOffset? EndedAtUtc { get; set; }
/// Bytes forwarded from the client to the target.
public long BytesClientToTarget { get; set; }
/// Bytes forwarded from the target to the client.
public long BytesTargetToClient { get; set; }
/// Why it ended. while open.
public RelayCloseReason CloseReason { get; set; }
}
/// Why a relay session ended.
///
/// Numbered as RelaySessionCloseReason on the wire is, and held to it by a test rather than by
/// hope: the endpoint converts between the two by value.
///
public enum RelayCloseReason
{
/// Not a legal value for a closed session; what an open one carries.
Unspecified = 0,
/// The client closed the connection.
ClientClosed = 1,
/// The target closed the connection.
TargetClosed = 2,
/// No traffic within the idle timeout.
IdleTimeout = 3,
/// The maximum session duration was reached.
DurationLimit = 4,
/// The server is shutting down and drained the session.
ServerShutdown = 5,
/// The connection to the target failed.
TargetUnreachable = 6,
/// An error ended the session.
Error = 7,
}