/
cast.py
122 lines (98 loc) 路 3.93 KB
/
cast.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
from .controllers import Message
from .controllers.connection import ConnectionController
from .controllers.heartbeat import HeartbeatController
from .controllers.media import MediaController
from .controllers.receiver import ReceiverController
from .controllers.spotify import SpotifyController
from .log import logger
from pychromecast.cast_channel_pb2 import CastMessage
import json
import logging
import socket
import ssl
import struct
def unpack_uint(buffer):
return struct.unpack(">I", buffer)[0]
def pack_uint(value):
return struct.pack(">I", value)
class CastException(Exception):
def __init__(self, message):
self.message = message
class CastServer:
def __init__(self, key_path, cert_path, port=8009, tcp_backlog=100):
raw_socket = socket.socket()
self._socket = ssl.wrap_socket(raw_socket, keyfile=key_path, certfile=cert_path, server_side=True)
self._socket.bind(("0.0.0.0", port))
self._socket.listen(tcp_backlog)
logger.info("started cast server on port %d", port)
def close(self):
self._socket.close()
def get_client(self):
client_socket, address = self._socket.accept()
logger.info("client connected at %s", str(address))
return CastClient(client_socket)
def run(self):
while True:
try:
client = self.get_client()
while True:
try:
client.receive_and_reply_once()
except CastException as ex:
logger.warning("exception in cast client: %s", ex.message)
break
except KeyboardInterrupt:
break
class CastClient:
def __init__(self, socket):
self._socket = socket
self._controllers = [
HeartbeatController(),
ConnectionController(),
ReceiverController(),
MediaController(),
SpotifyController(),
]
def close(self):
self._socket.close()
def _receive_message(self):
size_bytes = self._socket.recv(4)
if not size_bytes:
raise CastException("could not receive message size")
size = unpack_uint(size_bytes)
logger.debug("about to receive %d bytes", size)
cast_message_bytes = self._socket.recv(size)
if not cast_message_bytes:
raise CastException("could not receive message")
cast_message = CastMessage()
cast_message.ParseFromString(cast_message_bytes)
message = self._parse_message(cast_message)
logger.debug("received %s", str(message))
return message
def _parse_message(self, message):
payload_raw = message.payload_utf8
data = json.loads(payload_raw)
return Message(source_id=message.source_id, destination_id=message.destination_id, namespace=message.namespace, data=data)
def _send_message(self, message):
logger.debug("sending %s", str(message))
cast_message = CastMessage()
cast_message.protocol_version = cast_message.CASTV2_1_0
cast_message.source_id = message.source_id
cast_message.destination_id = message.destination_id
cast_message.payload_type = CastMessage.STRING
cast_message.namespace = message.namespace
cast_message.payload_utf8 = json.dumps(message.data)
size = cast_message.ByteSize()
size_bytes = pack_uint(size)
cast_message = cast_message.SerializeToString()
try:
self._socket.send(size_bytes + cast_message)
except:
raise CastException("could not send message")
def receive_and_reply_once(self):
message = self._receive_message()
replies = [controller.get_reply(message) for controller in self._controllers if controller.matches(message.namespace)]
if len(replies) > 0:
reply = replies[0]
if reply:
self._send_message(reply)