feat(adb): allow reverse.add to accept any string local address

ref #410
This commit is contained in:
Simon Chan 2022-05-02 12:12:27 +08:00
parent c3ccd6a1f8
commit b7c20dbd6f
No known key found for this signature in database
GPG key ID: A8B69F750B9BCEDD
3 changed files with 64 additions and 20 deletions

View file

@ -0,0 +1,44 @@
import { decodeUtf8, WritableStream } from "@yume-chan/adb";
import { makeAutoObservable, reaction, runInAction } from "mobx";
import { observer } from "mobx-react-lite";
import { NextPage } from "next";
import { GlobalState } from "../state";
const state = makeAutoObservable({
log: [] as string[],
});
reaction(() => GlobalState.device, async device => {
if (!device) {
return;
}
await device.reverse.remove('tcp:3000').catch(() => { });
await device.reverse.add('tcp:3000', 'tcp:1234', socket => {
runInAction(() => {
state.log.push(`received stream ${socket.localId}`);
});
socket.readable.pipeTo(new WritableStream({
write: chunk => {
runInAction(() => {
state.log.push(`data from ${socket.localId}: ${decodeUtf8(chunk)}`);
});
}
}));
// Return true to accept the connection.
return true;
});
}, { fireImmediately: true });
const ReverseTesterPage: NextPage = () => {
return (
<div>
{state.log.map(line => (
<div>{line}</div>
))}
</div>
);
};
export default observer(ReverseTesterPage);

View file

@ -16,21 +16,21 @@ export interface AdbForwardListener {
} }
const AdbReverseStringResponse = const AdbReverseStringResponse =
new Struct({ littleEndian: true }) new Struct()
.string('length', { length: 4 }) .string('length', { length: 4 })
.string('content', { lengthField: 'length', lengthFieldBase: 16 }); .string('content', { lengthField: 'length', lengthFieldRadix: 16 });
const AdbReverseErrorResponse = const AdbReverseErrorResponse =
new Struct({ littleEndian: true }) new Struct()
.fields(AdbReverseStringResponse) .fields(AdbReverseStringResponse)
.postDeserialize((value) => { .postDeserialize((value) => {
throw new Error(value.content); throw new Error(value.content);
}); });
export class AdbReverseCommand extends AutoDisposable { export class AdbReverseCommand extends AutoDisposable {
protected localPortToHandler = new Map<number, AdbIncomingSocketHandler>(); protected localAddressToHandler = new Map<string, AdbIncomingSocketHandler>();
protected deviceAddressToLocalPort = new Map<string, number>(); protected deviceAddressToLocalAddress = new Map<string, string>();
protected adb: Adb; protected adb: Adb;
@ -44,10 +44,10 @@ export class AdbReverseCommand extends AutoDisposable {
} }
protected handleIncomingSocket = async (socket: AdbSocket) => { protected handleIncomingSocket = async (socket: AdbSocket) => {
const address = socket.serviceString; let address = socket.serviceString;
// Address format: `tcp:12345\0` // ADB daemon appends `\0` to the service string
const port = Number.parseInt(address.substring(4)); address = address.replace(/\0/g, '');
return !!(await this.localPortToHandler.get(port)?.(socket)); return !!(await this.localAddressToHandler.get(address)?.(socket));
}; };
private async createBufferedStream(service: string) { private async createBufferedStream(service: string) {
@ -78,16 +78,16 @@ export class AdbReverseCommand extends AutoDisposable {
/** /**
* @param deviceAddress The address adbd on device is listening on. Can be `tcp:0` to let adbd choose an available TCP port by itself. * @param deviceAddress The address adbd on device is listening on. Can be `tcp:0` to let adbd choose an available TCP port by itself.
* @param localPort Native ADB will open a connection to localPort when reverse connection starts. In webadb, it's only used to uniquely identify a reverse registry, `handler` will be called on connection. * @param localAddress Native ADB client will open a connection to this address when reverse connection received. In WebADB, it's only used to uniquely identify a reverse tunnel registry, `handler` will be called to handle the connection.
* @param handler A callback to handle incoming connections * @param handler A callback to handle incoming connections
* @returns If `deviceAddress` is `tcp:0`, return `tcp:{ACTUAL_LISTENING_PORT}`; otherwise, return `deviceAddress`. * @returns If `deviceAddress` is `tcp:0`, return `tcp:{ACTUAL_LISTENING_PORT}`; otherwise, return `deviceAddress`.
*/ */
public async add( public async add(
deviceAddress: string, deviceAddress: string,
localPort: number, localAddress: string,
handler: AdbIncomingSocketHandler, handler: AdbIncomingSocketHandler,
): Promise<string> { ): Promise<string> {
const stream = await this.sendRequest(`reverse:forward:${deviceAddress};tcp:${localPort}`); const stream = await this.sendRequest(`reverse:forward:${deviceAddress};${localAddress}`);
// `tcp:0` tells the device to pick an available port. // `tcp:0` tells the device to pick an available port.
// Begin with Android 8, device will respond with the selected port for all `tcp:` requests. // Begin with Android 8, device will respond with the selected port for all `tcp:` requests.
@ -111,8 +111,8 @@ export class AdbReverseCommand extends AutoDisposable {
} }
} }
this.localPortToHandler.set(localPort, handler); this.localAddressToHandler.set(localAddress, handler);
this.deviceAddressToLocalPort.set(deviceAddress, localPort); this.deviceAddressToLocalAddress.set(deviceAddress, localAddress);
return deviceAddress; return deviceAddress;
// No need to close the stream, device will close it // No need to close the stream, device will close it
@ -121,9 +121,9 @@ export class AdbReverseCommand extends AutoDisposable {
public async remove(deviceAddress: string): Promise<void> { public async remove(deviceAddress: string): Promise<void> {
await this.sendRequest(`reverse:killforward:${deviceAddress}`); await this.sendRequest(`reverse:killforward:${deviceAddress}`);
if (this.deviceAddressToLocalPort.has(deviceAddress)) { if (this.deviceAddressToLocalAddress.has(deviceAddress)) {
this.localPortToHandler.delete(this.deviceAddressToLocalPort.get(deviceAddress)!); this.localAddressToHandler.delete(this.deviceAddressToLocalAddress.get(deviceAddress)!);
this.deviceAddressToLocalPort.delete(deviceAddress); this.deviceAddressToLocalAddress.delete(deviceAddress);
} }
// No need to close the stream, device will close it // No need to close the stream, device will close it
@ -132,8 +132,8 @@ export class AdbReverseCommand extends AutoDisposable {
public async removeAll(): Promise<void> { public async removeAll(): Promise<void> {
await this.sendRequest(`reverse:killforward-all`); await this.sendRequest(`reverse:killforward-all`);
this.deviceAddressToLocalPort.clear(); this.deviceAddressToLocalAddress.clear();
this.localPortToHandler.clear(); this.localAddressToHandler.clear();
// No need to close the stream, device will close it // No need to close the stream, device will close it
} }

View file

@ -105,7 +105,7 @@ export class ScrcpyClientReverseConnection extends ScrcpyClientConnection {
const writer = queue.writable.getWriter(); const writer = queue.writable.getWriter();
this.address = await this.device.reverse.add( this.address = await this.device.reverse.add(
'localabstract:scrcpy', 'localabstract:scrcpy',
27183, 'tcp:27183',
socket => { socket => {
writer.write(socket); writer.write(socket);
return true; return true;