From 852a8a00273c9128efeed0217a8e5d3fcd8bf780 Mon Sep 17 00:00:00 2001 From: Lena Date: Sat, 1 Aug 2026 00:00:00 +0000 Subject: app: harden mirroring lifecycle and state --- app/src/main/java/invalid/lena/scrcpy/Wire.java | 71 ++++++++++++++++++++----- 1 file changed, 59 insertions(+), 12 deletions(-) (limited to 'app/src/main/java/invalid/lena/scrcpy/Wire.java') diff --git a/app/src/main/java/invalid/lena/scrcpy/Wire.java b/app/src/main/java/invalid/lena/scrcpy/Wire.java index 7ed6c6a..3d78c9b 100644 --- a/app/src/main/java/invalid/lena/scrcpy/Wire.java +++ b/app/src/main/java/invalid/lena/scrcpy/Wire.java @@ -3,6 +3,10 @@ package invalid.lena.scrcpy; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.charset.CharacterCodingException; +import java.nio.charset.CodingErrorAction; +import java.nio.charset.StandardCharsets; // Byte-order helpers used everywhere we touch raw streams. // @@ -18,6 +22,7 @@ public final class Wire { // ---- big-endian (scrcpy) ---- public static int readBe32(byte[] b, int off) { + checkRange(b, off, 4); return ((b[off] & 0xff) << 24) | ((b[off + 1] & 0xff) << 16) | ((b[off + 2] & 0xff) << 8) @@ -25,10 +30,12 @@ public final class Wire { } public static long readBe64(byte[] b, int off) { + checkRange(b, off, 8); return ((long)(readBe32(b, off)) << 32) | (readBe32(b, off + 4) & 0xffffffffL); } public static void writeBe32(byte[] b, int off, int v) { + checkRange(b, off, 4); b[off] = (byte)(v >>> 24); b[off + 1] = (byte)(v >>> 16); b[off + 2] = (byte)(v >>> 8); @@ -36,6 +43,7 @@ public final class Wire { } public static void writeBe64(byte[] b, int off, long v) { + checkRange(b, off, 8); writeBe32(b, off, (int)(v >>> 32)); writeBe32(b, off + 4, (int) v); } @@ -43,6 +51,7 @@ public final class Wire { // ---- little-endian (adb sync) ---- public static int readLe32(byte[] b, int off) { + checkRange(b, off, 4); return (b[off] & 0xff) | ((b[off + 1] & 0xff) << 8) | ((b[off + 2] & 0xff) << 16) @@ -50,6 +59,7 @@ public final class Wire { } public static void writeLe32(byte[] b, int off, int v) { + checkRange(b, off, 4); b[off] = (byte) v; b[off + 1] = (byte)(v >>> 8); b[off + 2] = (byte)(v >>> 16); @@ -59,11 +69,17 @@ public final class Wire { // ---- I/O helpers ---- public static void readFully(InputStream in, byte[] buf, int off, int len) throws IOException { + if (in == null || buf == null) throw new NullPointerException(); + if (off < 0 || len < 0 || off > buf.length || len > buf.length - off) { + throw new IndexOutOfBoundsException(); + } int got = 0; while (got < len) { int n = in.read(buf, off + got, len - got); if (n < 0) throw new EOFException( "short read: wanted " + len + " got " + got); + if (n == 0) throw new IOException( + "input made no progress: wanted " + len + " got " + got); got += n; } } @@ -72,28 +88,59 @@ public final class Wire { readFully(in, buf, 0, buf.length); } - // scrcpy codec identifiers, mirrored from com.genymobile.scrcpy.{video,audio}.*Codec. - // Short names (raw, aac) are NUL-padded on the left, not space-padded on the right. + public static String decodeUtf8(byte[] data, int off, int len) + throws CharacterCodingException { + checkRange(data, off, len); + return StandardCharsets.UTF_8.newDecoder() + .onMalformedInput(CodingErrorAction.REPORT) + .onUnmappableCharacter(CodingErrorAction.REPORT) + .decode(ByteBuffer.wrap(data, off, len)).toString(); + } + + public static String decodeUtf8(byte[] data) throws CharacterCodingException { + return decodeUtf8(data, 0, data.length); + } + + // scrcpy codec identifiers, mirrored from + // vendor/scrcpy/server/src/main/java/com/genymobile/scrcpy/video/VideoCodec.java + // and .../audio/AudioCodec.java, which are the authoritative source. + // Every id is the 4-char ASCII name NUL-padded on the LEFT for names + // shorter than four characters (av1, raw, aac), never space-padded on + // the right. WireTest asserts that rule rather than copying the + // literals, so a mis-transcribed id fails the build. // Declared as int-literal constants so they can drive switch-case labels. public static final int CODEC_H264 = 0x68_32_36_34; // 'h264' public static final int CODEC_H265 = 0x68_32_36_35; // 'h265' - public static final int CODEC_AV1 = 0x61_76_30_31; // 'av01' + public static final int CODEC_AV1 = 0x00_61_76_31; // '\0av1' public static final int CODEC_OPUS = 0x6f_70_75_73; // 'opus' - public static final int CODEC_FLAC = 0x66_6c_61_63; // 'flac' - public static final int CODEC_AAC = 0x00_61_61_63; // '\0aac' public static final int CODEC_RAW = 0x00_72_61_77; // '\0raw' public static String fourccName(int v) { // Skip any leading NUL bytes - scrcpy left-pads short names like // 'raw' and 'aac' with \0, which would otherwise render as // non-printable characters in logs. - char[] cs = new char[]{ - (char)((v >>> 24) & 0xff), - (char)((v >>> 16) & 0xff), - (char)((v >>> 8) & 0xff), - (char)( v & 0xff)}; + int[] bytes = {(v >>> 24) & 0xff, (v >>> 16) & 0xff, + (v >>> 8) & 0xff, v & 0xff}; int from = 0; - while (from < cs.length && cs[from] == 0) from++; - return new String(cs, from, cs.length - from); + while (from < bytes.length && bytes[from] == 0) from++; + StringBuilder out = new StringBuilder(4); + char[] hex = "0123456789abcdef".toCharArray(); + for (int i = from; i < bytes.length; i++) { + int b = bytes[i]; + if (b >= 0x20 && b <= 0x7e) { + out.append((char) b); + } else { + out.append("\\x").append(hex[b >>> 4]).append(hex[b & 0xf]); + } + } + return out.toString(); + } + + private static void checkRange(byte[] data, int off, int len) { + if (data == null) throw new NullPointerException("data"); + if (off < 0 || len < 0 || off > data.length - len) { + throw new IndexOutOfBoundsException("off=" + off + " len=" + len + + " size=" + data.length); + } } } -- cgit v1.2.3