parse.gno
3.79 Kb · 130 lines
1package payout
2
3// Both parsers read their input in a single pass, byte by byte, and bound it
4// before they read it. On chain every interpreted operation is paid for, and
5// an oversized string must cost its sender nothing but a refusal.
6//
7// They only accept an address in its canonical form, see validAddress.
8
9const (
10 // addressLen is the length of a gno address: "g1" and 38 characters.
11 addressLen = 40
12 // maxPlayersLen bounds a players string: MaxPlayers addresses, each
13 // followed by a separator.
14 maxPlayersLen = MaxPlayers * (addressLen + 1)
15 // maxDeathsLen bounds a deaths string: MaxDeaths entries
16 // of two addresses, each followed by a separator.
17 maxDeathsLen = MaxDeaths * 2 * (addressLen + 1)
18)
19
20// ParsePlayers parses a players string: comma-separated addresses, for
21// example
22//
23// g1aaa...,g1bbb...,g1ccc...
24//
25// It returns 1 to MaxPlayers addresses in the order of the string, each one
26// valid and listed once. Whitespace, empty entries and a trailing separator
27// are refused. Whether there are enough players for a round is decided by
28// Rule.Settle, and by the realm that starts the round.
29func ParsePlayers(s string) ([]address, error) {
30 if s == "" {
31 return nil, ErrNoPlayers
32 }
33 if len(s) > maxPlayersLen {
34 return nil, ErrTooManyPlayers
35 }
36 var players []address
37 i := 0
38 for {
39 if len(players) == MaxPlayers {
40 return nil, ErrTooManyPlayers
41 }
42 end := indexFrom(s, i, ',')
43 addr := address(s[i:end])
44 if !validAddress(addr) {
45 return nil, ErrPlayersFormat
46 }
47 if indexOf(players, addr) >= 0 {
48 return nil, ErrDuplicatePlayer
49 }
50 players = append(players, addr)
51 if end == len(s) {
52 return players, nil
53 }
54 i = end + 1
55 if i == len(s) {
56 return nil, ErrPlayersFormat // a trailing ","
57 }
58 }
59}
60
61// ParseDeaths parses a deaths string: "victim:killer" entries
62// separated by ";", in the order they happened, "victim:" when nobody shot
63// the victim down, for example
64//
65// g1bbb...:g1aaa...;g1ccc...:
66//
67// The empty string means nobody died and gives an empty list. At
68// most MaxDeaths entries are accepted, and every address must be valid.
69// Whitespace, empty entries and a trailing separator are refused.
70//
71// It only checks the syntax. Whether the victims and the killers are players
72// of the round, and how often they died, is decided by Rule.Settle.
73func ParseDeaths(s string) ([]Death, error) {
74 if s == "" {
75 return nil, nil
76 }
77 if len(s) > maxDeathsLen {
78 return nil, ErrTooManyDeaths
79 }
80 var out []Death
81 i := 0
82 for {
83 if len(out) == MaxDeaths {
84 return nil, ErrTooManyDeaths
85 }
86 colon := indexFrom(s, i, ':')
87 if colon == len(s) {
88 return nil, ErrDeathsFormat
89 }
90 end := indexFrom(s, colon+1, ';')
91
92 victim := address(s[i:colon])
93 if !validAddress(victim) {
94 return nil, ErrDeathsFormat
95 }
96 killer := address(s[colon+1 : end])
97 if killer != "" && !validAddress(killer) {
98 return nil, ErrDeathsFormat
99 }
100 out = append(out, Death{Victim: victim, Killer: killer})
101 if end == len(s) {
102 return out, nil
103 }
104 i = end + 1
105 if i == len(s) {
106 return nil, ErrDeathsFormat // a trailing ";"
107 }
108 }
109}
110
111// validAddress reports whether addr is a valid address in its canonical,
112// lowercase form.
113//
114// Bech32 can also be written in all capitals, and address.IsValid accepts
115// that. To a realm the capitals would be a second name for the same account,
116// one that matches no player of a round. Bech32 does not allow mixing cases,
117// so looking at the first character is enough. The length is checked first:
118// it is free, and it keeps an overlong string away from the decoder.
119func validAddress(addr address) bool {
120 return len(addr) == addressLen && addr[0] == 'g' && addr.IsValid()
121}
122
123// indexFrom returns the index of the first c in s at or after i, or len(s)
124// when there is none.
125func indexFrom(s string, i int, c byte) int {
126 for i < len(s) && s[i] != c {
127 i++
128 }
129 return i
130}