async def wrapper(): if method == 'm_search': fn = lambda *_a, **_kw: cls.m_search( lan_address, gateway_address, timeout, igd_args, interface_name) else: try: u = await cls.discover(lan_address, gateway_address, timeout, igd_args, interface_name) except UPnPError as err: fut.set_exception(err) return if hasattr(u, method) and hasattr(getattr(u, method), "_cli"): fn = getattr(u, method) else: fut.set_exception( UPnPError("\"%s\" is not a recognized command" % method)) return try: result = await fn( **{k: fn.__annotations__[k](v) for k, v in kwargs.items()}) fut.set_result(result) except UPnPError as err: fut.set_exception(err) except Exception as err: log.exception("uncaught error") fut.set_exception(UPnPError("uncaught error: %s" % str(err)))
async def listen_ssdp( lan_address: str, gateway_address: str, loop=None, ignored: typing.Set[str] = None, unicast: bool = False ) -> typing.Tuple[DatagramTransport, SSDPProtocol, str, str]: loop = loop or asyncio.get_event_loop_policy().get_event_loop() try: sock = SSDPProtocol.create_multicast_socket(lan_address) listen_result: typing.Tuple = await loop.create_datagram_endpoint( lambda: SSDPProtocol(SSDP_IP_ADDRESS, lan_address, ignored, unicast ), sock=sock) transport: DatagramTransport = listen_result[0] protocol: SSDPProtocol = listen_result[1] except Exception as err: print(err) raise UPnPError(err) try: protocol.join_group(protocol.multicast_address, protocol.bind_address) protocol.set_ttl(1) except Exception as err: protocol.disconnect() raise UPnPError(err) return transport, protocol, gateway_address, lan_address
def parse_device_dict(xml_dict: Dict[str, Any]) -> Dict[str, Any]: keys = list(xml_dict.keys()) found = False for k in keys: m: List[Tuple[str, str, str, str, str, str]] = XML_ROOT_SANITY_PATTERN.findall(k) if len(m) == 3 and m[1][0] and m[2][5]: schema_key: str = m[1][0] root: str = m[2][5] flattened = flatten_keys(xml_dict, "{%s}" % schema_key) if root not in flattened: raise UPnPError("root device not found") xml_dict = flattened[root] found = True break if not found: raise UPnPError("device not found") result = {} for k, v in xml_dict.items(): if isinstance(xml_dict[k], dict): inner_d = {} for inner_k, inner_v in xml_dict[k].items(): parsed_k = XML_OTHER_KEYS.findall(inner_k) if len(parsed_k) == 2: inner_d[parsed_k[0]] = inner_v else: assert len(parsed_k) == 3, f"expected len=3, got {len(parsed_k)}" inner_d[parsed_k[1]] = inner_v result[k] = inner_d else: result[k] = v return result
def __init__(self, packet_type: str, kwargs: Optional[Dict[str, Union[str, int]]] = None) -> None: if packet_type not in [self._M_SEARCH, self._NOTIFY, self._OK]: raise UPnPError("unknown packet type: {}".format(packet_type)) self._packet_type = packet_type kw: Dict[str, Union[str, int]] = kwargs or OrderedDict() self._field_order: List[str] = [ k.lower().replace("-", "_") for k in kw.keys() ] self.host: Optional[str] = None self.man: Optional[str] = None self.mx: Optional[Union[str, int]] = None self.st: Optional[str] = None self.nt: Optional[str] = None self.nts: Optional[str] = None self.usn: Optional[str] = None self.location: Optional[str] = None self.cache_control: Optional[str] = None self.server: Optional[str] = None self.date: Optional[str] = None self.ext: Optional[str] = None for k, v in kw.items(): normalized = k.lower().replace("-", "_") if not normalized.startswith("_") and hasattr(self, normalized): if getattr(self, normalized, None) is None: setattr(self, normalized, v) self._case_mappings: Dict[str, str] = {k.lower(): k for k in kw.keys()} for k in self._required_fields[self._packet_type]: if getattr(self, k, None) is None: raise UPnPError("missing required field %s" % k)
def __init__(self, packet_type, kwargs: OrderedDict = None) -> None: if packet_type not in [self._M_SEARCH, self._NOTIFY, self._OK]: raise UPnPError("unknown packet type: {}".format(packet_type)) self._packet_type = packet_type kwargs = kwargs or OrderedDict() self._field_order: list = [ k.lower().replace("-", "_") for k in kwargs.keys() ] self.host = None self.man = None self.mx = None self.st = None self.nt = None self.nts = None self.usn = None self.location = None self.cache_control = None self.server = None self.date = None self.ext = None for k, v in kwargs.items(): normalized = k.lower().replace("-", "_") if not normalized.startswith("_") and hasattr( self, normalized) and getattr(self, normalized) is None: setattr(self, normalized, v) self._case_mappings: dict = {k.lower(): k for k in kwargs.keys()} for k in self._required_fields[self._packet_type]: if getattr(self, k) is None: raise UPnPError("missing required field %s" % k)
async def wrapper(): # wrap the upnp setup and call of the command in a coroutine if method == 'm_search': # if we're only m_searching don't do any device discovery fn = lambda *_a, **_kw: UPnP.m_search( lan_address, gateway_address, timeout, interface_name, igd_args, loop ) else: # automatically discover the gateway try: u = await UPnP.discover( lan_address, gateway_address, timeout, igd_args, interface_name, loop=loop ) except UPnPError as err: # pragma: no cover fut.set_exception(err) return if method not in cli_commands: fut.set_exception(UPnPError("\"%s\" is not a recognized command" % method)) # pragma: no cover return # pragma: no cover else: fn = getattr(u, method) try: # call the command result = await fn(**{k: fn.__annotations__[k](v) for k, v in kwargs.items()}) fut.set_result(result) except UPnPError as err: fut.set_exception(err) except Exception as err: # pragma: no cover log.exception("uncaught error") fut.set_exception(UPnPError("uncaught error: %s" % str(err)))
async def scpd_get( control_url: str, address: str, port: int, loop=None ) -> typing.Tuple[typing.Dict, bytes, typing.Optional[Exception]]: loop = loop or asyncio.get_event_loop_policy().get_event_loop() finished: asyncio.Future = asyncio.Future() packet = serialize_scpd_get(control_url, address) transport, protocol = await loop.create_connection( lambda: SCPDHTTPClientProtocol(packet, finished), address, port) assert isinstance(protocol, SCPDHTTPClientProtocol) error = None try: body, response_code, response_msg = await asyncio.wait_for( finished, 1.0) except asyncio.TimeoutError: error = UPnPError("get request timed out") body = b'' except UPnPError as err: error = err body = protocol.response_buff finally: transport.close() if not error: try: return deserialize_scpd_get_response(body), body, None except ElementTree.ParseError as err: error = UPnPError(err) return {}, body, error
def data_received(self, data: bytes) -> None: if self.finished.done(): # possible to hit during tests return self.response_buff += data for i, line in enumerate(self.response_buff.split(b'\r\n')): if not line: # we hit the blank line between the headers and the body if i == (len(self.response_buff.split(b'\r\n')) - 1): return None # the body is still yet to be written if not self._got_headers: try: self._headers, self._response_code, self._response_msg = parse_headers( b'\r\n'.join( self.response_buff.split(b'\r\n')[:i])) except ValueError as err: self.finished.set_exception(UPnPError(str(err))) return content_length = get_dict_val_case_insensitive( self._headers, b'Content-Length') if content_length is not None: self._content_length = int(content_length) else: self._has_content_length = False self._got_headers = True if self._got_headers and self._has_content_length: body = b'\r\n'.join( self.response_buff.split(b'\r\n')[i + 1:]) if self._content_length == len(body): self.finished.set_result( (self.response_buff, body, self._response_code, self._response_msg)) elif self._content_length > len(body): pass else: self.finished.set_exception( UPnPError( "too many bytes written to response (%i vs %i expected)" % (len(body), self._content_length))) elif any( map(self.response_buff.endswith, (b"</root>\r\n", b"</scpd>\r\n"))): # Actiontec has a router that doesn't give a Content-Length for the gateway xml body = b'\r\n'.join( self.response_buff.split(b'\r\n')[i + 1:]) self.finished.set_result( (self.response_buff, body, self._response_code, self._response_msg)) elif len(self.response_buff) >= 65535: self.finished.set_exception( UPnPError( "too many bytes written to response (%i) with unspecified content length" % len(self.response_buff))) return else: # needed for the actiontec case pass return None return None
def decode(cls, datagram: bytes) -> 'SSDPDatagram': try: packet = cls._from_string(datagram.decode()) except UnicodeDecodeError: raise UPnPError( f"failed to decode datagram: {binascii.hexlify(datagram).decode()}" ) if packet is None: raise UPnPError( f"failed to decode datagram: {binascii.hexlify(datagram).decode()}" ) return packet
def decode(cls, datagram: bytes) -> 'SSDPDatagram': packet = cls._from_string(datagram.decode()) if packet is None: raise UPnPError( "failed to decode datagram: {}".format(binascii.hexlify(datagram)) ) for attr_name in packet._required_fields[packet._packet_type]: if getattr(packet, attr_name, None) is None: raise UPnPError( "required field for {} is missing from m-search response: {}".format(packet._packet_type, attr_name) ) return packet
async def scpd_get( control_url: str, address: str, port: int, loop: typing.Optional[asyncio.AbstractEventLoop] = None ) -> typing.Tuple[typing.Dict[str, typing.Any], bytes, typing.Optional[Exception]]: loop = loop or asyncio.get_event_loop() packet = serialize_scpd_get(control_url, address) finished: 'asyncio.Future[typing.Tuple[bytes, bytes, int, bytes]]' = loop.create_future( ) proto_factory: typing.Callable[ [], SCPDHTTPClientProtocol] = lambda: SCPDHTTPClientProtocol( packet, finished) try: connect_tup: typing.Tuple[ asyncio.BaseTransport, asyncio.BaseProtocol] = await loop.create_connection( proto_factory, address, port) except ConnectionError as err: return {}, b'', UPnPError(f"{err.__class__.__name__}({str(err)})") protocol = connect_tup[1] transport = connect_tup[0] assert isinstance(protocol, SCPDHTTPClientProtocol) error = None wait_task: typing.Awaitable[typing.Tuple[bytes, bytes, int, bytes]] = asyncio.wait_for( protocol.finished, 1.0, loop=loop) body = b'' raw_response = b'' try: raw_response, body, response_code, response_msg = await wait_task except asyncio.TimeoutError: error = UPnPError("get request timed out") except UPnPError as err: error = err raw_response = protocol.response_buff finally: transport.close() if not error: try: return deserialize_scpd_get_response(body), raw_response, None except Exception as err: error = UPnPError(err) return {}, raw_response, error
async def m_search(cls, lan_address: str = '', gateway_address: str = '', timeout: int = 1, igd_args: OrderedDict = None, interface_name: str = 'default', ssdp_socket: socket.socket = None) -> Dict: try: lan_address, gateway_address = cls.get_lan_and_gateway( lan_address, gateway_address, interface_name) assert gateway_address and lan_address except Exception as err: raise UPnPError( "failed to get lan and gateway addresses for interface \"%s\": %s" % (interface_name, str(err))) if not igd_args: igd_args, datagram = await fuzzy_m_search(lan_address, gateway_address, timeout, ssdp_socket) else: igd_args = OrderedDict(igd_args) datagram = await m_search(lan_address, gateway_address, igd_args, timeout, ssdp_socket) return { 'lan_address': lan_address, 'gateway_address': gateway_address, 'm_search_kwargs': SSDPDatagram("M-SEARCH", igd_args).get_cli_igd_kwargs(), 'discover_reply': datagram.as_dict() }
def get_gateway_and_lan_addresses( interface_name: str) -> typing.Tuple[str, str]: for iface_name, (gateway, lan) in get_interfaces().items(): if interface_name == iface_name: return gateway, lan raise UPnPError( f'failed to get lan and gateway addresses for {interface_name}')
async def get_next_mapping(self, port: int, protocol: str, description: str, internal_port: int = None) -> int: if protocol not in ["UDP", "TCP"]: raise UPnPError("unsupported protocol: {}".format(protocol)) internal_port = int(internal_port or port) requested_port = int(internal_port) redirect_tups = [] cnt = 0 port = int(port) redirect = await self._get_port_mapping_by_index(cnt) while redirect: redirect_tups.append(redirect) cnt += 1 redirect = await self._get_port_mapping_by_index(cnt) redirects = {(ext_port, proto): (int_host, int_port, desc) for (ext_host, ext_port, proto, int_port, int_host, enabled, desc, _) in redirect_tups} while (port, protocol) in redirects: int_host, int_port, desc = redirects[(port, protocol)] if int_host == self.lan_address and int_port == requested_port and desc == description: return port port += 1 await self.add_port_mapping( # set one up port, protocol, internal_port, self.lan_address, description) return port
def data_received(self, data: bytes) -> None: self.response_buff += data for i, line in enumerate(self.response_buff.split(b'\r\n')): if not line: # we hit the blank line between the headers and the body if i == (len(self.response_buff.split(b'\r\n')) - 1): return None # the body is still yet to be written if not self._got_headers: self._headers, self._response_code, self._response_msg = parse_headers( b'\r\n'.join(self.response_buff.split(b'\r\n')[:i])) content_length = get_dict_val_case_insensitive( self._headers, b'Content-Length') if content_length is None: return None self._content_length = int(content_length or 0) self._got_headers = True body = b'\r\n'.join(self.response_buff.split(b'\r\n')[i + 1:]) if self._content_length == len(body): self.finished.set_result( (self.response_buff, body, self._response_code, self._response_msg)) elif self._content_length > len(body): pass else: self.finished.set_exception( UPnPError( "too many bytes written to response (%i vs %i expected)" % (len(body), self._content_length))) return None return None
async def _fuzzy_m_search(lan_address: str, gateway_address: str, timeout: int = 30, loop=None, ignored: typing.Set[str] = None, unicast: bool = False) -> typing.List[OrderedDict]: transport, protocol, gateway_address, lan_address = await listen_ssdp( lan_address, gateway_address, loop, ignored, unicast) packet_args = list(packet_generator()) batch_size = 2 batch_timeout = float(timeout) / float(len(packet_args)) while packet_args: args = packet_args[:batch_size] packet_args = packet_args[batch_size:] log.debug("sending batch of %i M-SEARCH attempts", batch_size) try: await asyncio.wait_for( protocol.m_search(gateway_address, batch_timeout, args), batch_timeout) protocol.disconnect() return args except asyncio.TimeoutError: continue protocol.disconnect() raise UPnPError("M-SEARCH for {}:{} timed out".format( gateway_address, SSDP_PORT))
async def scpd_post( control_url: str, address: str, port: int, method: str, param_names: list, service_id: bytes, loop: typing.Optional[asyncio.AbstractEventLoop] = None, **kwargs: typing.Dict[str, typing.Any] ) -> typing.Tuple[typing.Dict, bytes, typing.Optional[Exception]]: loop = loop or asyncio.get_event_loop() finished: 'asyncio.Future[typing.Tuple[bytes, bytes, int, bytes]]' = loop.create_future( ) packet = serialize_soap_post(method, param_names, service_id, address.encode(), control_url.encode(), **kwargs) proto_factory: typing.Callable[[], SCPDHTTPClientProtocol] = lambda:\ SCPDHTTPClientProtocol(packet, finished, soap_method=method, soap_service_id=service_id.decode()) try: connect_tup: typing.Tuple[ asyncio.BaseTransport, asyncio.BaseProtocol] = await loop.create_connection( proto_factory, address, port) except ConnectionError as err: return {}, b'', UPnPError(f"{err.__class__.__name__}({str(err)})") protocol = connect_tup[1] transport = connect_tup[0] assert isinstance(protocol, SCPDHTTPClientProtocol) try: wait_task: typing.Awaitable[typing.Tuple[bytes, bytes, int, bytes]] = asyncio.wait_for( finished, 1.0, loop=loop) raw_response, body, response_code, response_msg = await wait_task except asyncio.TimeoutError: return {}, b'', UPnPError("Timeout") except UPnPError as err: return {}, protocol.response_buff, err finally: transport.close() try: return (deserialize_soap_post_response(body, method, service_id.decode()), raw_response, None) except Exception as err: return {}, raw_response, UPnPError(err)
def get_cli_igd_kwargs(self) -> str: fields = [] for field in self._field_order: v = getattr(self, field, None) if v is None: raise UPnPError("missing required field %s" % field) fields.append("--%s=%s" % (self._case_mappings.get(field, field), v)) return " ".join(fields)
async def GetExternalIPAddress(self) -> str: """Returns (NewExternalIPAddress)""" name = "GetExternalIPAddress" if not self.is_registered(name): raise NotImplementedError() # pragma: no cover assert name in self._wrappers_no_args external_ip: str = await self._wrappers_no_args[name]() if not is_valid_public_ipv4(external_ip): raise UPnPError(f"Got invalid external ipv4 address: {external_ip}") return external_ip
def run_cli(method: str, igd_args: Dict[str, Union[bool, str, int]], lan_address: str = '', gateway_address: str = '', timeout: int = 30, interface_name: str = 'default', unicast: bool = True, kwargs: Optional[Dict[str, str]] = None, loop: Optional[asyncio.AbstractEventLoop] = None) -> None: kwargs = kwargs or {} igd_args = igd_args timeout = int(timeout) loop = loop or asyncio.get_event_loop() fut: 'asyncio.Future' = asyncio.Future(loop=loop) async def wrapper(): # wrap the upnp setup and call of the command in a coroutine if method == 'm_search': # if we're only m_searching don't do any device discovery fn = lambda *_a, **_kw: UPnP.m_search( lan_address, gateway_address, timeout, unicast, interface_name, igd_args, loop ) else: # automatically discover the gateway try: u = await UPnP.discover( lan_address, gateway_address, timeout, igd_args, interface_name, loop=loop ) except UPnPError as err: fut.set_exception(err) return if method not in cli_commands: fut.set_exception(UPnPError("\"%s\" is not a recognized command" % method)) return else: fn = getattr(u, method) try: # call the command result = await fn(**{k: fn.__annotations__[k](v) for k, v in kwargs.items()}) fut.set_result(result) except UPnPError as err: fut.set_exception(err) except Exception as err: log.exception("uncaught error") fut.set_exception(UPnPError("uncaught error: %s" % str(err))) if not hasattr(UPnP, method): fut.set_exception(UPnPError("\"%s\" is not a recognized command" % method)) else: loop.run_until_complete(wrapper()) try: result = fut.result() except UPnPError as err: print("aioupnp encountered an error: %s" % str(err)) return if isinstance(result, (list, tuple, dict)): print(json.dumps(result, indent=2)) else: print(result) return
async def m_search(lan_address: str, gateway_address: str, datagram_args: Dict[str, typing.Union[int, str]], timeout: int = 1, loop: Optional[asyncio.AbstractEventLoop] = None) -> SSDPDatagram: protocol, gateway_address, lan_address = await listen_ssdp( lan_address, gateway_address, loop ) try: return await protocol.m_search(address=gateway_address, timeout=timeout, datagrams=[datagram_args]) except asyncio.TimeoutError: raise UPnPError("M-SEARCH for {}:{} timed out".format(gateway_address, SSDP_PORT)) finally: protocol.disconnect()
async def m_search(lan_address: str, gateway_address: str, datagram_args: OrderedDict, timeout: int = 1, ssdp_socket: socket.socket = None, ignored: typing.Set[str] = None, unicast: bool = False) -> SSDPDatagram: transport, protocol, gateway_address, lan_address = await listen_ssdp( lan_address, gateway_address, ssdp_socket, ignored, unicast ) try: return await protocol.m_search(address=gateway_address, timeout=timeout, datagrams=[datagram_args]) except (asyncio.TimeoutError, asyncio.CancelledError): raise UPnPError("M-SEARCH for {}:{} timed out".format(gateway_address, SSDP_PORT)) finally: protocol.disconnect()
async def register_commands( self, service: Service, loop: Optional[asyncio.AbstractEventLoop] = None) -> None: if not service.SCPDURL: raise UPnPError("no scpd url") if not service.serviceType: raise UPnPError("no service type") log.debug("get descriptor for %s from %s", service.serviceType, service.SCPDURL) service_dict, xml_bytes, get_err = await scpd_get( service.SCPDURL, self.base_ip.decode(), self.port, loop=loop) self._service_descriptors[service.SCPDURL] = xml_bytes.decode() if get_err is not None: log.debug("failed to get descriptor for %s from %s", service.serviceType, service.SCPDURL) if xml_bytes: log.debug("response: %s", xml_bytes.decode()) return None if not service_dict: return None action_list = get_action_list(service_dict) for name, inputs, outputs in action_list: try: self.commands.register(name, service, inputs, outputs) self._registered_commands[name] = service.serviceType log.debug("registered %s::%s", service.serviceType, name) except AttributeError: self._unsupported_actions.setdefault(service.serviceType, []) self._unsupported_actions[service.serviceType].append(name) log.debug( "available command for %s does not have a wrapper implemented: %s %s %s", service.serviceType, name, inputs, outputs) log.debug("registered service %s", service.serviceType) return None
async def scpd_post( control_url: str, address: str, port: int, method: str, param_names: list, service_id: bytes, loop=None, **kwargs ) -> typing.Tuple[typing.Dict, bytes, typing.Optional[Exception]]: loop = loop or asyncio.get_event_loop_policy().get_event_loop() finished: asyncio.Future = asyncio.Future() packet = serialize_soap_post(method, param_names, service_id, address.encode(), control_url.encode(), **kwargs) transport, protocol = await loop.create_connection( lambda: SCPDHTTPClientProtocol( packet, finished, soap_method=method, soap_service_id=service_id.decode(), ), address, port) assert isinstance(protocol, SCPDHTTPClientProtocol) try: body, response_code, response_msg = await asyncio.wait_for( finished, 1.0) except asyncio.TimeoutError: return {}, b'', UPnPError("Timeout") except UPnPError as err: return {}, protocol.response_buff, err finally: transport.close() try: return (deserialize_soap_post_response(body, method, service_id.decode()), body, None) except (ElementTree.ParseError, UPnPError) as err: return {}, body, UPnPError(err)
def _send_m_search(self, address: str, packet: SSDPDatagram, fut: 'asyncio.Future[SSDPDatagram]') -> None: if not self.transport: if not fut.done(): fut.set_exception(UPnPError("SSDP transport not connected")) return assert packet.st is not None self._pending_searches.append( PendingSearch(address, packet.st, fut) ) self.transport.sendto(packet.encode().encode(), (SSDP_IP_ADDRESS, SSDP_PORT)) # also send unicast log.debug("send m search to %s: %s", address, packet.st) self.transport.sendto(packet.encode().encode(), (address, SSDP_PORT))
async def _gateway_from_igd_args( cls, lan_address: str, gateway_address: str, igd_args: typing.Dict[str, typing.Union[int, str]], timeout: int = 30, loop: Optional[asyncio.AbstractEventLoop] = None) -> 'Gateway': datagram = await m_search(lan_address, gateway_address, igd_args, timeout, loop) gateway = await cls._try_gateway_from_ssdp(datagram, lan_address, gateway_address, loop) if not gateway: raise UPnPError("no gateway found for given args") return gateway
async def fuzzy_m_search(lan_address: str, gateway_address: str, timeout: int = 30, ssdp_socket: socket.socket = None, ignored: typing.Set[str] = None, unicast: bool = False) -> typing.Tuple[OrderedDict, SSDPDatagram]: # we don't know which packet the gateway replies to, so send small batches at a time args_to_try = await _fuzzy_m_search(lan_address, gateway_address, timeout, ssdp_socket, ignored, unicast) # check the args in the batch that got a reply one at a time to see which one worked for args in args_to_try: try: packet = await m_search(lan_address, gateway_address, args, 3, ignored=ignored, unicast=unicast) return args, packet except UPnPError: continue raise UPnPError("failed to discover gateway")
def deserialize_soap_post_response( response: bytes, method: str, service_id: str) -> typing.Dict[str, typing.Dict[str, str]]: parsed: typing.List[ typing.List[bytes]] = CONTENT_NO_XML_VERSION_PATTERN.findall(response) content = b'' if not parsed else parsed[0][0] content_dict = xml_to_dict(content.decode()) envelope = content_dict[ENVELOPE] if not isinstance(envelope[BODY], dict): # raise UPnPError('blank response') return {} # TODO: raise response_body: typing.Dict[str, typing.Dict[str, typing.Dict[ str, str]]] = flatten_keys(envelope[BODY], f"{'{' + service_id + '}'}") if not response_body: # raise UPnPError('blank response') return {} # TODO: raise if FAULT in response_body: fault: typing.Dict[str, typing.Dict[str, typing.Dict[str, str]]] = flatten_keys( response_body[FAULT], "{%s}" % CONTROL) try: raise UPnPError(fault['detail']['UPnPError']['errorDescription']) except (KeyError, TypeError, ValueError): raise UPnPError( f"Failed to decode error response: {json.dumps(fault)}") response_key = None for key in response_body: if method in key: response_key = key break if not response_key: raise UPnPError( f"unknown response fields for {method}: {response_body}") return response_body[response_key]
async def discover(cls, lan_address: str = '', gateway_address: str = '', timeout: int = 30, igd_args: OrderedDict = None, interface_name: str = 'default', loop=None): try: lan_address, gateway_address = cls.get_lan_and_gateway( lan_address, gateway_address, interface_name) except Exception as err: raise UPnPError("failed to get lan and gateway addresses: %s" % str(err)) gateway = await Gateway.discover_gateway(lan_address, gateway_address, timeout, igd_args, loop) return cls(lan_address, gateway_address, gateway)
async def listen_ssdp(lan_address: str, gateway_address: str, loop: Optional[asyncio.AbstractEventLoop] = None) -> Tuple[SSDPProtocol, str, str]: loop = loop or asyncio.get_event_loop() try: sock: socket.socket = SSDPProtocol.create_multicast_socket(lan_address) listen_result: Tuple[asyncio.BaseTransport, asyncio.BaseProtocol] = await loop.create_datagram_endpoint( lambda: SSDPProtocol(SSDP_IP_ADDRESS, lan_address), sock=sock ) protocol = listen_result[1] assert isinstance(protocol, SSDPProtocol) except Exception as err: raise UPnPError(err) else: protocol.join_group(protocol.multicast_address, protocol.bind_address) protocol.set_ttl(1) return protocol, gateway_address, lan_address