package launchctx import ( "bytes" "crypto/sha256" "encoding/hex" "encoding/json" "errors" "io" "strings" "time" "unicode/utf8" "amadan.net/carlos/platform.git/launchctx/controlname" "amadan.net/carlos/platform.git/launchctx/wireid" ) const MaxLaunchBytes = 8192 var ErrLaunch = errors.New("maintenance: invalid private launch context") // LaunchContext is private transport metadata, never proof of approval, // exclusion, lease ownership or a trusted worker invocation. type LaunchContext struct { Version int `json:"version"` Account string `json:"account"` App string `json:"app"` Host string `json:"host"` RequestID string `json:"request_id"` AttemptID string `json:"attempt_id"` RequestDigest string `json:"request_digest"` RecordIdentity string `json:"record_identity"` ArtifactDigest string `json:"artifact_digest"` EnvironmentEnvelopeDigest string `json:"environment_envelope_digest"` GuardGeneration int64 `json:"guard_generation"` InputSHA256 string `json:"input_sha256"` DatabasePath string `json:"database_path"` DatabaseDevice uint64 `json:"database_device"` DatabaseInode uint64 `json:"database_inode"` ExecutionDeadline string `json:"execution_deadline"` ExecutionKind string `json:"execution_kind,omitempty"` OperationID string `json:"operation_id,omitempty"` ExpectedStateSHA256 string `json:"expected_state_sha256,omitempty"` ClosedCheckpoint *ClosedCheckpoint `json:"closed_checkpoint,omitempty"` } type ClosedCheckpoint struct { Version int `json:"version"` PreparationID string `json:"preparation_id"` CompletedAt string `json:"completed_at"` MainLength int64 `json:"main_length"` MainSHA256 string `json:"main_sha256"` EvidenceSHA256 string `json:"evidence_sha256"` } // ReadLaunch requires EOF and consumes at most the bound plus one byte. // The worker/adapter must separately bound a reader that can block. func ReadLaunch(r io.Reader) (LaunchContext, error) { if r == nil { return LaunchContext{}, ErrLaunch } raw, err := io.ReadAll(io.LimitReader(r, MaxLaunchBytes+1)) if err != nil { return LaunchContext{}, ErrLaunch } return DecodeLaunch(raw) } func DecodeLaunch(raw []byte) (LaunchContext, error) { fail := func() (LaunchContext, error) { return LaunchContext{}, ErrLaunch } if len(raw) == 0 || len(raw) > MaxLaunchBytes || !utf8.Valid(raw) || !validSurrogates(raw) { return fail() } fields, err := launchObject(raw) if err != nil { return fail() } var version int if json.Unmarshal(fields["version"], &version) != nil { return fail() } names := strings.Fields("version account app host request_id attempt_id request_digest record_identity artifact_digest environment_envelope_digest guard_generation input_sha256 database_path database_device database_inode execution_deadline") switch version { case 1: case 2: names = append(names, strings.Fields("execution_kind operation_id expected_state_sha256 closed_checkpoint")...) cp, err := launchObject(fields["closed_checkpoint"]) if err != nil || !exactLaunchFields(cp, strings.Fields("version preparation_id completed_at main_length main_sha256 evidence_sha256")) { return fail() } default: return fail() } if !exactLaunchFields(fields, names) { return fail() } var c LaunchContext if json.Unmarshal(raw, &c) != nil || !c.valid() { return fail() } return c, nil } func EncodeLaunch(c LaunchContext) ([]byte, error) { if !c.valid() { return nil, ErrLaunch } raw, err := json.Marshal(c) if err != nil || len(raw) > MaxLaunchBytes { return nil, ErrLaunch } return raw, nil } func launchObject(raw []byte) (map[string]json.RawMessage, error) { dec := json.NewDecoder(bytes.NewReader(raw)) tok, err := dec.Token() if err != nil || tok != json.Delim('{') { return nil, ErrLaunch } out := map[string]json.RawMessage{} for dec.More() { tok, err = dec.Token() if err != nil { return nil, ErrLaunch } key, ok := tok.(string) if !ok { return nil, ErrLaunch } if _, exists := out[key]; exists { return nil, ErrLaunch } var value json.RawMessage if dec.Decode(&value) != nil || bytes.Equal(bytes.TrimSpace(value), []byte("null")) { return nil, ErrLaunch } out[key] = value } tok, err = dec.Token() if err != nil || tok != json.Delim('}') { return nil, ErrLaunch } if _, err = dec.Token(); err != io.EOF { return nil, ErrLaunch } return out, nil } func exactLaunchFields(fields map[string]json.RawMessage, names []string) bool { if len(fields) != len(names) { return false } for _, name := range names { if _, ok := fields[name]; !ok { return false } } return true } func canonicalStamp(s string) (time.Time, bool) { t, err := time.Parse(time.RFC3339Nano, s) return t, err == nil && len(s) <= 30 && t.UTC().Format(time.RFC3339Nano) == s } func (c LaunchContext) valid() bool { if !controlname.ValidAccount(c.Account) || !controlname.ValidApp(c.App) || !controlname.ValidHostShape(c.Host) { return false } for _, id := range []string{c.RequestID, c.AttemptID} { if !wireid.ValidID(id) { return false } } for _, digest := range []string{c.RequestDigest, c.RecordIdentity, c.ArtifactDigest, c.EnvironmentEnvelopeDigest, c.InputSHA256} { if !wireid.ValidDigest(digest) { return false } } if c.GuardGeneration <= 0 || c.DatabaseDevice == 0 || c.DatabaseInode == 0 { return false } if len(c.DatabasePath) == 0 || len(c.DatabasePath) > 1024 || c.DatabasePath[0] != '/' || strings.ContainsRune(c.DatabasePath, 0) || !utf8.ValidString(c.DatabasePath) { return false } deadline, ok := canonicalStamp(c.ExecutionDeadline) if !ok { return false } switch c.Version { case 1: return c.ExecutionKind == "" && c.OperationID == "" && c.ExpectedStateSHA256 == "" && c.ClosedCheckpoint == nil case 2: if c.ExecutionKind != "compatibility-check" || !wireid.ValidID(c.OperationID) || !wireid.ValidDigest(c.ExpectedStateSHA256) || c.ClosedCheckpoint == nil { return false } p := c.ClosedCheckpoint completed, ok := canonicalStamp(p.CompletedAt) if !ok || completed.After(deadline) || p.Version != 1 || !wireid.ValidID(p.PreparationID) || p.MainLength <= 0 || p.MainLength > 268435456 || !wireid.ValidDigest(p.MainSHA256) || !wireid.ValidDigest(p.EvidenceSHA256) { return false } return p.EvidenceSHA256 == c.checkpointDigest() } return false } // CheckpointDigest correlates private bindings; it does not certify that a // checkpoint, writer exclusion or read-only namespace actually exists. func (c LaunchContext) CheckpointDigest() (string, error) { if c.Version != 2 || c.ClosedCheckpoint == nil { return "", ErrLaunch } return c.checkpointDigest(), nil } func (c LaunchContext) checkpointBytes() []byte { p := c.ClosedCheckpoint raw, _ := json.Marshal([]any{"carlos/closed-checkpoint/v1", c.Account, c.App, c.Host, c.OperationID, c.RequestID, c.AttemptID, c.RequestDigest, c.RecordIdentity, c.ArtifactDigest, c.EnvironmentEnvelopeDigest, c.GuardGeneration, c.InputSHA256, c.ExpectedStateSHA256, c.DatabasePath, c.DatabaseDevice, c.DatabaseInode, c.ExecutionDeadline, p.PreparationID, p.CompletedAt, p.MainLength, p.MainSHA256}) return raw } func (c LaunchContext) checkpointDigest() string { sum := sha256.Sum256(c.checkpointBytes()) return hex.EncodeToString(sum[:]) }