capture

package
v0.1.0 Latest Latest
Warning

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

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

Documentation

Overview

Package capture defines the protocol-agnostic traffic model (Flow, Message) and a thread-safe Store that the UI subscribes to. Nothing in this package knows about HTTP, TLS, or bubbletea — it is the shared vocabulary the proxy, the protocol parsers, and the TUI all speak.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func DecodeContentEncoding

func DecodeContentEncoding(body []byte, encoding string) []byte

DecodeContentEncoding decompresses a body per its Content-Encoding header (gzip, deflate, br, zstd). Empty/identity/unknown encodings and any decode error return the input unchanged, so display is always best-effort and never fails.

func Save

func Save(w io.Writer, flows []*Flow) error

Save writes flows to w as indented JSON — a capture session that can be reloaded later with Load.

func SaveFile

func SaveFile(path string, flows []*Flow) error

SaveFile writes a capture session to path.

Types

type Direction

type Direction int

Direction distinguishes client→server bytes from server→client bytes. It is the only orientation concept streaming protocols (WebSocket, gRPC, raw TCP) need, and request/response protocols map cleanly onto it too.

const (
	ClientToServer Direction = iota
	ServerToClient
)

func (Direction) String

func (d Direction) String() string

type Event

type Event struct {
	Flow *Flow
	Kind EventKind
}

Event describes a change to the Store. The TUI translates these into tea.Msg values so the Elm-style update loop stays the single UI writer.

type EventKind

type EventKind int
const (
	FlowAdded EventKind = iota
	FlowUpdated
)

type Flow

type Flow struct {
	ID         string
	Protocol   Protocol
	ClientAddr string
	ServerAddr string // host:port as the client requested it
	SNI        string // TLS server name, empty for plaintext
	Secure     bool   // was this connection TLS-terminated by us?
	StartedAt  time.Time
	Status     Status
	Flagged    bool  // user-marked as interesting (UI-only; survives session save)
	Err        error `json:"-"` // an error interface can't round-trip through JSON

	// Request/Response for request-response protocols.
	Request  *Message
	Response *Message

	// Messages for streaming protocols (ordered). Guard appends with
	// AddMessage: streaming protocols pump both directions concurrently.
	Messages []*Message
	// contains filtered or unexported fields
}

Flow is one logical exchange over one connection.

func Load

func Load(r io.Reader) ([]*Flow, error)

Load reads a capture session previously written by Save.

func LoadFile

func LoadFile(path string) ([]*Flow, error)

LoadFile reads a capture session from path.

func NewFlow

func NewFlow(client, server string) *Flow

NewFlow constructs an active flow with a fresh id and start time.

func (*Flow) AddMessage

func (f *Flow) AddMessage(m *Message)

AddMessage appends a streaming message under a lock, so the two per-direction pump goroutines of a streaming protocol don't race.

func (*Flow) Title

func (f *Flow) Title() string

Title is the compact label shown in the flow list.

type Injector

type Injector interface {
	Inject(dir Direction, opcode byte, payload []byte) error
}

Injector pushes a frame into a live bidirectional session (currently WebSocket). Direction chooses which peer receives it; opcode and payload are protocol-specific (a WebSocket opcode and its bytes). Implemented by the protocol handler for an active connection.

type Message

type Message struct {
	Direction Direction
	Timestamp time.Time
	// Summary is the one-line human view, e.g. "GET /v1/users HTTP/1.1" or
	// "200 OK (1.2 KB)". Parsers fill this in.
	Summary string
	Headers map[string][]string
	Body    []byte
	// Raw is the full serialized message exactly as it went (or will go) on the
	// wire — request line + headers + body for HTTP, the frame payload for
	// streaming protocols. It is what the tamper editor seeds from and what an
	// edited Resolution.EditedBody replaces.
	Raw []byte
	// Meta carries protocol-specific fields that don't fit Headers/Body
	// (gRPC status, WS opcode, HTTP method/path, …).
	Meta map[string]string
}

Message is one parsed unit of traffic. For request/response protocols a Flow has one client Message (the request) and one server Message (the response). For streaming protocols a Flow accumulates many Messages in order.

type Protocol

type Protocol string

Protocol names the application protocol a Flow was parsed as.

const (
	ProtoHTTP1     Protocol = "http/1.1"
	ProtoHTTP2     Protocol = "http/2"
	ProtoWebSocket Protocol = "websocket"
	ProtoGRPC      Protocol = "grpc"
	ProtoRawTCP    Protocol = "tcp"
	ProtoUnknown   Protocol = "unknown"
)

type SessionRegistrar

type SessionRegistrar interface {
	RegisterSession(flowID string, inj Injector)
	UnregisterSession(flowID string)
}

SessionRegistrar is an optional capability a Tamperer may also implement. Streaming protocol handlers type-assert their tamper to it and register their live session so the UI can inject into it; a tamper that doesn't support injection simply won't satisfy the assertion.

type Status

type Status int

Status tracks a Flow through the intercept lifecycle.

const (
	// StatusActive: bytes are flowing, nothing is paused.
	StatusActive Status = iota
	// StatusPending: matched an intercept rule and is paused awaiting a user
	// decision (forward / drop / edit).
	StatusPending
	// StatusComplete: the exchange finished normally.
	StatusComplete
	// StatusError: the connection or parse failed; see Flow.Err.
	StatusError
)

func (Status) String

func (s Status) String() string

type Store

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

Store is the thread-safe home for every captured Flow. The proxy writes; the UI reads and subscribes. It intentionally keeps flows in insertion order so the list pane is stable.

func NewStore

func NewStore() *Store

func (*Store) Add

func (s *Store) Add(f *Flow)

Add records a new flow and notifies subscribers.

func (*Store) Get

func (s *Store) Get(id string) (*Flow, bool)

Get returns a flow by id.

func (*Store) List

func (s *Store) List() []*Flow

List returns a snapshot of flows in insertion order. Callers get copies of the slice, not the underlying flows, so iteration is race-free even while the proxy keeps mutating flow contents.

func (*Store) Subscribe

func (s *Store) Subscribe() <-chan Event

Subscribe returns a channel that receives every subsequent Event. The buffer keeps a burst of captures from blocking the proxy on a slow UI.

func (*Store) Touch

func (s *Store) Touch(f *Flow)

Touch notifies subscribers that an already-stored flow changed in place. The proxy mutates the *Flow directly (it holds the pointer) and then calls Touch so the UI knows to re-render.

Jump to

Keyboard shortcuts

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