/src/pdns/pdns/sillyrecords.cc
Line | Count | Source |
1 | | #ifdef HAVE_CONFIG_H |
2 | | #include "config.h" |
3 | | #endif |
4 | | |
5 | | #include <algorithm> |
6 | | #include <boost/format.hpp> |
7 | | |
8 | | #include "utility.hh" |
9 | | #include <cstdio> |
10 | | #include <cmath> |
11 | | #include <cstdlib> |
12 | | #include <sys/types.h> |
13 | | #include <string> |
14 | | #include <cerrno> |
15 | | #include "dnsrecords.hh" |
16 | | |
17 | | const static std::array<unsigned int,10> poweroften = { |
18 | | 1, 10, 100, 1000, 10000, 100000, |
19 | | 1000000,10000000,100000000,1000000000 |
20 | | }; |
21 | | |
22 | | enum coordtype { LATITUDE, LONGITUDE }; |
23 | | |
24 | | // Skip whitespace. |
25 | | static void |
26 | | skipspace(const std::string& content, std::string::size_type& pos) |
27 | 3.28k | { |
28 | 144k | while (pos < content.length() && std::isspace(static_cast<unsigned char>(content.at(pos))) != 0) { |
29 | 141k | ++pos; |
30 | 141k | } |
31 | 3.28k | } |
32 | | |
33 | | // Skip 'm' and following whitespace if present. |
34 | | static void |
35 | | skipm(const std::string& content, std::string::size_type& pos) |
36 | 367 | { |
37 | 367 | if (pos < content.length()) { |
38 | 278 | switch (content.at(pos)) { |
39 | 89 | case 'M': case 'm': |
40 | 89 | ++pos; |
41 | 89 | skipspace(content, pos); |
42 | 89 | break; |
43 | 189 | default: |
44 | 189 | break; |
45 | 278 | } |
46 | 278 | } |
47 | 367 | } |
48 | | |
49 | | |
50 | | // Parse an unsigned integer, and skip following whitespace if present. |
51 | | static bool |
52 | | parsenum(const std::string& content, std::string::size_type& pos, unsigned int& number) |
53 | 2.20k | { |
54 | 2.20k | bool parsed{false}; |
55 | | |
56 | 2.20k | number = 0; |
57 | 64.9k | while (pos < content.length() && std::isdigit(static_cast<unsigned char>(content.at(pos))) != 0) { |
58 | 62.7k | parsed = true; |
59 | 62.7k | number = number * 10 + (content.at(pos) - '0'); |
60 | 62.7k | ++pos; |
61 | 62.7k | } |
62 | 2.20k | skipspace(content, pos); |
63 | 2.20k | return parsed; |
64 | 2.20k | } |
65 | | |
66 | | // Parse the fractional part of an integer up to the given number of digits, |
67 | | // and skip following whitespace if present. |
68 | | static bool |
69 | | parsefrac(const std::string& content, std::string::size_type& pos, unsigned int digits, unsigned int& number) |
70 | 477 | { |
71 | 477 | bool parsed{false}; |
72 | | |
73 | 477 | number = 0; |
74 | 477 | if (pos < content.length() && content.at(pos) == '.') { |
75 | 153 | ++pos; |
76 | 494 | while (digits-- != 0) { |
77 | 341 | number *= 10; |
78 | 341 | if (pos < content.length() && std::isdigit(static_cast<unsigned char>(content.at(pos))) != 0) { |
79 | 173 | parsed = true; // intentionally rejects '.' alone |
80 | 173 | number += (content.at(pos) - '0'); |
81 | 173 | ++pos; |
82 | 173 | } |
83 | 341 | } |
84 | | // skip any further digits |
85 | 79.3k | while (pos < content.length() && std::isdigit(static_cast<unsigned char>(content.at(pos))) != 0) { |
86 | 79.1k | ++pos; |
87 | 79.1k | } |
88 | 153 | } |
89 | 477 | skipspace(content, pos); |
90 | 477 | return parsed; |
91 | 477 | } |
92 | | |
93 | | // Parse an integer with up to two fractional digits, convert it to |
94 | | // X * 10**Y, cap X and Y at 9, and return 0xXY. |
95 | | static uint8_t precsize_aton(const std::string& content, std::string::size_type& pos) |
96 | 236 | { |
97 | 236 | unsigned int mval = 0; |
98 | 236 | unsigned int cmval = 0; |
99 | 236 | unsigned int exponent{}; |
100 | 236 | unsigned int mantissa{}; |
101 | | |
102 | 236 | if (!parsenum(content, pos, mval)) { |
103 | 47 | throw MOADNSException("Expecting a size or precision in LOC contents"); |
104 | 47 | } |
105 | 189 | parsefrac(content, pos, 2, cmval); |
106 | 189 | skipm(content, pos); |
107 | | |
108 | 189 | cmval = (mval * 100) + cmval; |
109 | | // We could compute exponent and mantissa by repeatedly dividing by 10, |
110 | | // this array search comes from RFC1876. |
111 | 670 | for (exponent = 0; exponent < 9; exponent++) { |
112 | 654 | if (cmval < poweroften.at(exponent+1)) { |
113 | 173 | break; |
114 | 173 | } |
115 | 654 | } |
116 | 189 | mantissa = cmval / poweroften.at(exponent); |
117 | 189 | mantissa = std::min(mantissa, 9U); |
118 | | |
119 | 189 | return (mantissa << 4) | exponent; |
120 | 236 | } |
121 | | |
122 | | // Converts text representation of latitude or longitude into an unsigned |
123 | | // encoded 32-bit number. Returns the type and updates the position within |
124 | | // the string. |
125 | | // Expected format: DEG [MIN [SEC[.FRAC]]] (N|S|E|W) |
126 | | static uint32_t |
127 | | latlon2ul(const std::string& content, std::string::size_type& pos, coordtype& type) |
128 | 869 | { |
129 | 869 | unsigned int deg = 0; |
130 | 869 | unsigned int min = 0; |
131 | 869 | unsigned int secs = 0; |
132 | 869 | unsigned int secsfrac = 0; |
133 | | |
134 | 869 | if (!parsenum(content, pos, deg)) { |
135 | 93 | throw MOADNSException("Expecting a latitude or longitude in LOC contents"); |
136 | 93 | } |
137 | | |
138 | 776 | if (parsenum(content, pos, min)) { |
139 | 186 | if (min >= 60) { |
140 | 49 | throw MOADNSException("Invalid latitude or longitude minutes in LOC contents"); |
141 | 49 | } |
142 | 137 | if (parsenum(content, pos, secs)) { |
143 | 110 | parsefrac(content, pos, 3, secsfrac); |
144 | | // According to RFC1876, we should reject 60.000, but records with such |
145 | | // values appear in the unit tests, and there is probably a reason behind |
146 | | // that, so allow this value for now. FIXME? |
147 | 110 | if (secs > 60 || (secs == 60 && secsfrac > 0)) { |
148 | 62 | std::cerr << content << std::endl; |
149 | 62 | throw MOADNSException("Invalid latitude or longitude seconds in LOC contents"); |
150 | 62 | } |
151 | 110 | } |
152 | 137 | } |
153 | | |
154 | 665 | if (pos >= content.length()) { |
155 | 40 | throw MOADNSException("Expecting hemisphere in LOC contents"); |
156 | 40 | } |
157 | | |
158 | 625 | bool add{true}; |
159 | 625 | switch (content.at(pos)) { |
160 | 183 | case 'N': case 'n': |
161 | 183 | type = LATITUDE; |
162 | 183 | add = true; |
163 | 183 | break; |
164 | 211 | case 'E': case 'e': |
165 | 211 | type = LONGITUDE; |
166 | 211 | add = true; |
167 | 211 | break; |
168 | 116 | case 'S': case 's': |
169 | 116 | type = LATITUDE; |
170 | 116 | add = false; |
171 | 116 | break; |
172 | 84 | case 'W': case 'w': |
173 | 84 | type = LONGITUDE; |
174 | 84 | add = false; |
175 | 84 | break; |
176 | 31 | default: |
177 | 31 | throw MOADNSException("Invalid hemisphere specification in LOC contents"); |
178 | 625 | } |
179 | | |
180 | 594 | if (type == LATITUDE && deg > 90) { |
181 | 43 | throw MOADNSException("Invalid latitude degrees in LOC contents"); |
182 | 43 | } |
183 | 551 | if (type == LONGITUDE && deg > 180) { |
184 | 39 | throw MOADNSException("Invalid longitude degrees in LOC contents"); |
185 | 39 | } |
186 | | |
187 | 512 | uint32_t retval = (((((deg * 60) + min) * 60) + secs) * 1000) + secsfrac; |
188 | 512 | if (add) { |
189 | 351 | retval = (1U<<31) + retval; |
190 | 351 | } |
191 | 161 | else { |
192 | 161 | retval = (1U<<31) - retval; |
193 | 161 | } |
194 | | |
195 | 512 | ++pos; |
196 | 512 | skipspace(content, pos); |
197 | | |
198 | 512 | return retval; |
199 | 551 | } |
200 | | |
201 | | void LOCRecordContent::report(const ReportIsOnlyCallableByReportAllTypes& /* unused */) |
202 | 4 | { |
203 | 4 | regist(1, QType::LOC, &make, &make, "LOC"); |
204 | 4 | regist(254, QType::LOC, &make, &make, "LOC"); |
205 | 4 | } |
206 | | |
207 | | std::shared_ptr<DNSRecordContent> LOCRecordContent::make(const string& content) |
208 | 590 | { |
209 | 590 | return std::make_shared<LOCRecordContent>(content); |
210 | 590 | } |
211 | | |
212 | | |
213 | | void LOCRecordContent::toPacket(DNSPacketWriter& pw) const |
214 | 166 | { |
215 | 166 | pw.xfr8BitInt(d_version); |
216 | 166 | pw.xfr8BitInt(d_size); |
217 | 166 | pw.xfr8BitInt(d_horizpre); |
218 | 166 | pw.xfr8BitInt(d_vertpre); |
219 | | |
220 | 166 | pw.xfr32BitInt(d_latitude); |
221 | 166 | pw.xfr32BitInt(d_longitude); |
222 | 166 | pw.xfr32BitInt(d_altitude); |
223 | 166 | } |
224 | | |
225 | | std::shared_ptr<LOCRecordContent::DNSRecordContent> LOCRecordContent::make(const DNSRecord& /* dr */, PacketReader& pr) |
226 | 1.57k | { |
227 | 1.57k | auto ret=std::make_shared<LOCRecordContent>(); |
228 | 1.57k | pr.xfr8BitInt(ret->d_version); |
229 | 1.57k | pr.xfr8BitInt(ret->d_size); |
230 | 1.57k | pr.xfr8BitInt(ret->d_horizpre); |
231 | 1.57k | pr.xfr8BitInt(ret->d_vertpre); |
232 | | |
233 | 1.57k | pr.xfr32BitInt(ret->d_latitude); |
234 | 1.57k | pr.xfr32BitInt(ret->d_longitude); |
235 | 1.57k | pr.xfr32BitInt(ret->d_altitude); |
236 | | |
237 | 1.57k | return ret; |
238 | 1.57k | } |
239 | | |
240 | | // 51 59 00.000 N 5 55 00.000 E 4.00m 1.00m 10000.00m 10.00m |
241 | | // convert this to d_version, d_size, d_horiz/vertpre, d_latitude, d_longitude, d_altitude |
242 | | LOCRecordContent::LOCRecordContent(const string& content, const string& /* zone */) |
243 | 590 | { |
244 | 590 | std::string::size_type pos{0}; |
245 | | |
246 | | // Parse latitude and longitude, in any order |
247 | 590 | coordtype type1{}; |
248 | 590 | auto lltemp1 = latlon2ul(content, pos, type1); |
249 | 590 | coordtype type2{}; |
250 | 590 | auto lltemp2 = latlon2ul(content, pos, type2); |
251 | 590 | if (type1 == type2) { |
252 | 8 | throw MOADNSException("Expecting latitude and longitude in LOC contents"); |
253 | 8 | } |
254 | 582 | if (type1 == LATITUDE) { |
255 | 172 | d_latitude = lltemp1; |
256 | 172 | d_longitude = lltemp2; |
257 | 172 | } |
258 | 410 | else { |
259 | 410 | d_longitude = lltemp1; |
260 | 410 | d_latitude = lltemp2; |
261 | 410 | } |
262 | | |
263 | | // Parse optional altitude |
264 | 582 | if (pos < content.length()) { |
265 | 190 | unsigned int altmeters = 0; |
266 | 190 | unsigned int altfrac = 0; |
267 | 190 | int altsign = 1; |
268 | 190 | if (content.at(pos) == '-') { |
269 | 16 | altsign = -1; |
270 | 16 | ++pos; |
271 | 16 | } |
272 | 174 | else if (content.at(pos) == '+') { |
273 | 2 | ++pos; |
274 | 2 | } |
275 | 190 | if (!parsenum(content, pos, altmeters)) { |
276 | 12 | throw MOADNSException("Expecting altitude in LOC contents"); |
277 | 12 | } |
278 | 178 | parsefrac(content, pos, 2, altfrac); |
279 | 178 | skipm(content, pos); |
280 | 178 | d_altitude += altsign * (altmeters * 100 + altfrac); |
281 | 178 | } |
282 | | |
283 | | // Parse optional size and precision |
284 | 570 | if (pos < content.length()) { |
285 | 114 | d_size = precsize_aton(content, pos); |
286 | 114 | } |
287 | 570 | if (pos < content.length()) { |
288 | 71 | d_horizpre = precsize_aton(content, pos); |
289 | 71 | } |
290 | 570 | if (pos < content.length()) { |
291 | 51 | d_vertpre = precsize_aton(content, pos); |
292 | 51 | } |
293 | 570 | } |
294 | | |
295 | | |
296 | | string LOCRecordContent::getZoneRepresentation(bool /* noDot */) const |
297 | 533 | { |
298 | | // convert d_version, d_size, d_horiz/vertpre, d_latitude, d_longitude, d_altitude to: |
299 | | // 51 59 00.000 N 5 55 00.000 E 4.00m 1.00m 10000.00m 10.00m |
300 | | |
301 | 533 | double altitude= static_cast<int32_t>(d_altitude) / 100.0 - 100000; |
302 | | |
303 | 533 | double size=0.01*((d_size>>4)&0xf); |
304 | 533 | unsigned int count = d_size & 0xf; |
305 | 3.21k | while (count-- != 0) { |
306 | 2.67k | size*=10; |
307 | 2.67k | } |
308 | | |
309 | 533 | double horizpre=0.01*((d_horizpre>>4) & 0xf); |
310 | 533 | count = d_horizpre & 0xf; |
311 | 3.40k | while (count-- != 0) { |
312 | 2.87k | horizpre*=10; |
313 | 2.87k | } |
314 | | |
315 | 533 | double vertpre=0.01*((d_vertpre>>4)&0xf); |
316 | 533 | count = d_vertpre & 0xf; |
317 | 2.79k | while (count-- != 0) { |
318 | 2.26k | vertpre*=10; |
319 | 2.26k | } |
320 | | |
321 | 533 | char hemLat = 'N'; |
322 | 533 | uint32_t lat = d_latitude; |
323 | 533 | if (lat >= 1U << 31) { |
324 | 286 | lat -= 1U << 31; |
325 | 286 | } |
326 | 247 | else { |
327 | 247 | hemLat = 'S'; |
328 | 247 | lat = (1U << 31) - lat; |
329 | 247 | } |
330 | 533 | auto fracLat = lat % 1000; |
331 | 533 | lat /= 1000; |
332 | 533 | auto secLat = lat % 60; |
333 | 533 | lat /= 60; |
334 | 533 | auto minutesLat = lat % 60; |
335 | 533 | auto degreesLat = lat / 60; |
336 | | |
337 | 533 | char hemLon= 'E'; |
338 | 533 | uint32_t lon = d_longitude; |
339 | 533 | if (lon >= 1U << 31) { |
340 | 294 | lon -= 1U << 31; |
341 | 294 | } |
342 | 239 | else { |
343 | 239 | hemLon = 'W'; |
344 | 239 | lon = (1U << 31) - lon; |
345 | 239 | } |
346 | 533 | auto fracLon = lon % 1000; |
347 | 533 | lon /= 1000; |
348 | 533 | auto secLon = lon % 60; |
349 | 533 | lon /= 60; |
350 | 533 | auto minutesLon = lon % 60; |
351 | 533 | auto degreesLon = lon / 60; |
352 | | |
353 | 533 | static const boost::format fmt("%d %d %d.%03d %c %d %d %d.%03d %c %.2fm %.2fm %.2fm %.2fm"); |
354 | 533 | std::string ret = boost::str( |
355 | 533 | boost::format(fmt) |
356 | 533 | % degreesLat % minutesLat % secLat % fracLat % hemLat |
357 | 533 | % degreesLon % minutesLon % secLon % fracLon % hemLon |
358 | 533 | % altitude % size |
359 | 533 | % horizpre % vertpre |
360 | 533 | ); |
361 | | |
362 | 533 | return ret; |
363 | 533 | } |