LegacyPasswordTreePayloadWriter.kt
package de.pflugradts.passbird.application.passwordtree
import de.pflugradts.kotlinextensions.Option
import de.pflugradts.passbird.application.util.copyBytes
import de.pflugradts.passbird.application.util.copyInt
import de.pflugradts.passbird.domain.model.egg.Egg
import de.pflugradts.passbird.domain.model.shell.EncryptedShell
import de.pflugradts.passbird.domain.model.shell.Shell
import de.pflugradts.passbird.domain.model.shell.Shell.Companion.shellOf
import java.util.Arrays
class LegacyPasswordTreePayloadWriter constructor() {
fun write(snapshot: PasswordTreeSnapshot): Shell {
val contentSize = calcRequiredContentSize(snapshot)
val bytes = ByteArray(signatureSize() + contentSize + checksumBytes())
var offset = copyBytes(legacySignature(), bytes, 0, signatureSize())
snapshot.memory.forEach { memoryListOption ->
memoryListOption.ifPresent { memoryList ->
memoryList.forEach { memoryEntry ->
memoryEntry.encryptedShellAsByteArray().let { offset += copyBytes(it, bytes, offset, it.size) }
}
}
}
snapshot.nests.forEach { nestShell ->
nestShell.nestAsByteArray().let { offset += copyBytes(it, bytes, offset, it.size) }
}
snapshot.eggs.forEach { egg ->
egg.eggAsByteArray().let { offset += copyBytes(it, bytes, offset, it.size) }
}
val checksumBytes = byteArrayOf(
if (contentSize > 0) checksum(Arrays.copyOfRange(bytes, signatureSize(), signatureSize() + contentSize)) else 0x0,
)
copyBytes(checksumBytes, bytes, offset, checksumBytes())
return shellOf(bytes)
}
private fun calcRequiredContentSize(snapshot: PasswordTreeSnapshot): Int {
val memorySize = 100 * Integer.BYTES + snapshot.memory.fold(0) { acc, inner ->
acc + inner.map { slots -> slots.sumOf { it.map(EncryptedShell::size).orElse(0) } }.orElse(0)
}
val eggDataSize = snapshot.eggs.sumOf { egg ->
intBytes() + egg.viewEggId().size + egg.viewPassword().size + egg.proteins.filter { it.isPresent }.sumOf {
it.get().viewType().size + it.get().viewStructure().size
}
}
val eggMetaSize = snapshot.eggs.size * 22 * intBytes()
val nestSize = snapshot.nests.size * intBytes() + snapshot.nests.filter { !it.isEmpty }.sumOf { it.size }
return memorySize + eggDataSize + eggMetaSize + nestSize
}
private fun Option<EncryptedShell>.encryptedShellAsByteArray() = if (isPresent) {
val shellBytesSize = get().size
val bytes = ByteArray(Integer.BYTES + shellBytesSize)
copyInt(shellBytesSize, bytes, 0)
copyBytes(get().toByteArray(), bytes, Integer.BYTES, shellBytesSize)
bytes
} else {
val bytes = ByteArray(Integer.BYTES)
copyInt(0, bytes, 0)
bytes
}
private fun Shell.nestAsByteArray(): ByteArray {
val nestBytesSize = size
val bytes = ByteArray(Integer.BYTES + nestBytesSize)
copyInt(nestBytesSize, bytes, 0)
if (!isEmpty) copyBytes(toByteArray(), bytes, Integer.BYTES, nestBytesSize)
return bytes
}
private fun Egg.eggAsByteArray(): ByteArray {
val eggIdSize = viewEggId().size
val passwordSize = viewPassword().size
val metaSize = 22 * Integer.BYTES
val bytes = ByteArray(
Integer.BYTES + eggIdSize + passwordSize + metaSize + proteins.filter { it.isPresent }.sumOf {
it.get().viewType().size + it.get().viewStructure().size
},
)
var offset = copyInt(associatedNest().index(), bytes, 0)
offset += copyInt(eggIdSize, bytes, offset)
offset += copyBytes(viewEggId().toByteArray(), bytes, offset, eggIdSize)
offset += copyInt(passwordSize, bytes, offset)
offset += copyBytes(viewPassword().toByteArray(), bytes, offset, passwordSize)
proteins.forEach { slottedProtein ->
slottedProtein.map { it.viewType().size }.orElse(0).let { typeSize ->
offset += copyInt(typeSize, bytes, offset)
if (typeSize > 0) offset += copyBytes(slottedProtein.get().viewType().toByteArray(), bytes, offset, typeSize)
}
slottedProtein.map { it.viewStructure().size }.orElse(0).let { structureSize ->
offset += copyInt(structureSize, bytes, offset)
if (structureSize > 0) offset += copyBytes(slottedProtein.get().viewStructure().toByteArray(), bytes, offset, structureSize)
}
}
return bytes
}
}