mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2025-01-10 17:38:13 +08:00
188 lines
6.1 KiB
Go
188 lines
6.1 KiB
Go
|
package models
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"strconv"
|
||
|
"strings"
|
||
|
|
||
|
"github.com/miekg/dns"
|
||
|
)
|
||
|
|
||
|
// SetTargetLOC sets the LOC fields from the rr.LOC type properties.
|
||
|
func (rc *RecordConfig) SetTargetLOC(ver uint8, lat uint32, lon uint32, alt uint32, siz uint8, hzp uint8, vtp uint8) error {
|
||
|
rc.LocVersion = ver
|
||
|
rc.LocLatitude = lat
|
||
|
rc.LocLongitude = lon
|
||
|
rc.LocAltitude = alt
|
||
|
rc.LocSize = siz
|
||
|
rc.LocHorizPre = hzp
|
||
|
rc.LocVertPre = vtp
|
||
|
|
||
|
if rc.Type == "" {
|
||
|
rc.Type = "LOC"
|
||
|
}
|
||
|
if rc.Type != "LOC" {
|
||
|
panic("assertion failed: SetTargetLOC called when .Type is not LOC")
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// SetLOCParams is an intermediate function which passes the 12 input parameters
|
||
|
// for further processing to the LOC native 7 input binary format:
|
||
|
// LocVersion (0), LocLatitude, LocLongitude, LocAltitude, LocSize, LocVertPre, LocHorizPre
|
||
|
func (rc *RecordConfig) SetLOCParams(d1 uint8, m1 uint8, s1 float32, ns string,
|
||
|
d2 uint8, m2 uint8, s2 float32, ew string, al int32, sz float32, hp float32, vp float32) error {
|
||
|
|
||
|
err := rc.calculateLOCFields(d1, m1, s1, ns, d2, m2, s2, ew, al, sz, hp, vp)
|
||
|
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// SetTargetLOCString is like SetTargetLOC but accepts one big string and origin
|
||
|
// Normally this is used when we receive a record string from provider records
|
||
|
// because e.g. the provider API passed rc.PopulateFromString()
|
||
|
func (rc *RecordConfig) SetTargetLOCString(origin string, contents string) error {
|
||
|
// This is where text from provider records ingresses into the target field.
|
||
|
// Fill the other fields derived from the TEXT here. LOC is special, and
|
||
|
// needs more math.
|
||
|
// We have to re-invent the wheel because the miekg dns library gives no
|
||
|
// access to the objects properties, and internally the object is represented
|
||
|
// by the dns.LOC format 💩
|
||
|
|
||
|
// Build a string with which to init the rr.LOC object:
|
||
|
str := fmt.Sprintf("%s. LOC %s\n", origin, contents)
|
||
|
loc, err := dns.NewRR(str)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("can't parse LOC data: %w", err)
|
||
|
}
|
||
|
// We 'normalize' the record thru rr.LOC, to get defaults for absent properties.
|
||
|
|
||
|
loctext := loc.String()
|
||
|
loctext = strings.TrimSpace(strings.Split(loctext, "LOC")[1])
|
||
|
|
||
|
err = rc.extractLOCFieldsFromStringInput(loctext)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("can't extractLOCFieldsFromStringInput from LOC data: %w", err)
|
||
|
}
|
||
|
rc.target = loctext
|
||
|
if rc.Type == "" {
|
||
|
rc.Type = "LOC"
|
||
|
}
|
||
|
if rc.Type != "LOC" {
|
||
|
panic("assertion failed: SetTargetLOC called when .Type is not LOC")
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// extractLOCFieldsFromStringInput is a helper to split an input string to
|
||
|
// the 12 variable inputs of integers and strings.
|
||
|
func (rc *RecordConfig) extractLOCFieldsFromStringInput(input string) error {
|
||
|
var d1, m1, d2, m2 uint8
|
||
|
var al int32
|
||
|
var s1, s2 float32
|
||
|
var ns, ew string
|
||
|
var sz, hp, vp float32
|
||
|
|
||
|
var err error
|
||
|
_, err = fmt.Sscanf(input+"~", "%d %d %f %s %d %d %f %s %dm %fm %fm %fm~",
|
||
|
&d1, &m1, &s1, &ns, &d2, &m2, &s2, &ew, &al, &sz, &hp, &vp)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("extractLOCFieldsFromStringInput: can't unpack LOC tex input data: %w", err)
|
||
|
}
|
||
|
// fmt.Printf("\ngot: %d %d %g %s %d %d %g %s %dm %0.2fm %0.2fm %0.2fm \n", d1, m1, s1, ns, d2, m2, s2, ew, al, sz, hp, vp)
|
||
|
|
||
|
rc.calculateLOCFields(d1, m1, s1, ns, d2, m2, s2, ew, al, sz, hp, vp)
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// calculateLOCFields converts from 12 user inputs to the LOC 7 binary fields
|
||
|
func (rc *RecordConfig) calculateLOCFields(d1 uint8, m1 uint8, s1 float32, ns string,
|
||
|
d2 uint8, m2 uint8, s2 float32, ew string, al int32, sz float32, hp float32, vp float32) error {
|
||
|
// Crazy hairy shit happens here.
|
||
|
// We already got the useful "string" version earlier. ¯\_(ツ)_/¯ code golf...
|
||
|
const LOCEquator uint64 = 0x80000000 // 1 << 31 // RFC 1876, Section 2.
|
||
|
const LOCPrimeMeridian uint64 = 0x80000000 // 1 << 31 // RFC 1876, Section 2.
|
||
|
const LOCHours uint32 = 60 * 1000
|
||
|
const LOCDegrees = 60 * LOCHours
|
||
|
const LOCAltitudeBase int32 = 100000
|
||
|
|
||
|
lat := uint64((uint32(d1) * LOCDegrees) + (uint32(m1) * LOCHours) + uint32(s1*1000))
|
||
|
lon := uint64((uint32(d2) * LOCDegrees) + (uint32(m2) * LOCHours) + uint32(s2*1000))
|
||
|
if strings.ToUpper(ns) == "N" {
|
||
|
rc.LocLatitude = uint32(LOCEquator + lat)
|
||
|
} else { // "S"
|
||
|
rc.LocLatitude = uint32(LOCEquator - lat)
|
||
|
}
|
||
|
if strings.ToUpper(ew) == "E" {
|
||
|
rc.LocLongitude = uint32(LOCPrimeMeridian + lon)
|
||
|
} else { // "W"
|
||
|
rc.LocLongitude = uint32(LOCPrimeMeridian - lon)
|
||
|
}
|
||
|
// Altitude
|
||
|
rc.LocAltitude = uint32(al+LOCAltitudeBase) * 100
|
||
|
var err error
|
||
|
// Size
|
||
|
rc.LocSize, err = getENotationInt(sz)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
// Horizontal Precision
|
||
|
rc.LocHorizPre, err = getENotationInt(hp)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
// Vertical Precision
|
||
|
rc.LocVertPre, err = getENotationInt(vp)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
// if hp != 0 {
|
||
|
// } else {
|
||
|
// rc.LocHorizPre = 22 // 1e6 10,000m default
|
||
|
// }
|
||
|
// if vp != 0 {
|
||
|
// } else {
|
||
|
// rc.LocVertPre = 19 // 1e3 10m default
|
||
|
// }
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// getENotationInt produces a mantissa_exponent 4bits:4bits into a uint8
|
||
|
func getENotationInt(x float32) (uint8, error) {
|
||
|
/*
|
||
|
9000000000cm = 9e9 == 153 (9^4 + 9) or 9<<4 + 9
|
||
|
800000000cm = 8e8 == 136 (8^4 + 8) or 8<<4 + 8
|
||
|
70000000cm = 7e7 == 119 (7^4 + 7) or 7<<4 + 7
|
||
|
6000000cm = 6e6 == 102 (6^4 + 6) or 6<<4 + 6
|
||
|
1000000cm = 1e6 == 22 (1^4 + 6) or 1<<4 + 6
|
||
|
500000cm = 5e5 == 85 (5^4 + 5) or 5<<4 + 5
|
||
|
40000cm = 4e4 == 68 (4^4 + 4) or 4<<4 + 4
|
||
|
3000cm = 3e3 == 51 (3^4 + 3) or 3<<4 + 3
|
||
|
1000cm = 1e3 == 19 (1^4 + 3) or 1<<4 + 1
|
||
|
200cm = 2e2 == 34 (2^4 + 2) or 2<<4 + 2
|
||
|
100cm = 1e2 == 18 (1^4 + 2) or 1<<4 + 2
|
||
|
10cm = 1e1 == 17 (1^4 + 1) or 1<<4 + 1
|
||
|
1cm = 1e0 == 16 (1^4 + 0) or 0<<4 + 0
|
||
|
0cm = 0e0 == 0
|
||
|
*/
|
||
|
// get int from cm value:
|
||
|
num := strconv.Itoa(int(x * 100))
|
||
|
// fmt.Printf("num: %s\n", num)
|
||
|
// split string on zeroes to count zeroes:
|
||
|
arr := strings.Split(num, "0")
|
||
|
// fmt.Printf("arr: %s\n", arr)
|
||
|
// get the leading digit:
|
||
|
prefix, err := strconv.Atoi(arr[0])
|
||
|
if err != nil {
|
||
|
return 0, fmt.Errorf("can't unpack LOC base/mantissa: %w", err)
|
||
|
}
|
||
|
// fmt.Printf("prefix: %d\n", prefix)
|
||
|
// fmt.Printf("lenArr-1: %d\n", len(arr)-1)
|
||
|
// construct our x^e uint8
|
||
|
value := uint8((prefix << 4) | (len(arr) - 1))
|
||
|
// fmt.Printf("m_e: %d\n", value)
|
||
|
return value, err
|
||
|
}
|