Coverage Report

Created: 2026-08-13 06:11

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}