Skip to content

Commit

Permalink
Merge pull request #3 from tuffnerdstuff/feature/altitude
Browse files Browse the repository at this point in the history
Feature/altitude
  • Loading branch information
Bartixxx32 committed Jun 15, 2022
2 parents f33b453 + 33e19cb commit 94f8866
Show file tree
Hide file tree
Showing 9 changed files with 163 additions and 78 deletions.
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/

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 @@ -123,6 +123,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");
}

$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 @@ -700,12 +700,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 @@ -726,14 +727,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 @@ -754,7 +764,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 @@ -797,6 +807,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 @@ -906,6 +930,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 @@ -264,7 +264,7 @@ a:last-child > .store-icon {

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

Expand Down Expand Up @@ -312,8 +312,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 @@ -334,7 +334,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 @@ -343,3 +343,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;
}

0 comments on commit 94f8866

Please sign in to comment.