Documentation
¶
Overview ¶
Package protocol is the boundary every byte crosses in either direction.
Every WebSocket message payload, both ways, is exactly one encoded gotthlive.v1.Frame sent as a binary frame. There is no JSON, no text framing, and no debug escape hatch. The schema, its refinement predicates, and the invariants the predicate grammar cannot express are specified in docs/protocol.md.
Inbound ¶
ParseInbound is the sole entry point for bytes arriving from a client, and there is no exported way to obtain a payload that has not passed through it. It unmarshals, applies the generated Liquid Proto validator to the envelope and then to the matched payload, checks version compatibility, walks every enum field against its descriptor, bounds every repeated field, and applies the cross-field invariants. A new frame kind cannot skip a step: a conformance test walks the payload oneof by protoreflect and asserts every member has both a case here and a generated Validate function.
Outbound ¶
ValidateOutbound re-checks a constructed frame against the same boundary immediately before marshalling, on the single write path, and it is not optional. Inbound frames are protected because they are parsed; outbound frames are constructed, and construction discipline is not a property a reviewer can check. Re-checking is what makes "no patch without an origin" a type-level fact rather than a convention.
Layout ¶
Generated code lives in gotthlivepb so that the reproducibility check has a clean target and hand-written code is never mixed with generated code. It holds the frame schema and generated validators. The canonical annotation schema and runtime come from pkg/liquidproto. Generated protocol code is committed, because consumers of this module must never need protoc.
Status ¶
Implemented: ParseInbound, ValidateOutbound, the framer, the limits table, the close-code enumeration, and the hand-checked invariants.
Index ¶
- Constants
- Variables
- func CheckSessionID(in IInbound, want [16]byte) error
- func FrameDescriptor() protoreflect.MessageDescriptor
- func ListBound(name protoreflect.FullName) (int, bool)
- func NewAck(session [16]byte, serverSeq uint64) *pb.Frame
- func NewError(session [16]byte, code pb.ErrorCode, message string, eventID, clientRef uint64, ...) *pb.Frame
- func NewHeartbeat(session [16]byte, nonce uint64, intervalMS uint32) *pb.Frame
- func NewPatch(session [16]byte, c Causal, o Origin, updates []Update) *pb.Frame
- func NewSnapshot(session [16]byte, c Causal, o Origin, p SessionParams, s Supersession, ...) *pb.Frame
- func ValidOriginSource(s string) bool
- func ValidateOutbound(f *pb.Frame) error
- type Causal
- type CloseCode
- type Encoded
- type EventField
- type Framer
- type IInbound
- type InboundAck
- type InboundClientTelemetry
- type InboundEvent
- type InboundHeartbeat
- type InboundResyncRequest
- type InvalidFrameError
- type Kind
- type Limits
- type Origin
- type RejectError
- type SessionParamRange
- type SessionParams
- type Supersession
- type Update
- type WriteFunc
Constants ¶
const ( // SourceMount is the origin of the first snapshot of a session. SourceMount = "mount" // SourceResync is the origin of a snapshot answering a resync request. SourceResync = "resync" // SourceEventPrefix namespaces a client-caused transition by event name. SourceEventPrefix = "event:" // SourceEffectPrefix namespaces a transition caused by an effect result. SourceEffectPrefix = "effect:" // SourceSlowClient is the origin of a transition the library synthesized // because the outbound window filled. SourceSlowClient = "timer:slow_client" // SourceClientRecovered is its counterpart when the window drained. SourceClientRecovered = "timer:client_recovered" // SourceEffectInvalid stands in for an effect whose own EffectSource() // cannot be namespaced into a legal Origin.source. The effect is refused // before it runs and the application is told through the ordinary failure // event; this is what that event's origin says, because a frame carrying // the offending string is exactly what must not be constructed. SourceEffectInvalid = "effect:invalid_source" )
Origin sources. The vocabulary is a convention rather than a schema: a source is whatever an effect reports, so cardinality is bounded at the metric rather than here.
const ( ReasonRefineFailed = "refine_failed" ReasonOversize = "oversize" ReasonUnknownKind = "unknown_kind" ReasonBadVersion = "bad_version" ReasonSessionMismatch = "session_mismatch" ReasonTextFrame = "text_frame" ReasonEnumDomain = "enum_domain" ReasonListBound = "list_bound" ReasonAckChannelFull = "ack_channel_full" )
Rejection reasons. These are the label values of gotthlive_frames_rejected_total{reason}; the set is closed, and one enforcement site owns each value.
const CoalesceFlushCeiling = 1024
CoalesceFlushCeiling is the H-4 bound on Origin.contributing_event_ids. The actor's own flush threshold defaults to half of it and is validated against it; this is the schema-level assertion that the flush worked.
const MaxOriginSource = 64
MaxOriginSource is the schema's byte bound on Origin.source (proto/gotthlive/v1/frame.proto, message Origin).
const Subprotocol = "gotth-live.v1"
Subprotocol is the WebSocket subprotocol token offered on the upgrade. It is a fast reject, not the source of truth: the negotiated version is re-asserted in band as Frame.protocol_version and validated there.
const Version uint32 = 1
Version is the protocol version this build speaks. A peer whose major version differs is refused with a reason rather than reinterpreted (H-2).
Variables ¶
var ( // HeartbeatIntervalMSRange refines Snapshot.heartbeat_interval_ms. HeartbeatIntervalMSRange = SessionParamRange{ Field: "heartbeat_interval_ms", Predicate: "this >= 1000 && this <= 300000", Min: 1000, Max: 300000, } // MaxInboundFrameBytesRange refines Snapshot.max_inbound_frame_bytes. MaxInboundFrameBytesRange = SessionParamRange{ Field: "max_inbound_frame_bytes", Predicate: "this >= 1024 && this <= 1048576", Min: 1024, Max: 1048576, } // AckWindowRange refines Snapshot.ack_window. AckWindowRange = SessionParamRange{ Field: "ack_window", Predicate: "this >= 1 && this <= 256", Min: 1, Max: 256, } )
The three session parameters a Snapshot carries, which is what makes them the fields an operator can set and a session can then fail to encode: they are the only configuration that leaves this process as refined wire values (D-23).
Functions ¶
func CheckSessionID ¶
CheckSessionID is H-3: a frame's session_id must equal the session bound to the connection it arrived on.
It is a separate call rather than a ParseInbound step because the expected value is transport state, and the transport ingress is the only place that holds it. A mismatch is a protocol violation and closes the connection: a client that names another session is not confused, it is probing.
func FrameDescriptor ¶
func FrameDescriptor() protoreflect.MessageDescriptor
FrameDescriptor returns the descriptor of the one message on the wire. The conformance tests walk it rather than a hand-maintained list of kinds.
func ListBound ¶
func ListBound(name protoreflect.FullName) (int, bool)
ListBound reports the H-4 bound declared for a repeated field, and whether one is declared at all. It is exported for the descriptor-walk test that holds the table complete.
func NewAck ¶
NewAck builds the one acknowledgement the server sends: the answer to a resync request that describes no gap, where a full snapshot would be waste.
func NewError ¶
func NewError(session [16]byte, code pb.ErrorCode, message string, eventID, clientRef uint64, fatal bool) *pb.Frame
NewError builds an error frame. eventID and clientRef are both zero unless the error concerns one event, which H-12 holds to.
func NewHeartbeat ¶
NewHeartbeat builds a heartbeat frame.
func NewPatch ¶
NewPatch builds a patch frame. It does not validate: the framer does that on the single write path, so there is exactly one place a constructed frame is checked and no way to reach the socket around it.
func NewSnapshot ¶
func NewSnapshot(session [16]byte, c Causal, o Origin, p SessionParams, s Supersession, updates []Update) *pb.Frame
NewSnapshot builds a snapshot frame carrying every registered fragment and the session parameters.
func ValidOriginSource ¶
ValidOriginSource reports whether s satisfies Origin.source's predicate: non-empty, at most MaxOriginSource bytes, and matching ^[a-z][a-z0-9_.:/-]*$.
It exists so that a string can be refused by the boundary that owns it rather than by the frame it eventually lands in. Origin.source is composed from application-supplied halves — an event name, an effect's reported source — and a composed value that fails the predicate is dropped by ValidateOutbound on the actor goroutine, three layers from the caller, as an INTERNAL error the application never hears about. That is the D-18 shape, and this is what lets the callers close it: live.New refuses an event name that cannot be namespaced, and the actor turns an unnameable effect into that effect's own deterministic failure.
It is a hand-written second implementation of a compiled predicate, which is a cost. The alternative — constructing a throwaway Origin and calling ValidateOrigin — allocates a message per check on the emit path and reports "some field of some Origin is wrong" rather than naming this one. The conformance suite asserts the two agree over the boundary cases, which is what keeps the duplication honest.
func ValidateOutbound ¶
ValidateOutbound re-checks a constructed frame against every predicate in the schema, plus the cross-field invariants, through the generated Liquid Proto validation boundary. The framer calls it immediately before marshalling, on the single write path, and it is not optional.
Inbound frames are protected because they are parsed. Outbound frames are constructed, and Go cannot forbid the zero value of an opaque type, so without this step a struct literal assembled anywhere in the emit path would produce an orphan patch that nothing catches — the client codec does not enforce the pattern predicates, so neither does an independent decode of the capture. Re-checking is what turns "no patch without an origin" from a discipline into a property.
Types ¶
type Causal ¶
type Causal struct {
// ServerSeq is the frame's place in the session's outbound order, from 1.
// It is what an Ack acknowledges and what a client detects a gap in.
ServerSeq uint64
// PatchID names this emitted frame, one per Patch or Snapshot, and is what
// a client's apply-latency report has to name to be believed.
PatchID uint64
// TransitionID names the reducer invocation this frame came from, one per
// invocation including one that changed nothing.
TransitionID uint64
// StateVersion rises if and only if the transition changed state, which is
// how a re-render is told apart from a state change downstream.
StateVersion uint64
}
Causal is the server-minted chain a sequenced frame carries. Every field is monotonic per session and every field is positive: the predicates make a frame with a hole in its chain unconstructable.
type CloseCode ¶
type CloseCode int
CloseCode is a WebSocket close code from the library's private-range enumeration. Every close names one of these: a connection closed for an unenumerated reason is a defect, not a diagnostic gap, and an architecture test walks every close call site to hold that true.
const ( // CloseNone is the zero value and is not a close code. It means "this // error does not close the connection". CloseNone CloseCode = 0 CloseNormal CloseCode = 4000 // client or server closed cleanly CloseGoingAway CloseCode = 4001 // server shutting down or draining CloseProtocolViolation CloseCode = 4002 // text frame, non-Frame bytes, H-3, H-7 CloseUnsupportedVersion CloseCode = 4003 // H-2 CloseUnauthenticated CloseCode = 4004 // identity hook failed post-upgrade CloseForbiddenOrigin CloseCode = 4005 // origin allowlist CloseFrameTooLarge CloseCode = 4007 // H-5 CloseRateLimited CloseCode = 4008 // inbound limits, including the H-14 resync bucket CloseSlowClient CloseCode = 4009 // outbound window exhausted CloseHeartbeatTimeout CloseCode = 4010 // peer-dead detection CloseSessionEvicted CloseCode = 4011 // idle timeout CloseInternalError CloseCode = 4012 // contained panic that could not be recovered into the session CloseResyncFailed CloseCode = 4013 // resync could not produce a consistent snapshot )
The enumeration. The numeric value is the wire code; Label is the metric label value, and dashboards, the audit, and this table therefore cannot drift apart.
func CloseCodes ¶
func CloseCodes() []CloseCode
CloseCodes returns the enumeration in ascending order. It exists so tests and the wire audit iterate the real table rather than a copy of it.
type Encoded ¶
type Encoded struct {
// contains filtered or unexported fields
}
Encoded is one frame that has passed ValidateOutbound and been marshalled: the only thing Write accepts, and the only thing this package will put on a socket.
Its fields are unexported and it has no constructor, so Framer.Encode is the only way to obtain a non-zero one and Write's argument cannot be assembled by hand. That is U-5's point. B-9 — every socket write in the module goes through Encode, which calls ValidateOutbound and refuses on failure — was true by grep and stayed true by nobody wanting to break it; Write took pre-encoded bytes and a Kind, so a future caller could marshal a frame itself and hand them over, and the coupling between "validated" and "written" was a convention rather than an invariant. A token only Encode can mint makes the bypass unconstructable instead of merely unobserved.
The split it preserves is not decorative: instrumentation §2.3 defines gotthlive_send_duration_seconds as "time in Conn.Write, the write-stall signal", and while validate, marshal and write were one call that histogram and gotthlive_encode_duration_seconds were equal by construction and neither could isolate a stalling client. Collapsing Write back into Send to unexport it would re-create exactly that (L9 ruling on U-5).
type EventField ¶
type EventField struct {
// Key is the validated form-control name. It is non-empty, at most 128
// bytes, and contains only ASCII letters, digits, underscores, periods,
// brackets, or hyphens.
Key string
// Value is the copied form-control value. Validation limits it to 8 KiB.
Value string
}
EventField is one validated event form field copied out of the decoded protobuf. Its string values do not alias mutable wire storage.
type Framer ¶
type Framer struct {
// OnSent is called after a frame reaches the transport, with the encoded
// size. It is the only incrementer of the frames-sent counter.
OnSent func(kind Kind, bytes int)
// OnInvalid is called when a constructed frame fails ValidateOutbound.
// The frame was built on this side, so it is never a client problem, and
// any occurrence is actionable. It is not necessarily a coding bug: some
// of what a frame carries comes from the application, and until D-18 an
// unbounded Event.Contributing arrived here as one.
OnInvalid func(kind Kind, err error)
// contains filtered or unexported fields
}
Framer is the single write path. Nothing reaches the socket except through it, which is what makes the emitted-frame counter checkable against a wire capture: any drift means a second write path exists.
It serializes writes with a mutex. That is not the actor model leaking: the socket is shared transport infrastructure rather than session state, and it has two legitimate writers. The actor writes every patch, snapshot and heartbeat; the read pump writes the error frame for an inbound frame it refuses before that frame ever reaches the mailbox — which it must be able to do precisely when the mailbox is full, since that is what the rejection is about.
func (*Framer) Encode ¶
Encode validates and marshals one frame without touching the socket.
It is separated from Write because the two are different work with different failure modes, and until FR-36's gotthlive.send span was implemented nothing in this library could tell them apart. The consequence was not only a missing span: instrumentation §2.3 defines gotthlive_send_duration_seconds as "time in Conn.Write, the write-stall signal", and with one combined call site the actor recorded the same interval — validate plus marshal plus write — into both that histogram and gotthlive_encode_duration_seconds. Two series that are equal by construction cannot detect the stall one of them is named for.
func (*Framer) Send ¶
Send validates, encodes and writes one frame, returning the number of bytes written. A frame that fails validation is never written and is reported as *InvalidFrameError.
func (*Framer) Write ¶
Write puts one encoded frame on the wire under the framer's mutex.
It stays a method on the framer rather than becoming a second write path: the serialization and the sent-frame counter are here, which is what makes the counter checkable against a wire capture at all (protocol.md P8).
It takes an Encoded rather than bytes and a Kind, so the only thing reachable here is something Encode validated. The zero value is the one Encoded a caller outside this package can name, and it is refused: an empty payload is not a frame, and accepting it would leave exactly the hole the type closes.
type IInbound ¶
type IInbound interface {
// Kind reports which payload this variant carries.
Kind() Kind
// Envelope returns a fresh copy of the validated frame envelope. Mutating
// the copy cannot alter the accepted IInbound value.
Envelope() *pb.Frame
// contains filtered or unexported methods
}
IInbound is a closed sum type over the frames a client may send. Values returned by ParseInbound hold immutable scalar snapshots copied only after the generated Liquid Proto validators succeed.
The type is closed by an unexported method: a package outside this one cannot add a variant, which is what makes the switch in the session ingress exhaustive in fact and not merely by convention.
func ParseInbound ¶
ParseInbound is the sole entry point for bytes arriving from a client. There is no exported way to obtain an inbound payload that has not passed through it.
It runs, in order: unmarshal into the generated frame, which is also where protobuf's own UTF-8 validation of string fields happens; the envelope refinement boundary; the version compatibility check; the payload's own refinement, per element for repeated messages; the enum domain walk; and the list cardinality walk. A new payload kind cannot skip a step, because the switch below is the only way to reach a payload and a conformance test walks the oneof descriptor to assert every member has a case here.
Errors are always *RejectError, so the caller has the metric label, the reply code and the close code without re-deriving any of them.
type InboundAck ¶
type InboundAck struct {
// contains filtered or unexported fields
}
InboundAck reports the highest contiguous server_seq the client has applied.
func (InboundAck) ServerSeq ¶
func (a InboundAck) ServerSeq() uint64
ServerSeq is the validated cumulative high-water mark.
type InboundClientTelemetry ¶
type InboundClientTelemetry struct {
// contains filtered or unexported fields
}
InboundClientTelemetry reports how long the browser took to apply a patch. Every field of it is untrusted input.
func (InboundClientTelemetry) ApplyMicros ¶
func (t InboundClientTelemetry) ApplyMicros() uint32
ApplyMicros is the validated total client-side patch application duration.
func (InboundClientTelemetry) MorphMicros ¶
func (t InboundClientTelemetry) MorphMicros() uint32
MorphMicros is the validated client-side DOM morph duration.
func (InboundClientTelemetry) PatchID ¶
func (t InboundClientTelemetry) PatchID() uint64
PatchID is the validated patch identifier the telemetry describes.
type InboundEvent ¶
type InboundEvent struct {
// contains filtered or unexported fields
}
InboundEvent is one interaction raised by the browser. Fields are refined element by element, because a repeated message field is neither refined nor bounded by the generated boundary.
func (InboundEvent) ClientRef ¶
func (e InboundEvent) ClientRef() uint64
ClientRef is the validated client-local correlation identifier.
func (InboundEvent) Fields ¶
func (e InboundEvent) Fields() []EventField
Fields returns a copy of the event's validated form fields.
func (InboundEvent) FragmentID ¶
func (e InboundEvent) FragmentID() string
FragmentID is the validated fragment the event targets.
func (InboundEvent) Name ¶
func (e InboundEvent) Name() string
Name is the validated registered event name.
func (InboundEvent) SeenServerSeq ¶
func (e InboundEvent) SeenServerSeq() uint64
SeenServerSeq is the latest server sequence the client had observed.
type InboundHeartbeat ¶
type InboundHeartbeat struct {
// contains filtered or unexported fields
}
InboundHeartbeat is the liveness signal.
type InboundResyncRequest ¶
type InboundResyncRequest struct {
// contains filtered or unexported fields
}
InboundResyncRequest asks for a full re-render. It is the one client frame that reaches the actor and triggers work proportional to the whole state, so it is rate limited in a bucket of its own.
func (InboundResyncRequest) LastAppliedSeq ¶
func (r InboundResyncRequest) LastAppliedSeq() uint64
LastAppliedSeq is the validated last contiguous sequence the client applied.
func (InboundResyncRequest) Reason ¶
func (r InboundResyncRequest) Reason() pb.ResyncReason
Reason is the validated reason the client requested a full snapshot.
type InvalidFrameError ¶
type InvalidFrameError struct {
// Kind is the payload the refused frame carried, which is what a log line
// needs to say which emit path built it.
Kind Kind
// Err is the validation failure, typically a refinement violation naming
// the offending field.
Err error
}
InvalidFrameError reports a frame this library constructed and could not validate. It is separated from a transport failure because the two demand different responses: a transport failure ends the connection, while an invalid frame is dropped and replaced by an Error carrying the same causal chain, leaving the sequence contiguous.
func (*InvalidFrameError) Error ¶
func (e *InvalidFrameError) Error() string
Error says explicitly that the server built this frame, because the first question asked of a validation failure is whose input caused it and here the answer is never the client's.
func (*InvalidFrameError) Unwrap ¶
func (e *InvalidFrameError) Unwrap() error
Unwrap exposes the validation failure to errors.Is and errors.As, so a caller can reach the *liquidproto.Error and name the field.
type Kind ¶
type Kind int
Kind names one member of the Frame payload oneof. It is the domain of the kind label on gotthlive_frames_received_total and _sent_total.
const ( KindUnknown Kind = iota KindEvent KindAck KindHeartbeat KindClientTelemetry KindResyncRequest KindPatch KindSnapshot KindError )
The eight kinds. KindUnknown is not a member of the oneof: it is what a frame carrying no payload, or a payload from a newer schema, parses as, and it is always a rejection rather than a silent pass.
func KindByField ¶
func KindByField(name protoreflect.Name) (Kind, bool)
KindByField returns the Kind of a payload oneof member by its proto field name, and whether one is declared. Exported for the conformance test.
func (Kind) ClientToServer ¶
ClientToServer reports whether a client may send this kind. A server-only kind arriving from a client is a protocol violation, not a curiosity.
type Limits ¶
type Limits struct {
// MaxInboundFrameBytes caps a decoded frame (H-5).
MaxInboundFrameBytes int
}
Limits are the inbound bounds this package enforces. They are the subset of the library's limits that the parse boundary needs; the rest belong to the session actor and the transport.
MaxInboundFrameBytes is the authoritative one. It is applied to the connection before any payload is allocated, so it bounds memory rather than merely rejecting after the fact; the per-field and per-list bounds are subordinate defence in depth that fail early with a field-specific error.
type Origin ¶
type Origin struct {
// Kind is the category of cause. The zero value is never emitted: the
// outbound boundary refuses a frame that names no category.
Kind pb.OriginKind
// EventID is the server-minted identity of the causing event, zero when
// the server started the transition itself. It is unforgeable, which is
// what makes it the authoritative root of the chain.
EventID uint64
// ClientRef is the client's own correlation handle, echoed unchanged. It
// is the only value here that untrusted input chose, and it is carried
// rather than trusted: nothing downstream keys on it.
ClientRef uint64
// Source is the specific cause, such as "event:cart.add". It is composed
// from application-supplied halves, so ValidOriginSource is what keeps a
// composed value from failing three layers away as an INTERNAL error.
Source string
// Contributing lists events whose state changes this patch carries but
// which were not individually patched, because coalescing collapsed them.
Contributing []uint64
}
Origin says what caused a patch. Nothing is ever emitted without one.
type RejectError ¶
type RejectError struct {
// Reason is the gotthlive_frames_rejected_total label value.
Reason string
// Code is the ErrorCode carried back to the client.
Code pb.ErrorCode
// Close names the close code when this rejection ends the connection, and
// is CloseNone when the connection survives.
Close CloseCode
// Detail is the operator-facing explanation. It never carries a token, a
// cookie, an authorization input, application state, or a raw frame body.
Detail string
// Err is the underlying cause, typically a refinement violation.
Err error
}
RejectError reports a frame this build refuses to accept, and carries everything the caller needs to answer it without re-deriving anything: the metric label, the ErrorCode for the reply frame, and the close code when the violation is fatal to the connection.
The message follows the library's error template — what failed, why, and what to do — because a rejection an operator cannot act on is a defect (FR-58).
func (*RejectError) Error ¶
func (e *RejectError) Error() string
Error renders the reason label, the operator-facing detail and the underlying cause in that order: the label is what a metric was incremented with, so a log line and a counter can be lined up without a lookup table.
func (*RejectError) Fatal ¶
func (e *RejectError) Fatal() bool
Fatal reports whether this rejection closes the connection.
func (*RejectError) Unwrap ¶
func (e *RejectError) Unwrap() error
Unwrap exposes the underlying refinement violation to errors.Is and errors.As, so a caller can inspect *refine.Error for the offending field.
type SessionParamRange ¶
type SessionParamRange struct {
// Field is the Snapshot field the predicate is declared on.
Field string
// Predicate is the predicate's source text, verbatim from the .proto. It
// is carried so that a rejection can say where the range comes from
// instead of asserting one on its own authority.
Predicate string
// Min and Max are the interval's inclusive endpoints.
Min, Max uint32
}
SessionParamRange is the closed interval one Snapshot session parameter's refinement admits.
The predicates are declared on gotthlive.v1.Snapshot in proto/gotthlive/v1/frame.proto (protocol.md §3.3) and compiled to native Go by protoc-gen-liquidproto, which emits a message validator and no constant for either endpoint. So a caller that wants to reject an out-of-range value at construction, rather than discover it when the frame it built is refused on the write path, has a validator to ask and no number to quote back to the operator. These are that number, in the one place this side of the wire names it.
They are not a second opinion about the range. The generated validator stays the only thing that decides a frame, and sessionparams_test.go holds every range below against the production outbound boundary that invokes it: accepted at Min and at Max, refused one past either end, with Field and Predicate compared verbatim against what the generator compiled. A predicate that moves in the .proto and is regenerated turns those specs red rather than leaving this file quietly wrong.
func (SessionParamRange) Contains ¶
func (r SessionParamRange) Contains(v int64) bool
Contains reports whether v is within the range.
It takes an int64 rather than a uint32 because every caller is narrowing from something wider — a time.Duration counted in milliseconds, an int — and narrowing first is how a value far outside the range becomes one inside it: 4294987296 ms is 49 days, and is 20 seconds once truncated to uint32.
type SessionParams ¶
type SessionParams struct {
// HeartbeatIntervalMS is how often the client should send a heartbeat.
// The schema refines it to 1000..300000, so a value outside that builds a
// frame this library then refuses to send.
HeartbeatIntervalMS uint32
// MaxInboundFrameBytes is the largest frame the server will read from this
// client. Refined to 1024..1048576, for the same reason.
MaxInboundFrameBytes uint32
// AckWindow is how many unacknowledged patches the server will hold before
// it stops emitting. Refined to 1..256.
AckWindow uint32
}
SessionParams are the values a session sends once, in its first snapshot, so that the client needs no configuration of its own.
type Supersession ¶
type Supersession struct {
// FromSeq is the first replaced server_seq, inclusive.
FromSeq uint64
// ThroughSeq is the last replaced server_seq, inclusive. A range rather
// than the union of the contributing event identifiers, because the union
// is unbounded and this is two varints.
ThroughSeq uint64
}
Supersession is the inclusive server_seq range a resync snapshot replaces. Both fields are zero on a session's first snapshot.
type Update ¶
type Update struct {
// FragmentID is the region this markup belongs to.
FragmentID string
// Op is how the client applies it. The zero value is refused at the
// outbound boundary rather than arriving as a silent morph.
Op pb.PatchOp
// HTML is the region's complete markup, not a diff: the diff happens in
// the browser, against the live DOM.
HTML string
}
Update is the new markup for one live region.