1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
|
package invalid.lena.scrcpy;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import org.junit.Test;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
public class ControlStreamTest {
private static final class RecordingSink implements ControlStream.InboundSink {
final List<String> clipboards = new ArrayList<>();
@Override public void onRemoteClipboard(String text) { clipboards.add(text); }
}
// ---- inbound (DeviceMessage) parsing ----
@Test
public void clipboardMessageDispatched() throws Exception {
String text = "héllo world";
byte[] payload = text.getBytes(StandardCharsets.UTF_8);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream out = new DataOutputStream(bos);
out.writeByte(0); // TYPE_CLIPBOARD
out.writeInt(payload.length);
out.write(payload);
RecordingSink sink = new RecordingSink();
ControlStream cs = new ControlStream(
new ByteArrayInputStream(bos.toByteArray()),
new ByteArrayOutputStream());
cs.setInboundSink(sink);
cs.runReader(); // synchronous; returns on EOF
assertEquals(1, sink.clipboards.size());
assertEquals(text, sink.clipboards.get(0));
}
@Test
public void ackClipboardConsumedSilently() throws Exception {
// type=1, then a long sequence number. No dispatch should occur.
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream out = new DataOutputStream(bos);
out.writeByte(1);
out.writeLong(0x1234567890abcdefL);
RecordingSink sink = new RecordingSink();
ControlStream cs = new ControlStream(
new ByteArrayInputStream(bos.toByteArray()),
new ByteArrayOutputStream());
cs.setInboundSink(sink);
cs.runReader();
assertEquals(0, sink.clipboards.size());
}
@Test
public void unknownTypeAborts() throws Exception {
// type=42 - runReader should log and return.
ByteArrayOutputStream bos = new ByteArrayOutputStream();
bos.write(42);
// a few trailing bytes so the reader doesn't EOF before processing
bos.write(new byte[]{0, 0, 0, 0});
RecordingSink sink = new RecordingSink();
ControlStream cs = new ControlStream(
new ByteArrayInputStream(bos.toByteArray()),
new ByteArrayOutputStream());
cs.setInboundSink(sink);
cs.runReader();
assertEquals(0, sink.clipboards.size());
}
// ---- outbound (send + writer) ----
@Test
public void writerDrainsInOrder() throws Exception {
// We submit three messages, then run the writer until the
// outbox is empty AND we've signalled stop.
ByteArrayOutputStream out = new ByteArrayOutputStream();
ControlStream cs = new ControlStream(
new ByteArrayInputStream(new byte[0]), out);
byte[] a = new byte[]{1, 2, 3};
byte[] b = new byte[]{4, 5};
byte[] c = new byte[]{6};
cs.send(a);
cs.send(b);
cs.send(c);
Thread writer = new Thread(cs::runWriter, "writer-under-test");
writer.start();
// give the writer a moment to drain
for (int i = 0; i < 50 && out.size() < 6; i++) Thread.sleep(10);
cs.stop();
writer.join(1_000);
byte[] got = out.toByteArray();
assertEquals(6, got.length);
assertEquals(1, got[0]); assertEquals(2, got[1]); assertEquals(3, got[2]);
assertEquals(4, got[3]); assertEquals(5, got[4]);
assertEquals(6, got[5]);
}
@Test
public void keyEventLandsAfterOutboxOverflow() throws Exception {
// Fill the bounded outbox with touch-MOVE messages, then enqueue
// one key event. The MOVE must be evicted to make room; the key
// event must be preserved and end up last in the drain.
ByteArrayOutputStream out = new ByteArrayOutputStream();
ControlStream cs = new ControlStream(
new ByteArrayInputStream(new byte[0]), out);
for (int i = 0; i < 256; i++) {
cs.send(ControlMessages.touch(/* MOVE */ 2, 0L, i, i, 1080, 2400, 0xffff, 0, 0));
}
byte[] key = ControlMessages.keycode(0, 29, 0, 0);
cs.send(key);
Thread w = new Thread(cs::runWriter, "drain");
w.start();
// Drain to quiescence (size stable for 100ms).
long deadline = System.currentTimeMillis() + 2_000;
int last = -1, stable = 0;
while (System.currentTimeMillis() < deadline) {
int now = out.size();
if (now == last) { stable++; if (stable > 5) break; }
else { stable = 0; last = now; }
Thread.sleep(20);
}
cs.stop();
w.join(1_000);
byte[] bytes = out.toByteArray();
assertNotEquals(0, bytes.length);
// The key event should be the final 14 bytes of the drained stream
// (queued last, FIFO order, only touch-MOVEs evicted on overflow).
byte[] tail = new byte[14];
System.arraycopy(bytes, bytes.length - 14, tail, 0, 14);
for (int i = 0; i < 14; i++) assertEquals(key[i], tail[i]);
}
}
|