From 9c686723109a70703b5fc9d57a368e8be8956837 Mon Sep 17 00:00:00 2001 From: Davide Depau Date: Mon, 24 Dec 2018 02:59:40 +0100 Subject: [PATCH] Add block device Input/Output Stream wrappers --- .../blockdevice/BlockDeviceInputStream.kt | 116 ++++++++++++++++++ .../blockdevice/BlockDeviceOutputStream.kt | 85 +++++++++++++ 2 files changed, 201 insertions(+) create mode 100644 app/src/main/java/eu/depau/etchdroid/utils/blockdevice/BlockDeviceInputStream.kt create mode 100644 app/src/main/java/eu/depau/etchdroid/utils/blockdevice/BlockDeviceOutputStream.kt diff --git a/app/src/main/java/eu/depau/etchdroid/utils/blockdevice/BlockDeviceInputStream.kt b/app/src/main/java/eu/depau/etchdroid/utils/blockdevice/BlockDeviceInputStream.kt new file mode 100644 index 0000000..a980cd3 --- /dev/null +++ b/app/src/main/java/eu/depau/etchdroid/utils/blockdevice/BlockDeviceInputStream.kt @@ -0,0 +1,116 @@ +package eu.depau.etchdroid.utils.blockdevice + +import com.github.mjdev.libaums.driver.BlockDeviceDriver +import java.io.InputStream +import java.nio.ByteBuffer + +class BlockDeviceInputStream( + private val blockDev: BlockDeviceDriver, + private val prefetchBlocks: Int = 2048 +) : InputStream() { + + private val byteBuffer = ByteBuffer.allocate(blockDev.blockSize * prefetchBlocks) + + private var currentBlockOffset: Long = 0 + private val currentByteOffset: Long + get() = currentBlockOffset * blockDev.blockSize + byteBuffer.position() + + private var markedBlock: Long = 0 + private var markedBufferPosition: Int = 0 + + private val sizeBytes: Long + get() = blockDev.size.toLong() * blockDev.blockSize + + private fun isNextByteAfterEOF(): Boolean { + if (byteBuffer.hasRemaining()) + return false + return currentBlockOffset + 1 >= blockDev.size + } + + private fun fetch() { + byteBuffer.clear() + + if (blockDev.size - currentBlockOffset < prefetchBlocks) + byteBuffer.limit( + (currentBlockOffset - blockDev.size).toInt() * blockDev.blockSize + ) + + blockDev.read(currentBlockOffset, byteBuffer) + } + + private fun fetchNextIfNeeded() { + if (byteBuffer.hasRemaining()) + return + currentBlockOffset++ + fetch() + } + + override fun read(): Int { + if (isNextByteAfterEOF()) + return -1 + fetchNextIfNeeded() + return byteBuffer.get().toInt() and 0xFF + } + + override fun read(b: ByteArray): Int { + return read(b, 0, b.size) + } + + override fun read(b: ByteArray, off: Int, len: Int): Int { + if (isNextByteAfterEOF()) + return -1 + + val maxPos = (off + len) % (b.size + 1) + + if (len <= 0 || off > b.size) + return 0 + + var bytesRead = 0 + + for (i in off until maxPos) { + val readByte = read() + + if (readByte == -1) + break + + b[i] = readByte.toByte() + } + + return bytesRead + } + + override fun skip(n: Long): Long { + val actualSkipDistance = when { + currentByteOffset + n > sizeBytes -> sizeBytes - currentByteOffset + currentByteOffset + n < 0 -> -currentByteOffset + else -> n + } + + val newByteOffset = currentByteOffset + actualSkipDistance + currentBlockOffset = newByteOffset / blockDev.blockSize + + fetch() + byteBuffer.position((newByteOffset - currentBlockOffset * blockDev.blockSize).toInt()) + + return actualSkipDistance + } + + override fun available(): Int { + return byteBuffer.remaining() + } + + override fun mark(readlimit: Int) { + markedBlock = currentBlockOffset + markedBufferPosition = byteBuffer.position() + } + + override fun markSupported(): Boolean { + return true + } + + override fun reset() { + currentBlockOffset = markedBlock + fetch() + byteBuffer.position(markedBufferPosition) + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/depau/etchdroid/utils/blockdevice/BlockDeviceOutputStream.kt b/app/src/main/java/eu/depau/etchdroid/utils/blockdevice/BlockDeviceOutputStream.kt new file mode 100644 index 0000000..f9f3a69 --- /dev/null +++ b/app/src/main/java/eu/depau/etchdroid/utils/blockdevice/BlockDeviceOutputStream.kt @@ -0,0 +1,85 @@ +package eu.depau.etchdroid.utils.blockdevice + +import com.github.mjdev.libaums.driver.BlockDeviceDriver +import java.io.IOException +import java.io.OutputStream +import java.nio.ByteBuffer + +class BlockDeviceOutputStream( + private val blockDev: BlockDeviceDriver, + bufferBlocks: Int = 2048 +) : OutputStream() { + + private val byteBuffer = ByteBuffer.allocate(blockDev.blockSize * bufferBlocks) + + private var currentBlockOffset: Long = 0 + private val currentByteOffset: Long + get() = currentBlockOffset * blockDev.blockSize + byteBuffer.position() + + private val sizeBytes: Long + get() = blockDev.size.toLong() * blockDev.blockSize + + private val bytesUntilEOF: Long + get() = blockDev.size.toLong() * blockDev.size - currentByteOffset + + override fun write(b: Int) { + if (bytesUntilEOF < 1) + throw IOException("No space left on device") + + byteBuffer.put(b.toByte()) + + if (byteBuffer.remaining() == 0) + flush() + } + + override fun write(b: ByteArray) { + write(b, 0, b.size) + } + + override fun write(b: ByteArray, off: Int, len: Int) { + val maxPos = (off + len) % (b.size + 1) + + if (len <= 0 || off > b.size) + return + + for (i in off until maxPos) + write(b[i].toInt()) + } + + override fun flush() { + byteBuffer.flip() + + val toWrite = byteBuffer.limit() + val incompleteBlockFullBytes = toWrite % blockDev.blockSize + val fullBlocks = (toWrite - incompleteBlockFullBytes) / blockDev.blockSize + + // Check if we're trying to flush while the last written block isn't full + if (incompleteBlockFullBytes > 0) { + val incompleteBlockBuffer = ByteBuffer.allocate(blockDev.blockSize) + + // Load last block from device + blockDev.read(currentBlockOffset + fullBlocks, incompleteBlockBuffer) + + // Add it to the incomplete block + byteBuffer.limit(fullBlocks * blockDev.blockSize) + byteBuffer.put( + incompleteBlockBuffer.array(), + toWrite, blockDev.blockSize - incompleteBlockFullBytes + ) + byteBuffer.position(0) + } + + // Flush to device + blockDev.write(currentBlockOffset, byteBuffer) + + // Copy the incomplete block at the beginning, then push back the position + byteBuffer.apply { + position(fullBlocks * blockDev.blockSize) + limit(toWrite) + compact() + clear() + position(incompleteBlockFullBytes) + } + currentBlockOffset += fullBlocks; + } +} \ No newline at end of file