LegacyPasswordTreePayloadReader.kt
package de.pflugradts.passbird.application.passwordtree
import de.pflugradts.kotlinextensions.MutableOption
import de.pflugradts.kotlinextensions.MutableOption.Companion.mutableOptionOf
import de.pflugradts.passbird.application.configuration.ReadableConfiguration
import de.pflugradts.passbird.application.failure.ChecksumFailure
import de.pflugradts.passbird.application.failure.SignatureCheckFailure
import de.pflugradts.passbird.application.failure.reportFailure
import de.pflugradts.passbird.application.util.FAILURE_EXIT_STATUS
import de.pflugradts.passbird.application.util.SystemOperation
import de.pflugradts.passbird.application.util.scramble
import de.pflugradts.passbird.domain.model.egg.Egg
import de.pflugradts.passbird.domain.model.egg.Egg.Companion.createEgg
import de.pflugradts.passbird.domain.model.egg.EggIdMemory
import de.pflugradts.passbird.domain.model.egg.MemoryMap
import de.pflugradts.passbird.domain.model.egg.Protein
import de.pflugradts.passbird.domain.model.egg.Protein.Companion.createProtein
import de.pflugradts.passbird.domain.model.shell.EncryptedShell
import de.pflugradts.passbird.domain.model.shell.EncryptedShell.Companion.encryptedShellOf
import de.pflugradts.passbird.domain.model.shell.Shell
import de.pflugradts.passbird.domain.model.shell.Shell.Companion.shellOf
import de.pflugradts.passbird.domain.model.slot.Slot
import de.pflugradts.passbird.domain.model.slot.Slots
import de.pflugradts.passbird.domain.model.slot.Slots.Companion.slotIterator
import de.pflugradts.passbird.domain.service.password.tree.emptyMemory
import java.util.ArrayDeque
class LegacyPasswordTreePayloadReader constructor(
private val configuration: ReadableConfiguration,
private val systemOperation: SystemOperation,
) {
fun read(shell: Shell): PasswordTreeSnapshot {
val byteArray = shell.toByteArray()
try {
if (byteArray.isEmpty()) {
return PasswordTreeSnapshot()
}
validatePayloadEnvelope(byteArray)
if (!verifySignature(byteArray) || !verifyChecksum(byteArray)) {
return PasswordTreeSnapshot()
}
var offset = signatureSize()
val payloadEnd = payloadContentEnd(byteArray)
val memory = if (isPlaceholder(readPayloadBytes(byteArray, offset, placeHolder().size, payloadEnd))) {
validatePayloadRange(byteArray, offset, Slot.CAPACITY * placeHolder().size, payloadEnd)
repeat(Slot.CAPACITY) {
offset += placeHolder().size
}
emptyMemory()
} else {
val (incrementedOffset, retrievedMemory) = retrieveMemory(byteArray, offset)
offset = incrementedOffset
if (eggIdMemoryEnabled) retrievedMemory else emptyMemory()
}
val (nests, incrementedOffset) = retrieveNests(byteArray, offset)
offset = incrementedOffset
val eggs = ArrayDeque<Egg>()
while (offset < payloadEnd) {
val (egg, newOffset) = byteArray.asEgg(offset)
eggs.add(egg)
offset = newOffset
}
validatePayloadEnd(offset, payloadEnd)
return PasswordTreeSnapshot(eggs = eggs.toList(), memory = memory, nests = nests)
} finally {
byteArray.scramble()
}
}
private fun verifySignature(bytes: ByteArray): Boolean {
val expectedSignature = legacySignature()
val actualSignature = readPayloadBytes(bytes, 0, signatureSize())
try {
if (!expectedSignature.contentEquals(actualSignature)) {
val critical = configuration.adapter.passwordTree.verifySignature
val actualSignatureShell = shellOf(actualSignature)
try {
reportFailure(SignatureCheckFailure(actualSignatureShell, critical))
} finally {
actualSignatureShell.scramble()
}
if (critical) {
systemOperation.exit(FAILURE_EXIT_STATUS)
return false
}
}
return true
} finally {
actualSignature.scramble()
}
}
private fun verifyChecksum(bytes: ByteArray): Boolean {
val contentSize = calcActualContentSize(bytes.size)
val expectedChecksum = if (contentSize > 0) {
val checksumSource = readPayloadBytes(bytes, signatureSize(), contentSize, payloadContentEnd(bytes))
try {
checksum(checksumSource)
} finally {
checksumSource.scramble()
}
} else {
0x0
}
val actualCheckSum = bytes[bytes.size - 1]
if (expectedChecksum != actualCheckSum) {
val critical = configuration.adapter.passwordTree.verifyChecksum
reportFailure(ChecksumFailure(actualCheckSum, expectedChecksum, critical))
if (critical) {
systemOperation.exit(FAILURE_EXIT_STATUS)
return false
}
}
return true
}
private fun retrieveNests(bytes: ByteArray, offset: Int): Pair<List<Shell>, Int> {
var incrementedOffset = offset
val nests = ArrayList<Shell>(Slot.CAPACITY)
repeat(Slot.CAPACITY) {
val (nestShell, consumedBytes) = bytes.asNestShell(incrementedOffset)
nests.add(nestShell)
incrementedOffset += consumedBytes
}
return Pair(nests, incrementedOffset)
}
private val eggIdMemoryEnabled get() = with(configuration.domain.eggIdMemory) { enabled && persisted }
private fun calcActualContentSize(totalSize: Int) = totalSize - signatureSize() - checksumBytes()
private fun ByteArray.asNestShell(offset: Int): Pair<Shell, Int> {
val payloadEnd = payloadContentEnd(this)
var incrementedOffset = offset
val nestSize = readPayloadSize(this, incrementedOffset, payloadEnd)
incrementedOffset += Integer.BYTES
val result = if (nestSize > 0) {
val nestBytes = readPayloadBytes(this, incrementedOffset, nestSize, payloadEnd)
incrementedOffset += nestBytes.size
nestBytes.toShellAndScramble()
} else {
Shell.emptyShell()
}
return Pair(result, incrementedOffset - offset)
}
private fun ByteArray.asEgg(offset: Int): Pair<Egg, Int> {
val payloadEnd = payloadContentEnd(this)
var incrementedOffset = offset
val nestSlot = storedEggNestSlot(readPayloadInt(this, incrementedOffset, payloadEnd))
incrementedOffset += Integer.BYTES
val eggIdSize = readPayloadSize(this, incrementedOffset, payloadEnd)
incrementedOffset += Integer.BYTES
val eggIdShell = readPayloadBytes(this, incrementedOffset, eggIdSize, payloadEnd).toEncryptedShellAndScramble()
incrementedOffset += eggIdSize
val passwordSize = readPayloadSize(this, incrementedOffset, payloadEnd)
incrementedOffset += Integer.BYTES
val passwordShell = readPayloadBytes(this, incrementedOffset, passwordSize, payloadEnd).toEncryptedShellAndScramble()
incrementedOffset += passwordSize
try {
val proteins = (0..9).map {
val typeSize = readPayloadSize(this, incrementedOffset, payloadEnd)
incrementedOffset += Integer.BYTES
val typeBytes = if (typeSize > 0) readPayloadBytes(this, incrementedOffset, typeSize, payloadEnd) else byteArrayOf()
incrementedOffset += typeSize
val structureSize = readPayloadSize(this, incrementedOffset, payloadEnd)
incrementedOffset += Integer.BYTES
val structureBytes = if (structureSize > 0) {
readPayloadBytes(this, incrementedOffset, structureSize, payloadEnd)
} else {
byteArrayOf()
}
incrementedOffset += structureSize
proteinOption(typeBytes, structureBytes)
}
return Pair(createEgg(nestSlot, eggIdShell, passwordShell, proteins), incrementedOffset)
} finally {
eggIdShell.scramble()
passwordShell.scramble()
}
}
private fun proteinOption(typeBytes: ByteArray, structureBytes: ByteArray): MutableOption<Protein> {
if (typeBytes.isEmpty() || structureBytes.isEmpty()) {
typeBytes.scramble()
structureBytes.scramble()
return mutableOptionOf()
}
val typeShell = typeBytes.toEncryptedShellAndScramble()
val structureShell = structureBytes.toEncryptedShellAndScramble()
try {
return mutableOptionOf(createProtein(typeShell, structureShell))
} finally {
typeShell.scramble()
structureShell.scramble()
}
}
private fun isPlaceholder(byteArray: ByteArray): Boolean {
val encryptedShell = byteArray.toEncryptedShellAndScramble()
try {
return encryptedShell == placeHolder()
} finally {
encryptedShell.scramble()
}
}
private fun ByteArray.asMemoryEntry(offset: Int): Pair<MutableOption<EncryptedShell>, Int> {
val payloadEnd = payloadContentEnd(this)
var incrementedOffset = offset
val shellSize = readPayloadSize(this, incrementedOffset, payloadEnd)
incrementedOffset += Integer.BYTES
val encryptedShellOption = if (shellSize > 0) {
val shellBytes = readPayloadBytes(this, incrementedOffset, shellSize, payloadEnd)
incrementedOffset += shellSize
mutableOptionOf(shellBytes.toEncryptedShellAndScramble())
} else {
mutableOptionOf()
}
return Pair(encryptedShellOption, incrementedOffset)
}
private fun retrieveMemory(byteArray: ByteArray, offset: Int): Pair<Int, MemoryMap> {
var incrementedOffset = offset
return Slots<EggIdMemory>().apply {
slotIterator().forEach { nestSlot ->
this[nestSlot].set(
EggIdMemory().apply {
slotIterator().forEach { slot ->
val (entry, newOffset) = byteArray.asMemoryEntry(incrementedOffset)
entry.ifPresent { this[slot].set(it) }
incrementedOffset = newOffset
}
},
)
}
}.let { Pair(incrementedOffset, it) }
}
private fun ByteArray.toShellAndScramble() = try {
shellOf(this)
} finally {
scramble()
}
private fun ByteArray.toEncryptedShellAndScramble() = try {
encryptedShellOf(this)
} finally {
scramble()
}
}