kps

package module
v0.2.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 22, 2026 License: MIT Imports: 28 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Address

type Address struct {
	IP       string
	Port     int
	Certhash string
}

Address is a parsed kps address: a UDP endpoint plus a pinned certhash (SPEC §2). The form is "<ip>:<port>:<certhash>".

func ParseAddress

func ParseAddress(s string) (Address, error)

ParseAddress parses "<ip>:<port>:<certhash>". IPv6 hosts are bracketed, "[<ipv6>]:<port>:<certhash>", because the literal itself contains colons.

type Conn

type Conn interface {
	// OpenStream opens a new bidirectional byte stream, blocking until ready.
	OpenStream(ctx context.Context) (Stream, error)
	// AcceptStream returns the next stream opened by the peer.
	AcceptStream(ctx context.Context) (Stream, error)
	// Close tears down the connection and invalidates all its streams.
	Close() error
	// CloseWithError tears down the connection, conveying an error code to the
	// peer where the transport supports it (QUIC CONNECTION_CLOSE; a no-op code
	// on WebRTC). Mirrors the JS client's close(reason).
	CloseWithError(code ErrorCode) error
	// Closed is closed when the connection ends.
	Closed() <-chan struct{}
	// Err reports why the connection closed: nil while open or after a clean
	// close, non-nil otherwise. Best-effort — a reason is only available where
	// the transport carries one (QUIC), so WebRTC failures surface generically.
	Err() error
	// RemoteAddr returns the peer's UDP endpoint (e.g. for per-IP policy such
	// as rate limiting). It reflects the endpoint observed at connection
	// establishment and MAY change over the connection's life (QUIC path
	// migration, ICE renomination); on the dial side it is the dialed endpoint.
	RemoteAddr() net.Addr

	// Datagrams are unreliable, unordered, size-limited messages available on
	// every connection (SPEC §7). There is a per-connection size limit; an
	// oversized SendDatagram returns a *DatagramTooLargeError reporting it.
	// Delivery is best-effort: a sent datagram may never arrive.
	SendDatagram(p []byte) error
	ReceiveDatagram(ctx context.Context) ([]byte, error)
}

Conn is an authenticated, secure, multiplexed kps session (SPEC §4), carrying any number of independent byte Streams. It is implemented by both transports (WebRTC and QUIC); callers cannot tell which backs a connection.

func Dial

func Dial(ctx context.Context, addr string) (Conn, error)

Dial opens a kps connection to a pinned address over QUIC — the default transport for native clients (SPEC §5.4). The server's certificate is trusted iff it hashes to the address's certhash; no CA/hostname validation is done.

func DialWebRTC

func DialWebRTC(ctx context.Context, addr string) (Conn, error)

DialWebRTC opens a kps connection over the WebRTC transport from Go. Native clients default to QUIC (kps.Dial); this is the explicit override the spec allows for tests/debugging and for interop with browser-facing listeners (SPEC §5.4). It mirrors the browser client: the offerer synthesizes the server's answer from the address, derives the ICE password from the certhash, and lets pion pin the server's DTLS certificate against the answer fingerprint.

type DatagramTooLargeError

type DatagramTooLargeError struct {
	MaxDatagramPayloadSize int
}

DatagramTooLargeError is returned by SendDatagram when the payload exceeds the connection's current datagram size limit. The limit is transport- and path-dependent (so KPS does not expose it as a fixed property); this error reports it, mirroring QUIC. As a rule of thumb, payloads up to ~1100 bytes are safe on every connection; larger payloads may or may not fit.

func (*DatagramTooLargeError) Error

func (e *DatagramTooLargeError) Error() string

type ErrorCode

type ErrorCode uint32

ErrorCode is the application-level reset/cancel code carried in RESET and STOP_SENDING frames. The values are the canonical registry from SPEC §9.1 and are shared with the QUIC transport's stream error codes.

const (
	CodeNone             ErrorCode = 0
	CodeCancelled        ErrorCode = 1
	CodeClosed           ErrorCode = 2
	CodeReset            ErrorCode = 3
	CodeTimeout          ErrorCode = 4
	CodeNetworkError     ErrorCode = 5
	CodeProtocolError    ErrorCode = 6
	CodeUnsupported      ErrorCode = 7
	CodeTooLarge         ErrorCode = 8
	CodeQueueFull        ErrorCode = 9
	CodePermissionDenied ErrorCode = 10
	CodeInternalError    ErrorCode = 11
)

type Identity

type Identity struct {
	Certificate webrtc.Certificate
	Certhash    string // multibase 'u' + multihash sha256
	// contains filtered or unexported fields
}

Identity holds the server's persistent self-signed TLS cert and the matching multibase-encoded sha-256 multihash certhash that clients pin.

func GenerateIdentity

func GenerateIdentity() (*Identity, error)

GenerateIdentity mints a fresh ECDSA P-256 key + self-signed cert. Use this when you want to manage the on-disk format yourself; pair with (*Identity).PEM() for serialization and IdentityFromPEM for load.

func IdentityFromPEM

func IdentityFromPEM(pemStr string) (*Identity, error)

IdentityFromPEM parses the combined PEM produced by (*Identity).PEM().

func LoadOrCreateIdentity

func LoadOrCreateIdentity(keyPath string) (*Identity, error)

LoadOrCreateIdentity reads keyPath if it exists, otherwise generates a new ECDSA P-256 key + self-signed cert and writes them out together.

The file holds both the PRIVATE KEY and CERTIFICATE PEM blocks, so the certhash is byte-stable across restarts: the cert is built once and then loaded verbatim on subsequent starts.

For backwards compatibility, a file containing only a PRIVATE KEY block (the previous on-disk format) is accepted: a fresh cert is built from that key and the file is rewritten in the combined format. The cert hash will change at the migration boundary, but stay stable thereafter.

func (*Identity) PEM

func (i *Identity) PEM() (string, error)

PEM returns the combined PRIVATE KEY + CERTIFICATE PEM. Round-trips through IdentityFromPEM with the same certhash.

type Listener

type Listener struct {
	// contains filtered or unexported fields
}

Listener accepts kps connections on a UDP port. The same port serves any number of clients, demultiplexed by their ICE ufrag.

func Listen

func Listen(ctx context.Context, addr string, opts Options) (*Listener, error)

Listen binds a UDP socket and starts accepting kps connections. `addr` is a host:port string in net.Dial form (use ":0" for an ephemeral port).

func (*Listener) Accept

func (l *Listener) Accept(ctx context.Context) (Conn, error)

Accept returns the next established connection, blocking until one arrives, ctx is done, or the listener closes. Each Conn carries its own streams via Conn.AcceptStream.

func (*Listener) Address

func (l *Listener) Address(ip string) string

Address returns the public-facing kps address ("ip:port:certhash") for the requested ip. If ip is empty, attempts to use the bound socket's address; pass "127.0.0.1" or a LAN/public IP explicitly for clients to dial across machines.

func (*Listener) Certhash

func (l *Listener) Certhash() string

Certhash returns the multibase-encoded sha-256 multihash clients pin.

func (*Listener) Close

func (l *Listener) Close() error

func (*Listener) Port

func (l *Listener) Port() int

Port returns the UDP port the listener bound to.

type Options

type Options struct {
	// Identity, when set, is used directly. The Listener writes nothing
	// to disk; the caller is responsible for persistence. Use
	// kps.GenerateIdentity / kps.IdentityFromPEM / (*Identity).PEM.
	Identity *Identity

	// KeyFile path to the persistent combined PEM (PRIVATE KEY +
	// CERTIFICATE). Created if absent. Ignored when Identity is set.
	KeyFile string
}

type Stream

type Stream interface {
	io.Reader
	io.Writer
	// CloseWrite gracefully finishes the local write half; the peer observes
	// EOF after all previously written bytes.
	CloseWrite() error
	// CancelRead stops inbound bytes (cancellation, not EOF); where supported
	// the peer is told to stop sending.
	CancelRead(code ErrorCode) error
	// ResetWrite aborts the local write half; the peer observes a stream error.
	ResetWrite(code ErrorCode) error
	// Close tears down both halves of the stream.
	Close() error
	// CloseWithError tears down both halves, conveying an error code to the peer
	// (reset write + stop-sending read). Mirrors the JS stream's close(reason).
	CloseWithError(code ErrorCode) error
	// Closed is closed when the stream ends (either half torn down or peer gone).
	Closed() <-chan struct{}
	// Err reports why the stream closed: nil while open or after a clean close,
	// non-nil otherwise (best-effort, transport-dependent).
	Err() error
}

Stream is an unnamed, bidirectional, reliable, ordered byte stream (SPEC §6) with no message boundaries. It is an io.Reader and io.Writer with QUIC-like lifecycle controls.

type StreamError

type StreamError struct {
	Code ErrorCode
	// Remote is true when the code originated from the peer.
	Remote bool
}

StreamError is the error surfaced to the read side when the peer aborts its write half (RESET), or to the write side when the peer cancels its read (STOP_SENDING). Callers can inspect Code (SPEC §9.1).

func (*StreamError) Error

func (e *StreamError) Error() string

Directories

Path Synopsis
cmd
dial command
Command dial is a minimal KPS echo client used by the JS integration tests to exercise cross-implementation paths: it dials a server over the chosen transport, opens one stream, writes a message, half-closes, reads the echo back, and exits 0 iff the echo matches (non-zero otherwise).
Command dial is a minimal KPS echo client used by the JS integration tests to exercise cross-implementation paths: it dials a server over the chosen transport, opens one stream, writes a message, half-closes, reads the echo back, and exits 0 iff the echo matches (non-zero otherwise).
server command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL