class SipFrom(SipAddressHF):
    hf_names = ('from', 'f')
    relaxedparser = True

    def __init__(self, body = None, address = None):
        SipAddressHF.__init__(self, body, address)
        if body == None and address == None:
            self.address = SipAddress(name = 'Anonymous', url = SipURL(host = SipConf.my_address, port = SipConf.my_port))

    def getTag(self):
        return self.address.getParam('tag')

    def genTag(self):
        salt = str((random() * 1000000000) + time())
        self.address.setParam('tag', md5(salt.encode()).hexdigest())

    def setTag(self, value):
        self.address.setParam('tag', value)

    def delTag(self):
        self.address.delParam('tag')

    def getCanName(self, name, compact = False):
        if compact:
            return 'f'
        return 'From' class SipTo(SipFrom):
    hf_names = ('to', 't')
    relaxedparser = True

    def getCanName(self, name, compact = False):
        if compact:
            return 't'
        return 'To' class UA(object):
    global_config = None
    state = None
    event_cb = None
    uasReq = None
    uacResp = None
    username = None
    password = None
    auth_enalgs = None
    equeue = None
    dId = None
    credit_time = None
    credit_times = None
    credit_timer = None
    conn_cbs = None
    disc_cbs = None
    fail_cbs = None
    ring_cbs = None
    dead_cbs = None
    rCSeq = None
    lTag = None
    lUri = None
    rUri = None
    cId = None
    lCSeq = None
    lContact = None
    rAddr = None
    rAddr0 = None
    routes = None
    rTarget = None
    uasResp = None
    lSDP = None
    rSDP = None
    kaInterval = 0
    branch = None
    reqs = None
    extra_headers = None
    useRefer = True
    expire_time = None
    expire_timer = None
    no_progress_time = None
    no_progress_timer = None
    no_reply_time = None
    no_reply_timer = None
    on_local_sdp_change = None
    on_remote_sdp_change = None
    last_scode = 100
    setup_ts = None
    p100_ts = None
    p1xx_ts = None
    connect_ts = None
    disconnect_ts = None
    local_ua = None
    remote_ua = None
    elast_seq = None
    origin = None
    source_address = None
    outbound_proxy = None
    pass_auth = False
    pending_tr = None
    late_media = False
    godead_timeout = 32.0
    compact_sip = False
    uas_lossemul = 0

    def __init__(self, global_config, event_cb = None, username = None, password = None, nh_address = None, credit_time = None, \
      conn_cbs = None, disc_cbs = None, fail_cbs = None, ring_cbs = None, dead_cbs = None, ltag = None, extra_headers = None, \
      expire_time = None, no_progress_time = None):
        self.global_config = global_config
        self.event_cb = event_cb
        self.equeue = []
        self.username = username
        self.password = password
        self.rAddr = nh_address
        self.rAddr0 = self.rAddr
        self.credit_time = credit_time
        self.credit_times = {}
        if conn_cbs != None:
            self.conn_cbs = conn_cbs
        else:
            self.conn_cbs = ()
        if disc_cbs != None:
            self.disc_cbs = disc_cbs
        else:
            self.disc_cbs = ()
        if fail_cbs != None:
            self.fail_cbs = fail_cbs
        else:
            self.fail_cbs = ()
        if ring_cbs != None:
            self.ring_cbs = ring_cbs
        else:
            self.ring_cbs = ()
        if dead_cbs != None:
            self.dead_cbs = dead_cbs
        else:
            self.dead_cbs = ()
        if ltag != None:
            self.lTag = ltag
        else:
            salt = str((random() * 1000000000) + time())
            self.lTag = md5(salt.encode()).hexdigest()
        self.reqs = {}
        self.extra_headers = extra_headers
        self.expire_time = expire_time
        self.no_progress_time = no_progress_time
        #print self.username, self.password

    def recvRequest(self, req, sip_t):
        #print 'Received request %s in state %s instance %s' % (req.getMethod(), self.state, self)
        #print self.rCSeq, req.getHFBody('cseq').getCSeqNum()
        sip_t.compact = self.compact_sip
        if self.remote_ua == None:
            self.update_ua(req)
        if self.rCSeq != None and self.rCSeq >= req.getHFBody('cseq').getCSeqNum():
            return (req.genResponse(500, 'Server Internal Error', server = self.local_ua), None, None)
        self.rCSeq = req.getHFBody('cseq').getCSeqNum()
        if self.state == None:
            if req.getMethod() == 'INVITE':
                self.changeState((UasStateIdle,))
            else:
                return None
        newstate = self.state.recvRequest(req)
        if newstate != None:
            self.changeState(newstate)
        self.emitPendingEvents()
        if newstate != None and req.getMethod() == 'INVITE':
            return (None, self.state.cancel, self.disconnect)
        else:
            return None

    def processChallenge(self, resp, cseq, ch_hfname, auth_hfname):
        if self.username == None or self.password == None or \
          self.reqs[cseq].countHFs(auth_hfname) != 0:
            return False
        for challenge in resp.getHFBodys(ch_hfname):
            #print(self.processChallenge, cseq, challenge, challenge.algorithm)
            if self.auth_enalgs != None and challenge.algorithm not in self.auth_enalgs:
                continue
            supported, qop = challenge.supportedAlgorithm()
            if supported:
                break
        else:
            return False
        req = self.genRequest('INVITE', self.lSDP, (challenge, qop))
        self.lCSeq += 1
        self.tr = self.global_config['_sip_tm'].newTransaction(req, self.recvResponse, \
          laddress = self.source_address, cb_ifver = 2, compact = self.compact_sip)
        del self.reqs[cseq]
        return True

    def recvResponse(self, resp, tr):
        if self.state == None:
            return
        self.update_ua(resp)
        code, reason = resp.getSCode()
        cseq, method = resp.getHFBody('cseq').getCSeq()
        if method == 'INVITE' and not self.pass_auth and cseq in self.reqs:
            if code == 401 and self.processChallenge(resp, cseq, \
              'www-authenticate', 'authorization'):
                return None
            if code == 407 and self.processChallenge(resp, cseq, \
              'proxy-authenticate', 'proxy-authorization'):
                return None
        if code >= 200 and cseq in self.reqs:
            del self.reqs[cseq]
        newstate = self.state.recvResponse(resp, tr)
        if newstate != None:
            self.changeState(newstate)
        self.emitPendingEvents()

    def recvEvent(self, event):
        #print self, event
        if self.state == None:
            if isinstance(event, CCEventTry) or isinstance(event, CCEventFail) or isinstance(event, CCEventDisconnect):
                self.changeState((UacStateIdle,))
            else:
                return
        newstate = self.state.recvEvent(event)
        if newstate != None:
            self.changeState(newstate)
        self.emitPendingEvents()

    def disconnect(self, rtime = None):
        if rtime == None:
            rtime = MonoTime()
        self.equeue.append(CCEventDisconnect(rtime = rtime))
        self.recvEvent(CCEventDisconnect(rtime = rtime))

    def expires(self):
        self.expire_timer = None
        self.disconnect()

    def no_progress_expires(self):
        self.no_progress_timer = None
        self.disconnect()

    def no_reply_expires(self):
        self.no_reply_timer = None
        self.disconnect()

    def credit_expires(self, rtime):
        self.credit_timer = None
        self.disconnect(rtime)

    def changeState(self, newstate):
        if self.state != None:
            self.state.onStateChange(newstate[0])
        self.state = newstate[0](self)
        if len(newstate) > 1:
            for callback in newstate[1]:
                callback(self, *newstate[2:])

    def emitEvent(self, event):
        if self.event_cb != None:
            if self.elast_seq != None and self.elast_seq >= event.seq:
                #print 'ignoring out-of-order event', event, event.seq, self.elast_seq, self.cId
                return
            self.elast_seq = event.seq
            self.event_cb(event, self)

    def emitPendingEvents(self):
        while len(self.equeue) != 0 and self.event_cb != None:
            event = self.equeue.pop(0)
            if self.elast_seq != None and self.elast_seq >= event.seq:
                #print 'ignoring out-of-order event', event, event.seq, self.elast_seq, self.cId
                continue
            self.elast_seq = event.seq
            self.event_cb(event, self)

    def genRequest(self, method, body = None, cqop = None, \
      reason = None, max_forwards = None):
        if self.outbound_proxy != None:
            target = self.outbound_proxy
        else:
            target = self.rAddr
        if max_forwards != None:
            max_forwards_hf = SipMaxForwards(number = max_forwards)
        else:
            max_forwards_hf = None
        req = SipRequest(method = method, ruri = self.rTarget, to = self.rUri, fr0m = self.lUri,
                         cseq = self.lCSeq, callid = self.cId, contact = self.lContact,
                         routes = self.routes, target = target,
                         user_agent = self.local_ua, maxforwards = max_forwards_hf)
        if cqop != None:
            challenge, qop = cqop
            if body != None and qop == 'auth-int':
                sbody = str(body)
                #print(len(sbody), sbody)
            else:
                sbody = None
            auth = challenge.genAuthHF(self.username, self.password, method, \
              str(self.rTarget), sbody, qop)
            req.appendHeader(SipHeader(body = auth))
        if body != None:
            req.setBody(body)
        if self.extra_headers != None:
            req.appendHeaders(self.extra_headers)
        if reason != None:
            req.appendHeader(SipHeader(body = reason))
        self.reqs[self.lCSeq] = req
        return req

    def sendUasResponse(self, scode, reason, body = None, contacts = None, \
      reason_rfc3326 = None, extra_headers = None, ack_wait = False):
        uasResp = self.uasResp.getCopy()
        uasResp.setSCode(scode, reason)
        uasResp.setBody(body)
        if contacts != None:
            for contact in contacts:
                uasResp.appendHeader(SipHeader(name = 'contact', body = contact))
        if reason_rfc3326 != None:
            uasResp.appendHeader(SipHeader(body = reason_rfc3326))
        if extra_headers != None:
            uasResp.appendHeaders(extra_headers)
        if ack_wait:
            ack_cb = self.recvACK
        else:
            ack_cb = None
        self.global_config['_sip_tm'].sendResponse(uasResp, ack_cb = ack_cb, \
          lossemul = self.uas_lossemul)

    def recvACK(self, req):
        if not self.isConnected():
            return
        #print 'UA::recvACK', req
        newstate = self.state.recvACK(req)
        if newstate != None:
            self.changeState(newstate)
        self.emitPendingEvents()

    def isYours(self, req = None, call_id = None, from_tag = None, to_tag = None):
        #print self.branch, req.getHFBody('via').getBranch()
        if req != None:
            if req.getMethod() != 'BYE' and self.branch != None and \
              self.branch != req.getHFBody('via').getBranch():
                return None
            call_id = str(req.getHFBody('call-id'))
            from_tag = req.getHFBody('from').getTag()
            to_tag = req.getHFBody('to').getTag()
        #print str(self.cId), call_id
        if call_id != str(self.cId):
            return None
        #print self.rUri.getTag(), from_tag
        if self.rUri != None and self.rUri.getTag() != from_tag:
            return None
        #print self.lUri.getTag(), to_tag
        if self.lUri != None and self.lUri.getTag() != to_tag:
            return None
        return self

    def isDead(self):
        if self.state != None:
            return self.state.dead
        return False

    def isConnected(self):
        if self.state != None:
            return self.state.connected
        return False

    def getCLD(self):
        if self.rUri == None:
            return None
        return self.rUri.getUrl().username

    def getCLI(self):
        if self.lUri == None:
            return None
        return self.lUri.getUrl().username

    def getCallingName(self):
        if self.lUri == None:
            return None
        return self.lUri.getUri().name

    def getRAddr0(self):
        return self.rAddr0

    def getCID(self):
        # Return tuple containing call-id, local tag and remote tag
        rval = [str(self.cId), None, None]
        if self.lUri != None:
            rval[1] = self.lUri.getTag()
        if self.rUri != None:
            rval[2] = self.rUri.getTag()
        return tuple(rval)

    def delayed_remote_sdp_update(self, event, remote_sdp_body):
        self.rSDP = remote_sdp_body.getCopy()
        self.equeue.append(event)
        self.emitPendingEvents()

    def getAcct(self):
        if self.disconnect_ts != None:
            disconnect_ts = self.disconnect_ts
            disconnected = True
        else:
            disconnect_ts = MonoTime()
            disconnected = False
        if self.connect_ts != None:
            return (disconnect_ts - self.connect_ts, self.connect_ts - self.setup_ts, True, disconnected)
        return (0, disconnect_ts - self.setup_ts, False, disconnected)

    def update_ua(self, msg):
        if msg.countHFs('user-agent') > 0:
            self.remote_ua = msg.getHFBody('user-agent').name
        elif msg.countHFs('server') > 0:
            self.remote_ua = msg.getHFBody('server').name
        return

    def cancelCreditTimer(self):
        if self.credit_timer != None:
            self.credit_timer.cancel()
            self.credit_timer = None

    def startCreditTimer(self, rtime):
        if self.credit_time != None:
            self.credit_times[0] = rtime.getOffsetCopy(self.credit_time)
            self.credit_time = None
        try:
            credit_time = min([x for x in self.credit_times.values() if x != None])
        except ValueError:
            return
        self.credit_timer = TimeoutAbsMono(self.credit_expires, credit_time, credit_time)

    def resetCreditTime(self, rtime, new_credit_times):
        self.credit_times.update(new_credit_times)
        if self.state.connected:
            self.cancelCreditTimer()
            self.startCreditTimer(rtime)

    def cleanup(self):
        pass class CCEventRing(CCEventGeneric):
    name = 'CCEventRing'
    pass class CCEventConnect(CCEventGeneric):
    name = 'CCEventConnect'
    pass class CCEventDisconnect(CCEventGeneric):
    name = 'CCEventDisconnect'
    pass class CCEventTry(CCEventGeneric):
    name = 'CCEventTry'
    max_forwards = None

    def getCopy(self):
        cself = CCEventGeneric.getCopy(self)
        cself.max_forwards = self.max_forwards
        return cself

    # Speacial method allowing tweaking internal parameters of the UAC
    # after everything has been setup but before INVITE goes out.
    def onUacSetupComplete(self, uac):
        pass class CCEventUpdate(CCEventGeneric):
    name = 'CCEventUpdate'
    max_forwards = None

    def getCopy(self):
        cself = CCEventGeneric.getCopy(self)
        cself.max_forwards = self.max_forwards
        return cself class CCEventFail(CCEventGeneric):
    name = 'CCEventFail'
    challenges = None
    warning = None

    def getCopy(self):
        cself = CCEventGeneric.getCopy(self)
        if self.challenges != None:
            cself.challenges = [x.getCopy() for x in self.challenges]
        return cself

    def setWarning(self, eistr):
        self.warning = SipHeader(body = SipWarning(text = eistr)) class UasStateTrying(UaStateGeneric):
    sname = 'Trying(UAS)'

    def recvEvent(self, event):
        if isinstance(event, CCEventRing):
            scode = event.getData()
            if scode == None:
                code, reason, body = (180, 'Ringing', None)
            else:
                code, reason, body = scode
                if code == 100:
                    return None
                if body != None and self.ua.on_local_sdp_change != None and body.needs_update:
                    self.ua.on_local_sdp_change(body, lambda x: self.ua.recvEvent(event))
                    return None
            self.ua.lSDP = body
            self.ua.sendUasResponse(code, reason, body)
            if self.ua.no_progress_timer != None:
                self.ua.no_progress_timer.cancel()
                self.ua.no_progress_timer = None
                if self.ua.expire_time != None:
                    self.ua.expire_timer = TimeoutAbsMono(self.ua.expires, self.ua.expire_mtime)
            if self.ua.p1xx_ts == None:
                self.ua.p1xx_ts = event.rtime
            return (UasStateRinging, self.ua.ring_cbs, event.rtime, event.origin, code)
        elif isinstance(event, CCEventConnect) or isinstance(event, CCEventPreConnect):
            code, reason, body = event.getData()
            if body != None and self.ua.on_local_sdp_change != None and body.needs_update:
                self.ua.on_local_sdp_change(body, lambda x: self.ua.recvEvent(event))
                return None
            if event.extra_headers != None:
                extra_headers = tuple(event.extra_headers)
            else:
                extra_headers = None
            self.ua.lSDP = body
            if self.ua.no_progress_timer != None:
                self.ua.no_progress_timer.cancel()
                self.ua.no_progress_timer = None
            if isinstance(event, CCEventConnect):
                self.ua.sendUasResponse(code, reason, body, (self.ua.lContact,), ack_wait = False, \
                  extra_headers = extra_headers)
                if self.ua.expire_timer != None:
                    self.ua.expire_timer.cancel()
                    self.ua.expire_timer = None
                self.ua.startCreditTimer(event.rtime)
                self.ua.connect_ts = event.rtime
                return (UaStateConnected, self.ua.conn_cbs, event.rtime, event.origin)
            else:
                self.ua.sendUasResponse(code, reason, body, (self.ua.lContact,), ack_wait = True, \
                  extra_headers = extra_headers)
                return (UaStateConnected,)
        elif isinstance(event, CCEventRedirect):
            scode = event.getData()
            contacts = None
            if scode == None:
                scode = (500, 'Failed', None, None)
            elif scode[3] != None:
                contacts = tuple(SipContact(address = x) for x in scode[3])
            self.ua.sendUasResponse(scode[0], scode[1], scode[2], contacts)
            if self.ua.expire_timer != None:
                self.ua.expire_timer.cancel()
                self.ua.expire_timer = None
            if self.ua.no_progress_timer != None:
                self.ua.no_progress_timer.cancel()
                self.ua.no_progress_timer = None
            self.ua.disconnect_ts = event.rtime
            return (UaStateFailed, self.ua.fail_cbs, event.rtime, event.origin, scode[0])
        elif isinstance(event, CCEventFail):
            scode = event.getData()
            if scode == None:
                scode = (500, 'Failed')
            extra_headers = []
            if event.extra_headers != None:
                extra_headers.extend(event.extra_headers)
            if event.challenges != None:
                extra_headers.extend(event.challenges)
            if len(extra_headers) == 0:
                extra_headers = None
            else:
                extra_headers = tuple(extra_headers)
            self.ua.sendUasResponse(scode[0], scode[1], reason_rfc3326 = event.reason, \
              extra_headers = extra_headers)
            if self.ua.expire_timer != None:
                self.ua.expire_timer.cancel()
                self.ua.expire_timer = None
            if self.ua.no_progress_timer != None:
                self.ua.no_progress_timer.cancel()
                self.ua.no_progress_timer = None
            self.ua.disconnect_ts = event.rtime
            return (UaStateFailed, self.ua.fail_cbs, event.rtime, event.origin, scode[0])
        elif isinstance(event, CCEventDisconnect):
            #import sys, traceback
            #traceback.print_stack(file = sys.stdout)
            self.ua.sendUasResponse(500, 'Disconnected', reason_rfc3326 = event.reason)
            if self.ua.expire_timer != None:
                self.ua.expire_timer.cancel()
                self.ua.expire_timer = None
            if self.ua.no_progress_timer != None:
                self.ua.no_progress_timer.cancel()
                self.ua.no_progress_timer = None
            self.ua.disconnect_ts = event.rtime
            return (UaStateDisconnected, self.ua.disc_cbs, event.rtime, event.origin, self.ua.last_scode)
        #print 'wrong event %s in the Trying state' % event
        return None

    def cancel(self, rtime, req):
        self.ua.disconnect_ts = rtime
        self.ua.changeState((UaStateDisconnected, self.ua.disc_cbs, rtime, self.ua.origin))
        event = CCEventDisconnect(rtime = rtime, origin = self.ua.origin)
        if req != None:
            try:
                event.reason = req.getHFBody('reason')
            except:
                pass
        self.ua.emitEvent(event) class UasStateRinging(UaStateGeneric):
    sname = 'Ringing(UAS)'
    rseq = None

    def recvEvent(self, event):
        if isinstance(event, CCEventRing):
            scode = event.getData()
            if scode == None:
                code, reason, body = (180, 'Ringing', None)
            else:
                code, reason, body = scode
                if code == 100:
                    return None
                if body != None and self.ua.on_local_sdp_change != None and body.needs_update:
                    self.ua.on_local_sdp_change(body, lambda x: self.ua.recvEvent(event))
                    return None
            self.ua.lSDP = body
            if self.ua.p1xx_ts == None:
                self.ua.p1xx_ts = event.rtime
            self.ua.sendUasResponse(code, reason, body)
            for ring_cb in self.ua.ring_cbs:
                ring_cb(self.ua, event.rtime, event.origin, code)
            return None
        elif isinstance(event, CCEventConnect) or isinstance(event, CCEventPreConnect):
            code, reason, body = event.getData()
            if body != None and self.ua.on_local_sdp_change != None and body.needs_update:
                self.ua.on_local_sdp_change(body, lambda x: self.ua.recvEvent(event))
                return None
            if event.extra_headers != None:
                extra_headers = tuple(event.extra_headers)
            else:
                extra_headers = None
            self.ua.lSDP = body
            if isinstance(event, CCEventConnect):
                self.ua.sendUasResponse(code, reason, body, (self.ua.lContact,), ack_wait = False, \
                  extra_headers = extra_headers)
                if self.ua.expire_timer != None:
                    self.ua.expire_timer.cancel()
                    self.ua.expire_timer = None
                self.ua.startCreditTimer(event.rtime)
                self.ua.connect_ts = event.rtime
                return (UaStateConnected, self.ua.conn_cbs, event.rtime, event.origin)
            else:
                self.ua.sendUasResponse(code, reason, body, (self.ua.lContact,), ack_wait = True, \
                  extra_headers = extra_headers)
                return (UaStateConnected,)
        elif isinstance(event, CCEventRedirect):
            scode = event.getData()
            contacts = None
            if scode == None:
                scode = (500, 'Failed', None, None)
            elif scode[3] != None:
                contacts = tuple(SipContact(address = x) for x in scode[3])
            self.ua.sendUasResponse(scode[0], scode[1], scode[2], contacts)
            if self.ua.expire_timer != None:
                self.ua.expire_timer.cancel()
                self.ua.expire_timer = None
            self.ua.disconnect_ts = event.rtime
            return (UaStateFailed, self.ua.fail_cbs, event.rtime, event.origin, scode[0])
        elif isinstance(event, CCEventFail):
            scode = event.getData()
            if scode == None:
                scode = (500, 'Failed')
            if event.extra_headers != None:
                extra_headers = tuple(event.extra_headers)
            else:
                extra_headers = None
            self.ua.sendUasResponse(scode[0], scode[1], reason_rfc3326 = event.reason, \
              extra_headers = extra_headers)
            if self.ua.expire_timer != None:
                self.ua.expire_timer.cancel()
                self.ua.expire_timer = None
            self.ua.disconnect_ts = event.rtime
            return (UaStateFailed, self.ua.fail_cbs, event.rtime, event.origin, scode[0])
        elif isinstance(event, CCEventDisconnect):
            #import sys, traceback
            #traceback.print_stack(file = sys.stdout)
            self.ua.sendUasResponse(500, 'Disconnected', reason_rfc3326 = event.reason)
            if self.ua.expire_timer != None:
                self.ua.expire_timer.cancel()
                self.ua.expire_timer = None
            self.ua.disconnect_ts = event.rtime
            return (UaStateDisconnected, self.ua.disc_cbs, event.rtime, event.origin, self.ua.last_scode)
        #print 'wrong event %s in the Ringing state' % event
        return None

    def recvRequest(self, req):
        if req.getMethod() == 'BYE':
            self.ua.sendUasResponse(487, 'Request Terminated')
            self.ua.global_config['_sip_tm'].sendResponse(req.genResponse(200, 'OK',
              server = self.ua.local_ua), lossemul = self.ua.uas_lossemul)
            #print 'BYE received in the Ringing state, going to the Disconnected state'
            if req.countHFs('also') > 0:
                also = req.getHFBody('also').getCopy()
            else:
                also = None
            event = CCEventDisconnect(also, rtime = req.rtime, origin = self.ua.origin)
            try:
                event.reason = req.getHFBody('reason')
            except:
                pass
            self.ua.equeue.append(event)
            if self.ua.expire_timer != None:
                self.ua.expire_timer.cancel()
                self.ua.expire_timer = None
            self.ua.disconnect_ts = req.rtime
            return (UaStateDisconnected, self.ua.disc_cbs, req.rtime, self.ua.origin)
        return None

    def cancel(self, rtime, req):
        self.ua.disconnect_ts = rtime
        self.ua.changeState((UaStateDisconnected, self.ua.disc_cbs, rtime, self.ua.origin))
        event = CCEventDisconnect(rtime = rtime, origin = self.ua.origin)
        if req != None:
            try:
                event.reason = req.getHFBody('reason')
            except:
                pass
        self.ua.emitEvent(event) class UaStateDead(UaStateGeneric):
    sname = 'Dead'
    dead = True

    def __init__(self, ua):
        UaStateGeneric.__init__(self, None)
        if ua.cId != None:
            ua.global_config['_sip_tm'].unregConsumer(ua, str(ua.cId))
        ua.tr = None
        ua.event_cb = None
        ua.conn_cbs = ()
        ua.disc_cbs = ()
        ua.fail_cbs = ()
        ua.on_local_sdp_change = None
        ua.on_remote_sdp_change = None
        ua.expire_timer = None
        ua.no_progress_timer = None
        ua.credit_timer = None
        # Keep this at the very end of processing
        for callback in ua.dead_cbs:
            callback(ua)
        ua.dead_cbs = ()
        ua.cleanup()
        # Break cross-ref chain
        self.ua = None class SipHeader(object):
    name = None
    body = None

    def __init__(self, s = None, name = None, body = None, bodys = None, fixname = False):
        if s != None:
            name, bodys = [x.strip() for x in s.split(':', 1)]
        if name != None:
            self.name = name.lower()
        if body == None:
            try:
                try:
                    body = hf_types[self.name](bodys)
                except KeyError:
                    body = SipGenericHF(bodys, name)
            except ESipHeaderCSV as einst:
                einst.name = self.name
                raise einst
        self.body = body
        # If no name is provided use canonic name from the body-specific
        # class.
        if self.name == None or fixname:
            self.name = body.hf_names[0]

    def __str__(self):
        return str(self.body.getCanName(self.name)) + ': ' + str(self.body)

    def localStr(self, local_addr = None, local_port = None, compact = False):
        return str(self.body.getCanName(self.name, compact)) + ': ' + \
          self.body.localStr(local_addr, local_port)

    def getBody(self):
        if not self.body.parsed:
            self.body.parse()
        return self.body

    def getBCopy(self):
        return self.body.getCopy()

    def getCopy(self):
        cself = self.__class__(name = self.name, body = self.body.getCopy())
        return cself  class RadiusAuthorisation(Radius_client):
    def do_auth(self, username, caller, callee, sip_cid, remote_ip, res_cb, \
      realm = None, nonce = None, uri = None, response = None, extra_attributes = None):
        sip_cid = str(sip_cid)
        attributes = None
        if None not in (realm, nonce, uri, response):
            attributes = [('User-Name', username), ('Digest-Realm', realm), \
              ('Digest-Nonce', nonce), ('Digest-Method', 'INVITE'), ('Digest-URI', uri), \
              ('Digest-Algorithm', 'MD5'), ('Digest-User-Name', username), ('Digest-Response', response)]
        else:
            attributes = [('User-Name', remote_ip), ('Password', 'cisco')]
        if caller == None:
            caller = ''
        attributes.extend((('Calling-Station-Id', caller), ('Called-Station-Id', callee), \
          ('call-id', sip_cid), ('h323-remote-address', remote_ip), ('h323-session-protocol', 'sipv2')))
        if extra_attributes != None:
            for a, v in extra_attributes:
                attributes.append((a, v))
        message = 'sending AAA request:\n' 
        message += reduce(lambda x, y: x + y, ['%-32s = \'%s\'\n' % (x[0], str(x[1])) for x in attributes])
        self.global_config['_sip_logger'].write(message, call_id = sip_cid)
        Radius_client.do_auth(self, attributes, self._process_result, res_cb, sip_cid, time())

    def _process_result(self, results, res_cb, sip_cid, btime):
        delay = time() - btime
        rcode = results[1]
        if rcode in (0, 1):
            if rcode == 0:
                message = 'AAA request accepted (delay is %.3f), processing response:\n' % delay
            else:
                message = 'AAA request rejected (delay is %.3f), processing response:\n' % delay
            if len(results[0]) > 0:
                message += reduce(lambda x, y: x + y, ['%-32s = \'%s\'\n' % x for x in results[0]])
        else:
            message = 'Error sending AAA request (delay is %.3f)\n' % delay
        self.global_config['_sip_logger'].write(message, call_id = sip_cid)
        res_cb(results) class RadiusAccounting(object):
    global_config = None
    drec = None
    crec = None
    iTime = None
    cTime = None
    sip_cid = None
    origin = None
    lperiod = None
    el = None
    send_start = None
    complete = False
    ms_precision = False
    user_agent = None
    p1xx_ts = None
    p100_ts = None

    def __init__(self, global_config, origin, lperiod = None, send_start = False):
        self.global_config = global_config
        self._attributes = [('h323-call-origin', origin), ('h323-call-type', 'VoIP'), \
          ('h323-session-protocol', 'sipv2')]
        self.drec = False
        self.crec = False
        self.origin = origin
        self.lperiod = lperiod
        self.send_start = send_start

    def setParams(self, username, caller, callee, sip_cid, remote_ip, \
      h323_in_cid = None):
        if caller == None:
            caller = ''
        self._attributes.extend((('User-Name', username), ('Calling-Station-Id', caller), \
          ('Called-Station-Id', callee), ('call-id', sip_cid), \
          ('Acct-Session-Id', sip_cid), ('h323-remote-address', remote_ip)))
        if h323_in_cid != None:
            self._attributes.append(('h323-incoming-conf-id', h323_in_cid))
        self.sip_cid = str(sip_cid)
        self.complete = True

    def conn(self, ua, rtime, origin):
        if self.crec:
            return
        self.crec = True
        self.iTime = ua.setup_ts
        self.cTime = ua.connect_ts
        if ua.remote_ua != None and self.user_agent == None:
            self.user_agent = ua.remote_ua
        if ua.p1xx_ts != None:
            self.p1xx_ts = ua.p1xx_ts
        if ua.p100_ts != None:
            self.p100_ts = ua.p100_ts
        if self.send_start:
            self.asend('Start', rtime, origin, ua)
        self._attributes.extend((('h323-voice-quality', 0), ('Acct-Terminate-Cause', 'User-Request')))
        if self.lperiod != None and self.lperiod > 0:
            self.el = Timeout(self.asend, self.lperiod, -1, 'Alive')

    def disc(self, ua, rtime, origin, result = 0):
        if self.drec:
            return
        self.drec = True
        if self.el != None:
            self.el.cancel()
            self.el = None
        if self.iTime == None:
            self.iTime = ua.setup_ts
        if self.cTime == None:
            self.cTime = rtime
        if ua.remote_ua != None and self.user_agent == None:
            self.user_agent = ua.remote_ua
        if ua.p1xx_ts != None:
            self.p1xx_ts = ua.p1xx_ts
        if ua.p100_ts != None:
            self.p100_ts = ua.p100_ts
        self.asend('Stop', rtime, origin, result, ua)

    def asend(self, type, rtime = None, origin = None, result = 0, ua = None):
        if not self.complete:
            return
        if rtime == None:
            rtime = time()
        if ua != None:
            duration, delay, connected = ua.getAcct()[:3]
        else:
            # Alive accounting
            duration = rtime - self.cTime
            delay = self.cTime - self.iTime
            connected = True
        if not(self.ms_precision):
            duration = round(duration)
            delay = round(delay)
        attributes = self._attributes[:]
        if type != 'Start':
            if result >= 400:
                try:
                    dc = sipErrToH323Err[result][0]
                except:
                    dc = '7f'
            elif result < 200:
                dc = '10'
            else:
                dc = '0'
            attributes.extend((('h323-disconnect-time', self.ftime(self.iTime.realt + delay + duration)), \
              ('Acct-Session-Time', '%d' % round(duration)), ('h323-disconnect-cause', dc)))
        if type == 'Stop':
            if origin == 'caller':
                release_source = '2'
            elif origin == 'callee':
                release_source = '4'
            else:
                release_source = '8'
            attributes.append(('release-source', release_source))
        attributes.extend((('h323-connect-time', self.ftime(self.iTime.realt + delay)), \
          ('h323-setup-time', self.ftime(self.iTime.realt)), ('Acct-Status-Type', type)))
        if self.user_agent != None:
            attributes.append(('h323-ivr-out', 'sip_ua:' + self.user_agent))
        if self.p1xx_ts != None:
            attributes.append(('Acct-Delay-Time', round(self.p1xx_ts)))
        if self.p100_ts != None:
            attributes.append(('provisional-timepoint', self.ftime(self.p100_ts.realt)))
        pattributes = ['%-32s = \'%s\'\n' % (x[0], str(x[1])) for x in attributes]
        pattributes.insert(0, 'sending Acct %s (%s):\n' % (type, self.origin.capitalize()))
        self.global_config['_sip_logger'].write(call_id = self.sip_cid, *pattributes)
        self.global_config['_radius_client'].do_acct(attributes, self._process_result, self.sip_cid, time())

    def ftime(self, t):
        gt = gmtime(t)
        day = strftime('%d', gt)
        if day[0] == '0':
            day = day[1]
        if self.ms_precision:
            msec = (t % 1) * 1000
        else:
            msec = 0
        return strftime('%%H:%%M:%%S.%.3d GMT %%a %%b %s %%Y' % (msec, day), gt)

    def _process_result(self, results, sip_cid, btime):
        delay = time() - btime
        rcode = results[1]
        if rcode in (0, 1):
            if rcode == 0:
                message = 'Acct/%s request accepted (delay is %.3f)\n' % (self.origin, delay)
            else:
                message = 'Acct/%s request rejected (delay is %.3f)\n' % (self.origin, delay)
        else:
            message = 'Error sending Acct/%s request (delay is %.3f)\n' % (self.origin, delay)
        self.global_config['_sip_logger'].write(message, call_id = sip_cid) class FakeAccounting(object):
    def __init__(self, *args):
        pass

    def conn(self, *args):
        pass

    def disc(self, *args):
        pass class Rtp_proxy_session(object):
    rtp_proxy_client = None
    call_id = None
    from_tag = None
    to_tag = None
    caller = None
    callee = None
    max_index = -1
    notify_socket = None
    notify_tag = None
    global_config = None
    my_ident = None
    insert_nortpp = False

    def __init__(self, global_config, call_id = None, from_tag = None, to_tag = None,
      notify_socket = None, notify_tag = None):
        self.global_config = global_config
        self.my_ident = get_ident()
        if '_rtp_proxy_clients' in global_config:
            rtp_proxy_clients = [x for x in global_config['_rtp_proxy_clients'] if x.online]
            n = len(rtp_proxy_clients)
            if n == 0:
                raise Exception('No online RTP proxy client has been found')
            self.rtp_proxy_client = rtp_proxy_clients[int(random() * n)]
        else:
            self.rtp_proxy_client = global_config['rtp_proxy_client']
            if not self.rtp_proxy_client.online:
                raise Exception('No online RTP proxy client has been found')
        if call_id != None:
            self.call_id = call_id
        else:
            salt = str(random()) + str(time())
            self.call_id = md5(salt.encode()).hexdigest()
        if from_tag != None:
            self.from_tag = from_tag
        else:
            salt = str(random()) + str(time())
            self.from_tag = md5(salt.encode()).hexdigest()
        if to_tag != None:
            self.to_tag = to_tag
        else:
            salt = str(random()) + str(time())
            self.to_tag = md5(salt.encode()).hexdigest()
        self.notify_socket = notify_socket
        self.notify_tag = notify_tag
        self.caller = _rtpps_side()
        self.callee = _rtpps_side()

    def version(self, result_callback):
        self.rtp_proxy_client.send_command('V', self.version_result, result_callback)

    def version_result(self, result, result_callback):
        result_callback(result)

    def play_caller(self, prompt_name, times = 1, result_callback = None, index = 0):
        return self.caller._play(self, prompt_name, times, result_callback, index)

    def play_callee(self, prompt_name, times = 1, result_callback = None, index = 0):
        return self.callee._play(self, prompt_name, times, result_callback, index)

    def stop_play_caller(self, result_callback = None, index = 0):
        return self.caller._stop_play(self, result_callback, index)

    def stop_play_callee(self, result_callback = None, index = 0):
        return self.callee._stop_play(self, result_callback, index)

    def copy_caller(self, remote_ip, remote_port, result_callback = None, index = 0):
        return self.caller._copy(self, remote_ip, remote_port, result_callback, index)

    def copy_callee(self, remote_ip, remote_port, result_callback = None, index = 0):
        return self.callee._copy(self, remote_ip, remote_port, result_callback, index)

    def start_recording(self, rname = None, result_callback = None, index = 0):
        if not self.caller.session_exists:
            self.caller.update(self, '0.0.0.0', 0, self._start_recording, '', index, 'IP4', rname, result_callback, index)
            return
        self._start_recording(None, self, rname, result_callback, index)

    def _start_recording(self, result, rtpps, rname, result_callback, index):
        if rname == None:
            command = 'R %s %s %s' % ('%s-%d' % (self.call_id, index), self.from_tag, self.to_tag)
            return self.rtp_proxy_client.send_command(command, self.command_result, result_callback)
        command = 'C %s %s.a %s %s' % ('%s-%d' % (self.call_id, index), rname, self.from_tag, self.to_tag)
        return self.rtp_proxy_client.send_command(command, self._start_recording1, \
          (rname, result_callback, index))

    def _start_recording1(self, result, args):
        rname, result_callback, index = args
        command = 'C %s %s.o %s %s' % ('%s-%d' % (self.call_id, index), rname, self.to_tag, self.from_tag)
        return self.rtp_proxy_client.send_command(command, self.command_result, result_callback)

    def command_result(self, result, result_callback):
        #print '%s.command_result(%s)' % (id(self), result)
        if result_callback != None:
            result_callback(result)

    def delete(self):
        if self.rtp_proxy_client == None:
            return
        while self.max_index >= 0:
            command = 'D %s %s %s' % ('%s-%d' % (self.call_id, self.max_index), self.from_tag, self.to_tag)
            self.rtp_proxy_client.send_command(command)
            self.max_index -= 1
        self.rtp_proxy_client = None

    def on_caller_sdp_change(self, sdp_body, result_callback, en_excpt = False):
        self.caller._on_sdp_change(self, sdp_body, result_callback, en_excpt)

    def on_callee_sdp_change(self, sdp_body, result_callback, en_excpt = False):
        self.callee._on_sdp_change(self, sdp_body, result_callback, en_excpt)

    def stats(self, result_callback, index = 0, *callback_parameters):
        command = 'Q %s %s %s' % ('%s-%d' % (self.call_id, index), self.from_tag, self.to_tag)
        self.rtp_proxy_client.send_command(command, self.stats_result, (result_callback, callback_parameters))

    def stats_result(self, result, args):
        t1 = result.split()
        result_callback, callback_parameters = args
        if len(t1) == 1:
            result_callback(None, *callback_parameters)
            return
        ttl, caller_rcount, callee_rcount, fwd_rcount, drop_rcount = [int(x) for x in t1]
        result_callback((ttl, caller_rcount, callee_rcount, fwd_rcount, drop_rcount), *callback_parameters)

    def __del__(self):
        if self.my_ident != get_ident():
            #print 'Rtp_proxy_session.__del__() from wrong thread, re-routing'
            ED2.callFromThread(self.delete)
        else:
            self.delete() class SipTransactionManager(object):
    global_config = None
    l4r = None
    tclient = None
    tserver = None
    req_cb = None
    l1rcache = None
    l2rcache = None
    nat_traversal = False
    req_consumers = None
    provisional_retr = 0
    ploss_out_rate = 0.0
    pdelay_out_max = 0.0
    nworkers_udp = None

    def __init__(self, global_config, req_cb = None):
        self.global_config = global_config
        self.l4r = local4remote(global_config, self.handleIncoming, self.nworkers_udp)
        self.l4r.ploss_out_rate = self.ploss_out_rate
        self.l4r.pdelay_out_max = self.pdelay_out_max
        self.tclient = {}
        self.tserver = {}
        self.req_cb = req_cb
        self.l1rcache = {}
        self.l2rcache = {}
        self.req_consumers = {}
        Timeout(self.rCachePurge, 32, -1)

    def handleIncoming(self, data_in, address, server, rtime):
        if len(data_in) < 32:
            return
        self.global_config['_sip_logger'].write('RECEIVED message from %s:%d:\n' % address, \
          data_in.decode(errors = 'backslashreplace'), ltime = rtime.realt)
        data = data_in.decode()
        checksum = md5(data_in).digest()
        retrans = self.l1rcache.get(checksum, None)
        if retrans == None:
            retrans = self.l2rcache.get(checksum, None)
        if retrans != None:
            if retrans.data == None:
                return
            self.transmitData(retrans.userv, retrans.data, retrans.address, \
              lossemul = retrans.lossemul)
            return
        if data.startswith('SIP/2.0 '):
            try:
                resp = SipResponse(data)
                tid = resp.getTId(True, True)
            except Exception as exception:
                dump_exception('can\'t parse SIP response from %s:%d' % (address[0], address[1]), extra = data)
                self.l1rcache[checksum] = SipTMRetransmitO()
                return
            if resp.getSCode()[0] < 100 or resp.getSCode()[0] > 999:
                print(datetime.now(), 'invalid status code in SIP response from %s:%d:' % address)
                print(data)
                sys.stdout.flush()
                self.l1rcache[checksum] = SipTMRetransmitO()
                return
            resp.rtime = rtime
            if not tid in self.tclient:
                #print 'no transaction with tid of %s in progress' % str(tid)
                self.l1rcache[checksum] = SipTMRetransmitO()
                return
            t = self.tclient[tid]
            if self.nat_traversal and resp.countHFs('contact') > 0 and not check1918(t.address[0]):
                cbody = resp.getHFBody('contact')
                if not cbody.asterisk:
                    curl = cbody.getUrl()
                    if check1918(curl.host):
                        curl.host, curl.port = address
            resp.setSource(address)
            self.incomingResponse(resp, t, checksum)
        else:
            try:
                req = SipRequest(data)
                tids = req.getTIds()
            except Exception as exception:
                if isinstance(exception, ESipParseException) and exception.sip_response != None:
                    self.transmitMsg(server, exception.sip_response, address, checksum)
                dump_exception('can\'t parse SIP request from %s:%d' % (address[0], address[1]), extra = data)
                self.l1rcache[checksum] = SipTMRetransmitO()
                return
            req.rtime = rtime
            via0 = req.getHFBody('via')
            ahost, aport = via0.getAddr()
            rhost, rport = address
            if self.nat_traversal and rport != aport and check1918(ahost):
                req.nated = True
            if ahost != rhost:
                via0.params['received'] = rhost
            if 'rport' in via0.params or req.nated:
                via0.params['rport'] = str(rport)
            if self.nat_traversal and req.countHFs('contact') > 0 and req.countHFs('via') == 1:
                try:
                    cbody = req.getHFBody('contact')
                except Exception as exception:
                    dump_exception('can\'t parse SIP request from %s:%d: %s:' % (address[0], address[1]), extra = data)
                    self.l1rcache[checksum] = SipTMRetransmitO()
                    return
                if not cbody.asterisk:
                    curl = cbody.getUrl()
                    if check1918(curl.host) or curl.port == 0 or curl.host == '255.255.255.255':
                        curl.host, curl.port = address
                        req.nated = True
            req.setSource(address)
            self.incomingRequest(req, checksum, tids, server)

    # 1. Client transaction methods
    def newTransaction(self, msg, resp_cb = None, laddress = None, userv = None, \
      cb_ifver = 1, compact = False, t = None):
        if t == None:
            t = SipTransaction()
        t.rtime = MonoTime()
        t.compact = compact
        t.method = msg.getMethod()
        t.cb_ifver = cb_ifver
        t.tid = msg.getTId(True, True)
        if t.tid in self.tclient:
            raise ValueError('BUG: Attempt to initiate transaction with the same TID as existing one!!!')
        t.tout = 0.5
        t.fcode = None
        t.address = msg.getTarget()
        if userv == None:
            if laddress == None:
                t.userv = self.l4r.getServer(t.address)
            else:
                t.userv = self.l4r.getServer(laddress, is_local = True)
        else:
            t.userv = userv
        t.data = msg.localStr(*t.userv.uopts.getSIPaddr(), compact = t.compact)
        if t.method == 'INVITE':
            try:
                t.expires = msg.getHFBody('expires').getNum()
                if t.expires <= 0:
                    t.expires = 300
            except IndexError:
                t.expires = 300
            t.needack = True
            t.ack = msg.genACK()
            t.cancel = msg.genCANCEL()
        else:
            t.expires = 32
            t.needack = False
            t.ack = None
            t.cancel = None
        t.cancelPending = False
        t.resp_cb = resp_cb
        t.teA = Timeout(self.timerA, t.tout, 1, t)
        if resp_cb != None:
            t.r408 = msg.genResponse(408, 'Request Timeout')
        t.teB = Timeout(self.timerB, 32.0, 1, t)
        t.teC = None
        t.state = TRYING
        self.tclient[t.tid] = t
        self.transmitData(t.userv, t.data, t.address)
        if t.req_out_cb != None:
            t.req_out_cb(msg)
        return t

    def cancelTransaction(self, t, reason = None):
        # If we got at least one provisional reply then (state == RINGING)
        # then start CANCEL transaction, otherwise deffer it
        if t.state != RINGING:
            t.cancelPending = True
        else:
            if reason != None:
                t.cancel.appendHeader(SipHeader(body = reason))
            self.newTransaction(t.cancel, userv = t.userv)

    def incomingResponse(self, msg, t, checksum):
        # In those two states upper level already notified, only do ACK retransmit
        # if needed
        if t.state == TERMINATED:
            return

        if t.state == TRYING:
            # Stop timers
            if t.teA != None:
                t.teA.cancel()
                t.teA = None

        if t.state in (TRYING, RINGING):
            if t.teB != None:
                t.teB.cancel()
                t.teB = None

            if msg.getSCode()[0] < 200:
                # Privisional response - leave everything as is, except that
                # change state and reload timeout timer
                if t.state == TRYING:
                    t.state = RINGING
                    if t.cancelPending:
                        self.newTransaction(t.cancel, userv = t.userv)
                        t.cancelPending = False
                t.teB = Timeout(self.timerB, t.expires, 1, t)
                self.l1rcache[checksum] = SipTMRetransmitO()
                if t.resp_cb != None:
                    if t.cb_ifver == 1:
                        t.resp_cb(msg)
                    else:
                        t.resp_cb(msg, t)
            else:
                # Final response - notify upper layer and remove transaction
                if t.needack:
                    # Prepare and send ACK if necessary
                    fcode = msg.getSCode()[0]
                    tag = msg.getHFBody('to').getTag()
                    if tag != None:
                        t.ack.getHFBody('to').setTag(tag)
                    rAddr = None
                    if msg.getSCode()[0] >= 200 and msg.getSCode()[0] < 300:
                        # Some hairy code ahead
                        if msg.countHFs('contact') > 0:
                            rTarget = msg.getHFBody('contact').getUrl().getCopy()
                        else:
                            rTarget = None
                        routes = [x.getCopy() for x in msg.getHFBodys('record-route')]
                        routes.reverse()
                        if len(routes) > 0:
                            if not routes[0].getUrl().lr:
                                if rTarget != None:
                                    routes.append(SipRoute(address = SipAddress(url = rTarget)))
                                rTarget = routes.pop(0).getUrl()
                                rAddr = rTarget.getAddr()
                            else:
                                rAddr = routes[0].getAddr()
                        elif rTarget != None:
                            rAddr = rTarget.getAddr()
                        if rTarget != None:
                            t.ack.setRURI(rTarget)
                        if rAddr != None:
                            t.ack.setTarget(rAddr)
                        t.ack.delHFs('route')
                        t.ack.appendHeaders([SipHeader(name = 'route', body = x) for x in routes])
                    if fcode >= 200 and fcode < 300:
                        t.ack.getHFBody('via').genBranch()
                    if rAddr == None:
                        rAddr = t.address
                    if not t.uack:
                        self.transmitMsg(t.userv, t.ack, rAddr, checksum, t.compact)
                        if t.req_out_cb != None:
                            t.req_out_cb(t.ack)
                    else:
                        t.state = UACK
                        t.ack_rAddr = rAddr
                        t.ack_checksum = checksum
                        self.l1rcache[checksum] = SipTMRetransmitO()
                        t.teG = Timeout(self.timerG, 64, 1, t)
                else:
                    self.l1rcache[checksum] = SipTMRetransmitO()
                if t.resp_cb != None:
                    if t.cb_ifver == 1:
                        t.resp_cb(msg)
                    else:
                        t.resp_cb(msg, t)
                if t.state == UACK:
                    return
                del self.tclient[t.tid]
                t.cleanup()

    def timerA(self, t):
        #print 'timerA', t
        self.transmitData(t.userv, t.data, t.address)
        t.tout *= 2
        t.teA = Timeout(self.timerA, t.tout, 1, t)

    def timerB(self, t):
        #print 'timerB', t
        t.teB = None
        if t.teA != None:
            t.teA.cancel()
            t.teA = None
        t.state = TERMINATED
        #print '2: Timeout(self.timerC, 32.0, 1, t)', t
        t.teC = Timeout(self.timerC, 32.0, 1, t)
        if t.resp_cb == None:
            return
        t.r408.rtime = MonoTime()
        if t.cb_ifver == 1:
            t.resp_cb(t.r408)
        else:
            t.resp_cb(t.r408, t)
        #try:
        #    t.resp_cb(SipRequest(t.data).genResponse(408, 'Request Timeout'))
        #except:
        #    print 'SipTransactionManager: unhandled exception when processing response!'

    def timerC(self, t):
        #print 'timerC', t
        #print self.tclient
        t.teC = None
        del self.tclient[t.tid]
        t.cleanup()

    # 2. Server transaction methods
    def incomingRequest(self, msg, checksum, tids, server):
        for tid in tids:
            if tid in self.tclient:
                t = self.tclient[tid]
                resp = msg.genResponse(482, 'Loop Detected')
                self.transmitMsg(server, resp, resp.getHFBody('via').getTAddr(), checksum, \
                  t.compact)
                return
        if  msg.getMethod() != 'ACK':
            tid = msg.getTId(wBRN = True)
        else:
            tid = msg.getTId(wTTG = True)
        t = self.tserver.get(tid, None)
        if t != None:
            #print 'existing transaction'
            if msg.getMethod() == t.method:
                # Duplicate received, check that we have sent any response on this
                # request already
                if t.data != None:
                    self.transmitData(t.userv, t.data, t.address, checksum)
                return
            elif msg.getMethod() == 'CANCEL':
                # RFC3261 says that we have to reply 200 OK in all cases if
                # there is such transaction
                resp = msg.genResponse(200, 'OK')
                self.transmitMsg(t.userv, resp, resp.getHFBody('via').getTAddr(), checksum, \
                  t.compact)
                if t.state in (TRYING, RINGING):
                    self.doCancel(t, msg.rtime, msg)
            elif msg.getMethod() == 'ACK' and t.state == COMPLETED:
                t.state = CONFIRMED
                if t.teA != None:
                    t.teA.cancel()
                    t.teA = None
                t.teD.cancel()
                # We have done with the transaction, no need to wait for timeout
                del self.tserver[t.tid]
                if t.ack_cb != None:
                    t.ack_cb(msg)
                t.cleanup()
                self.l1rcache[checksum] = SipTMRetransmitO()
        elif msg.getMethod() == 'ACK':
            # Some ACK that doesn't match any existing transaction.
            # Drop and forget it - upper layer is unlikely to be interested
            # to seeing this anyway.
            print(datetime.now(), 'unmatched ACK transaction - ignoring')
            sys.stdout.flush()
            self.l1rcache[checksum] = SipTMRetransmitO()
        elif msg.getMethod() == 'CANCEL':
            resp = msg.genResponse(481, 'Call Leg/Transaction Does Not Exist')
            self.transmitMsg(server, resp, resp.getHFBody('via').getTAddr(), checksum)
        else:
            #print 'new transaction', msg.getMethod()
            t = SipTransaction()
            t.tid = tid
            t.state = TRYING
            t.teA = None
            t.teD = None
            t.teE = None
            t.teF = None
            t.teG = None
            t.method = msg.getMethod()
            t.rtime = msg.rtime
            t.data = None
            t.address = None
            t.noack_cb = None
            t.ack_cb = None
            t.cancel_cb = None
            t.checksum = checksum
            if not server.uopts.isWildCard():
                t.userv = server
            else:
                # For messages received on the wildcard interface find
                # or create more specific server.
                t.userv = self.l4r.getServer(msg.getSource())
            if msg.getMethod() == 'INVITE':
                t.r487 = msg.genResponse(487, 'Request Terminated')
                t.needack = True
                t.branch = msg.getHFBody('via').getBranch()
                try:
                    e = msg.getHFBody('expires').getNum()
                    if e <= 0:
                        e = 300
                except IndexError:
                    e = 300
                t.teE = Timeout(self.timerE, e, 1, t)
            else:
                t.r487 = None
                t.needack = False
                t.branch = None
            self.tserver[t.tid] = t
            for consumer in self.req_consumers.get(t.tid[0], ()):
                cobj = consumer.cobj.isYours(msg)
                if cobj != None:
                    t.compact = consumer.compact
                    rval = cobj.recvRequest(msg, t)
                    break
            else:
                if self.req_cb == None:
                    self.l1rcache[checksum] = SipTMRetransmitO()
                    return
                rval = self.req_cb(msg, t)
            if rval == None:
                if t.teA != None or t.teD != None or t.teE != None or t.teF != None:
                    return
                if t.tid in self.tserver:
                    del self.tserver[t.tid]
                t.cleanup()
                return
            resp, t.cancel_cb, t.noack_cb = rval
            if resp != None:
                self.sendResponse(resp, t, lossemul = resp.lossemul)

    def regConsumer(self, consumer, call_id, compact = False):
        cons = SipTransactionConsumer(consumer, compact)
        self.req_consumers.setdefault(call_id, []).append(cons)

    def unregConsumer(self, consumer, call_id):
        # Usually there will be only one consumer per call_id, so that
        # optimize management for this case
        consumers = self.req_consumers.pop(call_id)
        for cons in consumers:
            if cons.cobj != consumer:
                continue
            consumers.remove(cons)
            cons.cleanup()
            if len(consumers) > 0:
                self.req_consumers[call_id] = consumers
            break
        else:
            self.req_consumers[call_id] = consumers
            raise IndexError('unregConsumer: consumer %s for call-id %s is not registered' % \
              (str(consumer), call_id))

    def sendResponse(self, resp, t = None, retrans = False, ack_cb = None,
      lossemul = 0):
        #print self.tserver
        if t == None:
            tid = resp.getTId(wBRN = True)
            t = self.tserver[tid]
        if t.state not in (TRYING, RINGING) and not retrans:
            raise ValueError('BUG: attempt to send reply on already finished transaction!!!')
        scode = resp.getSCode()[0]
        toHF = resp.getHFBody('to')
        if scode > 100 and toHF.getTag() == None:
            toHF.genTag()
        t.data = resp.localStr(*t.userv.uopts.getSIPaddr(), compact = t.compact)
        t.address = resp.getHFBody('via').getTAddr()
        self.transmitData(t.userv, t.data, t.address, t.checksum, lossemul)
        if t.res_out_cb != None:
            t.res_out_cb(resp)
        if scode < 200:
            t.state = RINGING
            if self.provisional_retr > 0 and scode > 100:
                if t.teF != None:
                    t.teF.cancel()
                t.teF = Timeout(self.timerF, self.provisional_retr, 1, t)
        else:
            t.state = COMPLETED
            if t.teE != None:
                t.teE.cancel()
                t.teE = None
            if t.teF != None:
                t.teF.cancel()
                t.teF = None
            if t.needack:
                # Schedule removal of the transaction
                t.ack_cb = ack_cb
                t.teD = Timeout(self.timerD, 32.0, 1, t)
                if scode >= 200:
                    # Black magick to allow proxy send us another INVITE
                    # same branch and From tag. Use To tag to match
                    # ACK transaction after this point. Branch tag in ACK
                    # could differ as well.
                    del self.tserver[t.tid]
                    t.tid = list(t.tid[:-1])
                    t.tid.append(resp.getHFBody('to').getTag())
                    t.tid = tuple(t.tid)
                    self.tserver[t.tid] = t
                # Install retransmit timer if necessary
                t.tout = 0.5
                t.teA = Timeout(self.timerA, t.tout, 1, t)
            else:
                # We have done with the transaction
                del self.tserver[t.tid]
                t.cleanup()

    def doCancel(self, t, rtime = None, req = None):
        if rtime == None:
            rtime = MonoTime()
        if t.r487 != None:
            self.sendResponse(t.r487, t, True)
        if t.cancel_cb != None:
            t.cancel_cb(rtime, req)

    def timerD(self, t):
        #print 'timerD'
        t.teD = None
        if t.teA != None:
            t.teA.cancel()
            t.teA = None
        if t.noack_cb != None and t.state != CONFIRMED:
            t.noack_cb()
        del self.tserver[t.tid]
        t.cleanup()

    def timerE(self, t):
        #print 'timerE'
        t.teE = None
        if t.teF != None:
            t.teF.cancel()
            t.teF = None
        if t.state in (TRYING, RINGING):
            if t.r487 != None:
                t.r487.reason = 'Request Expired'
            self.doCancel(t)

    # Timer to retransmit the last provisional reply every
    # 2 seconds
    def timerF(self, t):
        #print 'timerF', t.state
        t.teF = None
        if t.state == RINGING and self.provisional_retr > 0:
            self.transmitData(t.userv, t.data, t.address)
            t.teF = Timeout(self.timerF, self.provisional_retr, 1, t)

    def timerG(self, t):
        #print 'timerG', t.state
        t.teG = None
        if t.state == UACK:
            print(datetime.now(), 'INVITE transaction stuck in the UACK state, possible UAC bug')

    def rCachePurge(self):
        self.l2rcache = self.l1rcache
        self.l1rcache = {}
        self.l4r.rotateCache()

    def transmitMsg(self, userv, msg, address, cachesum, compact = False):
        data = msg.localStr(*userv.uopts.getSIPaddr(), compact = compact)
        self.transmitData(userv, data, address, cachesum)

    def transmitData(self, userv, data, address, cachesum = None, \
      lossemul = 0):
        if lossemul == 0:
            userv.send_to(data, address)
            logop = 'SENDING'
        else:
            logop = 'DISCARDING'
        self.global_config['_sip_logger'].write('%s message to %s:%d:\n' % \
          (logop, address[0], address[1]), data)
        if cachesum != None:
            if lossemul > 0:
                lossemul -= 1
            self.l1rcache[cachesum] = SipTMRetransmitO(userv, data, address, \
              None, lossemul)

    def sendACK(self, t):
        #print 'sendACK', t.state
        if t.teG != None:
            t.teG.cancel()
            t.teG = None
        self.transmitMsg(t.userv, t.ack, t.ack_rAddr, t.ack_checksum, t.compact)
        if t.req_out_cb != None:
            t.req_out_cb(t.ack)
        del self.tclient[t.tid]
        t.cleanup() class SipCallId(SipGenericHF):
    hf_names = ('call-id', 'i')
    body = None

    def __init__(self, body = None):
        SipGenericHF.__init__(self, body)
        self.parsed = True
        if body == None:
            salt = str((random() * 1000000000) + time())
            self.body = md5(salt.encode()).hexdigest() + '@' + str(SipConf.my_address)

    def __add__(self, other):
        return SipCallId(self.body + str(other))

    def genCallId(self):
        salt = str((random() * 1000000000) + time())
        self.body = md5(salt.encode()).hexdigest() + '@' + str(SipConf.my_address)

    def getCanName(self, name, compact = False):
        if compact:
            return 'i'
        return 'Call-ID' class StatefulProxy:
    global_config = None
    destination = None

    def __init__(self, global_config, destination):
        print(destination)
        self.global_config = global_config
        self.destination = destination

    def recvRequest(self, req):
        via0 = SipVia()
        via0.genBranch()
        via1 = req.getHF('via')
        req.insertHeaderBefore(via1, SipHeader(name = 'via', body = via0))
        req.setTarget(self.destination)
        print(req)
        self.global_config['_sip_tm'].newTransaction(req, self.recvResponse)
        return (None, None, None)

    def recvResponse(self, resp):
        resp.removeHeader(resp.getHF('via'))
        self.global_config['_sip_tm'].sendResponse(resp) class B2BRoute(object):
    rnum = None
    addrinfo = None
    cld = None
    cld_set = False
    hostport = None
    hostonly = None
    credit_time = None
    crt_set = False
    expires = None
    no_progress_expires = None
    no_reply_expires = None
    forward_on_fail = False
    user = None
    passw = None
    cli = None
    cli_set = False
    params = None
    ainfo = None
    extra_headers = None

    def __init__(self, sroute = None, cself = None):
        if cself != None:
            self.rnum = cself.rnum
            self.addrinfo = cself.addrinfo
            self.cld = cself.cld
            self.cld_set = cself.cld_set
            self.hostport = cself.hostport
            self.hostonly = cself.hostonly
            self.credit_time = cself.credit_time
            self.crt_set = cself.crt_set
            self.expires = cself.expires
            self.no_progress_expires = cself.no_progress_expires
            self.no_reply_expires = cself.no_reply_expires
            self.forward_on_fail = cself.forward_on_fail
            self.user = cself.user
            self.passw = cself.passw
            self.cli = cself.cli
            self.cli_set = cself.cli_set
            self.params = dict(cself.params)
            self.ainfo = cself.ainfo
            if cself.extra_headers != None:
                self.extra_headers = tuple([x.getCopy() for x in cself.extra_headers])
            return
        route = sroute.split(';')
        if route[0].find('@') != -1:
            self.cld, self.hostport = route[0].split('@', 1)
            if len(self.cld) == 0:
                # Allow CLD to be forcefully removed by sending `Routing:@host' entry,
                # as opposed to the Routing:host, which means that CLD should be obtained
                # from the incoming call leg.
                self.cld = None
            self.cld_set = True
        else:
            self.hostport = route[0]
        if not self.hostport.startswith('['):
            hostport = self.hostport.split(':', 1)
            af = 0
            self.hostonly = hostport[0]
        else:
            hostport = self.hostport[1:].split(']', 1)
            if len(hostport) > 1:
                if len(hostport[1]) == 0:
                    del hostport[1]
                else:
                    hostport[1] = hostport[1][1:]
            af = AF_INET6
            self.hostonly = '[%s]' % hostport[0]
        if len(hostport) == 1:
            port = SipConf.default_port
        else:
            port = int(hostport[1])
        self.ainfo = getaddrinfo(hostport[0], port, af, SOCK_STREAM)
        self.params = {}
        extra_headers = []
        for a, v in [x.split('=', 1) for x in route[1:]]:
            if a == 'credit-time':
                self.credit_time = int(v)
                if self.credit_time < 0:
                    self.credit_time = None
                self.crt_set = True
            elif a == 'expires':
                self.expires = int(v)
                if self.expires < 0:
                    self.expires = None
            elif a == 'hs_scodes':
                self.params['huntstop_scodes'] = tuple([int(x) for x in v.split(',') if len(x.strip()) > 0])
            elif a == 'np_expires':
                self.no_progress_expires = int(v)
                if self.no_progress_expires < 0:
                    self.no_progress_expires = None
            elif a == 'nr_expires':
                self.no_reply_expires = int(v)
                if self.no_reply_expires < 0:
                    self.no_reply_expires = None
            elif a == 'forward_on_fail':
                self.forward_on_fail = True
            elif a == 'auth':
                self.user, self.passw = v.split(':', 1)
            elif a == 'cli':
                self.cli = v
                if len(self.cli) == 0:
                    self.cli = None
                self.cli_set = True
            elif a == 'cnam':
                caller_name = unquote(v)
                if len(caller_name) == 0:
                    caller_name = None
                self.params['caller_name'] = caller_name
            elif a == 'ash':
                ash = SipHeader(unquote(v))
                extra_headers.append(ash)
            elif a == 'rtpp':
                self.params['rtpp'] = (int(v) != 0)
            elif a == 'op':
                host_port = v.split(':', 1)
                if len(host_port) == 1:
                    self.params['outbound_proxy'] = (v, 5060)
                else:
                    self.params['outbound_proxy'] = (host_port[0], int(host_port[1]))
            else:
                self.params[a] = v
        if len(extra_headers) > 0:
            self.extra_headers = tuple(extra_headers)

    def customize(self, rnum, default_cld, default_cli, default_credit_time, \
      pass_headers, max_credit_time):
        self.rnum = rnum
        if not self.cld_set:
            self.cld = default_cld
        if not self.cli_set:
            self.cli = default_cli
        if not self.crt_set:
            self.crt_set = default_credit_time
        if 'gt' in self.params:
            timeout, skip = self.params['gt'].split(',', 1)
            self.params['group_timeout'] = (int(timeout), rnum + int(skip))
        if self.extra_headers != None:
            self.extra_headers = self.extra_headers + tuple(pass_headers)
        else:
            self.extra_headers = tuple(pass_headers)
        if max_credit_time != None:
            if self.credit_time == None or self.credit_time > max_credit_time:
                self.credit_time = max_credit_time

    def getCopy(self):
        return self.__class__(cself = self)

    def getNHAddr(self, source):
        if source[0].startswith('['):
            af = AF_INET6
        else:
            af = AF_INET
        amatch = [x[4] for x in self.ainfo if x[0] == af]
        same_af = True
        if len(amatch) == 0:
            same_af = False
            amatch = self.ainfo[0][4]
            af = self.ainfo[0][0]
        else:
            amatch = amatch[0]
        if af == AF_INET6:
            return ((('[%s]' % amatch[0], amatch[1]), same_af))
        return (((amatch[0], amatch[1]), same_af)) class MyConfigParser(RawConfigParser):
    _default_section = None
    _private_keys = None

    def __init__(self, default_section = 'general'):
        self._default_section = default_section
        self._private_keys = {}
        RawConfigParser.__init__(self)
        self.add_section(self._default_section)

    def __getitem__(self, key):
        if key.startswith('_'):
            return self._private_keys[key]
        value_type  = SUPPORTED_OPTIONS[key][0]
        if value_type  == 'B':
            return self.getboolean(self._default_section, key)
        elif value_type == 'I':
            return self.getint(self._default_section, key)
        return self.get(self._default_section, key)

    def __setitem__(self, key, value):
        if key.startswith('_'):
            self._private_keys[key] = value
        else:
            self.set(self._default_section, key, str(value))
        return

    def has_key(self, key):
        return self.__contains__(key)

    def __contains__(self, key):
        if key.startswith('_'):
            return key in self._private_keys
        return self.has_option(self._default_section, key)

    def get(self, *args, **kwargs):
        if len(args) == 1:
            return self.__getitem__(args[0])
        return RawConfigParser.get(self, *args, **kwargs)

    def getdefault(self, key, default_value):
        if self.__contains__(key):
            return self.__getitem__(key)
        return default_value

    def get_longopts(self):
        return tuple([x + '=' for x in SUPPORTED_OPTIONS.keys()])

    def read(self, fname):
        RawConfigParser.readfp(self, open(fname))
        for key in tuple(self.options(self._default_section)):
            self.check_and_set(key, RawConfigParser.get(self, \
              self._default_section, key), False)

    def check_and_set(self, key, value, compat = True):
        value = value.strip()
        if compat:
            if key == 'rtp_proxy_client':
                # XXX compatibility option
                if self.has_key('_rtp_proxy_clients'):
                    self['_rtp_proxy_clients'].append(value)
                else:
                    self['_rtp_proxy_clients'] = [value,]
                if self.has_key('rtp_proxy_clients'):
                    self['rtp_proxy_clients'] += ',' + value
                else:
                    self['rtp_proxy_clients'] = value
                return
            elif key == 'pass_header':
                # XXX compatibility option
                if self.has_key('_pass_headers'):
                    self['_pass_headers'].append(value)
                else:
                    self['_pass_headers'] = [value,]
                if self.has_key('pass_headers'):
                    self['pass_headers'] += ',' + value
                else:
                    self['pass_headers'] = value
                return

        value_type  = SUPPORTED_OPTIONS[key][0]
        if value_type == 'B':
            if value.lower() not in _boolean_states:
                raise ValueError('Not a boolean: %s' % value)
        elif value_type == 'I':
            _value = int(value)
        if key in ('keepalive_ans', 'keepalive_orig'):
            if _value < 0:
                raise ValueError('keepalive_ans should be non-negative')
        elif key == 'max_credit_time':
            if _value <= 0:
                raise ValueError('max_credit_time should be more than zero')
        elif key == 'allowed_pts':
            self['_allowed_pts'] = [int(x) for x in value.split(',')]
        elif key in ('accept_ips', 'rtp_proxy_clients'):
            self['_' + key] = [x.strip() for x in value.split(',')]
        elif key == 'pass_headers':
            self['_' + key] = [x.strip().lower() for x in value.split(',')]
        elif key == 'sip_address':
            if 'my' in dir(value):
                self['_sip_address'] = value
                value = '*'
            elif value in ('*', '0.0.0.0', '::'):
                self['_sip_address'] = SipConf.my_address
            else:
                self['_sip_address'] = value
        elif key == 'sip_port':
            if _value <= 0 or _value > 65535:
                raise ValueError('sip_port should be in the range 1-65535')
            self['_sip_port'] = _value
        self[key] = value

    def options_help(self):
        supported_options = sorted(SUPPORTED_OPTIONS.items())
        for option, (value_type, helptext) in supported_options:
            if value_type == 'B':
                value = 'on/off'
            elif value_type == 'I':
                value = 'number'
            else:
                value = '"string"'
            print('--%s=%s\n\t%s\n' % (option, value, helptext))
import sys
import gc, getopt, os
from os.path import dirname, join as p_join
from sippy.Core.EventDispatcher import ED2
from sippy.Time.MonoTime import MonoTime
from sippy.Time.Timeout import Timeout
from sippy.Signal import Signal
from sippy.SipFrom import SipFrom
from sippy.SipTo import SipTo
from sippy.UA import UA
from sippy.CCEvents import CCEventRing, CCEventConnect, CCEventDisconnect, CCEventTry, CCEventUpdate, CCEventFail
from sippy.UasStateTrying import UasStateTrying
from sippy.UasStateRinging import UasStateRinging
from sippy.UaStateDead import UaStateDead
from sippy.SipConf import SipConf
from sippy.SipHeader import SipHeader
from sippy.RadiusAuthorisation import RadiusAuthorisation
from sippy.RadiusAccounting import RadiusAccounting
from sippy.FakeAccounting import FakeAccounting
from sippy.SipLogger import SipLogger
from sippy.Rtp_proxy_session import Rtp_proxy_session
from sippy.Rtp_proxy_client import Rtp_proxy_client
from signal import SIGHUP, SIGPROF, SIGUSR1, SIGUSR2, SIGTERM
from sippy.CLIManager import CLIConnectionManager
from sippy.SipTransactionManager import SipTransactionManager
from sippy.SipCallId import SipCallId
from sippy.StatefulProxy import StatefulProxy
from sippy.misc import daemonize
from sippy.B2BRoute import B2BRoute
from re import sub
    from urllib import quote
    from urllib.parse import quote
from hashlib import md5
from sippy.MyConfigParser import MyConfigParser
from traceback import print_exc
from datetime import datetime
        caller_name = [x[1][5:] for x in results[0] if x[0] == 'h323-ivr-in' and x[1].startswith('CNAM:')]
        if len(caller_name) > 0:
            self.caller_name = caller_name[0]
            if len(self.caller_name) == 0:
                self.caller_name = None
        credit_time = [x for x in results[0] if x[0] == 'h323-credit-time']
        if len(credit_time) > 0:
            credit_time = int(credit_time[0][1])
        else:
            credit_time = None
        if not '_static_route' in self.global_config:
            routing = [x for x in results[0] if x[0] == 'h323-ivr-in' and x[1].startswith('Routing:')]
            if len(routing) == 0:
                self.uaA.recvEvent(CCEventFail((500, 'Internal Server Error (2)')))
                self.state = CCStateDead
                return
            routing = [B2BRoute(x[1][8:]) for x in routing]
        else:
            routing = [self.global_config['_static_route'].getCopy(),]
        rnum = 0
        for oroute in routing:
            rnum += 1
            max_credit_time = self.global_config.getdefault('max_credit_time', None)
            oroute.customize(rnum, self.cld, self.cli, credit_time, self.pass_headers, \
              max_credit_time)
            if oroute.credit_time == 0 or oroute.expires == 0:
                continue
            self.routes.append(oroute)
            #print 'Got route:', oroute.hostport, oroute.cld
        if len(self.routes) == 0:
            self.uaA.recvEvent(CCEventFail((500, 'Internal Server Error (3)')))
            self.state = CCStateDead
            return
        self.state = CCStateARComplete
        self.placeOriginate(self.routes.pop(0))

    def placeOriginate(self, oroute):
        cId, cli, cld, body, auth, caller_name = self.eTry.getData()
        cld = oroute.cld
        self.huntstop_scodes = oroute.params.get('huntstop_scodes', ())
        if 'static_tr_out' in self.global_config:
            cld = re_replace(self.global_config['static_tr_out'], cld)
        if oroute.hostport == 'sip-ua':
            host = self.source[0]
            nh_address, same_af = self.source, True
        else:
            host = oroute.hostonly
            nh_address, same_af = oroute.getNHAddr(self.source)
        if not oroute.forward_on_fail and self.global_config['acct_enable']:
            self.acctO = RadiusAccounting(self.global_config, 'originate', \
              send_start = self.global_config['start_acct_enable'], lperiod = \
              self.global_config.getdefault('alive_acct_int', None))
            self.acctO.ms_precision = self.global_config.getdefault('precise_acct', False)
            self.acctO.setParams(oroute.params.get('bill-to', self.username), oroute.params.get('bill-cli', oroute.cli), \
              oroute.params.get('bill-cld', cld), self.cId, host)
        else:
            self.acctO = None
        self.acctA.credit_time = oroute.credit_time
        conn_handlers = [self.oConn]
        disc_handlers = []
        if not oroute.forward_on_fail and self.global_config['acct_enable']:
            disc_handlers.append(self.acctO.disc)
        self.uaO = UA(self.global_config, self.recvEvent, oroute.user, oroute.passw, nh_address, oroute.credit_time, tuple(conn_handlers), \
          tuple(disc_handlers), tuple(disc_handlers), dead_cbs = (self.oDead,), expire_time = oroute.expires, \
          no_progress_time = oroute.no_progress_expires, extra_headers = oroute.extra_headers)
        self.uaO.pass_auth = self.pass_auth
        self.uaO.local_ua = self.global_config['_uaname']
        self.uaO.no_reply_time = oroute.no_reply_expires
        if self.source != oroute.params.get('outbound_proxy', None):
            self.uaO.outbound_proxy = oroute.params.get('outbound_proxy', None)
        if self.rtp_proxy_session != None and oroute.params.get('rtpp', True):
            self.uaO.on_local_sdp_change = self.rtp_proxy_session.on_caller_sdp_change
            self.uaO.on_remote_sdp_change = self.rtp_proxy_session.on_callee_sdp_change
            self.rtp_proxy_session.caller.raddress = nh_address
            if body != None:
                body = body.getCopy()
            self.proxied = True
        self.uaO.kaInterval = self.global_config['keepalive_orig']
        if 'group_timeout' in oroute.params:
            timeout, skipto = oroute.params['group_timeout']
            Timeout(self.group_expires, timeout, 1, skipto)
        if self.global_config.getdefault('hide_call_id', False):
            cId = SipCallId(md5(str(cId).encode()).hexdigest() + ('-b2b_%d' % oroute.rnum))
        else:
            cId += '-b2b_%d' % oroute.rnum
        event = CCEventTry((cId, oroute.cli, cld, body, auth, \
          oroute.params.get('caller_name', self.caller_name)))
        if self.eTry.max_forwards != None:
            event.max_forwards = self.eTry.max_forwards - 1
            if event.max_forwards <= 0:
                self.uaA.recvEvent(CCEventFail((483, 'Too Many Hops')))
                self.state = CCStateDead
                return
        event.reason = self.eTry.reason
        self.uaO.recvEvent(event)

    def disconnect(self, rtime = None):
        self.uaA.disconnect(rtime = rtime)

    def oConn(self, ua, rtime, origin):
        if self.acctO != None:
            self.acctO.conn(ua, rtime, origin)

    def aConn(self, ua, rtime, origin):
        self.state = CCStateConnected
        self.acctA.conn(ua, rtime, origin)

    def aDisc(self, ua, rtime, origin, result = 0):
        if self.state == CCStateWaitRoute and self.auth_proc != None:
            self.auth_proc.cancel()
            self.auth_proc = None
        if self.uaO != None and self.state != CCStateDead:
            self.state = CCStateDisconnecting
        else:
            self.state = CCStateDead
        if self.acctA != None:
            self.acctA.disc(ua, rtime, origin, result)
        self.rtp_proxy_session = None

    def aDead(self, ua):

        if (self.uaO == None or isinstance(self.uaO.state, UaStateDead)):