package invalid.lena.scrcpy; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import org.junit.Test; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; public class SyncTest { private static final String PATH = "/data/local/tmp/scrcpy-server.jar"; private static final int MODE = 0100644; private static final int MTIME = 0x12345678; @Test public void emptyPayloadFraming() throws Exception { byte[] payload = new byte[0]; ByteArrayOutputStream framed = new ByteArrayOutputStream(); ByteArrayInputStream resp = okayResponse(); long total = Sync.push(new ByteArrayInputStream(payload), framed, resp, PATH, MODE, MTIME); assertEquals(0L, total); byte[] bytes = framed.toByteArray(); int pos = 0; // SEND + le32(headerLen) + header String header = PATH + "," + MODE; byte[] hb = header.getBytes(StandardCharsets.UTF_8); pos = assertTag(bytes, pos, "SEND"); assertEquals(hb.length, Wire.readLe32(bytes, pos)); pos += 4; for (int i = 0; i < hb.length; i++, pos++) assertEquals(hb[i], bytes[pos]); // No DATA chunks for empty payload. // DONE + le32(mtime) pos = assertTag(bytes, pos, "DONE"); assertEquals(MTIME, Wire.readLe32(bytes, pos)); pos += 4; assertEquals(bytes.length, pos); } @Test public void singleByteIsOneDataChunk() throws Exception { ByteArrayOutputStream framed = new ByteArrayOutputStream(); long total = Sync.push(new ByteArrayInputStream(new byte[]{0x42}), framed, okayResponse(), PATH, MODE, MTIME); assertEquals(1L, total); byte[] bytes = framed.toByteArray(); int pos = skipSendHeader(bytes); pos = assertTag(bytes, pos, "DATA"); assertEquals(1, Wire.readLe32(bytes, pos)); pos += 4; assertEquals(0x42, bytes[pos]); pos += 1; pos = assertTag(bytes, pos, "DONE"); assertEquals(MTIME, Wire.readLe32(bytes, pos)); pos += 4; assertEquals(bytes.length, pos); } @Test public void payloadLargerThanChunkSplits() throws Exception { int n = Sync.CHUNK + 1; byte[] payload = new byte[n]; for (int i = 0; i < n; i++) payload[i] = (byte) i; ByteArrayOutputStream framed = new ByteArrayOutputStream(); long total = Sync.push(new ByteArrayInputStream(payload), framed, okayResponse(), PATH, MODE, MTIME); assertEquals(n, total); byte[] bytes = framed.toByteArray(); int pos = skipSendHeader(bytes); // First DATA = full chunk pos = assertTag(bytes, pos, "DATA"); assertEquals(Sync.CHUNK, Wire.readLe32(bytes, pos)); pos += 4; pos += Sync.CHUNK; // Second DATA = the remaining one byte pos = assertTag(bytes, pos, "DATA"); assertEquals(1, Wire.readLe32(bytes, pos)); pos += 4; pos += 1; pos = assertTag(bytes, pos, "DONE"); assertEquals(MTIME, Wire.readLe32(bytes, pos)); pos += 4; assertEquals(bytes.length, pos); } @Test public void failResponseRaises() { ByteArrayOutputStream framed = new ByteArrayOutputStream(); ByteArrayInputStream resp = failResponse("disk full"); try { Sync.push(new ByteArrayInputStream(new byte[]{1, 2, 3}), framed, resp, PATH, MODE, MTIME); fail("expected IOException"); } catch (IOException e) { assertTrue(e.getMessage(), e.getMessage().contains("disk full")); assertTrue(e.getMessage(), e.getMessage().startsWith("sync FAIL")); } } private static ByteArrayInputStream okayResponse() { byte[] r = new byte[8]; r[0] = 'O'; r[1] = 'K'; r[2] = 'A'; r[3] = 'Y'; // length 0 return new ByteArrayInputStream(r); } private static ByteArrayInputStream failResponse(String msg) { byte[] mb = msg.getBytes(StandardCharsets.UTF_8); byte[] r = new byte[8 + mb.length]; r[0] = 'F'; r[1] = 'A'; r[2] = 'I'; r[3] = 'L'; Wire.writeLe32(r, 4, mb.length); System.arraycopy(mb, 0, r, 8, mb.length); return new ByteArrayInputStream(r); } private static int assertTag(byte[] bytes, int pos, String tag) { for (int i = 0; i < 4; i++) { assertEquals("tag pos=" + pos + " idx=" + i, (byte) tag.charAt(i), bytes[pos + i]); } return pos + 4; } private static int skipSendHeader(byte[] bytes) { // SEND + le32(len) + header int pos = 4; int len = Wire.readLe32(bytes, pos); return pos + 4 + len; } }