Пример #1
0
    def _OH_account_unregister(self, data):
        transaction = data.get('transaction', None)
        if transaction is None:
            log.warn('Transaction not specified!')
            return

        try:
            try:
                account = data['account']
            except KeyError:
                raise APIError('Invalid parameters: "account" must be specified')

            try:
                account_info = self.accounts_map[account]
            except KeyError:
                raise APIError('Unknown account specified: %s' % account)

            handle_id = account_info.janus_handle_id
            if handle_id is not None:
                block_on(self.backend.janus_detach(self.janus_session_id, handle_id))
                self.backend.janus_set_event_handler(handle_id, None)
                account_info.janus_handle_id = None
                self.account_handles_map.pop(handle_id)

            data = dict(sylkrtc='ack', transaction=transaction)
            self._send_data(json.dumps(data))
            log.msg('Account %s will unregister' % account)
        except APIError, e:
            log.error('account-unregister: %s' % e)
            data = dict(sylkrtc='error', transaction=transaction, error=str(e))
            self._send_data(json.dumps(data))
Пример #2
0
    def _OH_session_trickle(self, data):
        transaction = data.get('transaction', None)
        if transaction is None:
            log.warn('Transaction not specified!')
            return

        try:
            try:
                session = data['session']
                candidates = data['candidates']
            except KeyError:
                raise APIError('Invalid parameters: "session" and "candidates" must be specified')

            try:
                session_info = self.sessions_map[session]
            except KeyError:
                raise APIError('Unknown session specified: %s' % session)
            if session_info.state == 'terminated':
                raise APIError('Session is terminated')

            block_on(self.backend.janus_trickle(self.janus_session_id, session_info.janus_handle_id, candidates))
            data = dict(sylkrtc='ack', transaction=transaction)
            self._send_data(json.dumps(data))
            log.msg('Trickled ICE candidate(s) for session %s' % session)
        except APIError, e:
            log.error('session-trickle: %s' % e)
            data = dict(sylkrtc='error', transaction=transaction, error=str(e))
            self._send_data(json.dumps(data))
Пример #3
0
    def _OH_session_terminate(self, data):
        transaction = data.get('transaction', None)
        if transaction is None:
            log.warn('Transaction not specified!')
            return

        try:
            try:
                session = data['session']
            except KeyError:
                raise APIError('Invalid parameters: "session" must be specified')

            try:
                session_info = self.sessions_map[session]
            except KeyError:
                raise APIError('Unknown session specified: %s' % session)
            if session_info.state not in ('connecting', 'progress', 'accepted', 'established'):
                raise APIError('Invalid state for session terminate: \"%s\"' % session_info.state)

            if session_info.direction == 'incoming' and session_info.state == 'connecting':
                data = {'request': 'decline', 'code': 486}
            else:
                data = {'request': 'hangup'}
            block_on(self.backend.janus_message(self.janus_session_id, session_info.janus_handle_id, data))
            data = dict(sylkrtc='ack', transaction=transaction)
            self._send_data(json.dumps(data))
            log.msg('%s terminated session %s' % (session_info.account_id, session))
        except APIError, e:
            log.error('session-terminate: %s' % e)
            data = dict(sylkrtc='error', transaction=transaction, error=str(e))
            self._send_data(json.dumps(data))
Пример #4
0
    def _OH_session_answer(self, data):
        transaction = data.get('transaction', None)
        if transaction is None:
            log.warn('Transaction not specified!')
            return

        try:
            try:
                session = data['session']
                sdp = data['sdp']
            except KeyError:
                raise APIError('Invalid parameters: "session" and "sdp" must be specified')

            try:
                session_info = self.sessions_map[session]
            except KeyError:
                raise APIError('Unknown session specified: %s' % session)

            if session_info.direction != 'incoming':
                raise APIError('Cannot answer outgoing session')
            if session_info.state != 'connecting':
                raise APIError('Invalid state for session answer')

            data = {'request': 'accept'}
            jsep = {'type': 'answer', 'sdp': sdp}
            block_on(self.backend.janus_message(self.janus_session_id, session_info.janus_handle_id, data, jsep))
            data = dict(sylkrtc='ack', transaction=transaction)
            self._send_data(json.dumps(data))
            log.msg('%s answered session %s' % (session_info.account_id, session))
        except APIError, e:
            log.error('session-answer: %s' % e)
            data = dict(sylkrtc='error', transaction=transaction, error=str(e))
            self._send_data(json.dumps(data))
Пример #5
0
def _send_push_notification(payload):
    if GeneralConfig.firebase_server_key:
        try:
            r = yield agent.request('POST', FIREBASE_API_URL, headers, StringProducer(payload))
        except Exception, e:
            log.msg('Error sending Firebase message: %s', e)
        else:
            if r.code != 200:
                log.warn('Error sending Firebase message: %s' % r.phrase)
Пример #6
0
 def onMessage(self, payload, isBinary):
     if isBinary:
         log.warn('Unexpected binary payload received')
         return
     if JanusConfig.trace_janus:
         self.factory.janus_logger.msg("IN", ISOTimestamp.now(), payload)
     try:
         data = json.loads(payload)
     except Exception, e:
         log.warn('Error decoding payload: %s' % e)
         return
Пример #7
0
 def onMessage(self, payload, is_binary):
     if is_binary:
         log.warn('Received invalid binary message')
         return
     if GeneralConfig.trace_websocket:
         self.factory.ws_logger.msg("IN", ISOTimestamp.now(), payload)
     try:
         data = json.loads(payload)
     except Exception, e:
         log.warn('Error parsing WebSocket payload: %s' % e)
         return
Пример #8
0
 def onMessage(self, payload, isBinary):
     if isBinary:
         log.warn('Unexpected binary payload received')
         return
     if JanusConfig.trace_janus:
         self.factory.janus_logger.msg("IN", ISOTimestamp.now(), payload)
     try:
         data = json.loads(payload)
     except Exception, e:
         log.warn('Error decoding payload: %s' % e)
         return
Пример #9
0
 def _create_janus_session(self):
     if self.ready_event.is_set():
         data = dict(sylkrtc='event', event='ready')
         self._send_data(json.dumps(data))
         return
     try:
         self.janus_session_id = block_on(self.backend.janus_create_session())
         self.backend.janus_start_keepalive(self.janus_session_id)
     except Exception, e:
         log.warn('Error creating session, disconnecting: %s' % e)
         self.disconnect(3000, unicode(e))
         return
Пример #10
0
    def _OH_session_create(self, data):
        transaction = data.get('transaction', None)
        if transaction is None:
            log.warn('Transaction not specified!')
            return

        try:
            try:
                account = data['account']
                session = data['session']
                uri = data['uri']
                sdp = data['sdp']
            except KeyError:
                raise APIError('Invalid parameters: "account", "session", "uri" and "sdp" must be specified')

            try:
                account_info = self.accounts_map[account]
            except KeyError:
                raise APIError('Unknown account specified: %s' % account)

            if session in self.sessions_map:
                raise APIError('Session ID (%s) already in use' % session)

            # Create a new plugin handle and 'register' it, without actually doing so
            handle_id = block_on(self.backend.janus_attach(self.janus_session_id, 'janus.plugin.sip'))
            self.backend.janus_set_event_handler(handle_id, self._handle_janus_event)
            try:
                proxy = self._lookup_sip_proxy(account_info.id)
            except DNSLookupError:
                block_on(self.backend.janus_detach(self.janus_session_id, handle_id))
                self.backend.janus_set_event_handler(handle_id, None)
                raise APIError('DNS lookup error')
            account_uri = 'sip:%s' % account_info.id
            data = {'request': 'register', 'username': account_uri, 'ha1_secret': account_info.password, 'proxy': proxy, 'send_register': False}
            block_on(self.backend.janus_message(self.janus_session_id, handle_id, data))

            session_info = JanusSessionInfo(session)
            session_info.janus_handle_id = handle_id
            session_info.init_outgoing(account, uri)
            self.sessions_map[session_info.id] = session_info
            self.session_handles_map[handle_id] = session_info

            data = {'request': 'call', 'uri': 'sip:%s' % SIP_PREFIX_RE.sub('', uri)}
            jsep = {'type': 'offer', 'sdp': sdp}
            block_on(self.backend.janus_message(self.janus_session_id, handle_id, data, jsep))
            data = dict(sylkrtc='ack', transaction=transaction)
            self._send_data(json.dumps(data))
            log.msg('Outgoing session %s from %s to %s created' % (session, account, uri))
        except APIError, e:
            log.error('session-create: %s' % e)
            data = dict(sylkrtc='error', transaction=transaction, error=str(e))
            self._send_data(json.dumps(data))
Пример #11
0
def _send_push_notification(payload):
    if GeneralConfig.firebase_server_key:
        try:
            r = yield agent.request('POST', FIREBASE_API_URL, headers,
                                    StringProducer(payload))
        except Exception as e:
            log.info('Error sending Firebase message: %s', e)
        else:
            if r.code != 200:
                log.warn('Error sending Firebase message: %s' % r.phrase)
    else:
        log.warn(
            'Cannot send push notification: no Firebase server key configured')
Пример #12
0
    def _OH_account_register(self, data):
        transaction = data.get('transaction', None)
        if transaction is None:
            log.warn('Transaction not specified!')
            return

        try:
            try:
                account = data['account']
            except KeyError:
                raise APIError('Invalid parameters: "account" must be specified')

            try:
                account_info = self.accounts_map[account]
            except KeyError:
                raise APIError('Unknown account specified: %s' % account)

            proxy = self._lookup_sip_proxy(account)

            handle_id = account_info.janus_handle_id
            if handle_id is not None:
                # Destroy the existing plugin handle
                block_on(self.backend.janus_detach(self.janus_session_id, handle_id))
                self.backend.janus_set_event_handler(handle_id, None)
                self.account_handles_map.pop(handle_id)
                account_info.janus_handle_id = None

            # Create a plugin handle
            handle_id = block_on(self.backend.janus_attach(self.janus_session_id, 'janus.plugin.sip'))
            self.backend.janus_set_event_handler(handle_id, self._handle_janus_event)
            account_info.janus_handle_id = handle_id
            self.account_handles_map[handle_id] = account_info

            data = {'request': 'register',
                    'username': account_info.uri,
                    'ha1_secret': account_info.password,
                    'proxy': proxy}
            block_on(self.backend.janus_message(self.janus_session_id, handle_id, data))

            data = dict(sylkrtc='ack', transaction=transaction)
            self._send_data(json.dumps(data))
            log.msg('Account %s will register' % account)
        except APIError, e:
            log.error('account-register: %s' % e)
            data = dict(sylkrtc='error', transaction=transaction, error=str(e))
            self._send_data(json.dumps(data))
Пример #13
0
 def onMessage(self, payload, isBinary):
     if isBinary:
         log.warn('Unexpected binary payload received')
         return
     self.notification_center.post_notification('WebRTCJanusTrace',
                                                sender=self,
                                                data=NotificationData(
                                                    direction='INCOMING',
                                                    message=payload,
                                                    peer=self.peer))
     try:
         data = json.loads(payload)
     except Exception as e:
         log.warn('Error decoding payload: %s' % e)
         return
     try:
         message_type = data.pop('janus')
     except KeyError:
         log.warn('Received payload lacks message type: %s' % payload)
         return
     transaction_id = data.pop('transaction', None)
     if message_type == 'event' or transaction_id is None:
         # This is an event. Janus is not very consistent here, some 'events'
         # do have the transaction id set. So we check for the message type as well.
         handle_id = data.pop('sender', -1)
         handler = self._event_handlers.get(handle_id, Null)
         try:
             handler(handle_id, message_type, data)
         except Exception:
             log.exception()
         return
     try:
         request, deferred = self._pending_transactions.pop(transaction_id)
     except KeyError:
         log.warn('Discarding unexpected response: %s' % payload)
         return
     # events were handled above, so the only message types we get here are ack, success and error
     # todo: some plugin errors are delivered with message_type == 'success' and the error code is buried somewhere in plugindata
     if message_type == 'error':
         code = data['error']['code']
         reason = data['error']['reason']
         deferred.errback(JanusError(code, reason))
     elif message_type == 'ack':
         deferred.callback(None)
     else:  # success
         # keepalive and trickle only receive an ACK, thus are handled above in message_type == 'ack', not here
         if request.type in ('create', 'attach'):
             result = data['data']['id']
         elif request.type in ('destroy', 'detach'):
             result = None
         else:  # info, message (for synchronous message requests only)
             result = data
         deferred.callback(result)
Пример #14
0
    def _OH_account_add(self, data):
        transaction = data.get('transaction', None)
        if transaction is None:
            log.warn('Transaction not specified!')
            return

        try:
            try:
                account = data['account']
                password = data['password']
            except KeyError:
                raise APIError('Invalid parameters: "account" and "password" must be specified')

            if account in self.accounts_map:
                log.warn('Account %s already added' % account)
                data = dict(sylkrtc='error', transaction=transaction, error='Account already added')
                self._send_data(json.dumps(data))
                return

            # Validate URI
            uri = 'sip:%s' % account
            try:
                sip_uri = SIPURI.parse(uri)
            except SIPCoreError:
                raise APIError('Invalid account specified: %s' % account)
            if not {'*', sip_uri.host}.intersection(GeneralConfig.sip_domains):
                raise APIError('SIP domain not allowed: %s' % sip_uri.host)

            # Create and store our mapping
            account_info = AccountInfo(account, password)
            self.accounts_map[account_info.id] = account_info

            data = dict(sylkrtc='ack', transaction=transaction)
            self._send_data(json.dumps(data))
            log.msg('Account %s added' % account)
        except APIError, e:
            log.error('account_add: %s' % e)
            data = dict(sylkrtc='error', transaction=transaction, error=str(e))
            self._send_data(json.dumps(data))
Пример #15
0
def missed_session(originator, destination, tokens):
    for token in tokens:
        data = {'to': token,
                'notification': {},
                'data': {'sylkrtc': {}},
                'content_available': True
        }
        data['notification']['body'] = 'Missed session from %s' % originator
        data['priority'] = 'high'
        # No TTL, default is 4 weeks
        data['data']['sylkrtc']['event'] = 'missed_session'
        data['data']['sylkrtc']['originator'] = originator
        data['data']['sylkrtc']['destination'] = destination
        data['data']['sylkrtc']['timestamp'] = str(ISOTimestamp.now())
        _send_push_notification(json.dumps(data))


@defer.inlineCallbacks
def _send_push_notification(payload):
    if GeneralConfig.firebase_server_key:
        try:
            r = yield agent.request('POST', FIREBASE_API_URL, headers, StringProducer(payload))
        except Exception, e:
            log.msg('Error sending Firebase message: %s', e)
        else:
            if r.code != 200:
                log.warn('Error sending Firebase message: %s' % r.phrase)
    else:
        log.warn('Cannot send push notification: no Firebase server key configured')
Пример #16
0
class JanusClientProtocol(WebSocketClientProtocol):
    _janus_event_handlers = None
    _janus_pending_transactions = None
    _janus_keepalive_timers = None
    _janus_keepalive_interval = 45

    def onOpen(self):
        notification_center = NotificationCenter()
        notification_center.post_notification('JanusBackendConnected',
                                              sender=self)
        self._janus_pending_transactions = {}
        self._janus_keepalive_timers = {}
        self._janus_event_handlers = {}

    def onMessage(self, payload, isBinary):
        if isBinary:
            log.warn('Unexpected binary payload received')
            return
        if JanusConfig.trace_janus:
            self.factory.janus_logger.msg("IN", ISOTimestamp.now(), payload)
        try:
            data = json.loads(payload)
        except Exception, e:
            log.warn('Error decoding payload: %s' % e)
            return
        try:
            message_type = data.pop('janus')
        except KeyError:
            log.warn('Received payload lacks message type: %s' % payload)
            return
        transaction_id = data.pop('transaction', None)
        if message_type == 'event' or transaction_id is None:
            # This is an event. Janus is not very consistent here, some 'events'
            # do have the transaction id set. So we check for the message type as well.
            handle_id = data.pop('sender', -1)
            handler = self._janus_event_handlers.get(handle_id, Null)
            try:
                handler(handle_id, message_type, data)
            except Exception:
                log.err()
            return
        try:
            req, d = self._janus_pending_transactions.pop(transaction_id)
        except KeyError:
            log.warn('Discarding unexpected response: %s' % payload)
            return
        if message_type == 'error':
            code = data['error']['code']
            reason = data['error']['reason']
            d.errback(JanusError(code, reason))
        else:
            if req.type == 'info':
                result = data
            elif req.type == 'create':
                result = data['data']['id']
            elif req.type == 'destroy':
                result = None
            elif req.type == 'attach':
                result = data['data']['id']
            elif req.type == 'detach':
                result = None
            elif req.type == 'keepalive':
                result = None
            elif req.type == 'ack':
                result = None
            else:
                result = data
            d.callback(result)
Пример #17
0
    def _OH_janus_event(self, data):
        handle_id = data['handle_id']
        event_type = data['event_type']
        event = data['event']

        if event_type == 'event':
            event_data = event['plugindata']['data']
            if 'result' in event_data:
                jsep = event.get('jsep', None)
                event_type = event_data['result']['event']
                if event_type in ('registering', 'registered', 'registration_failed', 'incomingcall'):
                    # skip 'registered' events from session handles
                    if event_type == 'registered' and event_data['result']['register_sent'] in (False, 'false'):
                        return
                    # account event
                    try:
                        account_info = self.account_handles_map[handle_id]
                    except KeyError:
                        log.warn('Could not find account for handle ID %s' % handle_id)
                        return
                    if event_type == 'incomingcall':
                        originator = SIP_PREFIX_RE.sub('', event_data['result']['username'])
                        jsep = event.get('jsep', None)
                        assert jsep is not None
                        session_id = uuid.uuid4().hex
                        session = JanusSessionInfo(session_id)
                        session.janus_handle_id = handle_id
                        session.init_incoming(account_info.id, originator)
                        self.sessions_map[session_id] = session
                        self.session_handles_map[handle_id] = session
                        data = dict(sylkrtc='account_event',
                                    account=account_info.id,
                                    session=session_id,
                                    event='incoming_session',
                                    data=dict(originator=originator, sdp=jsep['sdp']))
                        log.msg('Incoming session %s %s <-> %s created' % (session.id, session.remote_identity, session.local_identity))
                    else:
                        registration_state = event_type
                        if registration_state == 'registration_failed':
                            registration_state = 'failed'
                        if account_info.registration_state == registration_state:
                            return
                        account_info.registration_state = registration_state
                        registration_data = dict(state=registration_state)
                        if registration_state == 'failed':
                            code = event_data['result']['code']
                            reason = event_data['result']['reason']
                            registration_data['reason'] = '%d %s' % (code, reason)
                        data = dict(sylkrtc='account_event',
                                    account=account_info.id,
                                    event='registration_state',
                                    data=registration_data)
                        log.msg('Account %s registration state changed to %s' % (account_info.id, registration_state))
                    self._send_data(json.dumps(data))
                elif event_type in ('calling', 'accepted', 'hangup'):
                    # session event
                    try:
                        session_info = self.session_handles_map[handle_id]
                    except KeyError:
                        log.warn('Could not find session for handle ID %s' % handle_id)
                        return
                    if event_type == 'hangup' and session_info.state == 'terminated':
                        return
                    if event_type == 'calling':
                        session_info.state = 'progress'
                    elif event_type == 'accepted':
                        session_info.state = 'accepted'
                    elif event_type == 'hangup':
                        session_info.state = 'terminated'
                    data = dict(sylkrtc='session_event',
                                session=session_info.id,
                                event='state',
                                data=dict(state=session_info.state))
                    log.msg('%s session %s state: %s' % (session_info.direction.title(), session_info.id, session_info.state))
                    if session_info.state == 'accepted' and session_info.direction == 'outgoing':
                        assert jsep is not None
                        data['data']['sdp'] = jsep['sdp']
                    elif session_info.state == 'terminated':
                        code = event_data['result'].get('code', 0)
                        reason = event_data['result'].get('reason', 'Unknown')
                        reason = '%d %s' % (code, reason)
                        data['data']['reason'] = reason
                    self._send_data(json.dumps(data))
                    if session_info.state == 'terminated':
                        self._cleanup_session(session_info)
                        log.msg('%s session %s %s <-> %s terminated (%s)' % (session_info.direction.title(),
                                                                             session_info.id,
                                                                             session_info.local_identity,
                                                                             session_info.remote_identity,
                                                                             reason))
                elif event_type in ('ack', 'hangingup'):
                    # ignore
                    pass
                else:
                    log.warn('Unexpected event type: %s' % event_type)
            else:
                log.warn('Unexpected event: %s' % event)
        elif event_type == 'webrtcup':
            try:
                session_info = self.session_handles_map[handle_id]
            except KeyError:
                log.msg('Could not find session for handle ID %s' % handle_id)
                return
            session_info.state = 'established'
            data = dict(sylkrtc='session_event',
                        session=session_info.id,
                        event='state',
                        data=dict(state=session_info.state))
            log.msg('%s session %s state: %s' % (session_info.direction.title(), session_info.id, session_info.state))
            self._send_data(json.dumps(data))
        elif event_type == 'hangup':
            try:
                session_info = self.session_handles_map[handle_id]
            except KeyError:
                log.msg('Could not find session for handle ID %s' % handle_id)
                return
            if session_info.state != 'terminated':
                session_info.state = 'terminated'
                code = event.get('code', 0)
                reason = event.get('reason', 'Unknown')
                reason = '%d %s' % (code, reason)
                data = dict(sylkrtc='session_event',
                            session=session_info.id,
                            event='state',
                            data=dict(state=session_info.state, reason=reason))
                log.msg('%s session %s state: %s' % (session_info.direction.title(), session_info.id, session_info.state))
                self._send_data(json.dumps(data))
                self._cleanup_session(session_info)
                log.msg('%s session %s %s <-> %s terminated (%s)' % (session_info.direction.title(),
                                                                     session_info.id,
                                                                     session_info.local_identity,
                                                                     session_info.remote_identity,
                                                                     reason))
        elif event_type in ('media', 'declining'):
            # ignore
            pass
        else:
            log.warn('Received unexpected event type: %s' % event_type)