aboutsummaryrefslogtreecommitdiff
path: root/app/src/main/java/invalid/lena/scrcpy/Server.java
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main/java/invalid/lena/scrcpy/Server.java')
-rw-r--r--app/src/main/java/invalid/lena/scrcpy/Server.java96
1 files changed, 68 insertions, 28 deletions
diff --git a/app/src/main/java/invalid/lena/scrcpy/Server.java b/app/src/main/java/invalid/lena/scrcpy/Server.java
index 284dd6c..dbc6738 100644
--- a/app/src/main/java/invalid/lena/scrcpy/Server.java
+++ b/app/src/main/java/invalid/lena/scrcpy/Server.java
@@ -2,16 +2,15 @@ package invalid.lena.scrcpy;
import android.content.Context;
-import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
-import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ConnectException;
-import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
+import java.util.Locale;
+import java.util.concurrent.TimeUnit;
import io.github.muntashirakon.adb.AdbStream;
@@ -43,17 +42,12 @@ public final class Server {
public final AdbStream videoAds, audioAds, controlAds;
public final InputStream videoIn, audioIn, controlIn;
public final OutputStream controlOut;
- public final String deviceName;
- public final String scid;
- public final String version;
Streams(AdbStream va, AdbStream aa, AdbStream ca,
- InputStream vi, InputStream ai, InputStream ci, OutputStream co,
- String name, String scid, String version) {
+ InputStream vi, InputStream ai, InputStream ci, OutputStream co) {
this.videoAds = va; this.audioAds = aa; this.controlAds = ca;
this.videoIn = vi; this.audioIn = ai; this.controlIn = ci;
this.controlOut = co;
- this.deviceName = name; this.scid = scid; this.version = version;
}
}
@@ -101,9 +95,8 @@ public final class Server {
InputStream ci = ca.openInputStream();
OutputStream co = ca.openOutputStream();
- String name = readDeviceMeta(vi);
- Log.i("device name=%s", name);
- streams = new Streams(va, aa, ca, vi, ai, ci, co, name, scid, version);
+ Log.i("device name=%s", readDeviceMeta(vi));
+ streams = new Streams(va, aa, ca, vi, ai, ci, co);
committed = true;
return streams;
} finally {
@@ -140,8 +133,11 @@ public final class Server {
shellPump = null;
closeQuietly(s);
if (t != null) {
- try { t.join(CLOSE_GRACE_MS); }
- catch (InterruptedException ignored) {}
+ try {
+ t.join(CLOSE_GRACE_MS);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
if (t.isAlive()) t.interrupt();
}
}
@@ -158,11 +154,19 @@ public final class Server {
private String readVersion() throws IOException {
try (InputStream in = ctx.getAssets().open(ASSET_VERSION)) {
byte[] buf = new byte[64];
- int n = 0, r;
- while ((r = in.read(buf, n, buf.length - n)) > 0) n += r;
- String v = new String(buf, 0, n, StandardCharsets.UTF_8).trim();
- if (v.isEmpty()) {
- throw new IOException("scrcpy-server.version is empty");
+ int n = 0;
+ while (n < buf.length) {
+ int r = in.read(buf, n, buf.length - n);
+ if (r < 0) break;
+ if (r == 0) throw new IOException("scrcpy-server.version read made no progress");
+ n += r;
+ }
+ if (n == buf.length && in.read() >= 0) {
+ throw new IOException("scrcpy-server.version is too long");
+ }
+ String v = Wire.decodeUtf8(buf, 0, n).trim();
+ if (!v.matches("[0-9]+(\\.[0-9]+)*")) {
+ throw new IOException("scrcpy-server.version is invalid");
}
return v;
}
@@ -171,7 +175,7 @@ public final class Server {
private static String newScid() {
// 31-bit random, 8 lowercase hex chars - matches scrcpy upstream client.
int v = new SecureRandom().nextInt() & 0x7fffffff;
- return String.format("%08x", v);
+ return String.format(Locale.ROOT, "%08x", v);
}
private String buildCmdline(String version, String scid) {
@@ -196,7 +200,9 @@ public final class Server {
args.add("max_size=" + maxSize);
args.add("video_bit_rate=" + videoBitR);
if (maxFps > 0) args.add("max_fps=" + maxFps);
- args.add("clipboard_autosync=true");
+ // Off means off at the source: the server never sends the
+ // target's clipboard, rather than us receiving and discarding it.
+ args.add("clipboard_autosync=" + Settings.clipboardSync(ctx));
args.add("tunnel_forward=true");
args.add("cleanup=true");
args.add("power_on=true");
@@ -205,9 +211,9 @@ public final class Server {
private AdbStream openAbstract(String scid) throws Exception {
String name = "scrcpy_" + scid;
- long deadline = System.currentTimeMillis() + LISTENER_DEADLINE_MS;
+ long deadline = monotonicMs() + LISTENER_DEADLINE_MS;
ConnectException last = null;
- while (System.currentTimeMillis() < deadline) {
+ while (monotonicMs() < deadline) {
if (serverEof) {
throw new IOException("server exited before opening " + name, last);
}
@@ -230,18 +236,27 @@ public final class Server {
private static String readDeviceMeta(InputStream in) throws IOException {
byte[] probe = new byte[1];
Wire.readFully(in, probe);
+ if (probe[0] != 0) throw new IOException("invalid scrcpy probe byte");
byte[] name = new byte[64];
Wire.readFully(in, name);
int n = 0;
while (n < name.length && name[n] != 0) n++;
- return new String(name, 0, n, StandardCharsets.UTF_8);
+ for (int i = n; i < name.length; i++) {
+ if (name[i] != 0) throw new IOException("invalid device-name padding");
+ }
+ return safeLogText(Wire.decodeUtf8(name, 0, n));
}
private void pump(InputStream in) {
- try (BufferedReader r = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) {
- String line;
- while ((line = r.readLine()) != null) {
- Log.i("server: %s", line);
+ // Do not use BufferedReader.readLine(): a hostile target can emit an
+ // unterminated line of arbitrary size and make it allocate until OOM.
+ byte[] bytes = new byte[2048];
+ try (InputStream source = in) {
+ int n;
+ while ((n = source.read(bytes)) >= 0) {
+ if (n == 0) throw new IOException("server stdout made no progress");
+ String chunk = safeLogBytes(bytes, n).trim();
+ if (!chunk.isEmpty()) Log.i("server: %s", chunk);
}
} catch (IOException e) {
if (!Thread.currentThread().isInterrupted()) Log.w("server-stdout closed: %s", e);
@@ -266,4 +281,29 @@ public final class Server {
try { sync.close(); } catch (IOException ignored) {}
}
}
+
+ private static String safeLogBytes(byte[] data, int len) {
+ StringBuilder out = new StringBuilder(len);
+ for (int i = 0; i < len; i++) {
+ int b = data[i] & 0xff;
+ if (b == '\n' || b == '\r' || b == '\t') out.append(' ');
+ else if (b >= 0x20 && b <= 0x7e) out.append((char) b);
+ else out.append('.');
+ }
+ return out.toString();
+ }
+
+ private static String safeLogText(String text) {
+ StringBuilder out = new StringBuilder(text.length());
+ for (int i = 0; i < text.length(); i++) {
+ char c = text.charAt(i);
+ int type = Character.getType(c);
+ out.append(Character.isISOControl(c) || type == Character.FORMAT ? '?' : c);
+ }
+ return out.toString();
+ }
+
+ private static long monotonicMs() {
+ return TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
+ }
}