import asyncio
import json
import os
import re
import socket
import ssl
import subprocess
import sys
import tempfile
import traceback
import unittest
import uuid

import websockets


eventloop = None


async def get_ws(cls, proto):
    for _ in range(1, 300):
        try:
            cls._ws = await websockets.connect(
                "ws://127.0.0.1:9191/", subprotocols=[proto]
            )
            break
        except:
            await asyncio.sleep(0.1)


async def testIO(self, msg):
    await self._ws.send(msg)
    self._res = await asyncio.wait_for(self._ws.recv(), timeout=10)


async def testIOJson(self, msg):
    await self._ws.send(json.dumps(msg))
    self._res = await asyncio.wait_for(self._ws.recv(), timeout=10)
    self._res = json.loads(self._res)


async def testIJson(self):
    self._res = await asyncio.wait_for(self._ws.recv(), timeout=10)
    self._res = json.loads(self._res)


async def testIJanus(self):
    self._res = await asyncio.wait_for(self._ws.recv(), timeout=10)
    self._res = json.loads(self._res)
    self.assertEqual(self._res["transaction"], self._trans)
    del self._res["transaction"]


async def testIOJanus(self, msg):
    trans = str(uuid.uuid4())
    msg["transaction"] = trans
    self._trans = trans
    await self._ws.send(json.dumps(msg))
    await testIJanus(self)


async def testOJanus(self, msg):
    trans = str(uuid.uuid4())
    msg["transaction"] = trans
    self._trans = trans
    await self._ws.send(json.dumps(msg))


class TestWSEcho(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        eventloop.run_until_complete(get_ws(cls, "echo.rtpengine.com"))

    @classmethod
    def tearDownClass(cls):
        eventloop.run_until_complete(cls._ws.close())

    def testEcho(self):
        eventloop.run_until_complete(testIO(self, b"foobar"))
        self.assertEqual(self._res, b"foobar")

    def testEchoText(self):
        eventloop.run_until_complete(testIO(self, "foobar"))
        self.assertEqual(self._res, b"foobar")


class TestWSCli(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        eventloop.run_until_complete(get_ws(cls, "cli.rtpengine.com"))

    @classmethod
    def tearDownClass(cls):
        eventloop.run_until_complete(cls._ws.close())

    def testListNumsessions(self):
        # race condition here if this runs at the same as the janus test (creates call)
        eventloop.run_until_complete(testIO(self, "list numsessions"))
        self.assertEqual(
            self._res,
            b"Current sessions own: 0\n"
            + b"Current sessions foreign: 0\n"
            + b"Current sessions total: 0\n"
            + b"Current transcoded media: 0\n"
            + b"Current sessions ipv4 only media: 0\n"
            + b"Current sessions ipv6 only media: 0\n"
            + b"Current sessions ip mixed  media: 0\n",
        )


class TestWSJanus(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        eventloop.run_until_complete(get_ws(cls, "janus-protocol"))

    @classmethod
    def tearDownClass(cls):
        eventloop.run_until_complete(cls._ws.close())

    def testPing(self):
        eventloop.run_until_complete(
            testIOJson(self, {"janus": "ping", "transaction": "test123"})
        )
        self.assertEqual(self._res, {"janus": "pong", "transaction": "test123"})

    def testPingNoTS(self):
        eventloop.run_until_complete(testIOJson(self, {"janus": "ping"}))
        self.assertEqual(
            self._res,
            {
                "janus": "error",
                "error": {
                    "code": 456,
                    "reason": "JSON object does not contain 'transaction' key",
                },
            },
        )

    def testInfo(self):
        eventloop.run_until_complete(
            testIOJson(self, {"janus": "info", "transaction": "foobar"})
        )
        # ignore version string
        self.assertTrue("version_string" in self._res)
        del self._res["version_string"]
        self.assertEqual(
            self._res,
            {
                "janus": "server_info",
                "name": "rtpengine Janus interface",
                "plugins": {
                    "janus.plugin.videoroom": {"name": "rtpengine Janus videoroom"}
                },
                "transaction": "foobar",
            },
        )


class TestVideoroom(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        eventloop.run_until_complete(get_ws(cls, "janus-protocol"))

    @classmethod
    def tearDownClass(cls):
        eventloop.run_until_complete(cls._ws.close())

    def startSession(self):
        self.maxDiff = None

        token = str(uuid.uuid4())

        eventloop.run_until_complete(
            testIOJanus(
                self,
                {
                    "janus": "add_token",
                    "token": token,
                    "admin_secret": "dfgdfgdvgLyATjHPvckg",
                },
            )
        )
        self.assertEqual(
            self._res,
            {"janus": "success", "data": {"plugins": ["janus.plugin.videoroom"]}},
        )

        # create session
        eventloop.run_until_complete(
            testIOJanus(
                self,
                {
                    "janus": "create",
                    "token": token,
                    "admin_secret": "dfgdfgdvgLyATjHPvckg",
                },
            )
        )
        session = self._res["data"]["id"]
        self.assertIsInstance(session, int)
        self.assertEqual(self._res, {"janus": "success", "data": {"id": session}})

        return (token, session)

    def startVideoroom(self):
        (token, session) = self.startSession()

        handle = self.createHandle(token, session)

        # create room
        eventloop.run_until_complete(
            testIOJanus(
                self,
                {
                    "janus": "message",
                    "body": {"request": "create", "publishers": 16},
                    "handle_id": handle,
                    "session_id": session,
                    "token": token,
                },
            )
        )
        room = self._res["plugindata"]["data"]["room"]
        self.assertIsInstance(room, int)
        self.assertNotEqual(room, handle)
        self.assertNotEqual(room, session)
        self.assertEqual(
            self._res,
            {
                "janus": "success",
                "session_id": session,
                "sender": handle,
                "plugindata": {
                    "plugin": "janus.plugin.videoroom",
                    "data": {
                        "videoroom": "created",
                        "room": room,
                        "permanent": False,
                    },
                },
            },
        )

        return (token, session, handle, room)

    def destroyVideoroom(self, token, session, handle, room):
        eventloop.run_until_complete(
            testIOJanus(
                self,
                {
                    "janus": "message",
                    "body": {"request": "destroy", "room": room},
                    "handle_id": handle,
                    "session_id": session,
                    "token": token,
                },
            )
        )
        self.assertNotEqual(room, handle)
        self.assertNotEqual(room, session)
        self.assertEqual(
            self._res,
            {
                "janus": "success",
                "session_id": session,
                "sender": handle,
                "plugindata": {
                    "plugin": "janus.plugin.videoroom",
                    "data": {
                        "videoroom": "destroyed",
                        "room": room,
                        "permanent": False,
                    },
                },
            },
        )

    def createHandle(self, token, session):
        eventloop.run_until_complete(
            testIOJanus(
                self,
                {
                    "janus": "attach",
                    "plugin": "janus.plugin.videoroom",
                    "session_id": session,
                    "token": token,
                    "opaque_id": None,
                },
            )
        )
        handle = self._res["data"]["id"]
        self.assertIsInstance(handle, int)
        self.assertNotEqual(handle, session)
        self.assertEqual(
            self._res,
            {"janus": "success", "session_id": session, "data": {"id": handle}},
        )

        return handle

    def createPublisher(self, token, session, room, handle, pubs=[]):
        eventloop.run_until_complete(
            testIOJanus(
                self,
                {
                    "janus": "message",
                    "body": {"request": "join", "ptype": "publisher", "room": room},
                    "handle_id": handle,
                    "session_id": session,
                    "token": token,
                },
            )
        )
        # ack is received first
        self.assertEqual(self._res, {"janus": "ack", "session_id": session})
        # followed by the joined event
        eventloop.run_until_complete(testIJanus(self))
        feed = self._res["plugindata"]["data"]["id"]
        self.assertIsInstance(feed, int)
        self.assertNotEqual(feed, session)
        self.assertNotEqual(feed, room)
        self.assertNotEqual(feed, handle)
        self.assertEqual(
            self._res,
            {
                "janus": "event",
                "session_id": session,
                "sender": handle,
                "plugindata": {
                    "plugin": "janus.plugin.videoroom",
                    "data": {
                        "videoroom": "joined",
                        "room": room,
                        "id": feed,
                        "publishers": pubs,
                    },
                },
            },
        )

        return feed

    def testKeepalive(self):
        (token, session) = self.startSession()

        eventloop.run_until_complete(
            testIOJanus(
                self, {"janus": "keepalive", "token": token, "session_id": session}
            )
        )
        self.assertEqual(self._res, {"janus": "ack", "session_id": session})

    def testVideoroomWebRTC(self):
        (token, session, control_handle, room) = self.startVideoroom()

        # timeout test
        eventloop.run_until_complete(asyncio.sleep(3))

        eventloop.run_until_complete(
            testIOJanus(
                self,
                {
                    "janus": "message",
                    "body": {
                        "request": "exists",
                        "room": room,
                    },
                    "handle_id": control_handle,
                    "session_id": session,
                    "token": token,
                },
            )
        )
        self.assertEqual(
            self._res,
            {
                "janus": "success",
                "session_id": session,
                "sender": control_handle,
                "plugindata": {
                    "plugin": "janus.plugin.videoroom",
                    "data": {
                        "videoroom": "success",
                        "room": room,
                        "exists": True,
                    },
                },
            },
        )

        pub_handle = self.createHandle(token, session)
        self.assertNotEqual(pub_handle, control_handle)

        feed = self.createPublisher(token, session, room, pub_handle)
        self.assertNotEqual(feed, control_handle)

        # publish as plain RTP
        eventloop.run_until_complete(
            testIOJanus(
                self,
                {
                    "janus": "message",
                    "body": {
                        "request": "configure",
                        "room": room,
                        "feed": feed,
                        "data": False,
                        "audio": True,
                        "video": True,
                    },
                    "jsep": {
                        "type": "offer",
                        "sdp": (
                            "v=0\r\n"
                            "o=x 123 123 IN IP4 203.0.113.3\r\n"
                            "c=IN IP4 203.0.113.2\r\n"
                            "s=foobar\r\n"
                            "t=0 0\r\n"
                            "m=audio 8000 RTP/AVP 8 0\r\n"
                            "a=sendonly\r\n"
                        ),
                    },
                    "handle_id": pub_handle,
                    "session_id": session,
                    "token": token,
                },
            )
        )
        # ack is received first
        self.assertEqual(self._res, {"janus": "ack", "session_id": session})
        # followed by the event notification
        eventloop.run_until_complete(testIJanus(self))
        sdp = self._res["jsep"]["sdp"]
        self.assertIsInstance(sdp, str)
        self.assertRegex(
            sdp,
            re.compile(
                "^v=0\r\n"
                "o=- \d+ \d+ IN IP4 203.0.113.1\r\n"
                "s=rtpengine.*?\r\n"
                "t=0 0\r\n"
                "m=audio \d+ RTP/AVP 8\r\n"
                "c=IN IP4 203.0.113.1\r\n"
                "a=rtpmap:8 PCMA/8000\r\n"
                "a=recvonly\r\n"
                "a=rtcp:\d+\r\n$",
                re.DOTALL,
            ),
        )
        self.assertEqual(
            self._res,
            {
                "janus": "event",
                "session_id": session,
                "sender": pub_handle,
                "plugindata": {
                    "plugin": "janus.plugin.videoroom",
                    "data": {
                        "videoroom": "event",
                        "room": room,
                        "configured": "ok",
                        "audio_codec": "PCMA",
                    },
                },
                "jsep": {"type": "answer", "sdp": sdp},
            },
        )

        sub_handle = self.createHandle(token, session)
        self.assertNotEqual(sub_handle, pub_handle)
        self.assertNotEqual(sub_handle, control_handle)

        # subscriber expects full WebRTC attributes
        eventloop.run_until_complete(
            testIOJanus(
                self,
                {
                    "janus": "message",
                    "body": {
                        "request": "join",
                        "ptype": "subscriber",
                        "room": room,
                        "feed": feed,
                    },
                    "handle_id": sub_handle,
                    "session_id": session,
                    "token": token,
                },
            )
        )
        # ack is received first
        self.assertEqual(self._res, {"janus": "ack", "session_id": session})
        # followed by the attached event
        eventloop.run_until_complete(testIJanus(self))
        self.assertEqual(feed, self._res["plugindata"]["data"]["id"])
        self.assertNotEqual(feed, control_handle)
        self.assertNotEqual(feed, session)
        self.assertNotEqual(feed, room)
        self.assertNotEqual(feed, pub_handle)
        self.assertNotEqual(feed, sub_handle)
        sdp = self._res["jsep"]["sdp"]
        self.assertIsInstance(sdp, str)
        self.assertRegex(
            sdp,
            re.compile(
                "^v=0\r\n"
                "o=x 123 123 IN IP4 203.0.113.3\r\n"
                "c=IN IP4 203.0.113.1\r\n"
                "s=foobar\r\n"
                "t=0 0\r\n"
                "m=audio \d+ UDP/TLS/RTP/SAVPF 8\r\n"
                "a=mid:1\r\n"
                "a=rtpmap:8 PCMA/8000\r\n"
                "a=sendonly\r\n"
                "a=rtcp-mux\r\n"
                "a=setup:actpass\r\n"
                "a=fingerprint:sha-256 .{95}\r\n"
                "a=ice-ufrag:.{8}\r\n"
                "a=ice-pwd:.{26}\r\n"
                "a=ice-options:trickle\r\n"
                "a=candidate:.{16} 1 UDP 2130706431 203.0.113.1 \d+ typ host\r\n"
                "a=end-of-candidates\r\n$",
                re.DOTALL,
            ),
        )
        self.assertEqual(
            self._res,
            {
                "janus": "event",
                "session_id": session,
                "sender": sub_handle,
                "plugindata": {
                    "plugin": "janus.plugin.videoroom",
                    "data": {
                        "videoroom": "attached",
                        "room": room,
                        "id": feed,
                    },
                },
                "jsep": {"type": "offer", "sdp": sdp},
            },
        )

        # subscriber #1 answer
        eventloop.run_until_complete(
            testIOJanus(
                self,
                {
                    "janus": "message",
                    "body": {"request": "start", "room": room, "feed": feed},
                    "jsep": {
                        "type": "answer",
                        "sdp": (
                            "v=0\r\n"
                            "o=x 123 123 IN IP4 203.0.113.2\r\n"
                            "c=IN IP4 0.0.0.0\r\n"
                            "s=foobar\r\n"
                            "t=0 0\r\n"
                            "m=audio 9 RTP/AVP 8\r\n"
                            "a=mid:audio\r\n"
                            "a=ice-ufrag:abcd\r\n"
                            "a=ice-pwd:WD1pLsdgsdfsdWuEBb0vjyZr\r\n"
                            "a=ice-options:trickle\r\n"
                            "a=rtcp-mux\r\n"
                            "a=recvonly\r\n"
                        ),
                    },
                    "handle_id": sub_handle,
                    "session_id": session,
                    "token": token,
                },
            )
        )
        # ack is received first
        self.assertEqual(self._res, {"janus": "ack", "session_id": session})
        # followed by the attached event
        eventloop.run_until_complete(testIJanus(self))
        self.assertEqual(
            self._res,
            {
                "janus": "event",
                "session_id": session,
                "sender": sub_handle,
                "plugindata": {
                    "plugin": "janus.plugin.videoroom",
                    "data": {
                        "videoroom": "event",
                        "started": "ok",
                        "room": room,
                    },
                },
            },
        )

        self.destroyVideoroom(token, session, control_handle, room)

        eventloop.run_until_complete(
            testIOJanus(
                self,
                {
                    "janus": "message",
                    "body": {
                        "request": "exists",
                        "room": room,
                    },
                    "handle_id": control_handle,
                    "session_id": session,
                    "token": token,
                },
            )
        )
        self.assertEqual(
            self._res,
            {
                "janus": "success",
                "session_id": session,
                "sender": control_handle,
                "plugindata": {
                    "plugin": "janus.plugin.videoroom",
                    "data": {
                        "videoroom": "success",
                        "room": room,
                        "exists": False,
                    },
                },
            },
        )

    def testVideoroomSDESDTLS(self):
        (token, session, control_handle, room) = self.startVideoroom()

        pub_handle = self.createHandle(token, session)
        self.assertNotEqual(pub_handle, control_handle)

        feed = self.createPublisher(token, session, room, pub_handle)
        self.assertNotEqual(feed, control_handle)

        eventloop.run_until_complete(
            testIOJanus(
                self,
                {
                    "janus": "message",
                    "body": {
                        "request": "configure",
                        "room": room,
                        "feed": feed,
                        "data": False,
                        "audio": True,
                        "video": True,
                    },
                    "jsep": {
                        "type": "offer",
                        "sdp": (
                            "v=0\r\n"
                            "o=x 123 123 IN IP4 203.0.113.5\r\n"
                            "c=IN IP4 203.0.113.4\r\n"
                            "s=foobar\r\n"
                            "t=0 0\r\n"
                            "m=audio 30000 RTP/SAVP 8 0 96\r\n"
                            "a=rtpmap:96 opus/48000\r\n"
                            "a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:cJOJ7kxQjhFBp2fP6AYjs3vKw7CeBdWZCj0isbJv\r\n"
                            "a=crypto:2 AES_CM_128_HMAC_SHA1_32 inline:VAzLKvoE3jG9cdH/AZsl/ZqWNXrUzyM4Gw6chrFr\r\n"
                            "a=crypto:3 AES_256_CM_HMAC_SHA1_80 inline:8AbZePWwsKhLGX3GlXA+yHYPQ3cgraer/9DkFJYCOPZZy3o9wC0NIbIFYZfyHw==\r\n"
                            "a=crypto:4 AES_256_CM_HMAC_SHA1_32 inline:2GLk3p/csdno4KlGO1TxCVaEt+bifmDlQ5NjnCb5cJYPURiGRSTBEtEq37db8g==\r\n"
                            "a=fingerprint:sha-256 1A:20:98:16:CA:26:8C:33:62:0B:70:94:73:A0:9B:30:00:1A:EA:26:FC:7D:84:8B:F1:F9:52:2D:A7:92:C5:3D\r\n"
                            "a=setup:actpass\r\n"
                            "a=sendonly\r\n"
                        ),
                    },
                    "handle_id": pub_handle,
                    "session_id": session,
                    "token": token,
                },
            )
        )
        # ack is received first
        self.assertEqual(self._res, {"janus": "ack", "session_id": session})
        # followed by the event notification
        eventloop.run_until_complete(testIJanus(self))
        sdp = self._res["jsep"]["sdp"]
        self.assertIsInstance(sdp, str)
        self.assertRegex(
            sdp,
            re.compile(
                "^v=0\r\n"
                "o=- \d+ \d+ IN IP4 203.0.113.1\r\n"
                "s=rtpengine.*?\r\n"
                "t=0 0\r\n"
                "m=audio \d+ RTP/SAVP 8\r\n"
                "c=IN IP4 203.0.113.1\r\n"
                "a=rtpmap:8 PCMA/8000\r\n"
                "a=recvonly\r\n"
                "a=rtcp:\d+\r\n"
                "a=setup:active\r\n"
                "a=fingerprint:sha-256 .{95}\r\n$",
                re.DOTALL,
            ),
        )
        self.assertEqual(
            self._res,
            {
                "janus": "event",
                "session_id": session,
                "sender": pub_handle,
                "plugindata": {
                    "plugin": "janus.plugin.videoroom",
                    "data": {
                        "videoroom": "event",
                        "room": room,
                        "configured": "ok",
                        "audio_codec": "PCMA",
                    },
                },
                "jsep": {"type": "answer", "sdp": sdp},
            },
        )

        self.destroyVideoroom(token, session, control_handle, room)

    def testVideoroomSDES(self):
        (token, session, control_handle, room) = self.startVideoroom()

        pub_handle = self.createHandle(token, session)
        self.assertNotEqual(pub_handle, control_handle)

        feed = self.createPublisher(token, session, room, pub_handle)
        self.assertNotEqual(feed, control_handle)

        eventloop.run_until_complete(
            testIOJanus(
                self,
                {
                    "janus": "message",
                    "body": {
                        "request": "configure",
                        "room": room,
                        "feed": feed,
                        "data": False,
                        "audio": True,
                        "video": True,
                    },
                    "jsep": {
                        "type": "offer",
                        "sdp": (
                            "v=0\r\n"
                            "o=x 123 123 IN IP4 203.0.113.5\r\n"
                            "c=IN IP4 203.0.113.4\r\n"
                            "s=foobar\r\n"
                            "t=0 0\r\n"
                            "m=audio 30000 RTP/SAVP 8 0 96\r\n"
                            "a=rtpmap:96 opus/48000\r\n"
                            "a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:cJOJ7kxQjhFBp2fP6AYjs3vKw7CeBdWZCj0isbJv\r\n"
                            "a=crypto:2 AES_CM_128_HMAC_SHA1_32 inline:VAzLKvoE3jG9cdH/AZsl/ZqWNXrUzyM4Gw6chrFr\r\n"
                            "a=crypto:3 AES_256_CM_HMAC_SHA1_80 inline:8AbZePWwsKhLGX3GlXA+yHYPQ3cgraer/9DkFJYCOPZZy3o9wC0NIbIFYZfyHw==\r\n"
                            "a=crypto:4 AES_256_CM_HMAC_SHA1_32 inline:2GLk3p/csdno4KlGO1TxCVaEt+bifmDlQ5NjnCb5cJYPURiGRSTBEtEq37db8g==\r\n"
                            "a=sendonly\r\n"
                        ),
                    },
                    "handle_id": pub_handle,
                    "session_id": session,
                    "token": token,
                },
            )
        )
        # ack is received first
        self.assertEqual(self._res, {"janus": "ack", "session_id": session})
        # followed by the event notification
        eventloop.run_until_complete(testIJanus(self))
        sdp = self._res["jsep"]["sdp"]
        self.assertIsInstance(sdp, str)
        self.assertRegex(
            sdp,
            re.compile(
                "^v=0\r\n"
                "o=- \d+ \d+ IN IP4 203.0.113.1\r\n"
                "s=rtpengine.*?\r\n"
                "t=0 0\r\n"
                "m=audio \d+ RTP/SAVP 8\r\n"
                "c=IN IP4 203.0.113.1\r\n"
                "a=rtpmap:8 PCMA/8000\r\n"
                "a=recvonly\r\n"
                "a=rtcp:\d+\r\n"
                "a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:.{40}\r\n",
                re.DOTALL,
            ),
        )
        self.assertEqual(
            self._res,
            {
                "janus": "event",
                "session_id": session,
                "sender": pub_handle,
                "plugindata": {
                    "plugin": "janus.plugin.videoroom",
                    "data": {
                        "videoroom": "event",
                        "room": room,
                        "configured": "ok",
                        "audio_codec": "PCMA",
                    },
                },
                "jsep": {"type": "answer", "sdp": sdp},
            },
        )

        self.destroyVideoroom(token, session, control_handle, room)

    def testVideoroomDTLS(self):
        (token, session, control_handle, room) = self.startVideoroom()

        pub_handle = self.createHandle(token, session)
        self.assertNotEqual(pub_handle, control_handle)

        feed = self.createPublisher(token, session, room, pub_handle)
        self.assertNotEqual(feed, control_handle)

        eventloop.run_until_complete(
            testIOJanus(
                self,
                {
                    "janus": "message",
                    "body": {
                        "request": "configure",
                        "room": room,
                        "feed": feed,
                        "data": False,
                        "audio": True,
                        "video": True,
                    },
                    "jsep": {
                        "type": "offer",
                        "sdp": (
                            "v=0\r\n"
                            "o=x 123 123 IN IP4 203.0.113.5\r\n"
                            "c=IN IP4 203.0.113.4\r\n"
                            "s=foobar\r\n"
                            "t=0 0\r\n"
                            "m=audio 30000 UDP/TLS/RTP/SAVPF 8 0 96\r\n"
                            "a=mid:audio\r\n"
                            "a=rtpmap:96 opus/48000\r\n"
                            "a=fingerprint:sha-256 1A:20:98:16:CA:26:8C:33:62:0B:70:94:73:A0:9B:30:00:1A:EA:26:FC:7D:84:8B:F1:F9:52:2D:A7:92:C5:3D\r\n"
                            "a=setup:actpass\r\n"
                            "a=sendonly\r\n"
                        ),
                    },
                    "handle_id": pub_handle,
                    "session_id": session,
                    "token": token,
                },
            )
        )
        # ack is received first
        self.assertEqual(self._res, {"janus": "ack", "session_id": session})
        # followed by the event notification
        eventloop.run_until_complete(testIJanus(self))
        sdp = self._res["jsep"]["sdp"]
        self.assertIsInstance(sdp, str)
        self.assertRegex(
            sdp,
            re.compile(
                "^v=0\r\n"
                "o=- \d+ \d+ IN IP4 203.0.113.1\r\n"
                "s=rtpengine.*?\r\n"
                "t=0 0\r\n"
                "m=audio \d+ UDP/TLS/RTP/SAVPF 8\r\n"
                "c=IN IP4 203.0.113.1\r\n"
                "a=mid:audio\r\n"
                "a=rtpmap:8 PCMA/8000\r\n"
                "a=recvonly\r\n"
                "a=rtcp:\d+\r\n"
                "a=setup:active\r\n"
                "a=fingerprint:sha-256 .{95}\r\n$",
                re.DOTALL,
            ),
        )
        self.assertEqual(
            self._res,
            {
                "janus": "event",
                "session_id": session,
                "sender": pub_handle,
                "plugindata": {
                    "plugin": "janus.plugin.videoroom",
                    "data": {
                        "videoroom": "event",
                        "room": room,
                        "configured": "ok",
                        "audio_codec": "PCMA",
                    },
                },
                "jsep": {"type": "answer", "sdp": sdp},
            },
        )

        self.destroyVideoroom(token, session, control_handle, room)

    def testVideoroomWebrtcup(self):
        (token, session, control_handle, room) = self.startVideoroom()

        pub_handle = self.createHandle(token, session)
        self.assertNotEqual(pub_handle, control_handle)

        feed = self.createPublisher(token, session, room, pub_handle)
        self.assertNotEqual(feed, control_handle)

        eventloop.run_until_complete(
            testIOJanus(
                self,
                {
                    "janus": "message",
                    "body": {
                        "request": "configure",
                        "room": room,
                        "feed": feed,
                        "data": False,
                        "audio": True,
                        "video": True,
                    },
                    "jsep": {
                        "type": "offer",
                        "sdp": (
                            "v=0\r\n"
                            "o=x 123 123 IN IP4 203.0.113.4\r\n"
                            "c=IN IP4 203.0.113.4\r\n"
                            "s=foobar\r\n"
                            "t=0 0\r\n"
                            "m=audio 30000 RTP/AVP 8 0 96\r\n"
                            "a=mid:audio\r\n"
                            "a=rtpmap:96 opus/48000\r\n"
                            "a=sendonly\r\n"
                        ),
                    },
                    "handle_id": pub_handle,
                    "session_id": session,
                    "token": token,
                },
            )
        )
        # ack is received first
        self.assertEqual(self._res, {"janus": "ack", "session_id": session})
        # followed by the event notification
        eventloop.run_until_complete(testIJanus(self))
        sdp = self._res["jsep"]["sdp"]
        self.assertIsInstance(sdp, str)
        match_re = re.compile(
            "^v=0\r\n"
            "o=- \d+ \d+ IN IP4 203.0.113.1\r\n"
            "s=rtpengine.*?\r\n"
            "t=0 0\r\n"
            "m=audio (\d+) RTP/AVP 8\r\n"
            "c=IN IP4 203.0.113.1\r\n"
            "a=mid:audio\r\n"
            "a=rtpmap:8 PCMA/8000\r\n"
            "a=recvonly\r\n"
            "a=rtcp:\d+\r\n$",
            re.DOTALL,
        )
        self.assertRegex(sdp, match_re)
        matches = match_re.search(sdp)
        port = int(matches.group(1))
        self.assertEqual(
            self._res,
            {
                "janus": "event",
                "session_id": session,
                "sender": pub_handle,
                "plugindata": {
                    "plugin": "janus.plugin.videoroom",
                    "data": {
                        "videoroom": "event",
                        "room": room,
                        "configured": "ok",
                        "audio_codec": "PCMA",
                    },
                },
                "jsep": {"type": "answer", "sdp": sdp},
            },
        )

        pub_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        pub_sock.settimeout(1)
        pub_sock.bind(("203.0.113.4", 30000))
        pub_sock.connect(("203.0.113.1", port))

        # send fake RTP to trigger event
        m = pub_sock.send(
            b"\x80\x08\x12\x34\x43\x32\x12\x45\x65\x45\x34\x23\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
        )

        # wait for webrtcup event
        eventloop.run_until_complete(testIJson(self))
        self.assertEqual(
            self._res,
            {"janus": "webrtcup", "session_id": session, "sender": pub_handle},
        )

        self.destroyVideoroom(token, session, control_handle, room)
        pub_sock.close()

    def testVideoroomWebRTCVideo(self):
        (token, session, control_handle, room) = self.startVideoroom()

        pub_handle = self.createHandle(token, session)
        self.assertNotEqual(pub_handle, control_handle)

        feed = self.createPublisher(token, session, room, pub_handle)
        self.assertNotEqual(feed, control_handle)

        eventloop.run_until_complete(
            testIOJanus(
                self,
                {
                    "janus": "message",
                    "body": {
                        "request": "configure",
                        "room": room,
                        "feed": feed,
                        "data": False,
                        "audio": True,
                        "video": True,
                    },
                    "jsep": {
                        "type": "offer",
                        "sdp": (
                            "v=0\r\n"
                            "o=- 3959345330719813235 2 IN IP4 127.0.0.1\r\n"
                            "s=-\r\n"
                            "t=0 0\r\n"
                            "a=group:BUNDLE 0 1\r\n"
                            "a=extmap-allow-mixed\r\n"
                            "a=msid-semantic: WMS hJifdaJwqEqHxSG0pVbs1DrLAwiHqz7fKlqC\r\n"
                            "m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 110 112 113 126\r\n"
                            "c=IN IP4 0.0.0.0\r\n"
                            "a=rtcp:9 IN IP4 0.0.0.0\r\n"
                            "a=ice-ufrag:+JrN\r\n"
                            "a=ice-pwd:TMWORlSHr9fd+0bUNXnlBs5D\r\n"
                            "a=ice-options:trickle\r\n"
                            "a=fingerprint:sha-256 FD:56:1A:DB:3E:7B:8E:0B:75:4E:2E:49:1A:91:52:E4:69:9E:66:91:FF:34:A2:50:58:72:C0:8E:C2:87:CA:1F\r\n"
                            "a=setup:actpass\r\n"
                            "a=mid:0\r\n"
                            "a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\n"
                            "a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\n"
                            "a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r\n"
                            "a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid\r\n"
                            "a=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\r\n"
                            "a=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id\r\n"
                            "a=sendonly\r\n"
                            "a=msid:hJifdaJwqEqHxSG0pVbs1DrLAwiHqz7fKlqC 2de0f1b0-3a39-450e-9804-8305ec87452b\r\n"
                            "a=rtcp-mux\r\n"
                            "a=rtpmap:111 opus/48000/2\r\n"
                            "a=rtcp-fb:111 transport-cc\r\n"
                            "a=fmtp:111 minptime=10;useinbandfec=1\r\n"
                            "a=rtpmap:103 ISAC/16000\r\n"
                            "a=rtpmap:104 ISAC/32000\r\n"
                            "a=rtpmap:9 G722/8000\r\n"
                            "a=rtpmap:0 PCMU/8000\r\n"
                            "a=rtpmap:8 PCMA/8000\r\n"
                            "a=rtpmap:106 CN/32000\r\n"
                            "a=rtpmap:105 CN/16000\r\n"
                            "a=rtpmap:13 CN/8000\r\n"
                            "a=rtpmap:110 telephone-event/48000\r\n"
                            "a=rtpmap:112 telephone-event/32000\r\n"
                            "a=rtpmap:113 telephone-event/16000\r\n"
                            "a=rtpmap:126 telephone-event/8000\r\n"
                            "a=ssrc:677770262 cname:NMNDwVd66x2SfiO0\r\n"
                            "a=ssrc:677770262 msid:hJifdaJwqEqHxSG0pVbs1DrLAwiHqz7fKlqC 2de0f1b0-3a39-450e-9804-8305ec87452b\r\n"
                            "a=ssrc:677770262 mslabel:hJifdaJwqEqHxSG0pVbs1DrLAwiHqz7fKlqC\r\n"
                            "a=ssrc:677770262 label:2de0f1b0-3a39-450e-9804-8305ec87452b\r\n"
                            "m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 102 121 127 120 125 107 108 109 35 36 124 119 123\r\n"
                            "c=IN IP4 0.0.0.0\r\n"
                            "a=rtcp:9 IN IP4 0.0.0.0\r\n"
                            "a=ice-ufrag:+JrN\r\n"
                            "a=ice-pwd:TMWORlSHr9fd+0bUNXnlBs5D\r\n"
                            "a=ice-options:trickle\r\n"
                            "a=fingerprint:sha-256 FD:56:1A:DB:3E:7B:8E:0B:75:4E:2E:49:1A:91:52:E4:69:9E:66:91:FF:34:A2:50:58:72:C0:8E:C2:87:CA:1F\r\n"
                            "a=setup:actpass\r\n"
                            "a=mid:1\r\n"
                            "a=extmap:14 urn:ietf:params:rtp-hdrext:toffset\r\n"
                            "a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\n"
                            "a=extmap:13 urn:3gpp:video-orientation\r\n"
                            "a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r\n"
                            "a=extmap:12 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay\r\n"
                            "a=extmap:11 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type\r\n"
                            "a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing\r\n"
                            "a=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/color-space\r\n"
                            "a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid\r\n"
                            "a=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\r\n"
                            "a=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id\r\n"
                            "a=sendonly\r\n"
                            "a=msid:hJifdaJwqEqHxSG0pVbs1DrLAwiHqz7fKlqC 6d6ec7a7-e3d7-4c82-b03c-45e017713abd\r\n"
                            "a=rtcp-mux\r\n"
                            "a=rtcp-rsize\r\n"
                            "a=rtpmap:96 VP8/90000\r\n"
                            "a=rtcp-fb:96 goog-remb\r\n"
                            "a=rtcp-fb:96 transport-cc\r\n"
                            "a=rtcp-fb:96 ccm fir\r\n"
                            "a=rtcp-fb:96 nack\r\n"
                            "a=rtcp-fb:96 nack pli\r\n"
                            "a=rtpmap:97 rtx/90000\r\n"
                            "a=fmtp:97 apt=96\r\n"
                            "a=rtpmap:98 VP9/90000\r\n"
                            "a=rtcp-fb:98 goog-remb\r\n"
                            "a=rtcp-fb:98 transport-cc\r\n"
                            "a=rtcp-fb:98 ccm fir\r\n"
                            "a=rtcp-fb:98 nack\r\n"
                            "a=rtcp-fb:98 nack pli\r\n"
                            "a=fmtp:98 profile-id=0\r\n"
                            "a=rtpmap:99 rtx/90000\r\n"
                            "a=fmtp:99 apt=98\r\n"
                            "a=rtpmap:100 VP9/90000\r\n"
                            "a=rtcp-fb:100 goog-remb\r\n"
                            "a=rtcp-fb:100 transport-cc\r\n"
                            "a=rtcp-fb:100 ccm fir\r\n"
                            "a=rtcp-fb:100 nack\r\n"
                            "a=rtcp-fb:100 nack pli\r\n"
                            "a=fmtp:100 profile-id=2\r\n"
                            "a=rtpmap:101 rtx/90000\r\n"
                            "a=fmtp:101 apt=100\r\n"
                            "a=rtpmap:102 H264/90000\r\n"
                            "a=rtcp-fb:102 goog-remb\r\n"
                            "a=rtcp-fb:102 transport-cc\r\n"
                            "a=rtcp-fb:102 ccm fir\r\n"
                            "a=rtcp-fb:102 nack\r\n"
                            "a=rtcp-fb:102 nack pli\r\n"
                            "a=fmtp:102 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f\r\n"
                            "a=rtpmap:121 rtx/90000\r\n"
                            "a=fmtp:121 apt=102\r\n"
                            "a=rtpmap:127 H264/90000\r\n"
                            "a=rtcp-fb:127 goog-remb\r\n"
                            "a=rtcp-fb:127 transport-cc\r\n"
                            "a=rtcp-fb:127 ccm fir\r\n"
                            "a=rtcp-fb:127 nack\r\n"
                            "a=rtcp-fb:127 nack pli\r\n"
                            "a=fmtp:127 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f\r\n"
                            "a=rtpmap:120 rtx/90000\r\n"
                            "a=fmtp:120 apt=127\r\n"
                            "a=rtpmap:125 H264/90000\r\n"
                            "a=rtcp-fb:125 goog-remb\r\n"
                            "a=rtcp-fb:125 transport-cc\r\n"
                            "a=rtcp-fb:125 ccm fir\r\n"
                            "a=rtcp-fb:125 nack\r\n"
                            "a=rtcp-fb:125 nack pli\r\n"
                            "a=fmtp:125 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f\r\n"
                            "a=rtpmap:107 rtx/90000\r\n"
                            "a=fmtp:107 apt=125\r\n"
                            "a=rtpmap:108 H264/90000\r\n"
                            "a=rtcp-fb:108 goog-remb\r\n"
                            "a=rtcp-fb:108 transport-cc\r\n"
                            "a=rtcp-fb:108 ccm fir\r\n"
                            "a=rtcp-fb:108 nack\r\n"
                            "a=rtcp-fb:108 nack pli\r\n"
                            "a=fmtp:108 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f\r\n"
                            "a=rtpmap:109 rtx/90000\r\n"
                            "a=fmtp:109 apt=108\r\n"
                            "a=rtpmap:35 AV1X/90000\r\n"
                            "a=rtcp-fb:35 goog-remb\r\n"
                            "a=rtcp-fb:35 transport-cc\r\n"
                            "a=rtcp-fb:35 ccm fir\r\n"
                            "a=rtcp-fb:35 nack\r\n"
                            "a=rtcp-fb:35 nack pli\r\n"
                            "a=rtpmap:36 rtx/90000\r\n"
                            "a=fmtp:36 apt=35\r\n"
                            "a=rtpmap:124 red/90000\r\n"
                            "a=rtpmap:119 rtx/90000\r\n"
                            "a=fmtp:119 apt=124\r\n"
                            "a=rtpmap:123 ulpfec/90000\r\n"
                            "a=ssrc-group:FID 3005569364 2001490794\r\n"
                            "a=ssrc:3005569364 cname:NMNDwVd66x2SfiO0\r\n"
                            "a=ssrc:3005569364 msid:hJifdaJwqEqHxSG0pVbs1DrLAwiHqz7fKlqC 6d6ec7a7-e3d7-4c82-b03c-45e017713abd\r\n"
                            "a=ssrc:3005569364 mslabel:hJifdaJwqEqHxSG0pVbs1DrLAwiHqz7fKlqC\r\n"
                            "a=ssrc:3005569364 label:6d6ec7a7-e3d7-4c82-b03c-45e017713abd\r\n"
                            "a=ssrc:2001490794 cname:NMNDwVd66x2SfiO0\r\n"
                            "a=ssrc:2001490794 msid:hJifdaJwqEqHxSG0pVbs1DrLAwiHqz7fKlqC 6d6ec7a7-e3d7-4c82-b03c-45e017713abd\r\n"
                            "a=ssrc:2001490794 mslabel:hJifdaJwqEqHxSG0pVbs1DrLAwiHqz7fKlqC\r\n"
                            "a=ssrc:2001490794 label:6d6ec7a7-e3d7-4c82-b03c-45e017713abd\r\n"
                        ),
                    },
                    "handle_id": pub_handle,
                    "session_id": session,
                    "token": token,
                },
            )
        )
        # ack is received first
        self.assertEqual(self._res, {"janus": "ack", "session_id": session})
        # followed by the event notification
        eventloop.run_until_complete(testIJanus(self))
        sdp = self._res["jsep"]["sdp"]
        self.assertIsInstance(sdp, str)
        self.assertRegex(
            sdp,
            re.compile(
                "^v=0\r\n"
                "o=- \d+ \d+ IN IP4 203.0.113.1\r\n"
                "s=rtpengine.*?\r\n"
                "t=0 0\r\n"
                "m=audio \d+ UDP/TLS/RTP/SAVPF 111\r\n"
                "c=IN IP4 203.0.113.1\r\n"
                "a=mid:0\r\n"
                "a=rtpmap:111 opus/48000/2\r\n"
                "a=fmtp:111 minptime=10;useinbandfec=1\r\n"
                "a=rtcp-fb:111 transport-cc\r\n"
                "a=recvonly\r\n"
                "a=rtcp:\d+\r\n"
                "a=rtcp-mux\r\n"
                "a=setup:active\r\n"
                "a=fingerprint:sha-256 .{95}\r\n"
                "a=ice-ufrag:.{8}\r\n"
                "a=ice-pwd:.{26}\r\n"
                "a=ice-options:trickle\r\n"
                "a=candidate:.{16} 1 UDP 2130706431 203.0.113.1 \d+ typ host\r\n"
                "a=end-of-candidates\r\n"
                "m=video \d+ UDP/TLS/RTP/SAVPF 96\r\n"
                "c=IN IP4 203.0.113.1\r\n"
                "a=mid:1\r\n"
                "a=rtpmap:96 VP8/90000\r\n"
                "a=rtcp-fb:96 goog-remb\r\n"
                "a=rtcp-fb:96 transport-cc\r\n"
                "a=rtcp-fb:96 ccm fir\r\n"
                "a=rtcp-fb:96 nack\r\n"
                "a=rtcp-fb:96 nack pli\r\n"
                "a=recvonly\r\n"
                "a=rtcp:\d+\r\n"
                "a=rtcp-mux\r\n"
                "a=setup:active\r\n"
                "a=fingerprint:sha-256 .{95}\r\n"
                "a=ice-ufrag:.{8}\r\n"
                "a=ice-pwd:.{26}\r\n"
                "a=ice-options:trickle\r\n"
                "a=candidate:.{16} 1 UDP 2130706431 203.0.113.1 \d+ typ host\r\n"
                "a=end-of-candidates\r\n$",
                re.DOTALL,
            ),
        )
        self.assertEqual(
            self._res,
            {
                "janus": "event",
                "session_id": session,
                "sender": pub_handle,
                "plugindata": {
                    "plugin": "janus.plugin.videoroom",
                    "data": {
                        "videoroom": "event",
                        "room": room,
                        "configured": "ok",
                        "audio_codec": "opus",
                        "video_codec": "VP8",
                    },
                },
                "jsep": {"type": "answer", "sdp": sdp},
            },
        )

        # subscriber
        sub_handle = self.createHandle(token, session)
        eventloop.run_until_complete(
            testIOJanus(
                self,
                {
                    "janus": "message",
                    "body": {
                        "request": "join",
                        "ptype": "subscriber",
                        "room": room,
                        "feed": feed,
                    },
                    "handle_id": sub_handle,
                    "session_id": session,
                    "token": token,
                },
            )
        )

        self.assertEqual(self._res, {"janus": "ack", "session_id": session})
        eventloop.run_until_complete(testIJanus(self))
        sdp = self._res["jsep"]["sdp"]
        self.assertIsInstance(sdp, str)
        self.assertRegex(
            sdp,
            re.compile(
                "^v=0\r\n"
                "o=- 3959345330719813235 2 IN IP4 127.0.0.1\r\n"
                "s=-\r\n"
                "t=0 0\r\n"
                "a=extmap-allow-mixed\r\n"
                "a=msid-semantic: WMS hJifdaJwqEqHxSG0pVbs1DrLAwiHqz7fKlqC\r\n"
                "m=audio \d+ UDP/TLS/RTP/SAVPF 111\r\n"
                "c=IN IP4 203.0.113.1\r\n"
                "a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\n"
                "a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\n"
                "a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r\n"
                "a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid\r\n"
                "a=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\r\n"
                "a=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id\r\n"
                "a=msid:hJifdaJwqEqHxSG0pVbs1DrLAwiHqz7fKlqC 2de0f1b0-3a39-450e-9804-8305ec87452b\r\n"
                "a=ssrc:677770262 cname:NMNDwVd66x2SfiO0\r\n"
                "a=ssrc:677770262 msid:hJifdaJwqEqHxSG0pVbs1DrLAwiHqz7fKlqC 2de0f1b0-3a39-450e-9804-8305ec87452b\r\n"
                "a=ssrc:677770262 mslabel:hJifdaJwqEqHxSG0pVbs1DrLAwiHqz7fKlqC\r\n"
                "a=ssrc:677770262 label:2de0f1b0-3a39-450e-9804-8305ec87452b\r\n"
                "a=mid:0\r\n"
                "a=rtpmap:111 opus/48000/2\r\n"
                "a=fmtp:111 minptime=10;useinbandfec=1\r\n"
                "a=rtcp-fb:111 transport-cc\r\n"
                "a=sendonly\r\n"
                "a=rtcp-mux\r\n"
                "a=setup:actpass\r\n"
                "a=fingerprint:sha-256 .{95}\r\n"
                "a=ice-ufrag:.{8}\r\n"
                "a=ice-pwd:.{26}\r\n"
                "a=ice-options:trickle\r\n"
                "a=candidate:.{16} 1 UDP 2130706431 203.0.113.1 \d+ typ host\r\n"
                "a=end-of-candidates\r\n"
                "m=video \d+ UDP/TLS/RTP/SAVPF 96\r\n"
                "c=IN IP4 203.0.113.1\r\n"
                "a=extmap:14 urn:ietf:params:rtp-hdrext:toffset\r\n"
                "a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\n"
                "a=extmap:13 urn:3gpp:video-orientation\r\n"
                "a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r\n"
                "a=extmap:12 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay\r\n"
                "a=extmap:11 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type\r\n"
                "a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing\r\n"
                "a=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/color-space\r\n"
                "a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid\r\n"
                "a=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\r\n"
                "a=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id\r\n"
                "a=msid:hJifdaJwqEqHxSG0pVbs1DrLAwiHqz7fKlqC 6d6ec7a7-e3d7-4c82-b03c-45e017713abd\r\n"
                "a=rtcp-rsize\r\n"
                "a=ssrc-group:FID 3005569364 2001490794\r\n"
                "a=ssrc:3005569364 cname:NMNDwVd66x2SfiO0\r\n"
                "a=ssrc:3005569364 msid:hJifdaJwqEqHxSG0pVbs1DrLAwiHqz7fKlqC 6d6ec7a7-e3d7-4c82-b03c-45e017713abd\r\n"
                "a=ssrc:3005569364 mslabel:hJifdaJwqEqHxSG0pVbs1DrLAwiHqz7fKlqC\r\n"
                "a=ssrc:3005569364 label:6d6ec7a7-e3d7-4c82-b03c-45e017713abd\r\n"
                "a=ssrc:2001490794 cname:NMNDwVd66x2SfiO0\r\n"
                "a=ssrc:2001490794 msid:hJifdaJwqEqHxSG0pVbs1DrLAwiHqz7fKlqC 6d6ec7a7-e3d7-4c82-b03c-45e017713abd\r\n"
                "a=ssrc:2001490794 mslabel:hJifdaJwqEqHxSG0pVbs1DrLAwiHqz7fKlqC\r\n"
                "a=ssrc:2001490794 label:6d6ec7a7-e3d7-4c82-b03c-45e017713abd\r\n"
                "a=mid:1\r\n"
                "a=rtpmap:96 VP8/90000\r\n"
                "a=rtcp-fb:96 goog-remb\r\n"
                "a=rtcp-fb:96 transport-cc\r\n"
                "a=rtcp-fb:96 ccm fir\r\n"
                "a=rtcp-fb:96 nack\r\n"
                "a=rtcp-fb:96 nack pli\r\n"
                "a=sendonly\r\n"
                "a=rtcp-mux\r\n"
                "a=setup:actpass\r\n"
                "a=fingerprint:sha-256 .{95}\r\n"
                "a=ice-ufrag:.{8}\r\n"
                "a=ice-pwd:.{26}\r\n"
                "a=ice-options:trickle\r\n"
                "a=candidate:.{16} 1 UDP 2130706431 203.0.113.1 \d+ typ host\r\n"
                "a=end-of-candidates\r\n$",
                re.DOTALL,
            ),
        )
        self.assertEqual(
            self._res,
            {
                "janus": "event",
                "session_id": session,
                "sender": sub_handle,
                "plugindata": {
                    "plugin": "janus.plugin.videoroom",
                    "data": {
                        "videoroom": "attached",
                        "room": room,
                        "id": feed,
                    },
                },
                "jsep": {"type": "offer", "sdp": sdp},
            },
        )

        self.destroyVideoroom(token, session, control_handle, room)

    def testVideoroomICE(self):
        (token, session, control_handle, room) = self.startVideoroom()

        pub_handle = self.createHandle(token, session)
        self.assertNotEqual(pub_handle, control_handle)

        feed = self.createPublisher(token, session, room, pub_handle)
        self.assertNotEqual(feed, control_handle)

        eventloop.run_until_complete(
            testIOJanus(
                self,
                {
                    "janus": "message",
                    "body": {
                        "request": "configure",
                        "room": room,
                        "feed": feed,
                        "data": False,
                        "audio": True,
                        "video": True,
                    },
                    "jsep": {
                        "type": "offer",
                        "sdp": (
                            "v=0\r\n"
                            "o=x 123 123 IN IP4 203.0.113.2\r\n"
                            "c=IN IP4 0.0.0.0\r\n"
                            "s=foobar\r\n"
                            "t=0 0\r\n"
                            "m=audio 9 RTP/AVP 8 0 96\r\n"
                            "a=mid:audio\r\n"
                            "a=rtpmap:96 opus/48000\r\n"
                            "a=ice-ufrag:62lL\r\n"
                            "a=ice-pwd:WD1pLdamJOWH2WuEBb0vjyZr\r\n"
                            "a=ice-options:trickle\r\n"
                            "a=rtcp-mux\r\n"
                            "a=sendonly\r\n"
                        ),
                    },
                    "handle_id": pub_handle,
                    "session_id": session,
                    "token": token,
                },
            )
        )
        # ack is received first
        self.assertEqual(self._res, {"janus": "ack", "session_id": session})
        # followed by the event notification
        eventloop.run_until_complete(testIJanus(self))
        sdp = self._res["jsep"]["sdp"]
        self.assertIsInstance(sdp, str)
        self.assertRegex(
            sdp,
            re.compile(
                "^v=0\r\n"
                "o=- \d+ \d+ IN IP4 203.0.113.1\r\n"
                "s=rtpengine.*?\r\n"
                "t=0 0\r\n"
                "m=audio \d+ RTP/AVP 8\r\n"
                "c=IN IP4 203.0.113.1\r\n"
                "a=mid:audio\r\n"
                "a=rtpmap:8 PCMA/8000\r\n"
                "a=recvonly\r\n"
                "a=rtcp:\d+\r\n"
                "a=rtcp-mux\r\n"
                "a=ice-ufrag:.{8}\r\n"
                "a=ice-pwd:.{26}\r\n"
                "a=ice-options:trickle\r\n"
                "a=candidate:.{16} 1 UDP 2130706431 203.0.113.1 \d+ typ host\r\n"
                "a=end-of-candidates\r\n$",
                re.DOTALL,
            ),
        )
        self.assertEqual(
            self._res,
            {
                "janus": "event",
                "session_id": session,
                "sender": pub_handle,
                "plugindata": {
                    "plugin": "janus.plugin.videoroom",
                    "data": {
                        "videoroom": "event",
                        "room": room,
                        "configured": "ok",
                        "audio_codec": "PCMA",
                    },
                },
                "jsep": {"type": "answer", "sdp": sdp},
            },
        )

        pub_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        pub_sock.settimeout(1)
        pub_sock.bind(("203.0.113.2", 30000))

        # trickle update
        eventloop.run_until_complete(
            testIOJanus(
                self,
                {
                    "janus": "trickle",
                    "candidate": {
                        "candidate": "candidate:3279615273 1 udp 2113937151 203.0.113.2 30000 typ host generation 0 ufrag 62lL network-cost 999",
                        "sdpMid": "audio",
                    },
                    "handle_id": pub_handle,
                    "session_id": session,
                    "token": token,
                },
            )
        )
        self.assertEqual(self._res, {"janus": "ack", "session_id": session})

        m = pub_sock.recv(1000)
        self.assertRegex(
            m,
            re.compile(
                b"^\x00\x01\x00.\x21\x12\xa4\x42(............)\x80\x22\x00.rtpengine.*?\x00\x06\x00\x0d62lL:(........)\x00\x00\x00\x80\\\x29\x00\x08........\x00\\\x24\x00\x04\x6e\xff\xff\xff\x00\x08\x00\x14....................\x80\\\x28\x00\x04....$",
                re.DOTALL,
            ),
        )

        sub_handle = self.createHandle(token, session)
        self.assertNotEqual(sub_handle, pub_handle)
        self.assertNotEqual(sub_handle, control_handle)

        # subscriber #1 joins publisher #1
        eventloop.run_until_complete(
            testIOJanus(
                self,
                {
                    "janus": "message",
                    "body": {
                        "request": "join",
                        "ptype": "subscriber",
                        "room": room,
                        "feed": feed,
                    },
                    "handle_id": sub_handle,
                    "session_id": session,
                    "token": token,
                },
            )
        )
        # ack is received first
        self.assertEqual(self._res, {"janus": "ack", "session_id": session})
        # followed by the attached event
        eventloop.run_until_complete(testIJanus(self))
        self.assertEqual(feed, self._res["plugindata"]["data"]["id"])
        self.assertNotEqual(feed, control_handle)
        self.assertNotEqual(feed, session)
        self.assertNotEqual(feed, room)
        self.assertNotEqual(feed, pub_handle)
        self.assertNotEqual(feed, sub_handle)
        sdp = self._res["jsep"]["sdp"]
        self.assertIsInstance(sdp, str)
        self.assertRegex(
            sdp,
            re.compile(
                "^v=0\r\n"
                "o=x 123 123 IN IP4 203.0.113.2\r\n"
                "c=IN IP4 203.0.113.1\r\n"
                "s=foobar\r\n"
                "t=0 0\r\n"
                "m=audio \d+ UDP/TLS/RTP/SAVPF 8\r\n"
                "a=mid:audio\r\n"
                "a=rtpmap:8 PCMA/8000\r\n"
                "a=sendonly\r\n"
                "a=rtcp-mux\r\n"
                "a=setup:actpass\r\n"
                "a=fingerprint:sha-256 .{95}\r\n"
                "a=ice-ufrag:.{8}\r\n"
                "a=ice-pwd:.{26}\r\n"
                "a=ice-options:trickle\r\n"
                "a=candidate:.{16} 1 UDP 2130706431 203.0.113.1 \d+ typ host\r\n"
                "a=end-of-candidates\r\n$",
                re.DOTALL,
            ),
        )
        self.assertEqual(
            self._res,
            {
                "janus": "event",
                "session_id": session,
                "sender": sub_handle,
                "plugindata": {
                    "plugin": "janus.plugin.videoroom",
                    "data": {
                        "videoroom": "attached",
                        "room": room,
                        "id": feed,
                    },
                },
                "jsep": {"type": "offer", "sdp": sdp},
            },
        )

        # subscriber #1 answer
        eventloop.run_until_complete(
            testIOJanus(
                self,
                {
                    "janus": "message",
                    "body": {"request": "start", "room": room, "feed": feed},
                    "jsep": {
                        "type": "answer",
                        "sdp": (
                            "v=0\r\n"
                            "o=x 123 123 IN IP4 203.0.113.2\r\n"
                            "c=IN IP4 0.0.0.0\r\n"
                            "s=foobar\r\n"
                            "t=0 0\r\n"
                            "m=audio 9 RTP/AVP 8\r\n"
                            "a=mid:audio\r\n"
                            "a=ice-ufrag:abcd\r\n"
                            "a=ice-pwd:WD1pLsdgsdfsdWuEBb0vjyZr\r\n"
                            "a=ice-options:trickle\r\n"
                            "a=rtcp-mux\r\n"
                            "a=recvonly\r\n"
                        ),
                    },
                    "handle_id": sub_handle,
                    "session_id": session,
                    "token": token,
                },
            )
        )
        # ack is received first
        self.assertEqual(self._res, {"janus": "ack", "session_id": session})
        # followed by the attached event
        eventloop.run_until_complete(testIJanus(self))
        self.assertEqual(
            self._res,
            {
                "janus": "event",
                "session_id": session,
                "sender": sub_handle,
                "plugindata": {
                    "plugin": "janus.plugin.videoroom",
                    "data": {
                        "videoroom": "event",
                        "started": "ok",
                        "room": room,
                    },
                },
            },
        )

        sub_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        sub_sock.settimeout(1)
        sub_sock.bind(("203.0.113.2", 30002))

        # trickle update
        eventloop.run_until_complete(
            testIOJanus(
                self,
                {
                    "janus": "trickle",
                    "candidate": {
                        "candidate": "candidate:3fgsdfs273 1 udp 2113937151 203.0.113.2 30002 typ host generation 0",
                        "sdpMid": "audio",
                        "usernameFragment": "abcd",
                    },
                    "handle_id": sub_handle,
                    "session_id": session,
                    "token": token,
                },
            )
        )
        self.assertEqual(self._res, {"janus": "ack", "session_id": session})

        m = sub_sock.recv(1000)
        self.assertRegex(
            m,
            re.compile(
                b"^\x00\x01\x00.\x21\x12\xa4\x42(............)\x80\x22\x00.rtpengine.*?\x00\x06\x00\x0dabcd:(........)\x00\x00\x00\x80\\\x29\x00\x08........\x00\\\x24\x00\x04\x6e\xff\xff\xff\x00\x08\x00\x14....................\x80\\\x28\x00\x04....$",
                re.DOTALL,
            ),
        )

        # TCP trickle test
        eventloop.run_until_complete(
            testIOJanus(
                self,
                {
                    "janus": "trickle",
                    "candidate": {
                        "candidate": "candidate:6 2 TCP 2105393406 2607:fea8:ab00:33::9f4 9 typ host tcptype active",
                        "sdpMid": "audio",
                        "usernameFragment": "abcd",
                    },
                    "handle_id": sub_handle,
                    "session_id": session,
                    "token": token,
                },
            )
        )
        self.assertEqual(self._res, {"janus": "ack", "session_id": session})

        self.destroyVideoroom(token, session, control_handle, room)
        pub_sock.close()
        sub_sock.close()

    def testVideoroomPubSub(self):
        (token, session, control_handle, room) = self.startVideoroom()

        # XXX add tests for requests for invalid IDs/handles

        handle_p_1 = self.createHandle(token, session)
        self.assertNotEqual(handle_p_1, control_handle)

        # create feed for publisher #1
        feed_1 = self.createPublisher(token, session, room, handle_p_1)
        self.assertNotEqual(feed_1, control_handle)

        # configure publisher feed #1 w broken SDP
        eventloop.run_until_complete(
            testIOJanus(
                self,
                {
                    "janus": "message",
                    "body": {
                        "request": "configure",
                        "room": room,
                        "feed": feed_1,
                        "data": False,
                        "audio": True,
                        "video": True,
                    },
                    "jsep": {
                        "type": "offer",
                        "sdp": "blah",
                    },
                    "handle_id": handle_p_1,
                    "session_id": session,
                    "token": token,
                },
            )
        )
        # ack is received first
        self.assertEqual(self._res, {"janus": "ack", "session_id": session})
        # followed by the event notification
        eventloop.run_until_complete(testIJanus(self))
        self.assertEqual(
            self._res,
            {
                "janus": "error",
                "session_id": session,
                "sender": handle_p_1,
                "error": {"code": 512, "reason": "Failed to parse SDP"},
                "plugindata": {"plugin": "janus.plugin.videoroom", "data": {}},
            },
        )

        # configure publisher feed #1
        eventloop.run_until_complete(
            testIOJanus(
                self,
                {
                    "janus": "message",
                    "body": {
                        "request": "configure",
                        "room": room,
                        "feed": feed_1,
                        "data": False,
                        "audio": True,
                        "video": True,
                    },
                    "jsep": {
                        "type": "offer",
                        "sdp": (
                            "v=0\r\n"
                            "o=x 123 123 IN IP4 203.0.113.2\r\n"
                            "c=IN IP4 203.0.113.2\r\n"
                            "s=foobar\r\n"
                            "t=0 0\r\n"
                            "m=audio 6000 RTP/AVP 96 8 0\r\n"
                            "a=rtpmap:96 opus/48000\r\n"
                            "a=sendonly\r\n"
                        ),
                    },
                    "handle_id": handle_p_1,
                    "session_id": session,
                    "token": token,
                },
            )
        )
        # ack is received first
        self.assertEqual(self._res, {"janus": "ack", "session_id": session})
        # followed by the event notification
        eventloop.run_until_complete(testIJanus(self))
        sdp = self._res["jsep"]["sdp"]
        self.assertIsInstance(sdp, str)
        # XXX check SDP
        self.assertEqual(
            self._res,
            {
                "janus": "event",
                "session_id": session,
                "sender": handle_p_1,
                "plugindata": {
                    "plugin": "janus.plugin.videoroom",
                    "data": {
                        "videoroom": "event",
                        "room": room,
                        "configured": "ok",
                        "audio_codec": "opus",
                    },
                },
                "jsep": {"type": "answer", "sdp": sdp},
            },
        )

        # attach subscriber handle #1
        handle_s_1 = self.createHandle(token, session)
        self.assertNotEqual(handle_s_1, control_handle)

        # subscriber #1 joins publisher #1
        eventloop.run_until_complete(
            testIOJanus(
                self,
                {
                    "janus": "message",
                    "body": {
                        "request": "join",
                        "ptype": "subscriber",
                        "room": room,
                        "feed": feed_1,
                    },
                    "handle_id": handle_s_1,
                    "session_id": session,
                    "token": token,
                },
            )
        )
        # ack is received first
        self.assertEqual(self._res, {"janus": "ack", "session_id": session})
        # followed by the attached event
        eventloop.run_until_complete(testIJanus(self))
        self.assertEqual(feed_1, self._res["plugindata"]["data"]["id"])
        self.assertNotEqual(feed_1, control_handle)
        self.assertNotEqual(feed_1, session)
        self.assertNotEqual(feed_1, room)
        self.assertNotEqual(feed_1, handle_p_1)
        self.assertNotEqual(feed_1, handle_s_1)
        sdp = self._res["jsep"]["sdp"]
        self.assertIsInstance(sdp, str)
        # XXX check SDP
        self.assertEqual(
            self._res,
            {
                "janus": "event",
                "session_id": session,
                "sender": handle_s_1,
                "plugindata": {
                    "plugin": "janus.plugin.videoroom",
                    "data": {
                        "videoroom": "attached",
                        "room": room,
                        "id": feed_1,
                    },
                },
                "jsep": {"type": "offer", "sdp": sdp},
            },
        )

        # subscriber #1 answer
        eventloop.run_until_complete(
            testIOJanus(
                self,
                {
                    "janus": "message",
                    "body": {"request": "start", "room": room, "feed": feed_1},
                    "jsep": {
                        "type": "answer",
                        "sdp": (
                            "v=0\r\n"
                            "o=x 123 123 IN IP4 203.0.113.2\r\n"
                            "c=IN IP4 203.0.113.2\r\n"
                            "s=foobar\r\n"
                            "t=0 0\r\n"
                            "m=audio 7000 RTP/AVP 96\r\n"
                            "a=rtpmap:96 opus/48000\r\n"
                            "a=recvonly\r\n"
                        ),
                    },
                    "handle_id": handle_s_1,
                    "session_id": session,
                    "token": token,
                },
            )
        )
        # ack is received first
        self.assertEqual(self._res, {"janus": "ack", "session_id": session})
        # followed by the attached event
        eventloop.run_until_complete(testIJanus(self))
        self.assertEqual(
            self._res,
            {
                "janus": "event",
                "session_id": session,
                "sender": handle_s_1,
                "plugindata": {
                    "plugin": "janus.plugin.videoroom",
                    "data": {
                        "videoroom": "event",
                        "started": "ok",
                        "room": room,
                    },
                },
            },
        )

        handle_p_2 = self.createHandle(token, session)
        self.assertNotEqual(handle_p_2, control_handle)

        feed_2 = self.createPublisher(
            token, session, room, handle_p_2, [{"id": feed_1}]
        )

        # configure publisher feed #2
        eventloop.run_until_complete(
            testIOJanus(
                self,
                {
                    "janus": "message",
                    "body": {
                        "request": "configure",
                        "room": room,
                        "feed": feed_2,
                        "data": False,
                        "audio": True,
                        "video": True,
                    },
                    "jsep": {
                        "type": "offer",
                        "sdp": (
                            "v=0\r\n"
                            "o=x 123 123 IN IP4 203.0.113.2\r\n"
                            "c=IN IP4 0.0.0.0\r\n"
                            "s=foobar\r\n"
                            "t=0 0\r\n"
                            "m=audio 9 RTP/AVP 8 0\r\n"
                            "a=mid:audio\r\n"
                            "a=rtpmap:96 opus/48000\r\n"
                            "a=sendonly\r\n"
                        ),
                    },
                    "handle_id": handle_p_2,
                    "session_id": session,
                    "token": token,
                },
            )
        )
        # ack is received first
        self.assertEqual(self._res, {"janus": "ack", "session_id": session})

        # followed by the notification for publisher #1
        eventloop.run_until_complete(testIJson(self))
        self.assertEqual(
            self._res,
            {
                "janus": "event",
                "session_id": session,
                "sender": handle_p_1,
                "plugindata": {
                    "plugin": "janus.plugin.videoroom",
                    "data": {
                        "videoroom": "event",
                        "room": room,
                        "publishers": [{"id": feed_2}],
                    },
                },
            },
        )

        # followed by the "ok" event for publisher #2
        eventloop.run_until_complete(testIJanus(self))
        sdp = self._res["jsep"]["sdp"]
        self.assertIsInstance(sdp, str)
        # XXX check SDP
        self.assertEqual(
            self._res,
            {
                "janus": "event",
                "session_id": session,
                "sender": handle_p_2,
                "plugindata": {
                    "plugin": "janus.plugin.videoroom",
                    "data": {
                        "videoroom": "event",
                        "room": room,
                        "configured": "ok",
                        "audio_codec": "PCMA",
                    },
                },
                "jsep": {"type": "answer", "sdp": sdp},
            },
        )

        # detach publisher #1
        eventloop.run_until_complete(
            testOJanus(
                self,
                {
                    "janus": "detach",
                    "handle_id": handle_p_1,
                    "session_id": session,
                    "token": token,
                },
            )
        )
        # unpublished event is received first
        eventloop.run_until_complete(testIJson(self))
        self.assertEqual(
            self._res,
            {
                "janus": "event",
                "session_id": session,
                "sender": handle_p_2,
                "plugindata": {
                    "plugin": "janus.plugin.videoroom",
                    "data": {
                        "videoroom": "event",
                        "room": room,
                        "unpublished": feed_1,
                    },
                },
            },
        )
        # followed by leaving event is received first
        eventloop.run_until_complete(testIJson(self))
        self.assertEqual(
            self._res,
            {
                "janus": "event",
                "session_id": session,
                "sender": handle_p_2,
                "plugindata": {
                    "plugin": "janus.plugin.videoroom",
                    "data": {
                        "videoroom": "event",
                        "room": room,
                        "leaving": feed_1,
                    },
                },
            },
        )
        # and finally the success
        eventloop.run_until_complete(testIJanus(self))
        self.assertEqual(
            self._res,
            {
                "janus": "success",
                "session_id": session,
                "sender": handle_p_1,
            },
        )

        self.destroyVideoroom(token, session, control_handle, room)


if __name__ == "__main__":
    eventloop = asyncio.new_event_loop();

    so = tempfile.NamedTemporaryFile(mode="wb", delete=False)
    se = tempfile.NamedTemporaryFile(mode="wb", delete=False)
    os.environ["GLIB_SLICE"] = "debug-blocks"
    proc = subprocess.Popen(
        [
            os.environ.get("RTPE_BIN"),
            "--config-file=none",
            "-t",
            "-1",
            "-i",
            "203.0.113.1",
            "-f",
            "-L",
            "7",
            "-E",
            "--listen-http=127.0.0.1:9191",
            "--janus-secret=dfgdfgdvgLyATjHPvckg",
            "--delete-delay=0",
        ],
        stdout=so,
        stderr=se,
    )

    code = 255

    try:
        unittest.main()
        code = 0
    except SystemExit as e:
        if e.code == 0:
            code = 0
        else:
            code = e.code
            traceback.print_exc()
    except:
        traceback.print_exc()

    proc.terminate()
    proc.wait()

    so.close()
    se.close()

    eventloop.close()

    if code == 0:
        os.unlink(so.name)
        os.unlink(se.name)
    else:
        print("HINT: Stdout and stderr are {} and {}".format(so.name, se.name))
        sys.exit(code)
