mirror of
https://github.com/comma-hacks/webrtc.git
synced 2025-10-17 05:31:08 +08:00
connect from secureput iphone app
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +1,2 @@
|
|||||||
__pycache__
|
__pycache__
|
||||||
|
secureput_identity.shelve
|
||||||
|
2
TODO
2
TODO
@@ -1,4 +1,4 @@
|
|||||||
- iphone support (swift client? or html?)
|
- [x] iphone
|
||||||
- control interface
|
- control interface
|
||||||
- battery view
|
- battery view
|
||||||
- camera switcher
|
- camera switcher
|
||||||
|
0
secureput/__init__.py
Normal file
0
secureput/__init__.py
Normal file
36
secureput/aes.py
Normal file
36
secureput/aes.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import os
|
||||||
|
import base64
|
||||||
|
from typing import Tuple
|
||||||
|
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||||
|
from cryptography.hazmat.backends import default_backend
|
||||||
|
|
||||||
|
def encrypt(key: bytes, insecure: bytes) -> bytes:
|
||||||
|
plain_text = insecure.encode()
|
||||||
|
iv = os.urandom(16)
|
||||||
|
key_bytes = key.encode()
|
||||||
|
|
||||||
|
cipher = Cipher(algorithms.AES(key_bytes), modes.CBC(iv), backend=default_backend())
|
||||||
|
encryptor = cipher.encryptor()
|
||||||
|
padded_plain_text = PKCS7Padding(plain_text, 16)
|
||||||
|
cipher_text = iv + encryptor.update(padded_plain_text) + encryptor.finalize()
|
||||||
|
|
||||||
|
return base64.b64encode(cipher_text).decode()
|
||||||
|
|
||||||
|
def decrypt(key: bytes, secure: bytes) -> bytes:
|
||||||
|
cipher_text = base64.b64decode(secure)
|
||||||
|
key_bytes = key.encode()
|
||||||
|
iv = cipher_text[:16]
|
||||||
|
cipher_text = cipher_text[16:]
|
||||||
|
|
||||||
|
cipher = Cipher(algorithms.AES(key_bytes), modes.CBC(iv), backend=default_backend())
|
||||||
|
decryptor = cipher.decryptor()
|
||||||
|
decrypted_text = decryptor.update(cipher_text) + decryptor.finalize()
|
||||||
|
|
||||||
|
padding_length = decrypted_text[-1]
|
||||||
|
decrypted_text = decrypted_text[:-padding_length]
|
||||||
|
return decrypted_text
|
||||||
|
|
||||||
|
def PKCS7Padding(data: bytes, block_size: int) -> bytes:
|
||||||
|
padding_length = block_size - (len(data) % block_size)
|
||||||
|
padding = bytes([padding_length]) * padding_length
|
||||||
|
return data + padding
|
36
secureput/app.py
Normal file
36
secureput/app.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import shelve
|
||||||
|
from uuid import uuid4
|
||||||
|
from socket import gethostname
|
||||||
|
import json
|
||||||
|
import pyqrcode
|
||||||
|
from secureput.secret import generate_secret_key
|
||||||
|
|
||||||
|
class App:
|
||||||
|
def __init__(self):
|
||||||
|
self.config = shelve.open("secureput_identity.shelve")
|
||||||
|
self.__init_config_default("deviceName", lambda: gethostname())
|
||||||
|
self.__init_config_default("deviceUUID", lambda: str(uuid4()))
|
||||||
|
self.__init_config_default("accountUUID", lambda: None)
|
||||||
|
|
||||||
|
def __init_config_default(self, key, default_value_lambda):
|
||||||
|
try:
|
||||||
|
self.config[key]
|
||||||
|
except KeyError:
|
||||||
|
self.config[key] = default_value_lambda()
|
||||||
|
|
||||||
|
def paired(self):
|
||||||
|
return self.config["accountUUID"] != None
|
||||||
|
|
||||||
|
def gen_pair_info(self):
|
||||||
|
pairing = {}
|
||||||
|
pairing["secret"] = generate_secret_key()
|
||||||
|
pairing["uuid"] = self.config["deviceUUID"]
|
||||||
|
self.config["deviceSecret"] = pairing["secret"]
|
||||||
|
url = pyqrcode.create(json.dumps(pairing))
|
||||||
|
print(url.terminal(quiet_zone=1))
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app = App()
|
||||||
|
print(app.config["deviceName"])
|
||||||
|
print(app.config["deviceUUID"])
|
||||||
|
print(app.gen_pair_info())
|
12
secureput/secret.py
Normal file
12
secureput/secret.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import random
|
||||||
|
|
||||||
|
runes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||||
|
|
||||||
|
def rand_seq(n):
|
||||||
|
b = []
|
||||||
|
for i in range(n):
|
||||||
|
b.append(random.choice(runes))
|
||||||
|
return "".join(b)
|
||||||
|
|
||||||
|
def generate_secret_key():
|
||||||
|
return rand_seq(32)
|
117
secureput/secureput_signaling.py
Normal file
117
secureput/secureput_signaling.py
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
import asyncio
|
||||||
|
from aiortc.contrib.signaling import RTCSessionDescription, RTCIceCandidate, candidate_from_sdp, candidate_to_sdp
|
||||||
|
import json
|
||||||
|
|
||||||
|
from secureput.websocket_signaling import WebsocketSignaling
|
||||||
|
import secureput.aes as aes
|
||||||
|
from secureput.app import App
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
class SecureputSignaling(WebsocketSignaling):
|
||||||
|
def __init__(self, server):
|
||||||
|
super().__init__(server)
|
||||||
|
self._app = App()
|
||||||
|
|
||||||
|
if not self._app.paired():
|
||||||
|
self._app.gen_pair_info()
|
||||||
|
|
||||||
|
async def connect(self):
|
||||||
|
await super().connect()
|
||||||
|
await self.sendIdentity()
|
||||||
|
|
||||||
|
async def close(self):
|
||||||
|
if self._websocket is not None and self._websocket.open is True:
|
||||||
|
await self._websocket.close()
|
||||||
|
|
||||||
|
def secret(self):
|
||||||
|
return self._app.config["deviceSecret"]
|
||||||
|
|
||||||
|
def encrypt(self, data):
|
||||||
|
return aes.encrypt(self.secret(), data)
|
||||||
|
|
||||||
|
def decrypt(self, data):
|
||||||
|
return aes.decrypt(self.secret(), data)
|
||||||
|
|
||||||
|
def __object_from_string(self, message_str):
|
||||||
|
message = json.loads(message_str)
|
||||||
|
|
||||||
|
if message["type"] == "wrapped":
|
||||||
|
message = json.loads(self.decrypt(message["payload"]["data"]))
|
||||||
|
|
||||||
|
return message
|
||||||
|
|
||||||
|
def __object_to_string(self, obj):
|
||||||
|
if isinstance(obj, RTCSessionDescription):
|
||||||
|
message = self.forwardWrap({
|
||||||
|
"type": "SessionDescription",
|
||||||
|
"payload": {"sdp": obj.sdp, "type": obj.type}
|
||||||
|
})
|
||||||
|
elif isinstance(obj, RTCIceCandidate):
|
||||||
|
message = self.forwardWrap({
|
||||||
|
"type": "IceCandidate",
|
||||||
|
"payload": {
|
||||||
|
"sdp": "candidate:" + candidate_to_sdp(obj),
|
||||||
|
"sdpMid": obj.sdpMid,
|
||||||
|
"sdpMLineIndex": obj.sdpMLineIndex,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
message = obj
|
||||||
|
|
||||||
|
return json.dumps(message, sort_keys=True)
|
||||||
|
|
||||||
|
async def send(self, descr):
|
||||||
|
data = self.__object_to_string(descr)
|
||||||
|
print("Websocket send: %s" % data)
|
||||||
|
await self._websocket.send(data + '\n')
|
||||||
|
|
||||||
|
async def receive(self):
|
||||||
|
try:
|
||||||
|
data = await self._websocket.recv()
|
||||||
|
except asyncio.IncompleteReadError:
|
||||||
|
return
|
||||||
|
ret = self.__object_from_string(data)
|
||||||
|
if ret == None:
|
||||||
|
print("remote host says good bye!")
|
||||||
|
elif isinstance(ret, dict):
|
||||||
|
if ret['type'] == 'claim':
|
||||||
|
await self.claim(ret['payload']['account'])
|
||||||
|
else:
|
||||||
|
if ret["type"] == "SessionDescription":
|
||||||
|
sdp = ret["payload"]["sdp"]
|
||||||
|
type = ret["payload"]["type"]
|
||||||
|
return RTCSessionDescription(sdp=sdp, type=type)
|
||||||
|
elif ret["type"] == "IceCandidate":
|
||||||
|
candidate = candidate_from_sdp(ret["payload"]["sdp"].split(":", 1)[1])
|
||||||
|
candidate.sdpMid = ret["payload"]["sdpMid"]
|
||||||
|
candidate.sdpMLineIndex = ret["payload"]["sdpMLineIndex"]
|
||||||
|
return candidate
|
||||||
|
return ret
|
||||||
|
|
||||||
|
async def sendIdentity(self):
|
||||||
|
await self._websocket.send(json.dumps({
|
||||||
|
"type": "identify-target",
|
||||||
|
"payload": {
|
||||||
|
"name": self._app.config["deviceName"],
|
||||||
|
"device": self._app.config["deviceUUID"],
|
||||||
|
"account": self._app.config["accountUUID"]
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
async def claim(self, account):
|
||||||
|
self._app.config["accountUUID"] = account
|
||||||
|
print("claimed to account %s" % account)
|
||||||
|
await self.sendIdentity()
|
||||||
|
|
||||||
|
def forwardWrap(self, json_data: dict) -> str:
|
||||||
|
msg = {
|
||||||
|
'to': self._app.config["accountUUID"],
|
||||||
|
'type': 'forward-wrapped',
|
||||||
|
}
|
||||||
|
# Encrypt the plaintext
|
||||||
|
ciphertext = self.encrypt(json.dumps(json_data))
|
||||||
|
|
||||||
|
# Install the ciphertext into the payload
|
||||||
|
msg['body'] = ciphertext
|
||||||
|
|
||||||
|
return msg
|
32
secureput/websocket_signaling.py
Normal file
32
secureput/websocket_signaling.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import asyncio
|
||||||
|
import websockets
|
||||||
|
|
||||||
|
from aiortc.contrib.signaling import object_from_string, object_to_string
|
||||||
|
|
||||||
|
class WebsocketSignaling:
|
||||||
|
def __init__(self, server):
|
||||||
|
self._server = server
|
||||||
|
self._websocket = None
|
||||||
|
|
||||||
|
async def connect(self):
|
||||||
|
self._websocket = await websockets.connect(self._server)
|
||||||
|
|
||||||
|
async def close(self):
|
||||||
|
if self._websocket is not None and self._websocket.open is True:
|
||||||
|
await self.send(None)
|
||||||
|
await self._websocket.close()
|
||||||
|
|
||||||
|
async def receive(self):
|
||||||
|
try:
|
||||||
|
data = await self._websocket.recv()
|
||||||
|
except asyncio.IncompleteReadError:
|
||||||
|
return
|
||||||
|
ret = object_from_string(data)
|
||||||
|
if ret == None:
|
||||||
|
print("remote host says good bye!")
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
async def send(self, descr):
|
||||||
|
data = object_to_string(descr)
|
||||||
|
await self._websocket.send(data + '\n')
|
126
server.py
126
server.py
@@ -6,104 +6,94 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import ssl
|
import ssl
|
||||||
from typing import OrderedDict
|
from typing import OrderedDict
|
||||||
from aiohttp import web
|
from aiortc import RTCPeerConnection, RTCSessionDescription, RTCIceCandidate, RTCRtpCodecCapability, RTCConfiguration, RTCIceServer
|
||||||
from aiortc import RTCPeerConnection, RTCSessionDescription, RTCRtpCodecCapability
|
|
||||||
from compressed_vipc_track import VisionIpcTrack
|
from compressed_vipc_track import VisionIpcTrack
|
||||||
from desktop_stream_track import DesktopStreamTrack
|
from desktop_stream_track import DesktopStreamTrack
|
||||||
|
from aiortc.contrib.signaling import BYE
|
||||||
|
from secureput.secureput_signaling import SecureputSignaling
|
||||||
|
|
||||||
ROOT = os.path.dirname(__file__)
|
# optional, for better performance
|
||||||
|
# try:
|
||||||
|
# import uvloop
|
||||||
|
# except ImportError:
|
||||||
|
# uvloop = None
|
||||||
|
|
||||||
cams = ["roadEncodeData","wideRoadEncodeData","driverEncodeData"]
|
cams = ["roadEncodeData","wideRoadEncodeData","driverEncodeData"]
|
||||||
|
cam = 1
|
||||||
|
|
||||||
async def index(request):
|
async def signal(pc, signaling):
|
||||||
content = open(os.path.join(ROOT, "index.html"), "r").read()
|
await signaling.connect()
|
||||||
return web.Response(content_type="text/html", text=content)
|
|
||||||
|
|
||||||
async def javascript(request):
|
while True:
|
||||||
content = open(os.path.join(ROOT, "client.js"), "r").read()
|
obj = await signaling.receive()
|
||||||
return web.Response(content_type="application/javascript", text=content)
|
|
||||||
|
|
||||||
|
# The peer trickles, but aiortc doesn't https://github.com/aiortc/aiortc/issues/227
|
||||||
|
# > aioice, the library which aiortc uses for ICE does not trickle ICE candidates:
|
||||||
|
# > you get all the candidates in one go. As such once you have called setLocalDescription()
|
||||||
|
# > for your offer or answer, all your ICE candidates are listed in pc.localDescription.
|
||||||
|
if isinstance(obj, RTCIceCandidate):
|
||||||
|
pc.addIceCandidate(obj)
|
||||||
|
|
||||||
async def offer(request):
|
if isinstance(obj, RTCSessionDescription):
|
||||||
params = await request.json()
|
if pc != None and pc.iceConnectionState != "failed":
|
||||||
offer = RTCSessionDescription(sdp=params["sdp"], type=params["type"])
|
await pc.close()
|
||||||
|
pc = RTCPeerConnection(configuration=RTCConfiguration([RTCIceServer("stun:stun.secureput.com:3478")]))
|
||||||
pc = RTCPeerConnection()
|
|
||||||
pcs.add(pc)
|
|
||||||
|
|
||||||
@pc.on("connectionstatechange")
|
@pc.on("connectionstatechange")
|
||||||
async def on_connectionstatechange():
|
async def on_connectionstatechange():
|
||||||
print("Connection state is %s" % pc.connectionState)
|
print("Connection state is %s" % pc.iceConnectionState)
|
||||||
if pc.connectionState == "failed":
|
if pc.iceConnectionState == "failed":
|
||||||
await pc.close()
|
await pc.close()
|
||||||
pcs.discard(pc)
|
|
||||||
|
|
||||||
|
@pc.on('datachannel')
|
||||||
|
def on_datachannel(channel):
|
||||||
|
print("data channel!")
|
||||||
|
@channel.on('message')
|
||||||
|
async def on_message(message):
|
||||||
|
print("message!!")
|
||||||
|
|
||||||
|
await pc.setRemoteDescription(obj)
|
||||||
|
|
||||||
|
if obj.type == 'offer':
|
||||||
# TODO: stream the microphone
|
# TODO: stream the microphone
|
||||||
audio = None
|
audio = None
|
||||||
|
|
||||||
# video = VisionIpcTrack(cams[int(args.cam)], args.addr)
|
video = VisionIpcTrack(cams[int(cam)], "tici")
|
||||||
video = DesktopStreamTrack()
|
# video = DesktopStreamTrack()
|
||||||
|
pc.addTrack(video)
|
||||||
video_sender = pc.addTrack(video)
|
|
||||||
|
|
||||||
await pc.setRemoteDescription(offer)
|
|
||||||
|
|
||||||
answer = await pc.createAnswer()
|
answer = await pc.createAnswer()
|
||||||
await pc.setLocalDescription(answer)
|
await pc.setLocalDescription(answer)
|
||||||
|
await signaling.send(pc.localDescription)
|
||||||
return web.Response(
|
|
||||||
content_type="application/json",
|
|
||||||
text=json.dumps(
|
|
||||||
{"sdp": pc.localDescription.sdp, "type": pc.localDescription.type}
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
pcs = set()
|
|
||||||
|
|
||||||
|
|
||||||
async def on_shutdown(app):
|
|
||||||
# close peer connections
|
|
||||||
coros = [pc.close() for pc in pcs]
|
|
||||||
await asyncio.gather(*coros)
|
|
||||||
pcs.clear()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
parser = argparse.ArgumentParser(description="Decode video streams and broadcast via WebRTC")
|
parser = argparse.ArgumentParser(description="Comma Body WebRTC Service")
|
||||||
parser.add_argument("--addr", default='tici', help="Address of comma three")
|
parser.add_argument("--addr", default='tici', help="Address of comma three")
|
||||||
|
|
||||||
# Not implemented (yet?). Geo already made the PoC for this, it should be possible.
|
# Not implemented (yet?). Geo already made the PoC for this, it should be possible.
|
||||||
# parser.add_argument("--nvidia", action="store_true", help="Use nvidia instead of ffmpeg")
|
# parser.add_argument("--nvidia", action="store_true", help="Use nvidia instead of ffmpeg")
|
||||||
|
|
||||||
parser.add_argument("--cam", default="0", help="Camera to stream")
|
parser.add_argument("--signaling-server", default="wss://signal.secureput.com", help="Signaling server to use")
|
||||||
|
parser.add_argument("--stun-server", default="stun:stun.secureput.com:3478", help="STUN server to use")
|
||||||
parser.add_argument("--cert-file", help="SSL certificate file (for HTTPS)")
|
|
||||||
parser.add_argument("--key-file", help="SSL key file (for HTTPS)")
|
|
||||||
parser.add_argument(
|
|
||||||
"--host", default="0.0.0.0", help="Host for HTTP server (default: 0.0.0.0)"
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--port", type=int, default=8080, help="Port for HTTP server (default: 8080)"
|
|
||||||
)
|
|
||||||
parser.add_argument("--verbose", "-v", action="count")
|
parser.add_argument("--verbose", "-v", action="count")
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
if args.verbose:
|
if args.verbose:
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
else:
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
|
||||||
|
|
||||||
if args.cert_file:
|
# if uvloop is not None:
|
||||||
ssl_context = ssl.SSLContext()
|
# asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
|
||||||
ssl_context.load_cert_chain(args.cert_file, args.key_file)
|
|
||||||
else:
|
|
||||||
ssl_context = None
|
|
||||||
|
|
||||||
app = web.Application()
|
pc = RTCPeerConnection(configuration=RTCConfiguration([RTCIceServer(args.stun_server)]))
|
||||||
app.on_shutdown.append(on_shutdown)
|
signaling = SecureputSignaling(args.signaling_server)
|
||||||
app.router.add_get("/", index)
|
|
||||||
app.router.add_get("/client.js", javascript)
|
coro = signal(pc, signaling)
|
||||||
app.router.add_post("/offer", offer)
|
|
||||||
web.run_app(app, host=args.host, port=args.port, ssl_context=ssl_context)
|
# run event loop
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
try:
|
||||||
|
loop.run_until_complete(coro)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
loop.run_until_complete(pc.close())
|
||||||
|
loop.run_until_complete(signaling.close())
|
217
signaling.py
217
signaling.py
@@ -1,217 +0,0 @@
|
|||||||
import asyncio
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from aiortc import RTCIceCandidate, RTCSessionDescription
|
|
||||||
from aiortc.sdp import candidate_from_sdp, candidate_to_sdp
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
BYE = object()
|
|
||||||
|
|
||||||
|
|
||||||
def object_from_string(message_str):
|
|
||||||
message = json.loads(message_str)
|
|
||||||
if message["type"] in ["answer", "offer"]:
|
|
||||||
return RTCSessionDescription(**message)
|
|
||||||
elif message["type"] == "candidate" and message["candidate"]:
|
|
||||||
candidate = candidate_from_sdp(message["candidate"].split(":", 1)[1])
|
|
||||||
candidate.sdpMid = message["id"]
|
|
||||||
candidate.sdpMLineIndex = message["label"]
|
|
||||||
return candidate
|
|
||||||
elif message["type"] == "bye":
|
|
||||||
return BYE
|
|
||||||
|
|
||||||
|
|
||||||
def object_to_string(obj):
|
|
||||||
if isinstance(obj, RTCSessionDescription):
|
|
||||||
message = {"sdp": obj.sdp, "type": obj.type}
|
|
||||||
elif isinstance(obj, RTCIceCandidate):
|
|
||||||
message = {
|
|
||||||
"candidate": "candidate:" + candidate_to_sdp(obj),
|
|
||||||
"id": obj.sdpMid,
|
|
||||||
"label": obj.sdpMLineIndex,
|
|
||||||
"type": "candidate",
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
assert obj is BYE
|
|
||||||
message = {"type": "bye"}
|
|
||||||
return json.dumps(message, sort_keys=True)
|
|
||||||
|
|
||||||
|
|
||||||
class CopyAndPasteSignaling:
|
|
||||||
def __init__(self):
|
|
||||||
self._read_pipe = sys.stdin
|
|
||||||
self._read_transport = None
|
|
||||||
self._reader = None
|
|
||||||
self._write_pipe = sys.stdout
|
|
||||||
|
|
||||||
async def connect(self):
|
|
||||||
loop = asyncio.get_event_loop()
|
|
||||||
self._reader = asyncio.StreamReader(loop=loop)
|
|
||||||
self._read_transport, _ = await loop.connect_read_pipe(
|
|
||||||
lambda: asyncio.StreamReaderProtocol(self._reader), self._read_pipe
|
|
||||||
)
|
|
||||||
|
|
||||||
async def close(self):
|
|
||||||
if self._reader is not None:
|
|
||||||
await self.send(BYE)
|
|
||||||
self._read_transport.close()
|
|
||||||
self._reader = None
|
|
||||||
|
|
||||||
async def receive(self):
|
|
||||||
print("-- Please enter a message from remote party --")
|
|
||||||
data = await self._reader.readline()
|
|
||||||
print()
|
|
||||||
return object_from_string(data.decode(self._read_pipe.encoding))
|
|
||||||
|
|
||||||
async def send(self, descr):
|
|
||||||
print("-- Please send this message to the remote party --")
|
|
||||||
self._write_pipe.write(object_to_string(descr) + "\n")
|
|
||||||
self._write_pipe.flush()
|
|
||||||
print()
|
|
||||||
|
|
||||||
|
|
||||||
class TcpSocketSignaling:
|
|
||||||
def __init__(self, host, port):
|
|
||||||
self._host = host
|
|
||||||
self._port = port
|
|
||||||
self._server = None
|
|
||||||
self._reader = None
|
|
||||||
self._writer = None
|
|
||||||
|
|
||||||
async def connect(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
async def _connect(self, server):
|
|
||||||
if self._writer is not None:
|
|
||||||
return
|
|
||||||
|
|
||||||
if server:
|
|
||||||
connected = asyncio.Event()
|
|
||||||
|
|
||||||
def client_connected(reader, writer):
|
|
||||||
self._reader = reader
|
|
||||||
self._writer = writer
|
|
||||||
connected.set()
|
|
||||||
|
|
||||||
self._server = await asyncio.start_server(
|
|
||||||
client_connected, host=self._host, port=self._port
|
|
||||||
)
|
|
||||||
await connected.wait()
|
|
||||||
else:
|
|
||||||
self._reader, self._writer = await asyncio.open_connection(
|
|
||||||
host=self._host, port=self._port
|
|
||||||
)
|
|
||||||
|
|
||||||
async def close(self):
|
|
||||||
if self._writer is not None:
|
|
||||||
await self.send(BYE)
|
|
||||||
self._writer.close()
|
|
||||||
self._reader = None
|
|
||||||
self._writer = None
|
|
||||||
if self._server is not None:
|
|
||||||
self._server.close()
|
|
||||||
self._server = None
|
|
||||||
|
|
||||||
async def receive(self):
|
|
||||||
await self._connect(False)
|
|
||||||
try:
|
|
||||||
data = await self._reader.readuntil()
|
|
||||||
except asyncio.IncompleteReadError:
|
|
||||||
return
|
|
||||||
return object_from_string(data.decode("utf8"))
|
|
||||||
|
|
||||||
async def send(self, descr):
|
|
||||||
await self._connect(True)
|
|
||||||
data = object_to_string(descr).encode("utf8")
|
|
||||||
self._writer.write(data + b"\n")
|
|
||||||
|
|
||||||
|
|
||||||
class UnixSocketSignaling:
|
|
||||||
def __init__(self, path):
|
|
||||||
self._path = path
|
|
||||||
self._server = None
|
|
||||||
self._reader = None
|
|
||||||
self._writer = None
|
|
||||||
|
|
||||||
async def connect(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
async def _connect(self, server):
|
|
||||||
if self._writer is not None:
|
|
||||||
return
|
|
||||||
|
|
||||||
if server:
|
|
||||||
connected = asyncio.Event()
|
|
||||||
|
|
||||||
def client_connected(reader, writer):
|
|
||||||
self._reader = reader
|
|
||||||
self._writer = writer
|
|
||||||
connected.set()
|
|
||||||
|
|
||||||
self._server = await asyncio.start_unix_server(
|
|
||||||
client_connected, path=self._path
|
|
||||||
)
|
|
||||||
await connected.wait()
|
|
||||||
else:
|
|
||||||
self._reader, self._writer = await asyncio.open_unix_connection(self._path)
|
|
||||||
|
|
||||||
async def close(self):
|
|
||||||
if self._writer is not None:
|
|
||||||
await self.send(BYE)
|
|
||||||
self._writer.close()
|
|
||||||
self._reader = None
|
|
||||||
self._writer = None
|
|
||||||
if self._server is not None:
|
|
||||||
self._server.close()
|
|
||||||
self._server = None
|
|
||||||
os.unlink(self._path)
|
|
||||||
|
|
||||||
async def receive(self):
|
|
||||||
await self._connect(False)
|
|
||||||
try:
|
|
||||||
data = await self._reader.readuntil()
|
|
||||||
except asyncio.IncompleteReadError:
|
|
||||||
return
|
|
||||||
return object_from_string(data.decode("utf8"))
|
|
||||||
|
|
||||||
async def send(self, descr):
|
|
||||||
await self._connect(True)
|
|
||||||
data = object_to_string(descr).encode("utf8")
|
|
||||||
self._writer.write(data + b"\n")
|
|
||||||
|
|
||||||
|
|
||||||
def add_signaling_arguments(parser):
|
|
||||||
"""
|
|
||||||
Add signaling method arguments to an argparse.ArgumentParser.
|
|
||||||
"""
|
|
||||||
parser.add_argument(
|
|
||||||
"--signaling",
|
|
||||||
"-s",
|
|
||||||
choices=["copy-and-paste", "tcp-socket", "unix-socket"],
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--signaling-host", default="127.0.0.1", help="Signaling host (tcp-socket only)"
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--signaling-port", default=1234, help="Signaling port (tcp-socket only)"
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--signaling-path",
|
|
||||||
default="aiortc.socket",
|
|
||||||
help="Signaling socket path (unix-socket only)",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def create_signaling(args):
|
|
||||||
"""
|
|
||||||
Create a signaling method based on command-line arguments.
|
|
||||||
"""
|
|
||||||
if args.signaling == "tcp-socket":
|
|
||||||
return TcpSocketSignaling(args.signaling_host, args.signaling_port)
|
|
||||||
elif args.signaling == "unix-socket":
|
|
||||||
return UnixSocketSignaling(args.signaling_path)
|
|
||||||
else:
|
|
||||||
return CopyAndPasteSignaling()
|
|
Reference in New Issue
Block a user