Skip to content
This repository was archived by the owner on Jan 10, 2023. It is now read-only.

Commit 6cc4d25

Browse files
committed
More robust handling of duplicate CLOSE commands from device.
For some reason some devices will send a CLSE command multiple times for the sme connection. It's not clear whether this is a bug in the device or some poorly-documented "feature" of the adb protocol, or a bug elsewhere in python-adb (possible, but I can't find it if so). Nevertheless, the [protocol specs](https://android.googlesource.com/platform/system/core/+/master/adb/protocol.txt) state: > A CLOSE message containing a remote-id which does not map to an > open stream on the recipient's side is ignored. The stream may have > already been closed by the recipient while this message was > in-flight. Thus, when opening a new connection, we should ignore any CLSE commands that may still be on the wire for some reason. The current architecture does not support multiple interleaved connections, and thus does not keep track of the remote-ids of previous connections. We could do that, but another approach is to send a monotonically increasing value for the local-id of each connection, rather than repeatedly reusing 1. This allows us to distinguish stray CLSE messages for a previous connection from CLSE or OKAY messages in response to the current connection by checking the remote-id (our local-id) of the command from the device. I observed that an emulated device running Android 8.1 sometimes sent multiple (2 or even 3) duplicate CLSE commands. Hence the while loop until we get a command intended for the correct recipient.
1 parent d9b94b2 commit 6cc4d25

File tree

1 file changed

+31
-12
lines changed

1 file changed

+31
-12
lines changed

adb/adb_protocol.py

+31-12
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ def ReadUntil(self, *expected_cmds):
129129
self.usb, expected_cmds, self.timeout_ms)
130130
if local_id != 0 and self.local_id != local_id:
131131
raise InterleavedDataError("We don't support multiple streams...")
132-
if remote_id != 0 and self.remote_id != remote_id:
132+
if remote_id != 0 and self.remote_id != remote_id and cmd != b'CLSE':
133133
raise InvalidResponseError(
134134
'Incorrect remote id, expected %s got %s' % (
135135
self.remote_id, remote_id))
@@ -185,6 +185,7 @@ class AdbMessage(object):
185185
format = b'<6I'
186186

187187
connections = 0
188+
_local_id = 1
188189

189190
def __init__(self, command=None, arg0=None, arg1=None, data=b''):
190191
self.command = self.commands[command]
@@ -347,6 +348,18 @@ def Connect(cls, usb, banner=b'notadb', rsa_keys=None, auth_timeout_ms=100):
347348
return banner
348349
return banner
349350

351+
@classmethod
352+
def _NextLocalId(cls):
353+
next_id = cls._local_id
354+
cls._local_id += 1
355+
# ADB message arguments are 32-bit words
356+
# Not very likely to reach this point but wrap back around to 1
357+
# just in case:
358+
if cls._local_id >= 2**32:
359+
cls._local_id = 1
360+
361+
return next_id
362+
350363
@classmethod
351364
def Open(cls, usb, destination, timeout_ms=None):
352365
"""Opens a new connection to the device via an OPEN message.
@@ -365,23 +378,26 @@ def Open(cls, usb, destination, timeout_ms=None):
365378
Returns:
366379
The local connection id.
367380
"""
368-
local_id = 1
381+
local_id = cls._NextLocalId()
369382
msg = cls(
370383
command=b'OPEN', arg0=local_id, arg1=0,
371384
data=destination + b'\0')
372385
msg.Send(usb, timeout_ms)
373-
cmd, remote_id, their_local_id, _ = cls.Read(usb, [b'CLSE', b'OKAY'],
374-
timeout_ms=timeout_ms)
375-
if local_id != their_local_id:
376-
raise InvalidResponseError(
377-
'Expected the local_id to be {}, got {}'.format(local_id, their_local_id))
378-
if cmd == b'CLSE':
379-
# Some devices seem to be sending CLSE once more after a request, this *should* handle it
386+
while True:
380387
cmd, remote_id, their_local_id, _ = cls.Read(usb, [b'CLSE', b'OKAY'],
381388
timeout_ms=timeout_ms)
382-
# Device doesn't support this service.
383-
if cmd == b'CLSE':
384-
return None
389+
if local_id != their_local_id:
390+
if cmd != b'CLSE':
391+
raise InvalidResponseError(
392+
'Expected the local_id to be {}, got {}'.format(local_id, their_local_id))
393+
continue
394+
395+
break
396+
397+
# Device doesn't support this service.
398+
if cmd == b'CLSE':
399+
return None
400+
385401
if cmd != b'OKAY':
386402
raise InvalidCommandError('Expected a ready response, got {}'.format(cmd),
387403
cmd, (remote_id, their_local_id))
@@ -436,6 +452,9 @@ def StreamingCommand(cls, usb, service, command='', timeout_ms=None):
436452
connection = cls.Open(
437453
usb, destination=b'%s:%s' % (service, command),
438454
timeout_ms=timeout_ms)
455+
if connection is None:
456+
raise usb_exceptions.AdbCommandFailureException(
457+
'Command failed: Device immediately closed the stream.')
439458
for data in connection.ReadUntilClose():
440459
yield data.decode('utf8')
441460

0 commit comments

Comments
 (0)