Something went wrong. Try again.
Monorepo for Tangled tangled.org
Something went wrong. Try again.
Go
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239package knotfeed
import ( "bytes" "fmt" "slices" "strings" "unicode/utf8"
"github.com/bluesky-social/indigo/atproto/syntax" "github.com/samber/lo" cbg "github.com/whyrusleeping/cbor-gen" "tangled.org/core/api/tangled")
const maxPushOptions = 64const GitRefCollection syntax.NSID = tangled.GitRefNSID
const mstKeyBudget = 256
const MaxRefRkeyBytes = mstKeyBudget - len(GitRefCollection) - 1
const hexDigits = "0123456789abcdef"
func plainByte(b byte) bool { switch { case b >= 'A' && b <= 'Z', b >= 'a' && b <= 'z', b >= '0' && b <= '9': return true case b == '-', b == '_': return true default: return false }}
func allPlain(s string) bool { for i := range len(s) { if !plainByte(s[i]) { return false } } return true}
func EscapeRefname(refname string) (syntax.RecordKey, bool) { out := make([]byte, 0, len(refname)) for i := range len(refname) { b := refname[i] if plainByte(b) { out = append(out, b) continue } out = append(out, '~', hexDigits[b>>4], hexDigits[b&0x0f]) } if len(out) > MaxRefRkeyBytes { return "", false } return syntax.RecordKey(out), true}
func UnescapeRkey(rkey syntax.RecordKey) (string, bool) { escaped := rkey.String() if !strings.Contains(escaped, "~") { return escaped, allPlain(escaped) } var out []byte rest := escaped for { idx := strings.IndexByte(rest, '~') if idx < 0 { if !allPlain(rest) { return "", false } if !utf8.Valid(out) { return "", false } return string(append(out, rest...)), true } head, chunk := rest[:idx], rest[idx+1:] if !allPlain(head) || len(chunk) < 2 { return "", false } hi, ok := nibble(chunk[0]) if !ok { return "", false } lo, ok := nibble(chunk[1]) if !ok { return "", false } out = append(out, head...) out = append(out, hi<<4|lo) rest = chunk[2:] }}
func nibble(b byte) (byte, bool) { switch { case b >= '0' && b <= '9': return b - '0', true case b >= 'a' && b <= 'f': return b - 'a' + 10, true default: return 0, false }}
type RefRecord struct { Sha ObjectID Editor syntax.DID PushOptions []string}
func DecodeRefRecord(data []byte) (RefRecord, error) { var rec RefRecord var rawSha, rawEditor string r := bytes.NewReader(data) maj, count, err := cbg.CborReadHeader(r) if err != nil { return rec, err } if maj != cbg.MajMap { return rec, fmt.Errorf("expected a cbor map, got major type %d", maj) } for range count { key, err := cbg.ReadString(r) if err != nil { return rec, err } switch key { case "sha": if rawSha, err = cbg.ReadString(r); err != nil { return rec, err } case "x-tngl-editor": if rawEditor, err = cbg.ReadString(r); err != nil { return rec, err } case "x-tngl-push-options": if rec.PushOptions, err = readStringArray(r); err != nil { return rec, err } default: if err := skipValue(r, 0); err != nil { return rec, err } } } if r.Len() != 0 { return rec, fmt.Errorf("ref record has %d trailing bytes after its map", r.Len()) } sha, ok := ParseObjectID(rawSha) if !ok || sha.IsZero() { return rec, fmt.Errorf("sha %q isn't 40 or 64 hex digits naming an object", rawSha) } rec.Sha = sha if rawEditor == "" { return rec, nil } editor, err := syntax.ParseDID(rawEditor) if err != nil { return rec, fmt.Errorf("ref record editor: %w", err) } rec.Editor = editor return rec, nil}
func readStringArray(r *bytes.Reader) ([]string, error) { maj, count, err := cbg.CborReadHeader(r) if err != nil { return nil, err } if maj != cbg.MajArray { return nil, fmt.Errorf("expected a cbor array, got major type %d", maj) } if count > maxPushOptions { return nil, fmt.Errorf("push options array claims %d entries", count) } out := make([]string, 0, count) for range count { s, err := cbg.ReadString(r) if err != nil { return nil, err } out = append(out, s) } return out, nil}
type cborBuf struct{ bytes.Buffer }
func (b *cborBuf) header(major byte, count uint64) { _ = cbg.CborWriteHeader(&b.Buffer, major, count)}
func (b *cborBuf) str(s string) { b.header(cbg.MajTextString, uint64(len(s))) b.WriteString(s)}
func (b *cborBuf) pair(key, value string) { b.str(key) b.str(value)}
func encodeRefRecord(rec RefRecord) ([]byte, error) { past := func(s string) bool { return len(s) > cbg.MaxLength } switch { case rec.Sha.IsZero(): return nil, fmt.Errorf("won't encode a ref record without a sha") case len(rec.PushOptions) > maxPushOptions: return nil, fmt.Errorf("won't encode %d push options, past the %d limit", len(rec.PushOptions), maxPushOptions) case past(rec.Editor.String()) || slices.ContainsFunc(rec.PushOptions, past): return nil, fmt.Errorf("ref record holds a string past the %d byte cbor limit", cbg.MaxLength) }
pairs := uint64(2) if rec.Editor != "" { pairs++ } if len(rec.PushOptions) > 0 { pairs++ }
var out cborBuf out.header(cbg.MajMap, pairs) out.pair("sha", rec.Sha.String()) out.pair("$type", GitRefCollection.String()) if rec.Editor != "" { out.pair("x-tngl-editor", rec.Editor.String()) } if len(rec.PushOptions) > 0 { out.str("x-tngl-push-options") out.header(cbg.MajArray, uint64(len(rec.PushOptions))) lo.ForEach(rec.PushOptions, func(option string, _ int) { out.str(option) }) } return out.Bytes(), nil}