librespot-python/librespot/audio/AudioKeyManager.py

115 lines
3.6 KiB
Python
Raw Normal View History

2021-02-24 00:46:59 +01:00
from __future__ import annotations
2021-04-25 01:57:15 +02:00
2021-05-22 03:26:35 +02:00
import logging
import queue
2021-04-25 01:57:15 +02:00
import struct
2021-05-22 03:26:35 +02:00
import threading
import typing
2021-04-25 01:57:15 +02:00
2021-02-24 00:46:59 +01:00
from librespot.common import Utils
from librespot.core import Session
from librespot.core.PacketsReceiver import PacketsReceiver
from librespot.crypto import Packet
2021-05-22 03:26:41 +02:00
from librespot.standard import ByteArrayOutputStream
from librespot.standard import BytesInputStream
2021-02-24 00:46:59 +01:00
class AudioKeyManager(PacketsReceiver):
_ZERO_SHORT: bytes = bytes([0, 0])
_LOGGER: logging = logging.getLogger(__name__)
_AUDIO_KEY_REQUEST_TIMEOUT: int = 20
_seqHolder: int = 0
_seqHolderLock: threading.Condition = threading.Condition()
2021-04-24 12:17:02 +02:00
_callbacks: typing.Dict[int, AudioKeyManager.Callback] = {}
2021-02-24 00:46:59 +01:00
_session: Session = None
def __init__(self, session: Session):
self._session = session
2021-05-22 03:27:30 +02:00
def get_audio_key(self,
gid: bytes,
file_id: bytes,
retry: bool = True) -> bytes:
2021-02-24 00:46:59 +01:00
seq: int
with self._seqHolderLock:
seq = self._seqHolder
self._seqHolder += 1
2021-03-07 06:38:41 +01:00
out = ByteArrayOutputStream()
out.write(buffer=bytearray(file_id))
out.write(buffer=bytearray(gid))
2021-04-25 01:57:15 +02:00
out.write(buffer=bytearray(struct.pack(">i", seq)))
2021-03-07 06:38:41 +01:00
out.write(buffer=bytearray(self._ZERO_SHORT))
2021-02-24 00:46:59 +01:00
2021-03-07 06:38:41 +01:00
self._session.send(Packet.Type.request_key, out.to_bytes())
2021-02-24 00:46:59 +01:00
callback = AudioKeyManager.SyncCallback(self)
self._callbacks[seq] = callback
key = callback.wait_response()
if key is None:
if retry:
return self.get_audio_key(gid, file_id, False)
raise RuntimeError(
"Failed fetching audio key! gid: {}, fileId: {}".format(
Utils.bytes_to_hex(gid), Utils.bytes_to_hex(file_id)))
2021-02-24 00:46:59 +01:00
return key
def dispatch(self, packet: Packet) -> None:
payload = BytesInputStream(packet.payload)
seq = payload.read_int()
callback = self._callbacks.get(seq)
if callback is None:
2021-05-22 03:27:30 +02:00
self._LOGGER.warning(
"Couldn't find callback for seq: {}".format(seq))
2021-02-24 00:46:59 +01:00
return
if packet.is_cmd(Packet.Type.aes_key):
key = payload.read(16)
callback.key(key)
elif packet.is_cmd(Packet.Type.aes_key_error):
code = payload.read_short()
callback.error(code)
else:
self._LOGGER.warning(
"Couldn't handle packet, cmd: {}, length: {}".format(
2021-05-22 03:27:30 +02:00
packet.cmd, len(packet.payload)))
2021-02-24 00:46:59 +01:00
class Callback:
def key(self, key: bytes) -> None:
pass
def error(self, code: int) -> None:
pass
class SyncCallback(Callback):
_audioKeyManager: AudioKeyManager
reference = queue.Queue()
reference_lock = threading.Condition()
def __init__(self, audio_key_manager: AudioKeyManager):
self._audioKeyManager = audio_key_manager
def key(self, key: bytes) -> None:
with self.reference_lock:
self.reference.put(key)
self.reference_lock.notify_all()
def error(self, code: int) -> None:
self._audioKeyManager._LOGGER.fatal(
2021-05-22 03:27:30 +02:00
"Audio key error, code: {}".format(code))
2021-02-24 00:46:59 +01:00
with self.reference_lock:
self.reference.put(None)
self.reference_lock.notify_all()
def wait_response(self) -> bytes:
with self.reference_lock:
2021-05-22 03:27:30 +02:00
self.reference_lock.wait(
AudioKeyManager._AUDIO_KEY_REQUEST_TIMEOUT)
2021-02-24 00:46:59 +01:00
return self.reference.get(block=False)
class AesKeyException(IOError):
pass