package link.roadio.riff.shared.config import android.os.Bundle import android.os.ParcelFileDescriptor import android.util.Log import java.io.ByteArrayOutputStream import java.io.IOException import java.nio.ByteBuffer import java.util.concurrent.Executors /** Only small metadata crosses Binder; aggregate UTF-8 travels through a read-only descriptor. * Authority: https://wiki.271.pics/Riff/1-1-0/configuration#one-coherent-startup * Binder's shared transaction buffer cannot carry every otherwise valid configuration document. * https://developer.android.com/reference/android/os/TransactionTooLargeException */ object ConfigurationHandoff { private const val MAX_BYTES = 64 * 1024 * 1024 private val writer = Executors.newSingleThreadExecutor() fun validate(text: String?) { if (text != null && text.toByteArray(Charsets.UTF_8).size > MAX_BYTES) throw IOException("Configuration aggregate exceeds bound") } fun put( bundle: Bundle, text: String?, ) { bundle.putInt("overlayLength", -1) if (text == null) return val bytes = text.toByteArray(Charsets.UTF_8) if (bytes.size > MAX_BYTES) throw IOException("Configuration aggregate exceeds bound") val pipe = ParcelFileDescriptor.createPipe() try { writer.execute { runCatching { ParcelFileDescriptor.AutoCloseOutputStream(pipe[1]).use { it.write(bytes) } } .onFailure { Log.w("RiffConfig", "configuration_handoff_failed", it) } } } catch (error: Exception) { pipe.forEach { it.close() } throw error } bundle.putInt("overlayLength", bytes.size) bundle.putParcelable("overlayFd", pipe[0]) } fun read(bundle: Bundle): String? { @Suppress("DEPRECATION") val descriptor = bundle.getParcelable("overlayFd") val length = bundle.getInt("overlayLength", -2) if (length == -1 && descriptor == null) return null if (length !in 0..MAX_BYTES || descriptor == null) { descriptor?.close() throw IOException("Invalid configuration handoff") } return ParcelFileDescriptor.AutoCloseInputStream(descriptor).use { input -> val output = ByteArrayOutputStream() val block = ByteArray(8192) while (true) { val count = input.read(block) if (count < 0) break if (count == 0 || output.size() > length - count) throw IOException("Invalid configuration handoff length") output.write(block, 0, count) } if (output.size() != length) throw IOException("Truncated configuration handoff") Charsets.UTF_8 .newDecoder() .decode(ByteBuffer.wrap(output.toByteArray())) .toString() } } }