You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

156 lines
4.0 KiB

"""Xbox One SmartGlass device discovery."""
import socket
import struct
import binascii
from datetime import timedelta
DISCOVERY_PORT = 5050
DISCOVERY_ADDRESS_BCAST = '<broadcast>'
DISCOVERY_ADDRESS_MCAST = '239.255.255.250'
DISCOVERY_REQUEST = 0xDD00
DISCOVERY_RESPONSE = 0xDD01
DISCOVERY_TIMEOUT = timedelta(seconds=2)
"""
SmartGlass Client type
XboxOne = 1
Xbox360 = 2
WindowsDesktop = 3
WindowsStore = 4
WindowsPhone = 5
iPhone = 6
iPad = 7
Android = 8
"""
DISCOVERY_CLIENT_TYPE = 4
class XboxSmartGlass:
"""Base class to discover Xbox SmartGlass devices."""
def __init__(self):
"""Initialize the Xbox SmartGlass discovery."""
self.entries = []
self._discovery_payload = self.discovery_packet()
@staticmethod
def discovery_packet():
"""Assemble discovery payload."""
version = 0
flags = 0
min_version = 0
max_version = 2
payload = struct.pack(
'>IHHH',
flags, DISCOVERY_CLIENT_TYPE, min_version, max_version
)
header = struct.pack(
'>HHH',
DISCOVERY_REQUEST, len(payload), version
)
return header + payload
@staticmethod
def parse_discovery_response(data):
"""Parse console's discovery response."""
pos = 0
# Header
# pkt_type, payload_len, version = struct.unpack_from(
# '>HHH',
# data, pos
# )
pos += 6
# Payload
flags, type_, name_len = struct.unpack_from(
'>IHH',
data, pos
)
pos += 8
name = data[pos:pos + name_len]
pos += name_len + 1 # including null terminator
uuid_len = struct.unpack_from(
'>H',
data, pos
)[0]
pos += 2
uuid = data[pos:pos + uuid_len]
pos += uuid_len + 1 # including null terminator
last_error, cert_len = struct.unpack_from(
'>IH',
data, pos
)
pos += 6
cert = data[pos:pos + cert_len]
return {
'device_type': type_,
'flags': flags,
'name': name.decode('utf-8'),
'uuid': uuid.decode('utf-8'),
'last_error': last_error,
'certificate': binascii.hexlify(cert).decode('utf-8')
}
def scan(self):
"""Scan the network."""
self.update()
def all(self):
"""Scan and return all found entries."""
self.scan()
return self.entries
@staticmethod
def verify_packet(data):
"""Parse packet if it has correct magic"""
if len(data) < 2:
return None
pkt_type = struct.unpack_from('>H', data)[0]
if pkt_type != DISCOVERY_RESPONSE:
return None
return XboxSmartGlass.parse_discovery_response(data)
def update(self):
"""Scan network for Xbox SmartGlass devices."""
entries = []
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.settimeout(DISCOVERY_TIMEOUT.seconds)
sock.sendto(self._discovery_payload,
(DISCOVERY_ADDRESS_BCAST, DISCOVERY_PORT))
sock.sendto(self._discovery_payload,
(DISCOVERY_ADDRESS_MCAST, DISCOVERY_PORT))
while True:
try:
data, (address, _) = sock.recvfrom(1024)
response = self.verify_packet(data)
if response:
entries.append((address, response))
except socket.timeout:
break
self.entries = entries
sock.close()
def main():
"""Test XboxOne discovery."""
from pprint import pprint
xbsmartglass = XboxSmartGlass()
pprint("Scanning for Xbox One SmartGlass consoles devices..")
xbsmartglass.update()
pprint(xbsmartglass.entries)
if __name__ == "__main__":
main()