srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/internal/ipc/ipc.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/ipc/ipc.go')
-rw-r--r--internal/ipc/ipc.go126
1 files changed, 126 insertions, 0 deletions
diff --git a/internal/ipc/ipc.go b/internal/ipc/ipc.go
new file mode 100644
index 0000000..c9d92b4
--- /dev/null
+++ b/internal/ipc/ipc.go
@@ -0,0 +1,126 @@
+// Package ipc is the protocol between mitmuxd (which owns the proxy and
+// the history database) and a client such as the TUI, spoken as
+// newline-agnostic JSON messages over a Unix domain socket. This keeps
+// the proxy engine running independently of any UI attached to it.
+package ipc
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "net"
+
+ "mitmux/internal/store"
+)
+
+// Request is sent by a client to the daemon.
+type Request struct {
+ Type string `json:"type"` // "list", "get", or "subscribe"
+ Limit int `json:"limit,omitempty"`
+ BeforeID int64 `json:"before_id,omitempty"`
+ ID int64 `json:"id,omitempty"`
+}
+
+// Response is sent by the daemon to a client.
+type Response struct {
+ Type string `json:"type"` // "list", "get", "new", or "error"
+ Entries []store.Summary `json:"entries,omitempty"` // for "list"
+ Detail *EntryDetail `json:"detail,omitempty"` // for "get"
+ New *store.Summary `json:"new,omitempty"` // for "new" (subscribe push)
+ Error string `json:"error,omitempty"`
+}
+
+// EntryDetail is a full history entry, raw bytes included.
+type EntryDetail struct {
+ store.Summary
+ RequestRaw []byte `json:"request_raw"`
+ ResponseRaw []byte `json:"response_raw"`
+ RequestExact bool `json:"request_exact"`
+ ResponseExact bool `json:"response_exact"`
+}
+
+// Client talks to a mitmuxd instance for request/response queries
+// (list, get). Use Subscribe separately for the live-update stream.
+type Client struct {
+ conn net.Conn
+ dec *json.Decoder
+ enc *json.Encoder
+}
+
+// Dial connects to the daemon's control socket at path.
+func Dial(path string) (*Client, error) {
+ conn, err := net.Dial("unix", path)
+ if err != nil {
+ return nil, fmt.Errorf("dial %s: %w", path, err)
+ }
+ return &Client{conn: conn, dec: json.NewDecoder(conn), enc: json.NewEncoder(conn)}, nil
+}
+
+// Close closes the connection to the daemon.
+func (c *Client) Close() error {
+ return c.conn.Close()
+}
+
+// List returns up to limit history summaries older than beforeID (0 for
+// the most recent), newest first.
+func (c *Client) List(limit int, beforeID int64) ([]store.Summary, error) {
+ if err := c.enc.Encode(Request{Type: "list", Limit: limit, BeforeID: beforeID}); err != nil {
+ return nil, err
+ }
+ var resp Response
+ if err := c.dec.Decode(&resp); err != nil {
+ return nil, err
+ }
+ if resp.Type == "error" {
+ return nil, errors.New(resp.Error)
+ }
+ return resp.Entries, nil
+}
+
+// Get returns the full entry (raw bytes included) for id.
+func (c *Client) Get(id int64) (*EntryDetail, error) {
+ if err := c.enc.Encode(Request{Type: "get", ID: id}); err != nil {
+ return nil, err
+ }
+ var resp Response
+ if err := c.dec.Decode(&resp); err != nil {
+ return nil, err
+ }
+ if resp.Type == "error" {
+ return nil, errors.New(resp.Error)
+ }
+ return resp.Detail, nil
+}
+
+// Subscribe opens a dedicated connection that streams newly captured
+// history entries as they happen. The returned channel is closed when
+// the connection ends; call the returned close func to stop early.
+func Subscribe(path string) (<-chan store.Summary, func() error, error) {
+ conn, err := net.Dial("unix", path)
+ if err != nil {
+ return nil, nil, fmt.Errorf("dial %s: %w", path, err)
+ }
+ if err := json.NewEncoder(conn).Encode(Request{Type: "subscribe"}); err != nil {
+ conn.Close()
+ return nil, nil, err
+ }
+
+ ch := make(chan store.Summary, 64)
+ go func() {
+ defer close(ch)
+ dec := json.NewDecoder(conn)
+ for {
+ var resp Response
+ if err := dec.Decode(&resp); err != nil {
+ return
+ }
+ if resp.Type == "new" && resp.New != nil {
+ select {
+ case ch <- *resp.New:
+ default:
+ }
+ }
+ }
+ }()
+ return ch, conn.Close, nil
+}