Files
mesh-sdk/go/stateobjects/send.go
T

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) }
}