| 1 | package suite |
| 2 | |
| 3 | import ( |
| 4 | "fmt" |
| 5 | "strings" |
| 6 | ) |
| 7 | |
| 8 | // SubjectKind says how to read an asker subject's value. Doctrine §3: the |
| 9 | // meaning is shape-dependent and nothing else on the wire says which shape |
| 10 | // the pair is in, so a receiver that does not key on the kind it is sent |
| 11 | // answers UNKNOWN — loudly — rather than failing to match and answering |
| 12 | // NOT_VISIBLE, which callers are entitled to act on silently. |
| 13 | type SubjectKind string |
| 14 | |
| 15 | const ( |
| 16 | // SubjectPersonRef is the assertion's opaque `sub` (home §5.3). Suite |
| 17 | // shape, where the home is the identity authority. |
| 18 | SubjectPersonRef SubjectKind = "person_ref" |
| 19 | // SubjectAddress is a lowercased address. Lone shape, where the instance |
| 20 | // keys its own members on the address it signed them in with. |
| 21 | SubjectAddress SubjectKind = "address" |
| 22 | ) |
| 23 | |
| 24 | // Subject is the named asker an S2 or S6 call carries beside its consumer |
| 25 | // token. S3 carries none, by design (§6). |
| 26 | type Subject struct { |
| 27 | Kind SubjectKind `json:"kind"` |
| 28 | Value string `json:"value"` |
| 29 | } |
| 30 | |
| 31 | // NewSubject builds a subject, refusing anything a receiver could not key on |
| 32 | // and normalising what it can. |
| 33 | func NewSubject(kind SubjectKind, value string) (Subject, error) { |
| 34 | switch kind { |
| 35 | case SubjectPersonRef: |
| 36 | // Opaque. Case may be significant, so it is passed through untouched. |
| 37 | case SubjectAddress: |
| 38 | // Lowercased, because that is what a lone instance stores. Sending |
| 39 | // mixed case to a receiver holding the lowercased form misses, and a |
| 40 | // miss answers NOT_VISIBLE — the silent value — for what is really a |
| 41 | // normalisation bug. |
| 42 | value = strings.ToLower(value) |
| 43 | default: |
| 44 | return Subject{}, fmt.Errorf("suite: unknown asker subject kind %q", kind) |
| 45 | } |
| 46 | |
| 47 | if value == "" { |
| 48 | return Subject{}, fmt.Errorf("suite: empty asker subject value for kind %q", kind) |
| 49 | } |
| 50 | return Subject{Kind: kind, Value: value}, nil |
| 51 | } |
| 52 | |
| 53 | // MaxS2Pairs caps one S2 question. 256 is memoria §6.1's number, carried here |
| 54 | // with the contract: it bounds the work a sibling can be made to do in one |
| 55 | // call, and it bounds the size of a response the total decoder has to keep in |
| 56 | // memory before it can answer. |
| 57 | const MaxS2Pairs = 256 |
| 58 | |
| 59 | // CheckS2Batch refuses an over-cap question rather than truncating it. A |
| 60 | // silently shortened question produces a confident answer about the items |
| 61 | // that survived; the ones dropped decode as UNKNOWN and nobody learns why. |
| 62 | func CheckS2Batch(pairs []ItemRevision) error { |
| 63 | if len(pairs) > MaxS2Pairs { |
| 64 | return fmt.Errorf("suite: %d pairs exceeds the S2 cap of %d", len(pairs), MaxS2Pairs) |
| 65 | } |
| 66 | return nil |
| 67 | } |
| 68 | |