Example #1
0
        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)))
Example #2
0
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
Example #3
0
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
Example #4
0
 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)
Example #5
0
 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)
Example #6
0
    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)))
Example #7
0
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
Example #8
0
 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
Example #9
0
 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
Example #10
0
 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
Example #11
0
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
Example #12
0
 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()
     }
Example #13
0
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}')
Example #14
0
    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
Example #15
0
 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
Example #16
0
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))
Example #17
0
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)
Example #18
0
 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)
Example #19
0
 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
Example #20
0
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
Example #21
0
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()
Example #22
0
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()
Example #23
0
    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
Example #24
0
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)
Example #25
0
    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))
Example #26
0
 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
Example #27
0
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")
Example #28
0
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]
Example #29
0
 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)
Example #30
0
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