Пример #1
0
def hscp_service_handler(
    mdns_service: mdns.Service, response: mdns.Response
) -> Optional[ScanHandlerReturn]:
    """Parse and return a new HSCP service."""
    name = mdns_service.properties.get("Machine Name", "Unknown")
    service = MutableService(
        get_unique_id(mdns_service.type, mdns_service.name, mdns_service.properties),
        Protocol.DMAP,
        port=mdns_service.port,
        properties=mdns_service.properties,
    )
    service.credentials = mdns_service.properties.get("hG")
    return name, service
Пример #2
0
async def service_info(
    service: MutableService,
    devinfo: DeviceInfo,
    services: Mapping[Protocol, BaseService],
) -> None:
    """Update service with additional information."""
    flags = int(service.properties.get("rpfl", "0x0"), 16)
    if flags & PAIRING_DISABLED_MASK:
        service.pairing = PairingRequirement.Disabled
    elif flags & PAIRING_WITH_PIN_SUPPORTED_MASK:
        service.pairing = PairingRequirement.Mandatory
    else:
        service.pairing = PairingRequirement.Unsupported
Пример #3
0
def homesharing_service_handler(
    mdns_service: mdns.Service, response: mdns.Response
) -> Optional[ScanHandlerReturn]:
    """Parse and return a new DMAP (Home Sharing) service."""
    name = mdns_service.properties.get("Name", "Unknown")
    service = MutableService(
        get_unique_id(mdns_service.type, mdns_service.name, mdns_service.properties),
        Protocol.DMAP,
        mdns_service.port,
        mdns_service.properties,
    )
    service.credentials = mdns_service.properties.get("hG")
    return name, service
Пример #4
0
async def service_info(
    service: MutableService,
    devinfo: DeviceInfo,
    services: Mapping[Protocol, BaseService],
) -> None:
    """Update service with additional information."""
    service.requires_password = is_password_required(service)

    if service.properties.get("acl", "0") == "1":
        # Access control might say that pairing is not possible, e.g. only devices
        # belonging to the same home (not supported by pyatv)
        service.pairing = PairingRequirement.Disabled
    else:
        service.pairing = get_pairing_requirement(service)
Пример #5
0
async def test_service_info_pairing_acl():
    raop_service = MutableService("id", Protocol.RAOP, 0, {})
    airplay_props = MutableService("id", Protocol.AirPlay, 0, {"acl": "1"})

    await service_info(
        raop_service,
        DeviceInfo({}),
        {
            Protocol.RAOP: raop_service,
            Protocol.AirPlay: airplay_props
        },
    )

    assert raop_service.pairing == PairingRequirement.Disabled
Пример #6
0
async def test_service_info_pairing(dmap_props, mrp_props, pairing_req):
    dmap_service = MutableService("id", Protocol.DMAP, 0, dmap_props)
    mrp_service = MutableService("mrp", Protocol.MRP, 0, mrp_props)

    assert dmap_service.pairing == PairingRequirement.Unsupported
    assert mrp_service.pairing == PairingRequirement.Unsupported

    await service_info(
        dmap_service,
        DeviceInfo({}),
        {Protocol.MRP: mrp_service, Protocol.DMAP: dmap_service},
    )

    assert dmap_service.pairing == pairing_req
    assert mrp_service.pairing == PairingRequirement.Unsupported
Пример #7
0
def mrp_service_handler(
        mdns_service: mdns.Service,
        response: mdns.Response) -> Optional[ScanHandlerReturn]:
    """Parse and return a new MRP service."""
    enabled = True

    # Disable this service if tvOS version is >= 15 as it doesn't work anymore
    build = mdns_service.properties.get("SystemBuildVersion", "")
    match = re.match(r"^(\d+)[A-Z]", build)
    if match:
        base = int(match.groups()[0])
        if base >= 19:
            _LOGGER.debug("Disabling MRP service since tvOS >= 15")
            enabled = False

    name = mdns_service.properties.get("Name", "Unknown")
    service = MutableService(
        get_unique_id(mdns_service.type, mdns_service.name,
                      mdns_service.properties),
        Protocol.MRP,
        mdns_service.port,
        properties=mdns_service.properties,
        enabled=enabled,
    )
    return name, service
Пример #8
0
async def test_service_info_password(raop_props, mrp_props, requires_password):
    raop_service = MutableService("id", Protocol.RAOP, 0, raop_props)
    mrp_service = MutableService("mrp", Protocol.MRP, 0, mrp_props)

    assert not raop_service.requires_password
    assert not mrp_service.requires_password

    await service_info(
        raop_service,
        DeviceInfo({}),
        {
            Protocol.MRP: mrp_service,
            Protocol.RAOP: raop_service
        },
    )

    assert raop_service.requires_password == requires_password
    assert not mrp_service.requires_password
Пример #9
0
async def test_service_info_pairing(airplay_props, mrp_props, pairing_req):
    mrp_service = MutableService("mrp", Protocol.MRP, 0, mrp_props)
    airplay_service = MutableService("id", Protocol.AirPlay, 0, airplay_props)

    assert mrp_service.pairing == PairingRequirement.Unsupported
    assert airplay_service.pairing == PairingRequirement.Unsupported

    await service_info(
        mrp_service,
        DeviceInfo({}),
        {
            Protocol.MRP: mrp_service,
            Protocol.AirPlay: airplay_service
        },
    )

    assert mrp_service.pairing == pairing_req
    assert airplay_service.pairing == PairingRequirement.Unsupported
Пример #10
0
async def test_service_info_password(airplay_props, mrp_props,
                                     requires_password):
    airplay_service = MutableService("id", Protocol.AirPlay, 0, airplay_props)
    mrp_service = MutableService("mrp", Protocol.MRP, 0, mrp_props)

    assert not airplay_service.requires_password
    assert not mrp_service.requires_password

    await service_info(
        airplay_service,
        DeviceInfo({}),
        {
            Protocol.MRP: mrp_service,
            Protocol.AirPlay: airplay_service
        },
    )

    assert airplay_service.requires_password == requires_password
    assert not mrp_service.requires_password
Пример #11
0
def companion_service_handler(
        mdns_service: mdns.Service,
        response: mdns.Response) -> Optional[ScanHandlerReturn]:
    """Parse and return a new Companion service."""
    service = MutableService(
        None,
        Protocol.Companion,
        mdns_service.port,
        mdns_service.properties,
    )
    return mdns_service.name, service
Пример #12
0
async def test_service_info_pairing(airplay_props, devinfo, pairing_req):
    airplay_service = MutableService("id", Protocol.AirPlay, 0, airplay_props)

    assert airplay_service.pairing == PairingRequirement.Unsupported

    await service_info(
        airplay_service,
        DeviceInfo(devinfo),
        {Protocol.AirPlay: airplay_service},
    )

    assert airplay_service.pairing == pairing_req
Пример #13
0
def dmap_service_handler(
    mdns_service: mdns.Service, response: mdns.Response
) -> Optional[ScanHandlerReturn]:
    """Parse and return a new DMAP service."""
    name = mdns_service.properties.get("CtlN", "Unknown")
    service = MutableService(
        get_unique_id(mdns_service.type, mdns_service.name, mdns_service.properties),
        Protocol.DMAP,
        mdns_service.port,
        mdns_service.properties,
    )
    return name, service
Пример #14
0
def airplay_service_handler(
        mdns_service: mdns.Service,
        response: mdns.Response) -> Optional[ScanHandlerReturn]:
    """Parse and return a new AirPlay service."""
    service = MutableService(
        get_unique_id(mdns_service.type, mdns_service.name,
                      mdns_service.properties),
        Protocol.AirPlay,
        mdns_service.port,
        properties=mdns_service.properties,
    )
    return mdns_service.name, service
Пример #15
0
async def test_service_info_pairing(raop_props, devinfo, pairing_req):
    raop_service = MutableService("id", Protocol.RAOP, 0, raop_props)

    assert raop_service.pairing == PairingRequirement.Unsupported

    await service_info(
        raop_service,
        DeviceInfo(devinfo),
        {Protocol.RAOP: raop_service},
    )

    assert raop_service.pairing == pairing_req
Пример #16
0
def raop_service_handler(
        mdns_service: mdns.Service,
        response: mdns.Response) -> Optional[ScanHandlerReturn]:
    """Parse and return a new RAOP service."""
    _, name = mdns_service.name.split("@", maxsplit=1)
    service = MutableService(
        get_unique_id(mdns_service.type, mdns_service.name,
                      mdns_service.properties),
        Protocol.RAOP,
        mdns_service.port,
        mdns_service.properties,
    )
    return name, service
Пример #17
0
async def service_info(
    service: MutableService,
    devinfo: DeviceInfo,
    services: Mapping[Protocol, BaseService],
) -> None:
    """Update service with additional information.

    Pairing has never been enforced by MRP (maybe by design), but it is
    possible to pair if AllowPairing is YES.
    """
    service.pairing = (PairingRequirement.Optional if service.properties.get(
        "allowpairing", "no").lower() == "yes" else
                       PairingRequirement.Disabled)
Пример #18
0
async def service_info(
    service: MutableService,
    devinfo: DeviceInfo,
    services: Mapping[Protocol, BaseService],
) -> None:
    """Update service with additional information."""
    airplay_service = services.get(Protocol.AirPlay)
    if airplay_service and airplay_service.properties.get("acl", "0") == "1":
        # Access control might say that pairing is not possible, e.g. only devices
        # belonging to the same home (not supported by pyatv)
        service.pairing = PairingRequirement.Disabled
    else:
        # Same behavior as for AirPlay expected, so re-using that here
        await airplay_service_info(service, devinfo, services)
Пример #19
0
async def service_info(
    service: MutableService,
    devinfo: DeviceInfo,
    services: Mapping[Protocol, BaseService],
) -> None:
    """Update service with additional information.

    If Home Sharing is enabled, then the "hG" property is present and can be used as
    credentials. If not enabled, then pairing must be performed.
    """
    service.pairing = (
        PairingRequirement.Optional
        if "hg" in service.properties
        else PairingRequirement.Mandatory
    )
Пример #20
0
 def __init__(self, loop, address, port, credentials):
     """Initialize a new instance of ProxyMrpAppleTV."""
     super().__init__(DEVICE_NAME)
     self.loop = loop
     self.buffer = b""
     self.transport = None
     self.chacha = None
     self.connection = MrpConnection(address, port, self.loop)
     self.protocol = MrpProtocol(
         self.connection,
         SRPAuthHandler(),
         MutableService(None,
                        Protocol.MRP,
                        port, {},
                        credentials=credentials),
     )
Пример #21
0
 def __init__(self, loop: asyncio.AbstractEventLoop, address: str,
              port: int, credentials: str) -> None:
     """Initialize a new instance of CompanionAppleTVProxy."""
     super().__init__(DEVICE_NAME)
     self.loop = loop
     self.buffer: bytes = b""
     self.transport = None
     self.chacha: Optional[chacha20.Chacha20Cipher] = None
     self.connection: Optional[CompanionConnection] = CompanionConnection(
         self.loop, address, port)
     self.protocol: CompanionProtocol = CompanionProtocol(
         self.connection,
         SRPAuthHandler(),
         MutableService(None,
                        Protocol.Companion,
                        port, {},
                        credentials=credentials),
     )
     self._receive_event: asyncio.Event = asyncio.Event()
     self._receive_task: Optional[asyncio.Future] = None
Пример #22
0
async def test_get_pairing_requirement(props, expected_req):
    service = MutableService("id", Protocol.AirPlay, 0, props)
    assert get_pairing_requirement(service) == expected_req
Пример #23
0
def test_is_remote_control_supported(props, credentials, expected_supported):
    service = MutableService("id", Protocol.AirPlay, 0, props)
    assert is_remote_control_supported(service,
                                       credentials) == expected_supported
Пример #24
0
def test_is_password_required(properties, requires_password):
    service = MutableService("id", Protocol.RAOP, 0, properties)
    assert is_password_required(service) == requires_password
Пример #25
0
def setup(  # pylint: disable=too-many-locals
    core: Core, ) -> Generator[SetupData, None, None]:
    """Set up a new AirPlay service."""
    # TODO: Split up in connect/protocol and Stream implementation
    stream = AirPlayStream(core.config, core.service)

    interfaces = {
        Features: AirPlayFeatures(core.service),
        Stream: stream,
    }

    async def _connect() -> bool:
        return True

    def _close() -> Set[asyncio.Task]:
        stream.close()
        return set()

    def _device_info() -> Dict[str, Any]:
        return device_info(list(scan().keys())[0], core.service.properties)

    yield SetupData(
        Protocol.AirPlay,
        _connect,
        _close,
        _device_info,
        interfaces,
        set([FeatureName.PlayUrl]),
    )

    credentials = extract_credentials(core.service)

    # Set up remote control channel if it is supported
    if not is_remote_control_supported(core.service, credentials):
        _LOGGER.debug("Remote control not supported by device")
    elif credentials.type not in [
            AuthenticationType.HAP, AuthenticationType.Transient
    ]:
        _LOGGER.debug("%s not supported by remote control channel",
                      credentials.type)
    else:
        _LOGGER.debug("Remote control channel is supported")

        control = RemoteControl(core.device_listener)
        control_port = core.service.port

        # A protocol requires its correaponding service to function, so add a
        # dummy one if we don't have one yet
        mrp_service = core.config.get_service(Protocol.MRP)
        if mrp_service is None:
            mrp_service = MutableService(None, Protocol.MRP, control_port, {})
            core.config.add_service(mrp_service)

        (
            _,
            mrp_connect,
            mrp_close,
            mrp_device_info,
            mrp_interfaces,
            mrp_features,
        ) = mrp.create_with_connection(
            Core(
                core.loop,
                core.config,
                mrp_service,
                core.device_listener,
                core.session_manager,
                core.takeover,
                core.state_dispatcher.create_copy(Protocol.MRP),
            ),
            AirPlayMrpConnection(control, core.device_listener),
            requires_heatbeat=False,  # Already have heartbeat on control channel
        )

        async def _connect_rc() -> bool:
            try:

                await control.start(str(core.config.address), control_port,
                                    credentials)
            except exceptions.HttpError as ex:
                if ex.status_code == 470:
                    _LOGGER.debug(
                        "Remote control authorization failed, missing credentials"
                    )
                else:
                    _LOGGER.exception(
                        "Failed to set up remote control channel")
            except Exception:
                _LOGGER.exception("Failed to set up remote control channel")
            else:
                await mrp_connect()
                return True
            return False

        def _close_rc() -> Set[asyncio.Task]:
            tasks = set()
            tasks.update(mrp_close())
            tasks.update(control.stop())
            return tasks

        yield SetupData(
            Protocol.MRP,
            _connect_rc,
            _close_rc,
            mrp_device_info,
            mrp_interfaces,
            mrp_features,
        )
Пример #26
0
async def test_service_info_pairing(properties, expected):
    service = MutableService(None, Protocol.Companion, 0, properties)

    assert service.pairing == PairingRequirement.Unsupported
    await service_info(service, DeviceInfo({}), {Protocol.Companion: service})
    assert service.pairing == expected