aboutsummaryrefslogtreecommitdiffstats
path: root/examples/dns.aup
blob: 8f7239a80b2685c6bd834fa9196b6a07ee1245e0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# DNS messages (RFC 1035): a message's bytes read into its header and records, and the message written back from them.
# Names are kept in their wire form uncompressed (length-prefixed labels, then the root), the owner's and those inside
# NS, CNAME, PTR, MX, SOA and SRV data (dnspython compresses SRV's too); other data as it is. Reading follows
# compression pointers, each pointing before the last (so they end); writing compresses the owners. A message that is
# none fails its event.
record Record {
  section = 0                                    # 0 question, 1 answer, 2 authority, 3 additional
  name: bytes
  type = 0
  class = 0
  ttl = 0
  data: bytes
}
record Seen {
  name: bytes                                    # a name written, from one of its labels to the root
  at = 0                                         # where it starts in the message
}
state id = 0
state flags = 0
state records: list of Record
input read = message { data: bytes }

fn word(d, i) = number(d[i] << 8 | d[i + 1])
fn long(d, i) = word(d, i) * 65536 + word(d, i + 2)
fn two(n) = byte(i64(n) >> 8) + byte(i64(n) & 0xff)

# The name at i uncompressed, and where it ends in place.
fn name(d, i) {
  let out = d[i .. i]
  let j = i
  let end = -1
  let below = i                                  # where the next pointer must point before
  while d[j] != 0 {
    if d[j] >= 0xc0 {
      let to = number((d[j] & 0x3f) << 8 | d[j + 1])
      require to < below
      if end < 0 { end = j + 2 }
      below = to
      j = to
    } else {
      require d[j] < 0x40
      out = out + d[j .. j + number(d[j]) + 1]
      j = j + number(d[j]) + 1
    }
  }
  require out.bytes < 255
  return out + byte(0), if end < 0 { j + 1 } else { end }
}

# A record's data at i (size bytes), the names in it uncompressed.
fn data(d, i, size, type) {
  if type == 2 || type == 5 || type == 12 || type == 15 || type == 33 {
    let skip = if type == 15 { 2 } else if type == 33 { 6 } else { 0 }  # MX's preference, SRV's three numbers
    let n, end = name(d, i + skip)
    require end == i + size
    return d[i .. i + skip] + n
  }
  if type == 6 {                                 # SOA: two names, five numbers
    let m, after = name(d, i)
    let r, end = name(d, after)
    require end + 20 == i + size
    return m + r + d[end .. end + 20]
  }
  return d[i .. i + size]
}

on read {
  let d = read.data
  require d.bytes >= 12
  while records.count > 0 { records.remove(records.first) }
  let at = 12
  for section in 0 .. 4 {
    for k in 0 .. word(d, 4 + 2 * section) {
      let owner, next = name(d, at)
      at = next + 4
      require at <= d.bytes
      if section == 0 {
        records.append(Record { section: 0, name: owner, type: word(d, next), class: word(d, next + 2) })
      } else {
        require at + 6 <= d.bytes && at + 6 + word(d, at + 4) <= d.bytes
        let size = word(d, at + 4)
        records.append(Record { section: section, name: owner, type: word(d, next), class: word(d, next + 2),
                                ttl: long(d, at), data: data(d, at + 6, size, word(d, next)) })
        at = at + 6 + size
      }
    }
  }
  require at == d.bytes
  id = word(d, 0)
  flags = word(d, 2)
}

# The message as bytes: each owner written from its first label whose rest was written before, as a pointer to it.
view wire {
  let seen: list of Seen
  let out = two(id) + two(flags)
  for section in 0 .. 4 { out = out + two(count(r in records where r.section == section)) }
  for r in records {
    let k = 0
    let done = false
    while !done && r.name[k] != 0 {
      for s in seen {
        if !done && s.name == r.name[k .. r.name.bytes] {
          out = out + two(49152 + s.at)            # 0xc000: a pointer
          done = true
        }
      }
      if !done {
        if out.bytes < 16384 { seen.append(Seen { name: r.name[k .. r.name.bytes], at: out.bytes }) }
        out = out + r.name[k .. k + number(r.name[k]) + 1]
        k = k + number(r.name[k]) + 1
      }
    }
    if !done { out = out + byte(0) }
    out = out + two(r.type) + two(r.class)
    if r.section > 0 { out = out + two(r.ttl // 65536) + two(r.ttl % 65536) + two(r.data.bytes) + r.data }
  }
  return out
}