29c5947fa5
---ci--- project: oy phase: 2 milestone: v0.5 status: complete requirements: covered: [REQ-034] partial: [] ---/ci---
131 lines
5.0 KiB
Go
131 lines
5.0 KiB
Go
package keeper
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
|
|
"github.com/oy/openyield/x/bearers/types"
|
|
)
|
|
|
|
// transport.go holds the store-backed BearerTransport impl (P2-02-01,
|
|
// REQ-034, A-522). The v0.2 BearerTransport Go interface (Send, Receive,
|
|
// Status) gains a store-backed runtime impl: the keeper IS the transport
|
|
// for simtest purposes — no hardware/RF Go libraries (D-054).
|
|
//
|
|
// The transport wraps the keeper's session store. Send appends a frame to
|
|
// the session's Frames slice. Receive marks the frame received (and
|
|
// transitions the session Open → Active on first ack). Status reports
|
|
// whether the session is Open or Active (i.e., still carrying traffic).
|
|
//
|
|
// Surveillance-resistant invariant (A-522): the transport carries NO
|
|
// geolocation / sender physical location fields. The surveillance-resistant
|
|
// locked const on OYSATLink/OYLRLink is a runtime invariant — the transport
|
|
// MUST NOT emit geolocation in events. A negative simtest asserts the event
|
|
// set contains NO geolocation fields.
|
|
|
|
// StoreTransport is the store-backed BearerTransport impl. It wraps a
|
|
// Keeper + the sdk.Context (bound at construction so the BearerTransport
|
|
// interface methods can stay parameterless per the v0.2 interface contract).
|
|
// The transport operates on a single session-id (a transport instance is
|
|
// scoped to one session — the bearer is a per-session handle in the simtest
|
|
// runtime).
|
|
type StoreTransport struct {
|
|
keeper Keeper
|
|
ctx sdk.Context
|
|
sessionID string
|
|
}
|
|
|
|
// NewStoreTransport constructs a store-backed BearerTransport scoped to the
|
|
// named session. The session must already exist (Open or Active). The
|
|
// transport reads/writes the session's Frames slice via the keeper store.
|
|
func NewStoreTransport(k Keeper, ctx sdk.Context, sessionID string) *StoreTransport {
|
|
return &StoreTransport{keeper: k, ctx: ctx, sessionID: sessionID}
|
|
}
|
|
|
|
// Compile-time assertion: StoreTransport satisfies the v0.2 BearerTransport
|
|
// interface (D-029, REQ-034). The interface contract is Send/Receive/Status
|
|
// (parameterless except Send takes a payload).
|
|
var _ types.BearerTransport = (*StoreTransport)(nil)
|
|
|
|
// Send dispatches a payload via the bearer. The store-backed impl appends
|
|
// the payload as a new Frame on the session's Frames slice. Returns an
|
|
// error if the session is not found or is terminal (Closed/Revoked) — a
|
|
// terminal session rejects further Send calls.
|
|
func (t *StoreTransport) Send(payload []byte) error {
|
|
s, ok := t.keeper.GetSession(t.ctx, t.sessionID)
|
|
if !ok {
|
|
return fmt.Errorf("bearers: session %q not found", t.sessionID)
|
|
}
|
|
if s.IsTerminal() {
|
|
return fmt.Errorf("bearers: session %q is terminal (%s), rejects Send", t.sessionID, s.Status)
|
|
}
|
|
frame := types.Frame{
|
|
FrameID: fmt.Sprintf("%s-frame-%d", t.sessionID, len(s.Frames)+1),
|
|
SenderReach: s.InitiatorReach,
|
|
PayloadBytes: payload,
|
|
SentAt: t.ctx.BlockTime().Unix(),
|
|
}
|
|
s.Frames = append(s.Frames, frame)
|
|
t.keeper.SetSession(t.ctx, s)
|
|
t.ctx.EventManager().EmitEvent(sdk.NewEvent(
|
|
"bearers.frame_sent",
|
|
sdk.NewAttribute("session_id", t.sessionID),
|
|
sdk.NewAttribute("frame_id", frame.FrameID),
|
|
sdk.NewAttribute("sender_reach", frame.SenderReach),
|
|
// NO geolocation (A-522 surveillance-resistant invariant).
|
|
))
|
|
return nil
|
|
}
|
|
|
|
// Receive accepts an inbound payload from the bearer. The store-backed impl
|
|
// marks the first unreceived frame as Received and transitions the session
|
|
// Open → Active on the first ack. Returns the payload and an error if the
|
|
// bearer has no inbound (unreceived) payload or the session is terminal.
|
|
func (t *StoreTransport) Receive() ([]byte, error) {
|
|
s, ok := t.keeper.GetSession(t.ctx, t.sessionID)
|
|
if !ok {
|
|
return nil, fmt.Errorf("bearers: session %q not found", t.sessionID)
|
|
}
|
|
if s.IsTerminal() {
|
|
return nil, fmt.Errorf("bearers: session %q is terminal (%s), rejects Receive", t.sessionID, s.Status)
|
|
}
|
|
// Find the first unreceived frame.
|
|
var received *types.Frame
|
|
for i := range s.Frames {
|
|
if !s.Frames[i].Received {
|
|
s.Frames[i].Received = true
|
|
received = &s.Frames[i]
|
|
break
|
|
}
|
|
}
|
|
if received == nil {
|
|
return nil, fmt.Errorf("bearers: no inbound frame on session %q", t.sessionID)
|
|
}
|
|
// Open → Active on the first ack.
|
|
if s.Status == types.SessionOpen {
|
|
s.Status = types.SessionActive
|
|
}
|
|
t.keeper.SetSession(t.ctx, s)
|
|
t.ctx.EventManager().EmitEvent(sdk.NewEvent(
|
|
"bearers.frame_received",
|
|
sdk.NewAttribute("session_id", t.sessionID),
|
|
sdk.NewAttribute("frame_id", received.FrameID),
|
|
sdk.NewAttribute("status", string(s.Status)),
|
|
// NO geolocation (A-522 surveillance-resistant invariant).
|
|
))
|
|
return received.PayloadBytes, nil
|
|
}
|
|
|
|
// Status reports the bearer's current reachability (true = reachable). The
|
|
// store-backed impl reports true iff the session exists and is Open or
|
|
// Active (still carrying traffic). A terminal or missing session is
|
|
// unreachable.
|
|
func (t *StoreTransport) Status() bool {
|
|
s, ok := t.keeper.GetSession(t.ctx, t.sessionID)
|
|
if !ok {
|
|
return false
|
|
}
|
|
return s.Status == types.SessionOpen || s.Status == types.SessionActive
|
|
}
|