Skip to content

Commit

Permalink
Add support for location messages (#24)
Browse files Browse the repository at this point in the history
  • Loading branch information
marchof committed Apr 3, 2022
1 parent 89c81f5 commit e006c4c
Show file tree
Hide file tree
Showing 5 changed files with 219 additions and 8 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and [Salty Coffee](https://github.com/NeilMadden/salty-coffee) for NaCl encrypti
The following resources have been used to implement this API:

* [Threema Message API](https://gateway.threema.ch/en/developer/api)
* [Threema E2E Message Formats](https://gateway.threema.ch/en/developer/e2e)
* [Cryptography Whitepaper](https://threema.ch/press-files/2_documentation/cryptography_whitepaper.pdf)
* [Threema Encryption Validation](https://threema.ch/validation/)

Expand Down
119 changes: 114 additions & 5 deletions src/main/java/com/mountainminds/three4j/PlainMessage.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2021 Mountainminds GmbH & Co. KG
* Copyright (c) 2022 Mountainminds GmbH & Co. KG
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
Expand All @@ -21,7 +21,6 @@
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.ArrayList;
Expand All @@ -41,7 +40,7 @@ public abstract class PlainMessage {

/**
* @return code of the specific message type: {@link Text#TYPE},
* {@link Image#TYPE}, {@link File#TYPE} oder
* {@link Location#TYPE}, {@link Image#TYPE}, {@link File#TYPE} or
* {@link DeliveryReceipt#TYPE}
*/
public abstract int getType();
Expand Down Expand Up @@ -82,6 +81,8 @@ public static PlainMessage decode(byte[] bytes) throws IllegalArgumentException
switch (type) {
case Text.TYPE:
return new Text(in);
case Location.TYPE:
return new Location(in);
case Image.TYPE:
return new Image(in);
case File.TYPE:
Expand All @@ -105,7 +106,7 @@ public final static class Text extends PlainMessage {
private final String text;

private Text(DataInputStream in) throws IOException {
text = new String(in.readAllBytes(), StandardCharsets.UTF_8);
text = new String(in.readAllBytes(), UTF_8);
}

public Text(String text) {
Expand All @@ -123,7 +124,7 @@ public int getType() {

@Override
void encode(DataOutputStream out) throws IOException {
out.write(text.getBytes(StandardCharsets.UTF_8));
out.write(text.getBytes(UTF_8));
}

@Override
Expand All @@ -133,6 +134,114 @@ public String toString() {

}

/**
* Location message.
*/
public final static class Location extends PlainMessage {

public static final int TYPE = 0x10;

private final double latitude;
private final double longitude;
private final double accuracy;

private String name;
private String address;

Location(DataInputStream in) throws IOException {
var lines = new String(in.readAllBytes(), UTF_8).split("\n");
var parts = lines[0].split(",");
this.latitude = Double.parseDouble(parts[0]);
this.longitude = Double.parseDouble(parts[1]);
this.accuracy = parts.length == 3 ? Double.parseDouble(parts[2]) : Double.NaN;
if (lines.length == 2) {
this.address = lines[1];
}
if (lines.length == 3) {
this.name = lines[1];
this.address = lines[2];
}

}

public Location(double latitude, double longitude, double accuracy) {
this.latitude = latitude;
this.longitude = longitude;
this.accuracy = accuracy;
}

public Location(double latitude, double longitude) {
this(latitude, longitude, Double.NaN);
}

public double getLatitude() {
return latitude;
}

public double getLongitude() {
return longitude;
}

/**
* @return accuracy in meters or {@link Double#NaN} if undefined
*/
public double getAccuracy() {
return accuracy;
}

public String getName() {
return name;
}

public String getAddress() {
return address;
}

public void setAddress(String address) {
setNameAndAddress(null, address);
}

public void setNameAndAddress(String name, String address) {
this.name = name;
this.address = address;
}

@Override
public int getType() {
return TYPE;
}

@Override
void encode(DataOutputStream out) throws IOException {
var str = new StringBuilder();
str.append(latitude).append(',').append(longitude);
if (!Double.isNaN(accuracy)) {
str.append(',').append(accuracy);
}
if (name != null) {
str.append('\n').append(name);
}
if (address != null) {
str.append('\n').append(address);
}
out.write(str.toString().getBytes(UTF_8));
}

@Override
public String toString() {
var str = new StringBuilder("Location[");
str.append(latitude).append(' ').append(longitude);
if (name != null) {
str.append(", ").append(name);
}
if (address != null) {
str.append(", ").append(address);
}
return str.append(']').toString();
}

}

/**
* Simple message with a reference to a image blob.
*/
Expand Down
5 changes: 3 additions & 2 deletions src/main/java/com/mountainminds/three4j/package-info.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2021 Mountainminds GmbH & Co. KG
* Copyright (c) 2022 Mountainminds GmbH & Co. KG
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
Expand All @@ -25,6 +25,7 @@
*
* <ul>
* <li>{@link PlainMessage.Text}</li>
* <li>{@link PlainMessage.Location}</li>
* <li>{@link PlainMessage.Image}</li>
* <li>{@link PlainMessage.File}</li>
* <li>{@link PlainMessage.DeliveryReceipt}</li>
Expand All @@ -44,7 +45,7 @@
*
* <p>
* For type safety the API uses specific wrapper types instead of
* <code>byte[]</code> of <code>String</code> values:
* <code>byte[]</code> or <code>String</code> values:
* </p>
*
* <ul>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2021 Mountainminds GmbH & Co. KG
* Copyright (c) 2022 Mountainminds GmbH & Co. KG
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
Expand Down Expand Up @@ -27,6 +27,7 @@
import com.mountainminds.three4j.PlainMessage.DeliveryReceipt.ReceiptType;
import com.mountainminds.three4j.PlainMessage.File;
import com.mountainminds.three4j.PlainMessage.Image;
import com.mountainminds.three4j.PlainMessage.Location;
import com.mountainminds.three4j.PlainMessage.Text;

import software.pando.crypto.nacl.CryptoBox;
Expand All @@ -52,6 +53,21 @@ public void should_encrypt_and_decrypt_text_messages() {
assertEquals("secret123", decrypted.getText());
}

@Test
public void should_encrypt_and_decrypt_location_messages() {
var msg = new Location(46.947, 7.444, 40.0);
msg.setNameAndAddress("Bundeshaus", "Bern");

var encrypted = msg.encrypt(alice.getPrivate(), bob.getPublic());
var decrypted = (Location) encrypted.decrypt(alice.getPublic(), bob.getPrivate());

assertEquals(46.947, decrypted.getLatitude());
assertEquals(7.444, decrypted.getLongitude());
assertEquals(40.0, decrypted.getAccuracy());
assertEquals("Bundeshaus", decrypted.getName());
assertEquals("Bern", decrypted.getAddress());
}

@Test
public void should_encrypt_and_decrypt_image_messages() {
var pair = KeyGenerator.generate();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*******************************************************************************
* Copyright (c) 2022 Mountainminds GmbH & Co. KG
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* SPDX-License-Identifier: MIT
*******************************************************************************/
package com.mountainminds.three4j;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

import org.junit.jupiter.api.Test;

import com.mountainminds.three4j.PlainMessage.Location;

public class PlainMessageLocationTest {

@Test
public void init_should_decode_location() throws IOException {
var encoded = "46.94675,7.44423".getBytes(StandardCharsets.UTF_8);
var l = new Location(new DataInputStream(new ByteArrayInputStream(encoded)));
assertEquals(46.94675, l.getLatitude());
assertEquals(7.44423, l.getLongitude());
assertTrue(Double.isNaN(l.getAccuracy()));
assertNull(l.getName());
assertNull(l.getAddress());
}

@Test
public void decode_should_decode_location_and_accuracy() throws IOException {
var encoded = "46.947,7.444,40.000".getBytes(StandardCharsets.UTF_8);
var l = new Location(new DataInputStream(new ByteArrayInputStream(encoded)));
assertEquals(46.947, l.getLatitude());
assertEquals(7.444, l.getLongitude());
assertEquals(40.0, l.getAccuracy());
assertNull(l.getName());
assertNull(l.getAddress());
}

@Test
public void decode_should_decode_location_and_address() throws IOException {
var encoded = "46.947,7.444,40.000\nBundesplatz 3, 3003 Bern, Switzerland".getBytes(StandardCharsets.UTF_8);
var l = new Location(new DataInputStream(new ByteArrayInputStream(encoded)));
assertEquals(46.947, l.getLatitude());
assertEquals(7.444, l.getLongitude());
assertEquals(40.0, l.getAccuracy());
assertNull(l.getName());
assertEquals("Bundesplatz 3, 3003 Bern, Switzerland", l.getAddress());
}

@Test
public void decode_should_decode_location_and_name_and_address() throws IOException {
var encoded = "46.947,7.444,40.000\nBundeshaus\nBundesplatz 3, 3003 Bern, Switzerland"
.getBytes(StandardCharsets.UTF_8);
var l = new Location(new DataInputStream(new ByteArrayInputStream(encoded)));
assertEquals(46.947, l.getLatitude());
assertEquals(7.444, l.getLongitude());
assertEquals(40.0, l.getAccuracy());
assertEquals("Bundeshaus", l.getName());
assertEquals("Bundesplatz 3, 3003 Bern, Switzerland", l.getAddress());
}

@Test
public void toString_should_return_location() {
var msg = new Location(46.947, 7.444, 40.0);
msg.setNameAndAddress("Bundeshaus", "Bern");

assertEquals("Location[46.947 7.444, Bundeshaus, Bern]", msg.toString());
}

}

0 comments on commit e006c4c

Please sign in to comment.