summaryrefslogtreecommitdiffhomepage
path: root/android/lib/model/src
diff options
context:
space:
mode:
Diffstat (limited to 'android/lib/model/src')
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/GeoIpLocation.kt4
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/GeographicLocationConstraint.kt6
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/LatLong.kt21
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/Latitude.kt55
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/Longitude.kt55
-rw-r--r--android/lib/model/src/test/kotlin/net/mullvad/mullvadvpn/model/LatLongTest.kt20
-rw-r--r--android/lib/model/src/test/kotlin/net/mullvad/mullvadvpn/model/LatitudeTest.kt163
-rw-r--r--android/lib/model/src/test/kotlin/net/mullvad/mullvadvpn/model/LongitudeTest.kt154
8 files changed, 474 insertions, 4 deletions
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/GeoIpLocation.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/GeoIpLocation.kt
index e15ab20376..625de76b29 100644
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/GeoIpLocation.kt
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/GeoIpLocation.kt
@@ -10,5 +10,7 @@ data class GeoIpLocation(
val ipv6: InetAddress?,
val country: String,
val city: String?,
- val hostname: String?
+ val latitude: Double,
+ val longitude: Double,
+ val hostname: String?,
) : Parcelable
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/GeographicLocationConstraint.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/GeographicLocationConstraint.kt
index 04f92a72ac..386257a72a 100644
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/GeographicLocationConstraint.kt
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/GeographicLocationConstraint.kt
@@ -9,20 +9,20 @@ sealed class GeographicLocationConstraint : Parcelable {
@Parcelize
data class Country(val countryCode: String) : GeographicLocationConstraint() {
override val location: GeoIpLocation
- get() = GeoIpLocation(null, null, countryCode, null, null)
+ get() = GeoIpLocation(null, null, countryCode, null, 0.0, 0.0, null)
}
@Parcelize
data class City(val countryCode: String, val cityCode: String) :
GeographicLocationConstraint() {
override val location: GeoIpLocation
- get() = GeoIpLocation(null, null, countryCode, cityCode, null)
+ get() = GeoIpLocation(null, null, countryCode, cityCode, 0.0, 0.0, null)
}
@Parcelize
data class Hostname(val countryCode: String, val cityCode: String, val hostname: String) :
GeographicLocationConstraint() {
override val location: GeoIpLocation
- get() = GeoIpLocation(null, null, countryCode, cityCode, hostname)
+ get() = GeoIpLocation(null, null, countryCode, cityCode, 0.0, 0.0, hostname)
}
}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/LatLong.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/LatLong.kt
new file mode 100644
index 0000000000..ae047130e8
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/LatLong.kt
@@ -0,0 +1,21 @@
+package net.mullvad.mullvadvpn.model
+
+import kotlin.math.pow
+import kotlin.math.sqrt
+
+data class LatLong(val latitude: Latitude, val longitude: Longitude) {
+
+ fun distanceTo(other: LatLong): Float =
+ sqrt(
+ latitude.distanceTo(other.latitude).pow(2f) +
+ (longitude.distanceTo(other.longitude).pow(2f))
+ )
+
+ operator fun plus(other: LatLong) =
+ LatLong(latitude + other.latitude, longitude + other.longitude)
+
+ operator fun minus(other: LatLong) =
+ LatLong(latitude - other.latitude, longitude - other.longitude)
+}
+
+const val COMPLETE_ANGLE = 360f
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/Latitude.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/Latitude.kt
new file mode 100644
index 0000000000..14c5b66983
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/Latitude.kt
@@ -0,0 +1,55 @@
+package net.mullvad.mullvadvpn.model
+
+import kotlin.math.absoluteValue
+
+@JvmInline
+value class Latitude(val value: Float) {
+ init {
+ require(value in LATITUDE_RANGE) {
+ "Latitude: '$value' must be between $MIN_LATITUDE_VALUE and $MAX_LATITUDE_VALUE"
+ }
+ }
+
+ fun distanceTo(other: Latitude) = (other.value - value).absoluteValue
+
+ operator fun plus(other: Latitude) = fromFloat(value + other.value)
+
+ operator fun minus(other: Latitude) = fromFloat(value - other.value)
+
+ companion object {
+ private const val MIN_LATITUDE_VALUE: Float = -90f
+ private const val MAX_LATITUDE_VALUE: Float = 90f
+ private val LATITUDE_RANGE = MIN_LATITUDE_VALUE..MAX_LATITUDE_VALUE
+
+ /**
+ * Create a [Latitude] from a float value.
+ *
+ * This function will unwind a float to a valid latitude value. E.g 190 will be unwound to
+ * -10 and 360 will be unwound to 0.
+ */
+ fun fromFloat(value: Float): Latitude {
+ val unwoundValue = unwind(value)
+ return Latitude(unwoundValue)
+ }
+
+ private fun unwind(value: Float): Float {
+ // Remove all 360 degrees
+ val withoutRotations = value % COMPLETE_ANGLE
+
+ // If we are above 180 or below -180, we wrapped half a turn and need to flip sign
+ val partiallyUnwound =
+ if (withoutRotations.absoluteValue > COMPLETE_ANGLE / 2) {
+ -withoutRotations % (COMPLETE_ANGLE / 2)
+ } else withoutRotations
+
+ return when {
+ partiallyUnwound < MIN_LATITUDE_VALUE ->
+ MIN_LATITUDE_VALUE - (partiallyUnwound % MIN_LATITUDE_VALUE)
+ partiallyUnwound > MAX_LATITUDE_VALUE ->
+ MAX_LATITUDE_VALUE - (partiallyUnwound % MAX_LATITUDE_VALUE)
+ // partiallyUnwound in range MIN_LATITUDE_VALUE..MAX_LATITUDE_VALUE
+ else -> partiallyUnwound
+ }
+ }
+ }
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/Longitude.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/Longitude.kt
new file mode 100644
index 0000000000..9f73a6ff17
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/Longitude.kt
@@ -0,0 +1,55 @@
+package net.mullvad.mullvadvpn.model
+
+import kotlin.math.absoluteValue
+
+@JvmInline
+value class Longitude(val value: Float) {
+ init {
+ require(value in LONGITUDE_RANGE) {
+ "Longitude: '$value' must be between $MIN_LONGITUDE_VALUE and $MAX_LONGITUDE_VALUE"
+ }
+ }
+
+ fun distanceTo(other: Longitude) = vectorTo(other).value.absoluteValue
+
+ fun vectorTo(other: Longitude): Longitude {
+ val diff = other.value - value
+ val vectorValue =
+ when {
+ diff > MAX_LONGITUDE_VALUE -> diff - COMPLETE_ANGLE
+ diff < MIN_LONGITUDE_VALUE -> diff + COMPLETE_ANGLE
+ else -> diff
+ }
+ return Longitude(vectorValue)
+ }
+
+ operator fun plus(other: Longitude) = fromFloat(value + other.value)
+
+ operator fun minus(other: Longitude) = fromFloat(value - other.value)
+
+ companion object {
+ private const val MIN_LONGITUDE_VALUE: Float = -180f
+ private const val MAX_LONGITUDE_VALUE: Float = 180f
+ private val LONGITUDE_RANGE = MIN_LONGITUDE_VALUE..MAX_LONGITUDE_VALUE
+
+ /**
+ * Create a [Longitude] from a float value.
+ *
+ * This function will unwind a float to a valid longitude value. E.g 190 will be unwound to
+ * -170 and 360 will be unwound to 0.
+ */
+ fun fromFloat(value: Float): Longitude {
+ val unwoundValue = unwind(value)
+ return Longitude(unwoundValue)
+ }
+
+ private fun unwind(value: Float): Float {
+ val unwound = value % COMPLETE_ANGLE
+ return when {
+ unwound > MAX_LONGITUDE_VALUE -> unwound - COMPLETE_ANGLE
+ unwound < MIN_LONGITUDE_VALUE -> unwound + COMPLETE_ANGLE
+ else -> unwound
+ }
+ }
+ }
+}
diff --git a/android/lib/model/src/test/kotlin/net/mullvad/mullvadvpn/model/LatLongTest.kt b/android/lib/model/src/test/kotlin/net/mullvad/mullvadvpn/model/LatLongTest.kt
new file mode 100644
index 0000000000..6644e25e82
--- /dev/null
+++ b/android/lib/model/src/test/kotlin/net/mullvad/mullvadvpn/model/LatLongTest.kt
@@ -0,0 +1,20 @@
+package net.mullvad.mullvadvpn.model
+
+import kotlin.math.sqrt
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Test
+
+class LatLongTest {
+
+ @Test
+ fun `distance between two LatLong should be same as hypotenuse`() {
+ val latLong1 = LatLong(Latitude(30f), Longitude(40f))
+ val latLong2 = LatLong(Latitude(-40f), Longitude(170f))
+
+ val latDiff = latLong1.latitude.distanceTo(latLong2.latitude)
+ val longDiff = latLong1.longitude.distanceTo(latLong2.longitude)
+ val hypotenuse = sqrt(latDiff * latDiff + longDiff * longDiff)
+
+ assertEquals(hypotenuse, latLong1.distanceTo(latLong2))
+ }
+}
diff --git a/android/lib/model/src/test/kotlin/net/mullvad/mullvadvpn/model/LatitudeTest.kt b/android/lib/model/src/test/kotlin/net/mullvad/mullvadvpn/model/LatitudeTest.kt
new file mode 100644
index 0000000000..8788c2123a
--- /dev/null
+++ b/android/lib/model/src/test/kotlin/net/mullvad/mullvadvpn/model/LatitudeTest.kt
@@ -0,0 +1,163 @@
+package net.mullvad.mullvadvpn.model
+
+import kotlin.math.absoluteValue
+import kotlin.test.assertEquals
+import org.junit.jupiter.api.Assertions.assertDoesNotThrow
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertThrows
+
+class LatitudeTest {
+ @Test
+ fun `creating a valid latitude should work`() {
+ assertDoesNotThrow { Latitude(30f) }
+ }
+
+ @Test
+ fun `creating a valid negative latitude should work`() {
+ assertDoesNotThrow { Latitude(-30f) }
+ }
+
+ @Test
+ fun `create with too high latitude should give IllegalArgumentException`() {
+ assertThrows<IllegalArgumentException> { Latitude(90.1f) }
+ }
+
+ @Test
+ fun `create with too low latitude should give IllegalArgumentException`() {
+ assertThrows<IllegalArgumentException> { Latitude(-90.1f) }
+ }
+
+ @Test
+ fun `fromFloat should accept and wrap large value`() {
+ val longFloat = 400f
+ val longitude = Latitude.fromFloat(longFloat)
+
+ assertEquals(40f, longitude.value)
+ }
+
+ @Test
+ fun `fromFloat should accept and support half-wrap`() {
+ val longFloat = 100f
+ val longitude = Latitude.fromFloat(longFloat)
+
+ assertEquals(80f, longitude.value)
+ }
+
+ @Test
+ fun `fromFloat should accept and support negative half-wrap`() {
+ val longFloat = -100f
+ val longitude = Latitude.fromFloat(longFloat)
+
+ assertEquals(-80f, longitude.value)
+ }
+
+ @Test
+ fun `adding two positive latitude should result in the sum`() {
+ val latFloat1 = 20f
+ val latitude1 = Latitude(latFloat1)
+ val latFloat2 = 30f
+ val latitude2 = Latitude(latFloat2)
+
+ assertEquals(latFloat1 + latFloat2, (latitude1 + latitude2).value)
+ }
+
+ @Test
+ fun `adding two large positive latitude should result in the sum wrapped`() {
+ val latFloat1 = 70f
+ val latitude1 = Latitude(latFloat1)
+ val latFloat2 = 50f
+ val latitude2 = Latitude(latFloat2)
+
+ val expectedResult = 60f
+
+ assertEquals(expectedResult, (latitude1 + latitude2).value)
+ }
+
+ @Test
+ fun `adding two negative latitude should result in the sum`() {
+ val latFloat1 = -20f
+ val latitude1 = Latitude(latFloat1)
+ val latFloat2 = -40f
+ val latitude2 = Latitude(latFloat2)
+
+ assertEquals(latFloat1 + latFloat2, (latitude1 + latitude2).value)
+ }
+
+ @Test
+ fun `adding two large negative latitude should result in the sum`() {
+ val latFloat1 = -70f
+ val latitude1 = Latitude(latFloat1)
+ val latFloat2 = -50f
+ val latitude2 = Latitude(latFloat2)
+
+ val expectedResult = -60f
+
+ assertEquals(expectedResult, (latitude1 + latitude2).value)
+ }
+
+ @Test
+ fun `subtracting two positive latitude should result in the sum`() {
+ val latFloat1 = 80f
+ val latitude1 = Latitude(latFloat1)
+ val latFloat2 = 30f
+ val latitude2 = Latitude(latFloat2)
+
+ assertEquals(latFloat1 - latFloat2, (latitude1 - latitude2).value)
+ }
+
+ @Test
+ fun `subtracting a large latitude should result in the sum wrapped`() {
+ val latFloat1 = -30f
+ val latitude1 = Latitude(latFloat1)
+ val latFloat2 = 80f
+ val latitude2 = Latitude(latFloat2)
+
+ val expectedResult = -70f
+
+ assertEquals(expectedResult, (latitude1 - latitude2).value)
+ }
+
+ @Test
+ fun `subtracting a negative latitude should result in same as addition`() {
+ val latFloat1 = -30f
+ val latitude1 = Latitude(latFloat1)
+ val latFloat2 = -40f
+ val latitude2 = Latitude(latFloat2)
+
+ assertEquals(latFloat1 + latFloat2.absoluteValue, (latitude1 - latitude2).value)
+ }
+
+ @Test
+ fun `subtracting a large negative latitude should result in same as addition wrapped`() {
+ val latFloat1 = 80f
+ val latitude1 = Latitude(latFloat1)
+ val latFloat2 = -90f
+ val latitude2 = Latitude(latFloat2)
+
+ val absoluteLatitude2 = Latitude.fromFloat(latFloat2.absoluteValue)
+
+ assertEquals(latitude1 + absoluteLatitude2, latitude1 - latitude2)
+ }
+
+ @Test
+ fun `distanceTo with two positive latitudes`() {
+ val latFloat1 = 80f
+ val latitude1 = Latitude(latFloat1)
+ val latFloat2 = 30f
+ val latitude2 = Latitude(latFloat2)
+
+ assertEquals(latFloat1 - latFloat2, latitude1.distanceTo(latitude2))
+ }
+
+ @Test
+ fun `distanceTo with two negative latitudes`() {
+ val latFloat1 = -80f
+ val latitude1 = Latitude(latFloat1)
+ val latFloat2 = -30f
+ val latitude2 = Latitude(latFloat2)
+
+ val expectedValue = 50f
+
+ assertEquals(expectedValue, latitude1.distanceTo(latitude2))
+ }
+}
diff --git a/android/lib/model/src/test/kotlin/net/mullvad/mullvadvpn/model/LongitudeTest.kt b/android/lib/model/src/test/kotlin/net/mullvad/mullvadvpn/model/LongitudeTest.kt
new file mode 100644
index 0000000000..de94661ad0
--- /dev/null
+++ b/android/lib/model/src/test/kotlin/net/mullvad/mullvadvpn/model/LongitudeTest.kt
@@ -0,0 +1,154 @@
+package net.mullvad.mullvadvpn.model
+
+import kotlin.math.absoluteValue
+import kotlin.test.assertEquals
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertDoesNotThrow
+import org.junit.jupiter.api.assertThrows
+
+class LongitudeTest {
+ @Test
+ fun `create longitude with longitude should work`() {
+ assertDoesNotThrow { Longitude(80f) }
+ }
+
+ @Test
+ fun `create longitude with negative longitude should work`() {
+ assertDoesNotThrow { Longitude(-80f) }
+ }
+
+ @Test
+ fun `create too high longitude should give IllegalArgumentException`() {
+ assertThrows<IllegalArgumentException> { Longitude(180.1f) }
+ }
+
+ @Test
+ fun `create too low longitude should give IllegalArgumentException`() {
+ assertThrows<IllegalArgumentException> { Longitude(-180.1f) }
+ }
+
+ @Test
+ fun `fromFloat should accept and wrap large value`() {
+ val longFloat = 720f
+ val longitude = Longitude.fromFloat(longFloat)
+
+ assertEquals(0f, longitude.value)
+ }
+
+ @Test
+ fun `fromFloat should accept and wrap large negative value`() {
+ val longFloat = -720f
+ val longitude = Longitude.fromFloat(longFloat)
+
+ assertEquals(0f, longitude.value, 0f)
+ }
+
+ @Test
+ fun `adding two positive longitude should result in the sum`() {
+ val longFloat1 = 80f
+ val longitude1 = Longitude(longFloat1)
+ val longFloat2 = 30f
+ val longitude2 = Longitude(longFloat2)
+
+ assertEquals(longFloat1 + longFloat2, (longitude1 + longitude2).value)
+ }
+
+ @Test
+ fun `adding two large positive longitude should result in the sum wrapped`() {
+ val longFloat1 = 170f
+ val longitude1 = Longitude(longFloat1)
+ val longFloat2 = 150f
+ val longitude2 = Longitude(longFloat2)
+
+ val expectedResult = -40f
+
+ assertEquals(expectedResult, (longitude1 + longitude2).value)
+ }
+
+ @Test
+ fun `adding two negative longitude should result in the sum wrapped`() {
+ val longFloat1 = -80f
+ val longitude1 = Longitude(longFloat1)
+ val longFloat2 = -40f
+ val longitude2 = Longitude(longFloat2)
+
+ assertEquals(longFloat1 + longFloat2, (longitude1 + longitude2).value)
+ }
+
+ @Test
+ fun `subtracting two positive longitude should result in the sum`() {
+ val longFloat1 = 80f
+ val longitude1 = Longitude(longFloat1)
+ val longFloat2 = 30f
+ val longitude2 = Longitude(longFloat2)
+
+ assertEquals(longFloat1 - longFloat2, (longitude1 - longitude2).value)
+ }
+
+ @Test
+ fun `subtracting a large longitude should result in the sum wrapped`() {
+ val longFloat1 = -30f
+ val longitude1 = Longitude(longFloat1)
+ val longFloat2 = 170f
+ val longitude2 = Longitude(longFloat2)
+
+ val expectedResult = 160f
+
+ assertEquals(expectedResult, (longitude1 - longitude2).value)
+ }
+
+ @Test
+ fun `subtracting a negative latitude should result in same as addition`() {
+ val longFloat1 = -80f
+ val longitude1 = Longitude(longFloat1)
+ val longFloat2 = -40f
+ val longitude2 = Longitude(longFloat2)
+
+ assertEquals(longFloat1 + longFloat2.absoluteValue, (longitude1 - longitude2).value)
+ }
+
+ @Test
+ fun `subtracting a large negative latitude should result in same as addition wrapped`() {
+ val longFloat1 = 80f
+ val longitude1 = Longitude(longFloat1)
+ val longFloat2 = -140f
+ val longitude2 = Longitude(longFloat2)
+
+ val absoluteLongitude2 = Longitude.fromFloat(longFloat2.absoluteValue)
+ assertEquals(longitude1 + absoluteLongitude2, longitude1 - longitude2)
+ }
+
+ @Test
+ fun `distanceTo with two positive longitudes`() {
+ val longFloat1 = 80f
+ val longitude1 = Longitude(longFloat1)
+ val longFloat2 = 30f
+ val longitude2 = Longitude(longFloat2)
+
+ assertEquals(longFloat1 - longFloat2, longitude1.distanceTo(longitude2))
+ }
+
+ @Test
+ fun `distanceTo with two negative longitudes`() {
+ val longFloat1 = -80f
+ val longitude1 = Longitude(longFloat1)
+ val longFloat2 = -30f
+ val longitude2 = Longitude(longFloat2)
+
+ val expectedValue = 50f
+
+ assertEquals(expectedValue, longitude1.distanceTo(longitude2))
+ }
+
+ @Test
+ fun `distanceTo with wrapping value as shortest path`() {
+ val longFloat1 = -170f
+ val longitude1 = Longitude(longFloat1)
+ val longFloat2 = 170f
+ val longitude2 = Longitude(longFloat2)
+
+ val expectedValue = 20f
+
+ assertEquals(expectedValue, longitude1.distanceTo(longitude2))
+ }
+}