Files: 7f0916643cf58906cafbf0ff7d3700b5bb09328f / lib / query.js
4642 bytesRaw
1 | var Pull = require("pull-stream"); |
2 | var KVSet = require("kvset"); |
3 | var Pad = require("pad-ipv6"); |
4 | var Query = module.exports = {}; |
5 | |
6 | Query.branches = function (sbot, name, type, _class, cb) { |
7 | if (!_class) _class = "IN"; |
8 | var branches = []; |
9 | Pull(Query.all(sbot), |
10 | Pull.filter(function (record) { |
11 | return record.name == name |
12 | && record.type == type |
13 | && record.class == _class; |
14 | }), |
15 | Query.drainSet(function (record) { |
16 | branches.push(record.id); |
17 | }, function (err) { |
18 | cb(err, branches); |
19 | })); |
20 | }; |
21 | |
22 | function msgToRecord(msg) { |
23 | var c = msg.value.content; |
24 | var r = c && c.record; |
25 | if (!r) return; |
26 | r.id = msg.key; |
27 | r.author = msg.value.author; |
28 | r.timestamp = msg.value.timestamp; |
29 | r.branch = c.branch; |
30 | if (!r.ttl) r.ttl = 500; |
31 | if (!r.class) r.class = "IN"; |
32 | if (r.value) r.data = r.value, delete r.value |
33 | if (r.type === 'AAAA') r.data = Pad(r.data); |
34 | return r; |
35 | } |
36 | |
37 | Query.all = function (sbot) { |
38 | return Pull(sbot.messagesByType({ |
39 | type: 'ssb-dns', |
40 | }), |
41 | Pull.map(msgToRecord), |
42 | Pull.filter()); |
43 | }; |
44 | |
45 | function expandName(name, wildcard) { |
46 | var names = {}; |
47 | for (var labels = name.split(/\./); labels.length; labels.shift()) { |
48 | if (wildcard) labels[0] = wildcard; |
49 | names[labels.join('.')] = true; |
50 | } |
51 | return names; |
52 | } |
53 | |
54 | function Wildcards() { |
55 | this.lengths = []; |
56 | this.recordsByLength = {}; |
57 | } |
58 | |
59 | Wildcards.prototype.addRecord = function (record) { |
60 | var len = record.name.length; |
61 | if (len in this.recordsByLength) { |
62 | this.recordsByLength[len].push(record); |
63 | } else { |
64 | this.recordsByLength[len] = [record]; |
65 | this.lengths.push(len); |
66 | } |
67 | }; |
68 | |
69 | Wildcards.prototype.getRecords = function () { |
70 | // get records for the longest name length |
71 | if (this.lengths.length) { |
72 | var len = Math.max.apply(Math, this.lengths); |
73 | return this.recordsByLength[len] || []; |
74 | } |
75 | return []; |
76 | }; |
77 | |
78 | function ZoneSerials() { |
79 | this.serials = {}; |
80 | } |
81 | |
82 | ZoneSerials.prototype.addRecord = function (record) { |
83 | for (var zone in expandName(record.name)) { |
84 | this.serials[zone] = (+this.serials[zone] || 0) + 1; |
85 | } |
86 | }; |
87 | |
88 | ZoneSerials.prototype.getSerial = function (zone) { |
89 | return this.serials[zone] % 0x100000000; |
90 | }; |
91 | |
92 | Query.drainSet = function (each, onEnd) { |
93 | var set = new KVSet(); |
94 | return Pull.drain(function (record) { |
95 | if (record.branch) set.remove(record.branch); |
96 | set.add(record.id, record); |
97 | }, function (err) { |
98 | if (err) return onEnd(err); |
99 | for (var key in set.heads) { |
100 | var record = set.heads[key]; |
101 | try { |
102 | each(record); |
103 | } catch(e) { |
104 | return onEnd(e); |
105 | } |
106 | } |
107 | onEnd(null); |
108 | }); |
109 | }; |
110 | |
111 | Query.query = function (sbot, question, cb) { |
112 | // look up records that match a question, including wildcard records |
113 | // and zone authority records |
114 | var authorityDomains = expandName(question.name); |
115 | var wildcardDomains = expandName(question.name, '*'); |
116 | var authorities = new Wildcards(); |
117 | var answers = new Wildcards(); |
118 | var zoneSerials = new ZoneSerials(); |
119 | var result = {}; |
120 | Pull(Query.all(sbot), |
121 | Pull.filter(function (record) { |
122 | zoneSerials.addRecord(record); |
123 | var nameMatches = record.name == question.name |
124 | || record.name in wildcardDomains |
125 | if (nameMatches) { |
126 | result.domainExists = true; |
127 | } |
128 | if (record.type === 'SOA') { |
129 | return record.name in authorityDomains |
130 | } |
131 | return nameMatches |
132 | && record.type == question.type |
133 | && record.class == question.class; |
134 | }), |
135 | Query.drainSet(function (record) { |
136 | if (record.type === 'SOA') { |
137 | result.authoritative = true; |
138 | if (question.type === 'SOA' |
139 | && question.class === record.class |
140 | && (question.name === record.name |
141 | || record.name in wildcardDomains)) { |
142 | answers.addRecord(record); |
143 | } else { |
144 | authorities.addRecord(record); |
145 | } |
146 | } else { |
147 | answers.addRecord(record); |
148 | } |
149 | }, function (err) { |
150 | if (err) return cb(err); |
151 | result.answers = answers.getRecords(); |
152 | result.authorities = authorities.getRecords(); |
153 | result.authorities.forEach(function (auth) { |
154 | if (!auth.data.serial) { |
155 | // special case: calculate a serial for the SOA |
156 | auth.data.serial = zoneSerials.getSerial(auth.name); |
157 | } |
158 | }); |
159 | cb(null, result); |
160 | })); |
161 | }; |
162 |
Built with git-ssb-web