Skip to content

Commit

Permalink
Add base support for new synchronization option: K-Server (#48)
Browse files Browse the repository at this point in the history
* Bump gradle and related dependencies

* Rename to avoid conflicts

* wip

* Add local dev server config

* wip

* wip

* Remove label, this should not be provided extra, labeling is an extra module

* Adjust server IPs

* align API

* align API

* Remove health endpoint query, not using it
  • Loading branch information
d4rken authored Aug 28, 2024
1 parent 79e35f5 commit d126857
Show file tree
Hide file tree
Showing 61 changed files with 2,759 additions and 77 deletions.
20 changes: 18 additions & 2 deletions PRIVACY_POLICY.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,34 @@ Camera data is not stored and only used for processing the QR-Code.

### Query installed apps

Octi allows you to see which apps are installed on your other devices, to do this, the `QUERY_ALL_PACKAGES` is required. This information is only available to you and encrypted when exchanged between devices. Two edge cases exist: Information about installed apps may be contained in manually generated [debug logs](#debug-log) and [automatic error reports](#automatic-error-reports).
Octi allows you to see which apps are installed on your other devices, to do this, the `QUERY_ALL_PACKAGES` is required.
This information is only available to you and encrypted when exchanged between devices. Two edge cases exist:
Information about installed apps may be contained in manually generated [debug logs](#debug-log)
and [automatic error reports](#automatic-error-reports).

## Sync services

Octi provides different mechanisms to syncronize data across different devices. The following explains the different
mechanisms and how your data is handled.

### K-Server

K-Server is an end to end encrypted open-source sync server hosted by me.

Synced data can't be viewed by me. Data is encrypted client-side. The encryption key is only available on your devices
and is unknown to the server.

Some meta data like access times and IP addresses are temporarily stored to allow for anti-abuse mechanisms.

Any stored data can be deleted from within the app by deleting your account. If your account is not accessed at least
once within 30 days, your data is also deleted.

### J-Server

J-Server is an end to end encrypted open-source sync server hosted by me.

Synced data can't be viewed by me. Data is encrypted client-side. The encryption key is only available on your devices and is unknown to the server.
Synced data can't be viewed by me. Data is encrypted client-side. The encryption key is only available on your devices
and is unknown to the server.

Some meta data like access times and IP addresses are temporarily stored to allow for anti-abuse mechanisms.

Expand Down

This file was deleted.

8 changes: 7 additions & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,11 @@ android {
useJUnitPlatform()
}
}
packaging {
resources {
excludes += "META-INF/INDEX.LIST"
}
}
}

dependencies {
Expand All @@ -124,8 +129,9 @@ dependencies {
implementation(project(":app-common"))
testImplementation(project(":app-common-test"))
implementation(project(":sync-core"))
implementation(project(":syncs-jserver"))
implementation(project(":syncs-gdrive"))
implementation(project(":syncs-jserver"))
implementation(project(":syncs-kserver"))
implementation(project(":module-core"))
implementation(project(":modules-meta"))
implementation(project(":modules-power"))
Expand Down
4 changes: 1 addition & 3 deletions app/src/androidTest/java/testhelper/BaseUITest.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
package testhelper

import testhelpers.BaseTestInstrumentation

abstract class BaseUITest : BaseTestInstrumentation()
abstract class BaseUITest
4 changes: 4 additions & 0 deletions app/src/debug/res/xml/network_security_config.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true" />
</network-security-config>
1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:networkSecurityConfig="@xml/network_security_config"
android:theme="@style/AppTheme">
<activity
android:name="eu.darken.octi.main.ui.MainActivity"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import eu.darken.octi.common.lists.modular.mods.DataBinderMod
import eu.darken.octi.common.lists.modular.mods.TypedVHCreatorMod
import eu.darken.octi.syncs.gdrive.ui.add.AddGDriveVH
import eu.darken.octi.syncs.jserver.ui.add.AddJServerDataVH
import eu.darken.octi.syncs.kserver.ui.add.AddKServerDataVH
import javax.inject.Inject


Expand All @@ -28,12 +29,13 @@ class SyncAddAdapter @Inject constructor() :
modules.add(DataBinderMod(data))
modules.add(TypedVHCreatorMod({ data[it] is AddGDriveVH.Item }) { AddGDriveVH(it) })
modules.add(TypedVHCreatorMod({ data[it] is AddJServerDataVH.Item }) { AddJServerDataVH(it) })
modules.add(TypedVHCreatorMod({ data[it] is AddKServerDataVH.Item }) { AddKServerDataVH(it) })
}

abstract class BaseVH<D : Item, B : ViewBinding>(
@LayoutRes layoutId: Int,
parent: ViewGroup
) : ModularAdapter.VH(layoutId, parent), BindableVH<D, B>
) : VH(layoutId, parent), BindableVH<D, B>

interface Item : DifferItem {
override val payloadProvider: ((DifferItem, DifferItem) -> DifferItem?)
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/java/eu/darken/octi/sync/ui/add/SyncAddVM.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import eu.darken.octi.common.debug.logging.logTag
import eu.darken.octi.common.uix.ViewModel3
import eu.darken.octi.syncs.gdrive.ui.add.AddGDriveVH
import eu.darken.octi.syncs.jserver.ui.add.AddJServerDataVH
import eu.darken.octi.syncs.kserver.ui.add.AddKServerDataVH
import kotlinx.coroutines.flow.flow
import javax.inject.Inject

Expand All @@ -26,6 +27,9 @@ class SyncAddVM @Inject constructor(
AddJServerDataVH.Item {
SyncAddFragmentDirections.actionSyncAddFragmentToAddJServerFragment().navigate()
}.run { items.add(this) }
AddKServerDataVH.Item {
SyncAddFragmentDirections.actionSyncAddFragmentToAddKServerFragment().navigate()
}.run { items.add(this) }

emit(items)
}.asLiveData2()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import eu.darken.octi.common.lists.modular.mods.DataBinderMod
import eu.darken.octi.common.lists.modular.mods.TypedVHCreatorMod
import eu.darken.octi.syncs.gdrive.ui.GDriveStateVH
import eu.darken.octi.syncs.jserver.ui.JServerStateVH
import eu.darken.octi.syncs.kserver.ui.KServerStateVH
import javax.inject.Inject


Expand All @@ -28,12 +29,13 @@ class SyncListAdapter @Inject constructor() :
modules.add(DataBinderMod(data))
modules.add(TypedVHCreatorMod({ data[it] is GDriveStateVH.Item }) { GDriveStateVH(it) })
modules.add(TypedVHCreatorMod({ data[it] is JServerStateVH.Item }) { JServerStateVH(it) })
modules.add(TypedVHCreatorMod({ data[it] is KServerStateVH.Item }) { KServerStateVH(it) })
}

abstract class BaseVH<D : Item, B : ViewBinding>(
@LayoutRes layoutId: Int,
parent: ViewGroup
) : ModularAdapter.VH(layoutId, parent), BindableVH<D, B>
) : VH(layoutId, parent), BindableVH<D, B>

interface Item : DifferItem {
override val payloadProvider: ((DifferItem, DifferItem) -> DifferItem?)
Expand Down
17 changes: 16 additions & 1 deletion app/src/main/java/eu/darken/octi/sync/ui/list/SyncListVM.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import eu.darken.octi.syncs.gdrive.core.GDriveAppDataConnector
import eu.darken.octi.syncs.gdrive.ui.GDriveStateVH
import eu.darken.octi.syncs.jserver.core.JServerConnector
import eu.darken.octi.syncs.jserver.ui.JServerStateVH
import eu.darken.octi.syncs.kserver.core.KServerConnector
import eu.darken.octi.syncs.kserver.ui.KServerStateVH
import kotlinx.coroutines.flow.*
import javax.inject.Inject

Expand Down Expand Up @@ -49,16 +51,29 @@ class SyncListVM @Inject constructor(
).navigate()
}
)

is JServerConnector -> JServerStateVH.Item(
credentials = connector.credentials,
ourState = state,
otherStates = (connectors - connector).map { it.state.first() },
onManage = {
SyncListFragmentDirections.actionSyncListFragmentToSyrvJServerActionsFragment(
SyncListFragmentDirections.actionSyncListFragmentToJServerActionsFragment(
connector.identifier
).navigate()
}
)

is KServerConnector -> KServerStateVH.Item(
credentials = connector.credentials,
ourState = state,
otherStates = (connectors - connector).map { it.state.first() },
onManage = {
SyncListFragmentDirections.actionSyncListFragmentToKServerActionsFragment(
connector.identifier
).navigate()
}
)

else -> {
log(TAG, WARN) { "Unknown connector type: $connector" }
null
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package eu.darken.octi.syncs.jserver.ui.link

enum class LinkOption {
enum class JServerLinkOption {
DIRECT,
QRCODE,
NFC
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import eu.darken.octi.common.debug.logging.logTag
import eu.darken.octi.common.uix.Fragment3
import eu.darken.octi.common.viewbinding.viewBinding
import eu.darken.octi.databinding.SyncJserverLinkClientFragmentBinding
import eu.darken.octi.syncs.jserver.ui.link.LinkOption
import eu.darken.octi.syncs.jserver.ui.link.JServerLinkOption


@AndroidEntryPoint
Expand All @@ -43,9 +43,9 @@ class JServerLinkClientFragment : Fragment3(R.layout.sync_jserver_link_client_fr

ui.linkOptions.setOnCheckedChangeListener { _, checkedId ->
when (checkedId) {
R.id.link_option_direct -> vm.onLinkOptionSelected(LinkOption.DIRECT)
R.id.link_option_qrcode -> vm.onLinkOptionSelected(LinkOption.QRCODE)
R.id.link_option_nfc -> vm.onLinkOptionSelected(LinkOption.NFC)
R.id.link_option_direct -> vm.onLinkOptionSelected(JServerLinkOption.DIRECT)
R.id.link_option_qrcode -> vm.onLinkOptionSelected(JServerLinkOption.QRCODE)
R.id.link_option_nfc -> vm.onLinkOptionSelected(JServerLinkOption.NFC)
}
}

Expand All @@ -70,19 +70,21 @@ class JServerLinkClientFragment : Fragment3(R.layout.sync_jserver_link_client_fr
}

vm.state.observe2(ui) { state ->
linkContainerDirect.isGone = state.linkOption != LinkOption.DIRECT
linkContainerQrcode.isGone = state.linkOption != LinkOption.QRCODE
linkContainerNfc.isGone = state.linkOption != LinkOption.NFC
linkContainerDirect.isGone = state.linkOption != JServerLinkOption.DIRECT
linkContainerQrcode.isGone = state.linkOption != JServerLinkOption.QRCODE
linkContainerNfc.isGone = state.linkOption != JServerLinkOption.NFC

when (state.linkOption) {
LinkOption.DIRECT -> {
JServerLinkOption.DIRECT -> {
linkOptions.check(R.id.link_option_direct)
linkCodeActual.text = null
}
LinkOption.QRCODE -> {

JServerLinkOption.QRCODE -> {
linkOptions.check(R.id.link_option_qrcode)
}
LinkOption.NFC -> {

JServerLinkOption.NFC -> {
linkOptions.check(R.id.link_option_nfc)
// TODO NOOP?
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import eu.darken.octi.common.debug.logging.logTag
import eu.darken.octi.common.uix.ViewModel3
import eu.darken.octi.syncs.jserver.core.JServerHub
import eu.darken.octi.syncs.jserver.core.LinkingData
import eu.darken.octi.syncs.jserver.ui.link.LinkOption
import eu.darken.octi.syncs.jserver.ui.link.JServerLinkOption
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
Expand All @@ -27,14 +27,14 @@ class JServerLinkClientVM @Inject constructor(

data class State(
val encodedLinkCode: String? = null,
val linkOption: LinkOption = LinkOption.QRCODE,
val linkOption: JServerLinkOption = JServerLinkOption.QRCODE,
val isBusy: Boolean = false,
)

private val _state = MutableStateFlow(State())
val state = _state.asLiveData2()

fun onLinkOptionSelected(option: LinkOption) = launch {
fun onLinkOptionSelected(option: JServerLinkOption) = launch {
log(TAG) { "onLinkOptionSelected(option=$option)" }
stateLock.withLock {
_state.value = _state.value.copy(linkOption = option)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import eu.darken.octi.common.navigation.popBackStack
import eu.darken.octi.common.uix.Fragment3
import eu.darken.octi.common.viewbinding.viewBinding
import eu.darken.octi.databinding.SyncJserverLinkHostFragmentBinding
import eu.darken.octi.syncs.jserver.ui.link.LinkOption
import eu.darken.octi.syncs.jserver.ui.link.JServerLinkOption


@AndroidEntryPoint
Expand All @@ -32,25 +32,26 @@ class JServerLinkHostFragment : Fragment3(R.layout.sync_jserver_link_host_fragme

ui.linkOptions.setOnCheckedChangeListener { _, checkedId ->
when (checkedId) {
R.id.link_option_direct -> vm.onLinkOptionSelected(LinkOption.DIRECT)
R.id.link_option_qrcode -> vm.onLinkOptionSelected(LinkOption.QRCODE)
R.id.link_option_nfc -> vm.onLinkOptionSelected(LinkOption.NFC)
R.id.link_option_direct -> vm.onLinkOptionSelected(JServerLinkOption.DIRECT)
R.id.link_option_qrcode -> vm.onLinkOptionSelected(JServerLinkOption.QRCODE)
R.id.link_option_nfc -> vm.onLinkOptionSelected(JServerLinkOption.NFC)
}
}

ui.linkCodeInputAction.setOnClickListener { vm.shareLinkCode(requireActivity()) }

vm.state.observe2(ui) { state ->
linkContainerDirect.isGone = state.linkOption != LinkOption.DIRECT
linkContainerQrcode.isGone = state.linkOption != LinkOption.QRCODE
linkContainerNfc.isGone = state.linkOption != LinkOption.NFC
linkContainerDirect.isGone = state.linkOption != JServerLinkOption.DIRECT
linkContainerQrcode.isGone = state.linkOption != JServerLinkOption.QRCODE
linkContainerNfc.isGone = state.linkOption != JServerLinkOption.NFC

when (state.linkOption) {
LinkOption.DIRECT -> {
JServerLinkOption.DIRECT -> {
linkOptions.check(R.id.link_option_direct)
linkCodeActual.text = state.encodedLinkCode
}
LinkOption.QRCODE -> {

JServerLinkOption.QRCODE -> {
linkOptions.check(R.id.link_option_qrcode)
try {
val size = ui.root.width
Expand All @@ -62,7 +63,8 @@ class JServerLinkHostFragment : Fragment3(R.layout.sync_jserver_link_host_fragme
e.asErrorDialogBuilder(requireContext()).show()
}
}
LinkOption.NFC -> {

JServerLinkOption.NFC -> {
linkOptions.check(R.id.link_option_nfc)
// TODO NOOP?
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import eu.darken.octi.sync.core.SyncManager
import eu.darken.octi.sync.core.SyncOptions
import eu.darken.octi.sync.core.getConnectorById
import eu.darken.octi.syncs.jserver.core.JServerConnector
import eu.darken.octi.syncs.jserver.ui.link.LinkOption
import eu.darken.octi.syncs.jserver.ui.link.JServerLinkOption
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*
Expand All @@ -37,7 +37,7 @@ class JServerLinkHostVM @Inject constructor(

data class State(
val encodedLinkCode: String? = null,
val linkOption: LinkOption = LinkOption.QRCODE,
val linkOption: JServerLinkOption = JServerLinkOption.QRCODE,
)

private val _state = MutableStateFlow(State())
Expand Down Expand Up @@ -77,7 +77,7 @@ class JServerLinkHostVM @Inject constructor(
}
}

fun onLinkOptionSelected(option: LinkOption) = launch {
fun onLinkOptionSelected(option: JServerLinkOption) = launch {
log(TAG) { "onLinkOptionSelected(option=$option)" }
stateLock.withLock {
_state.value = _state.value.copy(linkOption = option)
Expand Down
Loading

0 comments on commit d126857

Please sign in to comment.