fonoster/routr

View on GitHub
mod/core/processor/response_processor.js

Summary

Maintainability
B
6 hrs
Test Coverage
/**
 * @author Pedro Sanders
 * @since v1
 */
const System = Java.type('java.lang.System')
const DSSelector = require('@routr/data_api/ds_selector')
const GatewaysAPI = require('@routr/data_api/gateways_api')
const {
  isStackJob,
  isTransactional,
  mustAuthenticate,
  handleAuthChallenge,
  hasSDP,
  extractRTPEngineParams,
  isOk
} = require('@routr/core/processor/processor_utils')
const RTPEngineConnector = require('@routr/rtpengine/connector')
const ViaHeader = Java.type('javax.sip.header.ViaHeader')
const CSeqHeader = Java.type('javax.sip.header.CSeqHeader')
const ContentTypeHeader = Java.type('javax.sip.header.ContentTypeHeader')
const SipFactory = Java.type('javax.sip.SipFactory')
const headerFactory = SipFactory.getInstance().createHeaderFactory()
const { RTPBridgingNote } = require('@routr/rtpengine/rtp_bridging_note')
const { directionFromResponse } = require('@routr/rtpengine/utils')
const config = require('@routr/core/config_util')()
const LogManager = Java.type('org.apache.logging.log4j.LogManager')
const LOG = LogManager.getLogger(Java.type('io.routr.core.Launcher'))
const FluentLogger = Java.type('org.fluentd.logger.FluentLogger')
const HashMap = Java.type('java.util.HashMap')
const uLOG = FluentLogger.getLogger(
  'user',
  System.getenv('LOGS_DRIVER_HOST') || 'localhost',
  System.getenv('LOGS_DRIVER_PORT')
    ? parseInt(System.getenv('LOGS_DRIVER_PORT'))
    : 24224
)

function sendUserLog (log) {
  const data = new HashMap()
  const body = new HashMap()
  body.put('address', log.gwHost)
  body.put('transport', log.transport)
  body.put('requestMethod', log.requestMethod)
  body.put('gatewayRef', log.gwRef)
  body.put('numberRef', log.numberRef)
  body.put('number', log.number)
  body.put('code', log.code)
  body.put('error', log.error)
  data.put('accessKeyId', log.accessKeyId)
  data.put('eventType', 'sip')
  data.put('level', 'error')
  data.put('message', log.message)
  data.put('body', body)
  uLOG.log('follow', data)
}

class ResponseProcessor {
  constructor (sipProvider, contextStorage) {
    this.sipProvider = sipProvider
    this.contextStorage = contextStorage
    this.gatewaysAPI = new GatewaysAPI(DSSelector.getDS(config))
    if (config.spec.ex_rtpEngine.enabled)
      this.rtpeConnector = new RTPEngineConnector(config.spec.ex_rtpEngine)
  }

  process (event) {
    if (isStackJob(event.getResponse())) {
      return
    }
    // If it is not transactional and authentication is required it means
    // that the REGISTER request was originated by another sipStack
    if (mustAuthenticate(event.getResponse()) && isTransactional(event)) {
      const gwRef = event
        .getClientTransaction()
        .getRequest()
        .getHeader('X-Gateway-Ref').value
      const r = this.gatewaysAPI.getGateway(gwRef)
      handleAuthChallenge(this.sipProvider.getSipStack(), event, r.data)
      return
    }
    this.sendResponse(event)
  }

  async sendResponse (event) {
    LOG.debug(
      `core.processor.ResponseProcessor.sendResponse [Inconming response <=]`
    )
    LOG.debug(event.getResponse())
    LOG.debug(
      'core.processor.ResponseProcessor.sendResponse [reason phrase => ' +
        event.getResponse().getReasonPhrase() +
        ']'
    )
    LOG.debug(
      'core.processor.ResponseProcessor.sendResponse [status code => ' +
        event.getResponse().getStatusCode() +
        ']'
    )
    LOG.debug(
      'core.processor.ResponseProcessor.sendResponse [application data => ' +
        JSON.stringify(
          event.getClientTransaction()?.getApplicationData() || ''
        ) +
        ']'
    )
    const appData = event.getClientTransaction()?.getApplicationData()

    if (event.getResponse().getStatusCode() >= 400 && appData?.accessKeyId) {
      appData.message = `The call was rejected by the sip endpoint with error code ${event
        .getResponse()
        .getStatusCode()} (${event.getResponse().getReasonPhrase()})`
      appData.code = event.getResponse().getStatusCode()
      appData.error = event.getResponse().getReasonPhrase()
      appData.requestMethod = event
        .getResponse()
        .getHeader(CSeqHeader.NAME)
        .getMethod()
      sendUserLog(appData)
    }

    try {
      let response = event.getResponse().clone()
      let bridgingNote
      if (
        config.spec.ex_rtpEngine.enabled &&
        (isOk(response) || response.getStatusCode() === 183) &&
        hasSDP(response)
      ) {
        bridgingNote = directionFromResponse(response)
        const obj = await this.rtpeConnector.answer(
          bridgingNote,
          extractRTPEngineParams(response)
        )
        response.setContent(obj.sdp, response.getHeader(ContentTypeHeader.NAME))
      }

      // WARNINIG: We need to remove the SDP for response to WebRTC endpoints
      // else we will get the error "Called with SDP without DTLS fingerprint"
      if (
        config.spec.ex_rtpEngine.enabled &&
        response.getStatusCode() === 183 &&
        bridgingNote === RTPBridgingNote.WEB_TO_SIP
      ) {
        LOG.debug(
          `core.processor.ResponseProcessor.sendResponse [Removing SDP from 183 response]`
        )
        response.removeContent()
      }

      const viaHeader = response.getHeader(ViaHeader.NAME)
      const xReceivedHeader = headerFactory.createHeader(
        'X-Inf-Received',
        viaHeader.getReceived()
      )
      const xRPortHeader = headerFactory.createHeader(
        'X-Inf-RPort',
        `${viaHeader.getRPort()}`
      )
      response.addHeader(xReceivedHeader)
      response.addHeader(xRPortHeader)
      response.removeFirst(ViaHeader.NAME)

      if (isTransactional(event)) {
        const context = this.contextStorage.findContext(
          event.getClientTransaction().getBranchId()
        )
        if (context && context.serverTransaction) {
          context.serverTransaction.sendResponse(response)
        } else if (response.getHeader(ViaHeader.NAME) !== null) {
          this.sipProvider.sendResponse(response)
        }
      } else if (response.getHeader(ViaHeader.NAME) !== null) {
        // Could be a BYE due to Record-Route
        this.sipProvider.sendResponse(response)
      }
      LOG.debug(
        `core.processor.ResponseProcessor.sendResponse [Outgoing response => ]`
      )
      LOG.debug(response)
    } catch (e) {
      LOG.error(e)
    }
  }
}

module.exports = ResponseProcessor