I have no idea if this is a correct implementation or not, as I have no reference implementation to compare it to. I swear there used to be an implementation as part of Android but I can't find it now.
Last active
June 4, 2018 16:52
-
-
Save mitchellrj/23996aef648cf1901b7e1e765738b5d6 to your computer and use it in GitHub Desktop.
RFC6225 value calculator
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!doctype html> | |
<html> | |
<head> | |
<title>RFC6225 / RFC3825 calculator</title> | |
<script> | |
const AltitudeTypes = { | |
NO_KNOWN_ALTITUDE: 0, | |
ALTITUDE_IN_METERS: 1, | |
ALTITUDE_IN_FLOORS: 2 | |
}; | |
const Datum = { | |
WGS84: 1, | |
NAD83_NAVD88: 2, | |
NAD83_MLLW: 3 | |
}; | |
const FIXED_POINT_INTEGRAL_BITS = 9; | |
const FIXED_POINT_FRACTIONAL_BITS = 25; | |
const FIXED_POINT_SIGNED = true; | |
const geoLocVersion = 1; | |
function toFixedPointBytes(i, integralBits, fractionBits, signed) { | |
const negative = i < 0; | |
var resultBytes = []; | |
var j = 0; | |
// the number of bytes that have been requested of us | |
const byteCount = Math.ceil((integralBits + fractionBits) / 8); | |
// the number of bytes needed to represent the result | |
var bytesAvail = 0; | |
// this may well be bigger than Number.MAX_SAFE_INTEGER. | |
var total = 0; | |
if (this.BigInt !== undefined) { | |
total = BigInt(Math.abs(i)) * BigInt(1 << fractionBits); | |
} else if (Math.log2(Number.MAX_SAFE_INTEGER) < byteCount) { | |
throw new Error("Cannot convert to fixed point format"); | |
} else { | |
total = Math.round(Math.abs(i) * (1 << fractionBits)); | |
} | |
bytesAvail = Math.ceil(Math.log2(total) / 8); | |
// Iterate only over the bytes available first, then pad with zeros; | |
// JavaScript numbers overflow otherwise. | |
for (j=0; j < bytesAvail; j++) { | |
resultBytes.unshift((total >> (8 * j)) & 0xff); | |
} | |
for (j=0; j < (byteCount - bytesAvail); j++) { | |
resultBytes.unshift(0); | |
} | |
if (signed && negative) { | |
for (j=0; j < byteCount; j++) { | |
resultBytes[j] ^= 0xff; | |
} | |
// TODO: this totally fails if the last byte is already 255. | |
resultBytes[byteCount - 1] += 1; | |
} | |
return resultBytes; | |
} | |
function geoLocBytes(latUnc, lat, lonUnc, lon, aType, altUnc, alt, datum) { | |
const latBytes = toFixedPointBytes(lat, FIXED_POINT_INTEGRAL_BITS, FIXED_POINT_FRACTIONAL_BITS, FIXED_POINT_SIGNED); | |
const lonBytes = toFixedPointBytes(lon, FIXED_POINT_INTEGRAL_BITS, FIXED_POINT_FRACTIONAL_BITS, FIXED_POINT_SIGNED); | |
return [ | |
(latUnc << 2) | latBytes[0], // latunc (6 bits) + latitude (2 bits) | |
latBytes[1], | |
latBytes[2], | |
latBytes[3], | |
latBytes[4], | |
(lonUnc << 2) | lonBytes[0], // lonunc (6 bits) + longitude (2 bits) | |
lonBytes[1], | |
lonBytes[2], | |
lonBytes[3], | |
lonBytes[4], | |
(aType << 4) | (altUnc >> 2), // atype (4 bits) + altunc (4 bits) | |
((altUnc << 6) & 0xff) | ((alt >> 24) & 0x3f), // altunc (2 bits), alt (6 bits) | |
((alt >> 16) & 0xff), | |
((alt >> 8) & 0xff), | |
alt & 0xff, | |
(geoLocVersion << 6) | datum & 0x07 // version (2 bits), reserved (3 bits), datum (3 bits) | |
]; | |
} | |
function geoConfBytes(laRes, lat, lonRes, lon, aType, altRes, alt, datum) { | |
const latBytes = toFixedPointBytes(lat, FIXED_POINT_INTEGRAL_BITS, FIXED_POINT_FRACTIONAL_BITS, FIXED_POINT_SIGNED); | |
const lonBytes = toFixedPointBytes(lon, FIXED_POINT_INTEGRAL_BITS, FIXED_POINT_FRACTIONAL_BITS, FIXED_POINT_SIGNED); | |
return [ | |
(laRes << 2) | latBytes[0], // latRes (6 bits) + latitude (2 bits) | |
latBytes[1], | |
latBytes[2], | |
latBytes[3], | |
latBytes[4], | |
(lonRes << 2) | lonBytes[0], // lonRes (6 bits) + longitude (2 bits) | |
lonBytes[1], | |
lonBytes[2], | |
lonBytes[3], | |
lonBytes[4], | |
(aType << 4) | (altRes >> 2), // atype (4 bits) + altRes (4 bits) | |
((altRes << 6) & 0xff) | ((alt >> 24) & 0x3f), // altRes (2 bits), alt (6 bits) | |
((alt >> 16) & 0xff), | |
((alt >> 8) & 0xff), | |
alt & 0xff, | |
datum & 0x07 // reserved (5 bits), datum (3 bits) | |
]; | |
} | |
// Resolution: number of bits of the lat / lon values that are considered | |
// valid. | |
function getResolution(min, max) { | |
const minBytes = toFixedPointBytes(min, FIXED_POINT_INTEGRAL_BITS, FIXED_POINT_FRACTIONAL_BITS, FIXED_POINT_SIGNED); | |
const maxBytes = toFixedPointBytes(max, FIXED_POINT_INTEGRAL_BITS, FIXED_POINT_FRACTIONAL_BITS, FIXED_POINT_SIGNED); | |
var i = 0, j = 0; | |
var res = 0; | |
for (i; i < minBytes.length; i++) { | |
if (minBytes[i] === maxBytes[i]) { | |
res += 8; | |
} else { | |
for (j = 1; j < 8; j++) { | |
if ((minBytes[i] >> j) === (maxBytes[i] >> j)) { | |
res += j; | |
break | |
} | |
} | |
break; | |
} | |
} | |
return res; | |
} | |
// Uncertainty: 0 = uncertainty unknown, > 34 = reserved | |
function getUncertainty(center, min, max) { | |
return 8 - Math.ceil(Math.log2(Math.max(center - min, max - center))); | |
} | |
function getResults(lat, latMin, latMax, lon, lonMin, lonMax, aType, alt, altMin, altMax, datum) { | |
const latRes = getResolution(latMin, latMax); | |
const latUnc = getUncertainty(lat, latMin, latMax); | |
const lonRes = getResolution(lonMin, lonMax); | |
const lonUnc = getUncertainty(lon, lonMin, lonMax); | |
var altRes = 30; | |
var altUnc = 0; | |
var alt = 0; | |
if (aType === 1) { | |
var alt = (altMin + altMax) / 2; | |
var altRes = getResolution(altMin, altMax); | |
var altUnc = getUncertainty(alt, altMin, altMax); | |
} | |
return [ | |
geoConfBytes(latRes, lat, lonRes, lon, aType, altRes, alt, datum), | |
geoLocBytes(latUnc, lat, lonUnc, lon, aType, altUnc, alt, datum) | |
]; | |
} | |
</script> | |
<style> | |
label, input { display: block; } | |
</style> | |
</head> | |
<body> | |
<h1>RFC6225 / RFC3825 calculcator</h1> | |
<form id="generator"> | |
<label for="latitude">Latitude (center):</label><input type="latitude" id="latitude" /> | |
<label for="latitude-min">Latitude (min):</label><input type="latitude" id="latitude-min" /> | |
<label for="latitude-max">Latitude (max):</label><input type="latitude" id="latitude-max" /> | |
<label for="longitude">Longitude (center):</label><input type="longitude" id="longitude" /> | |
<label for="longitude-min">Longitude (min):</label><input type="longitude" id="longitude-min" /> | |
<label for="longitude-max">Longitude (max):</label><input type="longitude" id="longitude-max" /> | |
<label for="altitude-type">Altitude type:</label><select id="altitude-type"><option value="0">Unknown</option><option value="1">Meters</option><option value="2">Floors</option></select> | |
<label for="altitude">Altitude</label><input type="number" id="altitude" /> | |
<label for="altitude-min">Altitude (min):</label><input type="number" id="altitude-min" /> | |
<label for="altitude-max">Altitude (max):</label><input type="number" id="altitude-max" /> | |
<label for="coordinate-system">Co-ordinate system:</label><select id="coordinate-system"><option value="1">WGS84</option><option value="2">NAD83 + NAVD88</option><option value="3">NAD83 + MLLW</option></select> | |
<label for="with-option-header">With option header?</label><input id="with-option-header" type="checkbox" /> | |
<input type="submit" value="Generate" /> | |
<label for="result-dhcpv4-geoconf">DHCP v4 GeoConf option (123):</label> <input editable="false" id="result-dhcpv4-geoconf" /> | |
<label for="result-dhcpv4-geoloc">DHCP v4 GeoLoc option (144):</label> <input editable="false" id="result-dhcpv4-geoloc" /> | |
<label for="result-dhcpv6">DHCP v6 option (63):</label> <input editable="false" id="result-dhcpv6" /> | |
</form> | |
<script> | |
(function () { | |
const form = document.getElementById('generator'); | |
const lat = document.getElementById('latitude'); | |
const latMin = document.getElementById('latitude-min'); | |
const latMax = document.getElementById('latitude-max'); | |
const lon = document.getElementById('longitude'); | |
const lonMin = document.getElementById('longitude-min'); | |
const lonMax = document.getElementById('longitude-max'); | |
const aType = document.getElementById('altitude-type'); | |
const alt = document.getElementById('altitude'); | |
const altMin = document.getElementById('altitude-min'); | |
const altMax = document.getElementById('altitude-max'); | |
const datum = document.getElementById('coordinate-system'); | |
const withOptionHeader = document.getElementById('with-option-header'); | |
const resultDHCPv4GeoConf = document.getElementById('result-dhcpv4-geoconf'); | |
const resultDHCPv4GeoLoc = document.getElementById('result-dhcpv4-geoloc'); | |
const resultDHCPv6 = document.getElementById('result-dhcpv6'); | |
form.addEventListener('submit', function (ev) { | |
ev.preventDefault(); | |
var i = 0; | |
const results = getResults( | |
parseFloat(lat.value), | |
parseFloat(latMin.value), | |
parseFloat(latMax.value), | |
parseFloat(lon.value), | |
parseFloat(lonMin.value), | |
parseFloat(lonMax.value), | |
parseInt(aType.value, 10), | |
parseFloat(alt.value), | |
parseFloat(altMin.value), | |
parseFloat(altMax.value), | |
parseInt(datum.value, 10), | |
); | |
resultDHCPv4GeoConf.value = ""; | |
resultDHCPv4GeoLoc.value = ""; | |
resultDHCPv6.value = ""; | |
if (withOptionHeader.checked) { | |
resultDHCPv4GeoConf.value += '00:7b:00:10:'; | |
resultDHCPv4GeoLoc.value += '00:90:00:10:'; | |
resultDHCPv6.value += '00:3f:00:10:'; | |
} | |
resultDHCPv4GeoConf.value += results[0].map(function(n) { return ('00' + n.toString(16)).slice(-2); }).join(':'); | |
resultDHCPv4GeoLoc.value += results[1].map(function(n) { return ('00' + n.toString(16)).slice(-2); }).join(':'); | |
resultDHCPv6.value += results[1].map(function(n) { return ('00' + n.toString(16)).slice(-2); }).join(':'); | |
return false; | |
}); | |
}()); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment