1# Copyright (C) 2003-2007, 2009, 2010 Nominum, Inc.
2#
3# Permission to use, copy, modify, and distribute this software and its
4# documentation for any purpose with or without fee is hereby granted,
5# provided that the above copyright notice and this permission notice
6# appear in all copies.
7#
8# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
9# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
11# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
14# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15
16import cStringIO
17import struct
18
19import dns.exception
20import dns.rdata
21
22_pows = (1L, 10L, 100L, 1000L, 10000L, 100000L, 1000000L, 10000000L,
23         100000000L, 1000000000L, 10000000000L)
24
25def _exponent_of(what, desc):
26    exp = None
27    for i in xrange(len(_pows)):
28        if what // _pows[i] == 0L:
29            exp = i - 1
30            break
31    if exp is None or exp < 0:
32        raise dns.exception.SyntaxError("%s value out of bounds" % desc)
33    return exp
34
35def _float_to_tuple(what):
36    if what < 0:
37        sign = -1
38        what *= -1
39    else:
40        sign = 1
41    what = long(round(what * 3600000))
42    degrees = int(what // 3600000)
43    what -= degrees * 3600000
44    minutes = int(what // 60000)
45    what -= minutes * 60000
46    seconds = int(what // 1000)
47    what -= int(seconds * 1000)
48    what = int(what)
49    return (degrees * sign, minutes, seconds, what)
50
51def _tuple_to_float(what):
52    if what[0] < 0:
53        sign = -1
54        value = float(what[0]) * -1
55    else:
56        sign = 1
57        value = float(what[0])
58    value += float(what[1]) / 60.0
59    value += float(what[2]) / 3600.0
60    value += float(what[3]) / 3600000.0
61    return sign * value
62
63def _encode_size(what, desc):
64    what = long(what);
65    exponent = _exponent_of(what, desc) & 0xF
66    base = what // pow(10, exponent) & 0xF
67    return base * 16 + exponent
68
69def _decode_size(what, desc):
70    exponent = what & 0x0F
71    if exponent > 9:
72        raise dns.exception.SyntaxError("bad %s exponent" % desc)
73    base = (what & 0xF0) >> 4
74    if base > 9:
75        raise dns.exception.SyntaxError("bad %s base" % desc)
76    return long(base) * pow(10, exponent)
77
78class LOC(dns.rdata.Rdata):
79    """LOC record
80
81    @ivar latitude: latitude
82    @type latitude: (int, int, int, int) tuple specifying the degrees, minutes,
83    seconds, and milliseconds of the coordinate.
84    @ivar longitude: longitude
85    @type longitude: (int, int, int, int) tuple specifying the degrees,
86    minutes, seconds, and milliseconds of the coordinate.
87    @ivar altitude: altitude
88    @type altitude: float
89    @ivar size: size of the sphere
90    @type size: float
91    @ivar horizontal_precision: horizontal precision
92    @type horizontal_precision: float
93    @ivar vertical_precision: vertical precision
94    @type vertical_precision: float
95    @see: RFC 1876"""
96
97    __slots__ = ['latitude', 'longitude', 'altitude', 'size',
98                 'horizontal_precision', 'vertical_precision']
99
100    def __init__(self, rdclass, rdtype, latitude, longitude, altitude,
101                 size=1.0, hprec=10000.0, vprec=10.0):
102        """Initialize a LOC record instance.
103
104        The parameters I{latitude} and I{longitude} may be either a 4-tuple
105        of integers specifying (degrees, minutes, seconds, milliseconds),
106        or they may be floating point values specifying the number of
107        degrees.  The other parameters are floats."""
108
109        super(LOC, self).__init__(rdclass, rdtype)
110        if isinstance(latitude, int) or isinstance(latitude, long):
111            latitude = float(latitude)
112        if isinstance(latitude, float):
113            latitude = _float_to_tuple(latitude)
114        self.latitude = latitude
115        if isinstance(longitude, int) or isinstance(longitude, long):
116            longitude = float(longitude)
117        if isinstance(longitude, float):
118            longitude = _float_to_tuple(longitude)
119        self.longitude = longitude
120        self.altitude = float(altitude)
121        self.size = float(size)
122        self.horizontal_precision = float(hprec)
123        self.vertical_precision = float(vprec)
124
125    def to_text(self, origin=None, relativize=True, **kw):
126        if self.latitude[0] > 0:
127            lat_hemisphere = 'N'
128            lat_degrees = self.latitude[0]
129        else:
130            lat_hemisphere = 'S'
131            lat_degrees = -1 * self.latitude[0]
132        if self.longitude[0] > 0:
133            long_hemisphere = 'E'
134            long_degrees = self.longitude[0]
135        else:
136            long_hemisphere = 'W'
137            long_degrees = -1 * self.longitude[0]
138        text = "%d %d %d.%03d %s %d %d %d.%03d %s %0.2fm" % (
139            lat_degrees, self.latitude[1], self.latitude[2], self.latitude[3],
140            lat_hemisphere, long_degrees, self.longitude[1], self.longitude[2],
141            self.longitude[3], long_hemisphere, self.altitude / 100.0
142            )
143
144        if self.size != 1.0 or self.horizontal_precision != 10000.0 or \
145           self.vertical_precision != 10.0:
146            text += " %0.2fm %0.2fm %0.2fm" % (
147                self.size / 100.0, self.horizontal_precision / 100.0,
148                self.vertical_precision / 100.0
149            )
150        return text
151
152    def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True):
153        latitude = [0, 0, 0, 0]
154        longitude = [0, 0, 0, 0]
155        size = 1.0
156        hprec = 10000.0
157        vprec = 10.0
158
159        latitude[0] = tok.get_int()
160        t = tok.get_string()
161        if t.isdigit():
162            latitude[1] = int(t)
163            t = tok.get_string()
164            if '.' in t:
165                (seconds, milliseconds) = t.split('.')
166                if not seconds.isdigit():
167                    raise dns.exception.SyntaxError('bad latitude seconds value')
168                latitude[2] = int(seconds)
169                if latitude[2] >= 60:
170                    raise dns.exception.SyntaxError('latitude seconds >= 60')
171                l = len(milliseconds)
172                if l == 0 or l > 3 or not milliseconds.isdigit():
173                    raise dns.exception.SyntaxError('bad latitude milliseconds value')
174                if l == 1:
175                    m = 100
176                elif l == 2:
177                    m = 10
178                else:
179                    m = 1
180                latitude[3] = m * int(milliseconds)
181                t = tok.get_string()
182            elif t.isdigit():
183                latitude[2] = int(t)
184                t = tok.get_string()
185        if t == 'S':
186            latitude[0] *= -1
187        elif t != 'N':
188            raise dns.exception.SyntaxError('bad latitude hemisphere value')
189
190        longitude[0] = tok.get_int()
191        t = tok.get_string()
192        if t.isdigit():
193            longitude[1] = int(t)
194            t = tok.get_string()
195            if '.' in t:
196                (seconds, milliseconds) = t.split('.')
197                if not seconds.isdigit():
198                    raise dns.exception.SyntaxError('bad longitude seconds value')
199                longitude[2] = int(seconds)
200                if longitude[2] >= 60:
201                    raise dns.exception.SyntaxError('longitude seconds >= 60')
202                l = len(milliseconds)
203                if l == 0 or l > 3 or not milliseconds.isdigit():
204                    raise dns.exception.SyntaxError('bad longitude milliseconds value')
205                if l == 1:
206                    m = 100
207                elif l == 2:
208                    m = 10
209                else:
210                    m = 1
211                longitude[3] = m * int(milliseconds)
212                t = tok.get_string()
213            elif t.isdigit():
214                longitude[2] = int(t)
215                t = tok.get_string()
216        if t == 'W':
217            longitude[0] *= -1
218        elif t != 'E':
219            raise dns.exception.SyntaxError('bad longitude hemisphere value')
220
221        t = tok.get_string()
222        if t[-1] == 'm':
223            t = t[0 : -1]
224        altitude = float(t) * 100.0	# m -> cm
225
226        token = tok.get().unescape()
227        if not token.is_eol_or_eof():
228            value = token.value
229            if value[-1] == 'm':
230                value = value[0 : -1]
231            size = float(value) * 100.0	# m -> cm
232            token = tok.get().unescape()
233            if not token.is_eol_or_eof():
234                value = token.value
235                if value[-1] == 'm':
236                    value = value[0 : -1]
237                hprec = float(value) * 100.0	# m -> cm
238                token = tok.get().unescape()
239                if not token.is_eol_or_eof():
240                    value = token.value
241                    if value[-1] == 'm':
242                        value = value[0 : -1]
243                        vprec = float(value) * 100.0	# m -> cm
244                        tok.get_eol()
245
246        return cls(rdclass, rdtype, latitude, longitude, altitude,
247                   size, hprec, vprec)
248
249    from_text = classmethod(from_text)
250
251    def to_wire(self, file, compress = None, origin = None):
252        if self.latitude[0] < 0:
253            sign = -1
254            degrees = long(-1 * self.latitude[0])
255        else:
256            sign = 1
257            degrees = long(self.latitude[0])
258        milliseconds = (degrees * 3600000 +
259                        self.latitude[1] * 60000 +
260                        self.latitude[2] * 1000 +
261                        self.latitude[3]) * sign
262        latitude = 0x80000000L + milliseconds
263        if self.longitude[0] < 0:
264            sign = -1
265            degrees = long(-1 * self.longitude[0])
266        else:
267            sign = 1
268            degrees = long(self.longitude[0])
269        milliseconds = (degrees * 3600000 +
270                        self.longitude[1] * 60000 +
271                        self.longitude[2] * 1000 +
272                        self.longitude[3]) * sign
273        longitude = 0x80000000L + milliseconds
274        altitude = long(self.altitude) + 10000000L
275        size = _encode_size(self.size, "size")
276        hprec = _encode_size(self.horizontal_precision, "horizontal precision")
277        vprec = _encode_size(self.vertical_precision, "vertical precision")
278        wire = struct.pack("!BBBBIII", 0, size, hprec, vprec, latitude,
279                           longitude, altitude)
280        file.write(wire)
281
282    def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None):
283        (version, size, hprec, vprec, latitude, longitude, altitude) = \
284                  struct.unpack("!BBBBIII", wire[current : current + rdlen])
285        if latitude > 0x80000000L:
286            latitude = float(latitude - 0x80000000L) / 3600000
287        else:
288            latitude = -1 * float(0x80000000L - latitude) / 3600000
289        if latitude < -90.0 or latitude > 90.0:
290            raise dns.exception.FormError("bad latitude")
291        if longitude > 0x80000000L:
292            longitude = float(longitude - 0x80000000L) / 3600000
293        else:
294            longitude = -1 * float(0x80000000L - longitude) / 3600000
295        if longitude < -180.0 or longitude > 180.0:
296            raise dns.exception.FormError("bad longitude")
297        altitude = float(altitude) - 10000000.0
298        size = _decode_size(size, "size")
299        hprec = _decode_size(hprec, "horizontal precision")
300        vprec = _decode_size(vprec, "vertical precision")
301        return cls(rdclass, rdtype, latitude, longitude, altitude,
302                   size, hprec, vprec)
303
304    from_wire = classmethod(from_wire)
305
306    def _cmp(self, other):
307        f = cStringIO.StringIO()
308        self.to_wire(f)
309        wire1 = f.getvalue()
310        f.seek(0)
311        f.truncate()
312        other.to_wire(f)
313        wire2 = f.getvalue()
314        f.close()
315
316        return cmp(wire1, wire2)
317
318    def _get_float_latitude(self):
319        return _tuple_to_float(self.latitude)
320
321    def _set_float_latitude(self, value):
322        self.latitude = _float_to_tuple(value)
323
324    float_latitude = property(_get_float_latitude, _set_float_latitude,
325                              doc="latitude as a floating point value")
326
327    def _get_float_longitude(self):
328        return _tuple_to_float(self.longitude)
329
330    def _set_float_longitude(self, value):
331        self.longitude = _float_to_tuple(value)
332
333    float_longitude = property(_get_float_longitude, _set_float_longitude,
334                               doc="longitude as a floating point value")
335