// Copyright (c) 2026, Pim van Pelt package vpp import ( "fmt" "log/slog" "go.fd.io/govpp/api" "git.ipng.ch/ipng/vpp-maglev/internal/metrics" ) // loggedChannel wraps an api.Channel so that every VPP request/reply is // recorded via slog at DEBUG level. All code in this package MUST send VPP // messages through a loggedChannel (via Client.apiChannel) so we have a // complete audit trail of what was sent to the dataplane. type loggedChannel struct { ch api.Channel } // apiChannel opens a new API channel wrapped in logging. This is the only // approved way to talk to VPP; do not call conn.NewAPIChannel directly. func (c *Client) apiChannel() (*loggedChannel, error) { c.mu.Lock() conn := c.apiConn c.mu.Unlock() if conn == nil { return nil, errNotConnected } ch, err := conn.NewAPIChannel() if err != nil { return nil, err } return &loggedChannel{ch: ch}, nil } // Close closes the underlying channel. func (lc *loggedChannel) Close() { lc.ch.Close() } // SendRequest logs the outgoing message and returns a wrapped request context. func (lc *loggedChannel) SendRequest(msg api.Message) *loggedRequestCtx { name := msg.GetMessageName() slog.Debug("vpp-api-send", "msg", name, "crc", msg.GetCrcString(), "payload", fmt.Sprintf("%+v", msg), ) metrics.VPPAPITotal.WithLabelValues(name, "send", "success").Inc() return &loggedRequestCtx{ ctx: lc.ch.SendRequest(msg), name: name, } } // SendMultiRequest logs the outgoing message and returns a wrapped multi-request context. func (lc *loggedChannel) SendMultiRequest(msg api.Message) *loggedMultiRequestCtx { name := msg.GetMessageName() slog.Debug("vpp-api-send-multi", "msg", name, "crc", msg.GetCrcString(), "payload", fmt.Sprintf("%+v", msg), ) metrics.VPPAPITotal.WithLabelValues(name, "send", "success").Inc() return &loggedMultiRequestCtx{ ctx: lc.ch.SendMultiRequest(msg), name: name, } } // loggedRequestCtx wraps api.RequestCtx and logs the reply on ReceiveReply. type loggedRequestCtx struct { ctx api.RequestCtx name string } func (r *loggedRequestCtx) ReceiveReply(msg api.Message) error { err := r.ctx.ReceiveReply(msg) if err != nil { slog.Debug("vpp-api-recv", "req", r.name, "reply", msg.GetMessageName(), "err", err, ) metrics.VPPAPITotal.WithLabelValues(r.name, "recv", "failure").Inc() return err } slog.Debug("vpp-api-recv", "req", r.name, "reply", msg.GetMessageName(), "payload", fmt.Sprintf("%+v", msg), ) metrics.VPPAPITotal.WithLabelValues(r.name, "recv", "success").Inc() return nil } // loggedMultiRequestCtx wraps api.MultiRequestCtx and logs each reply. type loggedMultiRequestCtx struct { ctx api.MultiRequestCtx name string seq int } func (r *loggedMultiRequestCtx) ReceiveReply(msg api.Message) (bool, error) { stop, err := r.ctx.ReceiveReply(msg) if err != nil { slog.Debug("vpp-api-recv-multi", "req", r.name, "reply", msg.GetMessageName(), "seq", r.seq, "err", err, ) metrics.VPPAPITotal.WithLabelValues(r.name, "recv", "failure").Inc() return stop, err } if stop { slog.Debug("vpp-api-recv-multi-done", "req", r.name, "count", r.seq, ) return stop, nil } slog.Debug("vpp-api-recv-multi", "req", r.name, "reply", msg.GetMessageName(), "seq", r.seq, "payload", fmt.Sprintf("%+v", msg), ) metrics.VPPAPITotal.WithLabelValues(r.name, "recv", "success").Inc() r.seq++ return stop, nil }