Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Metric: Altitude #168

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
9 changes: 5 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
FROM php:apache
COPY backend-php/ /var/www/html/
COPY frontend/ /var/www/html/
COPY docker/start.sh .

RUN apt-get update && \
apt-get install -y memcached libmemcached-dev zlib1g-dev libldap2-dev && \
Expand All @@ -12,7 +9,11 @@ RUN apt-get update && \

EXPOSE 80/tcp
VOLUME /etc/hauk

STOPSIGNAL SIGINT

COPY docker/start.sh .
RUN chmod +x ./start.sh
COPY backend-php/ /var/www/html/
COPY frontend/ /var/www/html/
bilde2910 marked this conversation as resolved.
Show resolved Hide resolved

CMD ["./start.sh"]
1 change: 1 addition & 0 deletions android/app/src/main/java/info/varden/hauk/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ public enum Constants {
public static final String PACKET_PARAM_SPEED = "spd";
public static final String PACKET_PARAM_TIMESTAMP = "time";
public static final String PACKET_PARAM_USERNAME = "usr";
public static final String PACKET_PARAM_ALTITUDE = "alt";

// Packet OK response header. All valid packets start with this line.
public static final String PACKET_RESPONSE_OK = "OK";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,15 @@
import android.util.Base64;

import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;

import info.varden.hauk.Constants;
import info.varden.hauk.R;
Expand Down Expand Up @@ -48,36 +54,17 @@ protected LocationUpdatePacket(Context ctx, Session session, Location location,
super(ctx, session.getServerURL(), session.getConnectionParameters(), Constants.URL_PATH_POST_LOCATION);
setParameter(Constants.PACKET_PARAM_SESSION_ID, session.getID());

if (session.getDerivableE2EKey() == null) {
// If not using end-to-end encryption, send parameters in plain text.
setParameter(Constants.PACKET_PARAM_LATITUDE, String.valueOf(location.getLatitude()));
setParameter(Constants.PACKET_PARAM_LONGITUDE, String.valueOf(location.getLongitude()));
setParameter(Constants.PACKET_PARAM_PROVIDER_ACCURACY, String.valueOf(accuracy.getMode()));
setParameter(Constants.PACKET_PARAM_TIMESTAMP, String.valueOf(System.currentTimeMillis() / (double) TimeUtils.MILLIS_PER_SECOND));
Cipher cipher = initCipher(session);

// Not all devices provide these parameters:
if (location.hasSpeed()) setParameter(Constants.PACKET_PARAM_SPEED, String.valueOf(location.getSpeed()));
if (location.hasAccuracy()) setParameter(Constants.PACKET_PARAM_ACCURACY, String.valueOf(location.getAccuracy()));
} else {
// We're using end-to-end encryption - generate an IV and encrypt all parameters.
try {
Cipher cipher = Cipher.getInstance(Constants.E2E_TRANSFORMATION);
cipher.init(Cipher.ENCRYPT_MODE, session.getDerivableE2EKey().deriveSpec(), new SecureRandom());
byte[] iv = cipher.getIV();
setParameter(Constants.PACKET_PARAM_INIT_VECTOR, Base64.encodeToString(iv, Base64.DEFAULT));
encryptAndSetParameter(Constants.PACKET_PARAM_LATITUDE, location.getLatitude(), cipher);
encryptAndSetParameter(Constants.PACKET_PARAM_LONGITUDE, location.getLongitude(), cipher);
encryptAndSetParameter(Constants.PACKET_PARAM_PROVIDER_ACCURACY, accuracy.getMode(), cipher);
encryptAndSetParameter(Constants.PACKET_PARAM_TIMESTAMP, System.currentTimeMillis() / (double) TimeUtils.MILLIS_PER_SECOND, cipher);

setParameter(Constants.PACKET_PARAM_LATITUDE, Base64.encodeToString(cipher.doFinal(String.valueOf(location.getLatitude()).getBytes(StandardCharsets.UTF_8)), Base64.DEFAULT));
setParameter(Constants.PACKET_PARAM_LONGITUDE, Base64.encodeToString(cipher.doFinal(String.valueOf(location.getLongitude()).getBytes(StandardCharsets.UTF_8)), Base64.DEFAULT));
setParameter(Constants.PACKET_PARAM_PROVIDER_ACCURACY, Base64.encodeToString(cipher.doFinal(String.valueOf(accuracy.getMode()).getBytes(StandardCharsets.UTF_8)), Base64.DEFAULT));
setParameter(Constants.PACKET_PARAM_TIMESTAMP, Base64.encodeToString(cipher.doFinal(String.valueOf(System.currentTimeMillis() / (double) TimeUtils.MILLIS_PER_SECOND).getBytes(StandardCharsets.UTF_8)), Base64.DEFAULT));

// Not all devices provide these parameters:
if (location.hasSpeed()) setParameter(Constants.PACKET_PARAM_SPEED, Base64.encodeToString(cipher.doFinal(String.valueOf(location.getSpeed()).getBytes(StandardCharsets.UTF_8)), Base64.DEFAULT));
if (location.hasAccuracy()) setParameter(Constants.PACKET_PARAM_ACCURACY, Base64.encodeToString(cipher.doFinal(String.valueOf(location.getAccuracy()).getBytes(StandardCharsets.UTF_8)), Base64.DEFAULT));
} catch (Exception e) {
Log.e("Error was thrown when encrypting location data", e); //NON-NLS
}
}
// Not all devices provide these parameters:
if (location.hasSpeed()) encryptAndSetParameter(Constants.PACKET_PARAM_SPEED, location.getSpeed(), cipher);
if (location.hasAccuracy()) encryptAndSetParameter(Constants.PACKET_PARAM_ACCURACY, location.getAccuracy(), cipher);
if (location.hasAltitude()) encryptAndSetParameter(Constants.PACKET_PARAM_ALTITUDE, location.getAltitude(), cipher);
}

@SuppressWarnings("DesignForExtension")
Expand Down Expand Up @@ -113,4 +100,33 @@ protected void onSuccess(String[] data, Version backendVersion) throws ServerExc
throw new ServerException(err.toString());
}
}

private Cipher initCipher(Session session) {
Cipher cipher = null;
if (session.getDerivableE2EKey() != null) {
try {
cipher = Cipher.getInstance(Constants.E2E_TRANSFORMATION);
cipher.init(Cipher.ENCRYPT_MODE, session.getDerivableE2EKey().deriveSpec(), new SecureRandom());
byte[] iv = cipher.getIV();
setParameter(Constants.PACKET_PARAM_INIT_VECTOR, Base64.encodeToString(iv, Base64.DEFAULT));
} catch (NoSuchAlgorithmException | InvalidKeyException | InvalidKeySpecException | NoSuchPaddingException exception) {
Log.e("Error was thrown while initializing E2E encryption", exception); //NON-NLS
}
}
return cipher;
}

private <V> void encryptAndSetParameter(String key, V value, Cipher cipher) {
if (cipher != null) {
// We're using end-to-end encryption - generate an IV and encrypt all parameters.
try {
setParameter(key, Base64.encodeToString(cipher.doFinal(String.valueOf(value).getBytes(StandardCharsets.UTF_8)), Base64.DEFAULT));
} catch (BadPaddingException | IllegalBlockSizeException exception) {
Log.e("Error was thrown while encrypting location data", exception); //NON-NLS
}
} else {
// If not using end-to-end encryption, send parameters in plain text.
setParameter(key, String.valueOf(value));
}
}
}
52 changes: 22 additions & 30 deletions backend-php/api/post.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,45 +21,37 @@
$session = new Client($memcache, $sid);
if (!$session->exists()) die($LANG['session_expired']."\n");

if (!$session->isEncrypted()) {
// Perform input validation.
$lat = floatval($_POST["lat"]);
$lon = floatval($_POST["lon"]);
$time = floatval($_POST["time"]);
if ($lat < -90 || $lat > 90 || $lon < -180 || $lon > 180) die($LANG['location_invalid']."\n");

// Not all devices report speed and accuracy, but if available, report them
// too.
$speed = isset($_POST["spd"]) ? floatval($_POST["spd"]) : null;
$accuracy = isset($_POST["acc"]) ? floatval($_POST["acc"]) : null;
$provider = isset($_POST["prv"]) && $_POST["prv"] == "1" ? 1 : 0;

// The location data object contains the sharing interval (i), duration (d)
// and a location list (l). Each entry in the location list contains a
// latitude, longitude, timestamp, provider, accuracy and speed, in that
// order, as an array.
$session->addPoint([$lat, $lon, $time, $provider, $accuracy, $speed])->save();

} else {
// Input validation cannot be performed for end-to-end encrypted data.
$lat = $_POST["lat"];
$lon = $_POST["lon"];
$time = $_POST["time"];
$speed = isset($_POST["spd"]) ? $_POST["spd"] : null;
$accuracy = isset($_POST["acc"]) ? $_POST["acc"] : null;
$provider = isset($_POST["prv"]) ? $_POST["prv"] : null;

$lat = $_POST["lat"];
$lon = $_POST["lon"];
$time = $_POST["time"];
$speed = isset($_POST["spd"]) ? $_POST["spd"] : null;
$altitude = isset($_POST["alt"]) ? $_POST["alt"] : null;
$accuracy = isset($_POST["acc"]) ? $_POST["acc"] : null;
$provider = isset($_POST["prv"]) ? $_POST["prv"] : null;

// The location data object contains the sharing interval (i), duration (d)
// and a location list (l). Each entry in the location list contains a
// latitude, longitude, timestamp, provider, accuracy and speed, in that
// order, as an array.
$point = [$lat, $lon, $time, $provider, $accuracy, $speed, $altitude];

if ($session->isEncrypted()) {
// End-to-end encrypted connections also have an IV field used to decrypt
// the data fields.
requirePOST("iv");
$iv = $_POST["iv"];

// The IV field is prepended to the array to send to the client.
$session->addPoint([$iv, $lat, $lon, $time, $provider, $accuracy, $speed])->save();
array_unshift($point , $iv);
} else {
// Perform input validation
if (floatval($lat) < -90 || floatval($lat) > 90 || floatval($lon) < -180 || floatval($lon) > 180) die($LANG['location_invalid']."\n");
bilde2910 marked this conversation as resolved.
Show resolved Hide resolved
}

$session->addPoint($point)->save();

if ($session->hasExpired()) {
echo $LANG['session_expired']."\n";
} else {
echo "OK\n".getConfig("public_url")."?%s\n".implode(",", $session->getTargetIDs())."\n";
}
}
3 changes: 3 additions & 0 deletions backend-php/dynamic.js.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,8 @@
var VELOCITY_DELTA_TIME = <?php echo json_encode(getConfig("v_data_points")); ?>;
var TRAIL_COLOR = <?php echo json_encode(getConfig("trail_color")); ?>;
var VELOCITY_UNIT = <?php echo json_encode(getConfig("velocity_unit")); ?>;
var ALTITUDE_UNIT = <?php echo json_encode(getConfig("altitude_unit")); ?>;
var SHOW_VELOCITY = <?php echo json_encode(getConfig("show_velocity")); ?>;
var SHOW_ALTITUDE_AMSL = <?php echo json_encode(getConfig("show_altitude_amsl")); ?>;
var OFFLINE_TIMEOUT = <?php echo json_encode(getConfig("offline_timeout")); ?>;
var REQUEST_TIMEOUT = <?php echo json_encode(getConfig("request_timeout")); ?>;
10 changes: 10 additions & 0 deletions backend-php/include/config-sample.php
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,16 @@
// KILOMETERS_PER_HOUR, MILES_PER_HOUR, METERS_PER_SECOND
"velocity_unit" => KILOMETERS_PER_HOUR,

// The unit of measurement of altitude. Valid are:
// METERS, FEET
"altitude_unit" => METERS,

// Display velocity below marker
"show_velocity" => true,

// Display altitude AMSL below marker
"show_altitude_amsl"=> true,

// The publicly accessible URL to reach Hauk, with trailing slash.
"public_url" => 'https://example.com/'

Expand Down
13 changes: 13 additions & 0 deletions backend-php/include/inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,16 @@
"mpsMultiplier" => 1,
"unit" => "m/s"
);
const METERS = array(
// Absolute distance in meters
"metersMultiplier" => 1,
"unit" => "m"
);
const FEET = array(
// Absolute distance in feet
"metersMultiplier" => 3.280839895,
"unit" => "ft"
);

// Load fallback language.
include(__DIR__."/lang/en/texts.php");
Expand Down Expand Up @@ -149,6 +159,9 @@
"v_data_points" => 2,
"trail_color" => '#d80037',
"velocity_unit" => KILOMETERS_PER_HOUR,
"altitude_unit" => METERS,
"show_velocity" => true,
"show_altitude_amsl" => true,
"public_url" => 'https://example.com/'

);
Expand Down
56 changes: 44 additions & 12 deletions frontend/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -684,12 +684,13 @@ function processUpdate(data, init) {
var lastPoint = shares[user].points.length > 0 ? shares[user].points[shares[user].points.length - 1] : null;

for (var i = 0; i < users[user].length; i++) {
var lat = users[user][i][0];
var lon = users[user][i][1];
var time = users[user][i][2];
var prov = users[user][i][3];
var acc = users[user][i][4];
var spd = users[user][i][5];
var lat = getValueOrNull(users[user][i],0);
var lon = getValueOrNull(users[user][i],1);
var time = getValueOrNull(users[user][i],2);
var prov = getValueOrNull(users[user][i],3);
var acc = getValueOrNull(users[user][i],4);
var spd = getValueOrNull(users[user][i],5);
var alt = getValueOrNull(users[user][i],6);

// Default to "Fine" provider for older clients.
if (prov === null) prov = LOC_PROVIDER_FINE;
Expand All @@ -710,14 +711,23 @@ function processUpdate(data, init) {
'<div class="arrow still-' + shares[user].state + '" id="arrow-' + shares[user].id + '"></div>' +
'<p class="' + shares[user].state + '" id="label-' + shares[user].id + '">' +
'<span id="nickname-' + shares[user].id + '"></span>' +
'<span class="velocity">' +
'<span id="velocity-' + shares[user].id + '">0.0</span> ' +
VELOCITY_UNIT.unit +
'</span><span class="offline" id="last-seen-' + shares[user].id + '">' +
'<span class="metric' + (SHOW_VELOCITY ? "" : " hidden") + '">' +
'<span class="metric-label">vel:</span>' +
'<span class="metric-value"><span id="velocity-' + shares[user].id + '">0.0</span> ' +
VELOCITY_UNIT.unit + '</span>' +
'<br>' +
'</span>' +
'<span id="altitude-container-' + shares[user].id + '" class="metric'+ (SHOW_ALTITUDE_AMSL ? "" : " hidden") + '">' +
'<span class="metric-label">alt:</span>' +
'<span class="metric-value"><span id="altitude-' + shares[user].id + '">0.0</span> ' +
ALTITUDE_UNIT.unit + '</span>' +
'</span>' +
'<span class="offline" id="last-seen-' + shares[user].id + '">' +
'</span>' +
'</p>' +
'</div>',
iconAnchor: [33, 18]
// FIXME: hard-coded and dependend on style.css .marker
iconAnchor: [48, 18]
});
shares[user].marker = L.marker([lat, lon], {icon: shares[user].icon}).on("click", function() {
follow(this.haukUser);
Expand All @@ -738,7 +748,7 @@ function processUpdate(data, init) {
shares[user].circle.setLatLng([lat, lon]);
if (acc !== null) shares[user].circle.setRadius(acc);
}
shares[user].points.push({lat: lat, lon: lon, line: line, time: time, spd: spd, acc: acc});
shares[user].points.push({lat: lat, lon: lon, line: line, time: time, spd: spd, acc: acc, alt: alt});
lastPoint = shares[user].points[shares[user].points.length - 1];
}
}
Expand Down Expand Up @@ -781,6 +791,20 @@ function processUpdate(data, init) {
vel = velocity(dist, time);
eVelocity.textContent = vel.toFixed(1);;
}

// Altitude (If available)
var eAltitude = document.getElementById("altitude-" + shares[user].id);
var alt = 0;
var eAltitudeContainer = document.getElementById("altitude-container-" + shares[user].id);
if (lastPoint !== null && lastPoint.alt !== null && eAltitude !== null) {
alt = lastPoint.alt * ALTITUDE_UNIT.metersMultiplier;
eAltitude.textContent = alt.toFixed(1);
if (SHOW_ALTITUDE_AMSL) {
eAltitudeContainer.classList.remove("hidden");
}
} else {
eAltitudeContainer.classList.add("hidden");
}

// Flag that the first location has been received, for map centering.
if (lastPoint !== null && !hasReceivedFirst) {
Expand Down Expand Up @@ -890,6 +914,14 @@ function processUpdate(data, init) {
}
}

function getValueOrNull(points,idx) {
var value = null;
if (idx < points.length) {
value = points[idx];
}
return value;
}

// Calculates the distance between two points on a sphere using the Haversine
// algorithm.
function distance(from, to) {
Expand Down
25 changes: 21 additions & 4 deletions frontend/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ a:last-child > .store-icon {

/* The outer marker div. */
.marker {
width: 66px;
width: 96px;
height: 62px;
}

Expand Down Expand Up @@ -302,8 +302,8 @@ a:last-child > .store-icon {
width: 100%;
border-radius: 15px;
text-align: center;
padding: 2px 0;
line-height: 100%;
padding: 5px 0 5px;
line-height: 125%;
font-family: sans-serif;
overflow: hidden;
white-space: nowrap;
Expand All @@ -324,7 +324,7 @@ a:last-child > .store-icon {
background-color: rgba(165,0,42,0.5);
}

.marker p.dead > span.velocity {
.marker p.dead > span.metric {
display: none;
}

Expand All @@ -333,3 +333,20 @@ a:last-child > .store-icon {
background: none;
border: none;
}

.metric-label {
display:inline-block;
width: 30%;
text-align:right;
vertical-align: bottom;
padding-right: 0.5em;
}

.metric-value {
display:inline-block;
width: 70%;
text-align: left;
overflow: hidden;
text-overflow: ellipsis;
vertical-align: bottom;
}