Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
155 changes: 123 additions & 32 deletions app/src/main/java/com/papi/nova/computers/ComputerDatabaseManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
import java.util.LinkedList
import java.util.Locale
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject

Expand Down Expand Up @@ -48,17 +49,44 @@ class ComputerDatabaseManager(c: Context) {
)
)

var oldComputers = LegacyDatabaseReader.migrateAllComputers(c)
for (computer in oldComputers) {
updateComputer(computer)
migrateLegacyDatabase(c, LegacyDatabaseReader.COMPUTER_DB_NAME, LegacyDatabaseReader.readAllComputers(c))
migrateLegacyDatabase(c, LegacyDatabaseReader2.COMPUTER_DB_NAME, LegacyDatabaseReader2.readAllComputers(c))
migrateLegacyDatabase(c, LegacyDatabaseReader3.COMPUTER_DB_NAME, LegacyDatabaseReader3.readAllComputers(c))
}

private fun migrateLegacyDatabase(
c: Context,
databaseName: String,
oldComputers: List<ComputerDetails>?,
) {
if (oldComputers == null) {
return
}
oldComputers = LegacyDatabaseReader2.migrateAllComputers(c)
for (computer in oldComputers) {
updateComputer(computer)

var migrationSucceeded = false
try {
computerDb.beginTransaction()
migrationSucceeded = oldComputers.all { computer ->
computer.seedLegacyAddresses()
updateComputer(computer)
}
if (migrationSucceeded) {
computerDb.setTransactionSuccessful()
}
} catch (_: RuntimeException) {
migrationSucceeded = false
} finally {
if (computerDb.inTransaction()) {
try {
computerDb.endTransaction()
} catch (_: RuntimeException) {
migrationSucceeded = false
}
}
}
oldComputers = LegacyDatabaseReader3.migrateAllComputers(c)
for (computer in oldComputers) {
updateComputer(computer)

if (migrationSucceeded) {
c.deleteDatabase(databaseName)
}
}

Expand All @@ -67,6 +95,9 @@ class ComputerDatabaseManager(c: Context) {
}

fun updateComputer(details: ComputerDetails): Boolean {
if (details.uuid.isBlank()) {
return false
}
val values = ContentValues()
values.put(COMPUTER_UUID_COLUMN_NAME, details.uuid)
values.put(COMPUTER_NAME_COLUMN_NAME, details.name)
Expand All @@ -77,7 +108,12 @@ class ComputerDatabaseManager(c: Context) {
addresses.put(AddressFields.REMOTE, tupleToJson(details.remoteAddress))
addresses.put(AddressFields.MANUAL, tupleToJson(details.manualAddress))
addresses.put(AddressFields.IPV6, tupleToJson(details.ipv6Address))
values.put(ADDRESSES_COLUMN_NAME, addresses.toString())
addresses.put(AddressFields.KNOWN, tuplesToJson(details.knownAddresses))
val serializedAddresses = addresses.toString()
if (serializedAddresses.length > MAX_ADDRESSES_JSON_LENGTH) {
return false
}
values.put(ADDRESSES_COLUMN_NAME, serializedAddresses)
} catch (e: JSONException) {
throw RuntimeException(e)
}
Expand All @@ -90,9 +126,8 @@ class ComputerDatabaseManager(c: Context) {
} else {
values.put(SERVER_CERT_COLUMN_NAME, null as ByteArray?)
}
} catch (e: CertificateEncodingException) {
values.put(SERVER_CERT_COLUMN_NAME, null as ByteArray?)
e.printStackTrace()
} catch (_: CertificateEncodingException) {
return false
}
return -1L != computerDb.insertWithOnConflict(
COMPUTER_TABLE_NAME,
Expand All @@ -102,32 +137,48 @@ class ComputerDatabaseManager(c: Context) {
)
}

private fun getComputerFromCursor(c: Cursor): ComputerDetails {
private fun getComputerFromCursor(c: Cursor): ComputerDetails? {
val details = ComputerDetails()

details.uuid = c.getString(0) ?: ""
if (details.uuid.isBlank()) {
return null
}
details.name = c.getString(1) ?: ""
val serializedAddresses = c.getString(2) ?: return null
if (serializedAddresses.length > MAX_ADDRESSES_JSON_LENGTH) {
return null
}
try {
val addresses = JSONObject(c.getString(2))
val addresses = JSONObject(serializedAddresses)
details.localAddress = tupleFromJson(addresses, AddressFields.LOCAL)
details.remoteAddress = tupleFromJson(addresses, AddressFields.REMOTE)
details.manualAddress = tupleFromJson(addresses, AddressFields.MANUAL)
details.ipv6Address = tupleFromJson(addresses, AddressFields.IPV6)
} catch (e: JSONException) {
throw RuntimeException(e)
if (addresses.has(AddressFields.KNOWN)) {
for (knownAddress in tuplesFromJson(addresses, AddressFields.KNOWN)) {
details.rememberAddress(knownAddress)
}
} else {
details.seedLegacyAddresses()
}
} catch (_: JSONException) {
return null
} catch (_: IllegalArgumentException) {
return null
}

details.externalPort = details.remoteAddress?.port ?: NvHTTP.DEFAULT_HTTP_PORT
details.macAddress = c.getString(3)

try {
val derCertData = c.getBlob(4)
if (derCertData != null) {
val derCertData = c.getBlob(4)
if (derCertData != null) {
try {
details.serverCert = CertificateFactory.getInstance("X.509")
.generateCertificate(ByteArrayInputStream(derCertData)) as X509Certificate
} catch (_: CertificateException) {
return null
}
} catch (e: CertificateException) {
e.printStackTrace()
}

details.state = ComputerDetails.State.UNKNOWN
Expand All @@ -136,10 +187,18 @@ class ComputerDatabaseManager(c: Context) {
}

fun getAllComputers(): List<ComputerDetails> {
computerDb.rawQuery("SELECT * FROM $COMPUTER_TABLE_NAME", null).use { c ->
computerDb.query(
COMPUTER_TABLE_NAME,
null,
BOUNDED_ADDRESSES_SELECTION,
arrayOf(MAX_ADDRESSES_JSON_LENGTH.toString()),
null,
null,
null
).use { c ->
val computerList = LinkedList<ComputerDetails>()
while (c.moveToNext()) {
computerList.add(getComputerFromCursor(c))
getComputerFromCursor(c)?.let(computerList::add)
}
return computerList
}
Expand All @@ -149,26 +208,25 @@ class ComputerDatabaseManager(c: Context) {
computerDb.query(
COMPUTER_TABLE_NAME,
null,
"$COMPUTER_NAME_COLUMN_NAME=?",
arrayOf(name),
"$COMPUTER_NAME_COLUMN_NAME=? AND $BOUNDED_ADDRESSES_SELECTION",
arrayOf(name, MAX_ADDRESSES_JSON_LENGTH.toString()),
null,
null,
null
).use { c ->
if (!c.moveToFirst()) {
return null
while (c.moveToNext()) {
getComputerFromCursor(c)?.let { return it }
}

return getComputerFromCursor(c)
return null
}
}

fun getComputerByUUID(uuid: String): ComputerDetails? {
computerDb.query(
COMPUTER_TABLE_NAME,
null,
"$COMPUTER_UUID_COLUMN_NAME=?",
arrayOf(uuid),
"$COMPUTER_UUID_COLUMN_NAME=? AND $BOUNDED_ADDRESSES_SELECTION",
arrayOf(uuid, MAX_ADDRESSES_JSON_LENGTH.toString()),
null,
null,
null
Expand All @@ -186,6 +244,7 @@ class ComputerDatabaseManager(c: Context) {
const val REMOTE = "remote"
const val MANUAL = "manual"
const val IPV6 = "ipv6"
const val KNOWN = "known"
const val ADDRESS = "address"
const val PORT = "port"
}
Expand All @@ -198,6 +257,8 @@ class ComputerDatabaseManager(c: Context) {
private const val ADDRESSES_COLUMN_NAME = "Addresses"
private const val MAC_ADDRESS_COLUMN_NAME = "MacAddress"
private const val SERVER_CERT_COLUMN_NAME = "ServerCert"
private const val MAX_ADDRESSES_JSON_LENGTH = 64 * 1024
private const val BOUNDED_ADDRESSES_SELECTION = "length(Addresses) <= ?"

@JvmStatic
@Throws(JSONException::class)
Expand All @@ -213,6 +274,36 @@ class ComputerDatabaseManager(c: Context) {
return json
}

private fun tuplesToJson(tuples: List<ComputerDetails.AddressTuple>): JSONArray {
val json = JSONArray()
for (tuple in tuples) {
json.put(tupleToJson(tuple))
}
return json
}

private fun tuplesFromJson(json: JSONObject, name: String): List<ComputerDetails.AddressTuple> {
val tupleArray = json.optJSONArray(name) ?: return emptyList()
val firstIndex = maxOf(0, tupleArray.length() - ComputerDetails.MAX_KNOWN_ADDRESSES)
val tuples = ArrayList<ComputerDetails.AddressTuple>(tupleArray.length() - firstIndex)
for (index in firstIndex until tupleArray.length()) {
val tuple = tupleArray.optJSONObject(index) ?: continue
try {
tuples.add(
ComputerDetails.AddressTuple(
tuple.getString(AddressFields.ADDRESS),
tuple.getInt(AddressFields.PORT)
)
)
} catch (_: JSONException) {
// Ignore a malformed remembered route without losing the computer record.
} catch (_: IllegalArgumentException) {
// Ignore invalid addresses and ports from an older or damaged record.
}
}
return tuples
}

@JvmStatic
@Throws(JSONException::class)
fun tupleFromJson(json: JSONObject, name: String): ComputerDetails.AddressTuple? {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -637,7 +637,7 @@ class ComputerManagerService : Service() {
}

private fun isExpectedComputerUuid(expectedUuid: String?, returnedUuid: String?): Boolean {
return returnedUuid != null && (expectedUuid.isNullOrEmpty() || expectedUuid == returnedUuid)
return !returnedUuid.isNullOrBlank() && (expectedUuid.isNullOrEmpty() || expectedUuid == returnedUuid)
}

private class ParallelPollTuple(
Expand Down Expand Up @@ -731,7 +731,7 @@ class ComputerManagerService : Service() {
LimeLog.info("Parallel poll for " + details.name + " returned address: " + details.activeAddress)

return if (polledDetails != null) {
details.update(polledDetails)
details.updateFromVerifiedPoll(polledDetails)
true
} else {
false
Expand Down Expand Up @@ -1003,6 +1003,12 @@ class ComputerManagerService : Service() {
addUnique(details.manualAddress)
addUnique(details.remoteAddress)
addUnique(details.ipv6Address)
for (knownAddress in details.knownAddresses.asReversed()) {
addUnique(knownAddress)
if (knownAddress.port != NvHTTP.DEFAULT_HTTP_PORT) {
addUnique(ComputerDetails.AddressTuple(knownAddress.address, NvHTTP.DEFAULT_HTTP_PORT))
}
}

return addresses
}
Expand Down
Loading
Loading