// Package protowire binds the musiquay protocol vocabulary to the relay's // native event type. The vocabulary is a leaf (protocol imports only runtime, // errorf and text), so this package may import it without dragging the relay // into the vocabulary: it imports nothing from morly. // // Two jobs live here. The encoding job turns an event's tag list into the // [][]string the protocol parsers take. The policy job answers what a // blob-bearing event's asset address is, which the vocabulary itself cannot // know: it depends on the event kind. package protowire import ( "git.smesh.lol/musiquay/pkg/protocol" "git.smesh.lol/nostr/pkg/event" ) // TagsOf copies an event's tag list into the [][]string shape the protocol // parsers take. // // The outer slice and every inner row are freshly allocated and presized: // ev.Tags.T already knows both lengths, so there is nothing to grow and no // push. A nil tag entry is left as a nil row rather than copied, since a // parser skips rows shorter than one element anyway. func TagsOf(ev *event.E) (tags [][]string) { if ev == nil || ev.Tags == nil { return nil } src := ev.Tags.T tags = [][]string{:len(src)} for i := range src { tg := src[i] if tg == nil { continue } row := []string{:len(tg.T)} for j := range tg.T { row[j] = tg.T[j] } tags[i] = row } return } // ValidateEvent parses and validates a payload by kind. // // Every kind the protocol defines and has a parser for is checked. Kinds the // protocol defines without a parser, and every kind outside the vocabulary, // return nil: the relay stores a payload this build has no rule for rather // than rejecting it. A Musiquay kind added before its parser must therefore // fall through here, never reach a nil parser. func ValidateEvent(ev *event.E) (err error) { if ev == nil { return nil } tags := TagsOf(ev) content := string(ev.Content) switch ev.Kind { case protocol.KindTrack: v, perr := protocol.ParseTrack(tags, content) if perr != nil { return perr } return v.Validate() case protocol.KindAlbum: v, perr := protocol.ParseAlbum(tags, content) if perr != nil { return perr } return v.Validate() case protocol.KindPlaylist: v, perr := protocol.ParsePlaylist(tags, content) if perr != nil { return perr } return v.Validate() case protocol.KindSetlist: v, perr := protocol.ParseSetlist(tags, content) if perr != nil { return perr } return v.Validate() case protocol.KindReleaseCredits: v, perr := protocol.ParseReleaseCredits(tags, content) if perr != nil { return perr } return v.Validate() case protocol.KindTrackCredits: v, perr := protocol.ParseTrackCredits(tags, content) if perr != nil { return perr } return v.Validate() case protocol.KindEdition: v, perr := protocol.ParseEdition(tags, content) if perr != nil { return perr } return v.Validate() case protocol.KindAccessFilter: v, perr := protocol.ParseAccessFilter(tags, content) if perr != nil { return perr } return v.Validate() case protocol.KindPlayReport: v, perr := protocol.ParsePlayReport(tags, content) if perr != nil { return perr } return v.Validate() case protocol.KindPurchaseOrder: v, perr := protocol.ParsePurchaseOrder(tags, content) if perr != nil { return perr } return v.Validate() case protocol.KindDeliveryReceipt: v, perr := protocol.ParseDeliveryReceipt(tags, content) if perr != nil { return perr } return v.Validate() case protocol.KindHostAggregate: v, perr := protocol.ParseHostAggregate(tags, content) if perr != nil { return perr } return v.Validate() case protocol.KindPublisherRollup: v, perr := protocol.ParsePublisherRollup(tags, content) if perr != nil { return perr } return v.Validate() case protocol.KindCreditLabel: v, perr := protocol.ParseCreditLabel(tags, content) if perr != nil { return perr } return v.Validate() case protocol.KindComment: v, perr := protocol.ParseContribution(tags, content) if perr != nil { return perr } return v.Validate() case protocol.KindCalendar: v, perr := protocol.ParseCalendarEvent(tags, content) if perr != nil { return perr } return v.Validate() case protocol.KindRSVP: v, perr := protocol.ParseRSVP(tags, content) if perr != nil { return perr } return v.Validate() // The NIP-15 payloads are JSON in content with no payload tags, so their // parsers take content alone. case protocol.KindStall: v, perr := protocol.ParseStall(content) if perr != nil { return perr } return v.Validate() case protocol.KindProduct: v, perr := protocol.ParseProduct(content) if perr != nil { return perr } return v.Validate() case protocol.KindListing: v, perr := protocol.ParseListing(tags, content) if perr != nil { return perr } return v.Validate() } return nil } // AssetOf returns the asset address a blob-bearing event belongs to: the first // a tag for a track and a delivery receipt, and the asset inside the d value // for an access filter. Anything else has no asset here. // // An access filter whose d is a subscription tier (sub::) // names no asset, and so does one whose d fails to parse; both return ok=false // rather than an empty string that a caller might index. func AssetOf(ev *event.E) (asset string, ok bool) { if ev == nil { return "", false } switch ev.Kind { case protocol.KindTrack, protocol.KindDeliveryReceipt: tv := ev.Tags.GetFirst([]byte("a")) if tv == nil || tv.Len() < 2 { return "", false } return string(tv.Value()), true case protocol.KindAccessFilter: dv := ev.Tags.GetFirst([]byte("d")) if dv == nil || dv.Len() < 2 { return "", false } tier, perr := protocol.ParseFilterD(string(dv.Value())) if perr != nil || tier.Asset == nil { return "", false } return tier.Asset.String(), true } return "", false }