Builds: design 47 G §59, §407 Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
94 lines
3.7 KiB
Go
94 lines
3.7 KiB
Go
package stateobjects
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
stdio "git.novox.be/novox/mesh-sdk/go"
|
|
"git.novox.be/novox/mesh-sdk/go/buscalls"
|
|
)
|
|
|
|
// MessageLimit is the most a message of the bus carries when the sender does not know its connection's own
|
|
// (`nc.MaxPayload()`), and MessageHeaderRoom what the headers and the subject take of it.
|
|
const (
|
|
MessageLimit = 1 << 20
|
|
MessageHeaderRoom = 8 << 10
|
|
)
|
|
|
|
// Sender sends the body of a verb's reply or an event: a body over the per-message limit is put in the sender's store of
|
|
// sent objects and its reference is sent instead (ADR 0666 §16). The store is the bus module's `<module>_sent-objects`,
|
|
// granted to the sender alone and read by the callers its seat definition names and the consumers that declare the event;
|
|
// a sent object lives as long as that store's age, which is transport and not kept data.
|
|
type Sender struct {
|
|
// Objects is the sender's store of sent objects, and Store its name as a reader asks for it.
|
|
Objects Objects
|
|
Store string
|
|
// Limit is the connection's largest message; zero is MessageLimit.
|
|
Limit int
|
|
}
|
|
|
|
// Send is what to send for body: the body itself when it fits, else the reference of the object it was put in as
|
|
// `<label>.<sha256>`. label tells the object's use (the verb or event, the caller or the id).
|
|
func (s Sender) Send(label string, body []byte) ([]byte, error) {
|
|
limit := s.Limit
|
|
if limit <= 0 {
|
|
limit = MessageLimit
|
|
}
|
|
if len(body) <= limit-MessageHeaderRoom {
|
|
if _, isRef := ReadRef(body); isRef {
|
|
return nil, fmt.Errorf("%s: %w", label, errLooksLikeRef)
|
|
}
|
|
return body, nil
|
|
}
|
|
if most := s.Objects.MostBytes(); most > 0 && len(body) > most {
|
|
return nil, fmt.Errorf("%s: a bundle sends at most %d bytes of body, the runtime's channel carrying a message of 16 MiB; this one is %d", label, most, len(body))
|
|
}
|
|
ref := Ref{Store: s.Store, Object: objectName(label, digestOf(body)), Size: int64(len(body)), Digest: digestOf(body), Form: FormVersion}
|
|
if _, err := putObject(s.Objects, ref.Object, body); err != nil {
|
|
return nil, fmt.Errorf("the object for %s: %w", label, err)
|
|
}
|
|
return Wrap(ref), nil
|
|
}
|
|
|
|
// Fetch reads the object a reference names.
|
|
type Fetch func(Ref) ([]byte, error)
|
|
|
|
// Receive is the body that was sent: the body itself, or the object its reference names, fetched and checked against the
|
|
// reference's size and digest.
|
|
func Receive(body []byte, fetch Fetch) ([]byte, error) {
|
|
ref, ok := ReadRef(body)
|
|
if !ok {
|
|
return body, nil
|
|
}
|
|
data, err := fetch(ref)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("the object %s of %s: %w", ref.Object, ref.Store, err)
|
|
}
|
|
if int64(len(data)) != ref.Size || digestOf(data) != ref.Digest {
|
|
return nil, fmt.Errorf("the object %s of %s is not the one its reference names (size or digest differ)", ref.Object, ref.Store)
|
|
}
|
|
return data, nil
|
|
}
|
|
|
|
// BundleFetch reads a sent object through the runtime, which serves it under the standing grant the grants part derives
|
|
// from the manifests (buscalls.ObjectsSentGet).
|
|
func BundleFetch(ref Ref) ([]byte, error) {
|
|
if ref.Parts > 1 {
|
|
return nil, fmt.Errorf("the object %s is split over %d objects, which a bundle does not read: a sender in a bundle never splits one", ref.Object, ref.Parts)
|
|
}
|
|
raw, err := stdio.BusState(buscalls.ObjectsSentGet, buscalls.ObjectsSentGetRequest{Store: ref.Store, Object: ref.Object, Size: ref.Size, Digest: ref.Digest})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var d buscalls.ObjectData
|
|
if err := json.Unmarshal(raw, &d); err != nil {
|
|
return nil, fmt.Errorf("the runtime's answer to %s: %w", buscalls.ObjectsSentGet, err)
|
|
}
|
|
return d.Data, nil
|
|
}
|
|
|
|
// ProcessFetch reads a sent object through the reader's own object store.
|
|
func ProcessFetch(o Objects) Fetch {
|
|
return func(ref Ref) ([]byte, error) { return getObject(o, ref) }
|
|
}
|