peer-connection/helpers/statistics/parsers/parseSelectedCandidatePair.js

/* eslint-disable prefer-destructuring */
import parsers from './index';

// eslint-disable-next-line no-restricted-properties,no-unused-vars
const computePriortyFn = (controller, controlled) => (Math.pow(2, 32) * Math.min(controller, controlled)) + (2 * Math.max(controller, controlled)) + (controller > controlled ? 1 : 0);

const formatCanTypeFn = (type) => {
  if (type === 'relay') {
    return 'relayed';
  } if (type === 'host' || type.indexOf('host') > -1) {
    return 'local';
  } if (type === 'srflx') {
    return 'serverreflexive';
  }
  return type;
};

/**
 * Function that parses the raw stats from the RTCIceCandidatePairStats dictionary.
 * @param {SkylinkState} roomState - The room state.
 * @param {Object} output - Stats output object that stores the parsed stats values.
 * @param {String} prop - Stats dictionary identifier
 * @param {RTCPeerConnection} peerConnection - The peer connection.
 * @param {String} peerId - The peer Id.
 * @param {boolean} isAutoBwStats - The flag if auto bandwidth adjustment is true.
 * @memberOf PeerConnectionStatisticsParsers
 */
const parseSelectedCandidatePair = (roomState, output, prop, peerConnection, peerId, isAutoBwStats) => {
  const { peerBandwidth, peerStats } = roomState;
  const { raw, selectedCandidatePair } = output;

  const keys = Object.keys(output.raw);
  let transportStats = null;
  let selectedLocalCandidateId = null;
  let selectedRemoteCandidateId = null;

  if (!(raw[prop].type === 'remote-candidate' || raw[prop].type === 'local-candidate')) {
    return;
  }

  // Obtain selectedCandidatePairId from RTCTransportStats
  for (let i = 0; i < keys.length; i += 1) {
    if (raw[keys[i]].type === 'transport') {
      transportStats = raw[keys[i]];
    }
  }

  if (transportStats) {
    for (let i = 0; i < keys.length; i += 1) {
      if (raw[keys[i]].type === 'candidate-pair') {
        const candidatePairStats = raw[keys[i]];
        if (transportStats.selectedCandidatePairId === candidatePairStats.id) {
          selectedLocalCandidateId = candidatePairStats.localCandidateId;
          selectedRemoteCandidateId = candidatePairStats.remoteCandidateId;

          selectedCandidatePair.id = candidatePairStats.id;
          selectedCandidatePair.writable = candidatePairStats.writable;
          selectedCandidatePair.priority = candidatePairStats.priority;
          selectedCandidatePair.nominated = candidatePairStats.nominated;

          const prevStats = isAutoBwStats ? peerBandwidth[peerId][prop] : peerStats[peerId][prop];
          // FF has not implemented the following stats
          const totalRoundTripTime = parseInt(raw[keys[i]].totalRoundTripTime || '0', 10);
          selectedCandidatePair.totalRoundTripTime = totalRoundTripTime;
          selectedCandidatePair.totalRoundTripTime = parsers.tabulateStats(prevStats, raw[keys[i]], 'totalRoundTripTime');

          const consentRequestsSent = parseInt(raw[keys[i]].consentRequestsSent || '0', 10);
          selectedCandidatePair.consentRequests.totalSent = consentRequestsSent;
          selectedCandidatePair.consentRequests.sent = parsers.tabulateStats(prevStats, raw[keys[i]], 'consentRequestsSent');

          const requestsReceived = parseInt(raw[keys[i]].requestsReceived || '0', 10);
          selectedCandidatePair.requests.totalReceived = requestsReceived;
          selectedCandidatePair.requests.received = parsers.tabulateStats(prevStats, raw[keys[i]], 'requestsReceived');

          const requestsSent = parseInt(raw[keys[i]].requestsSent || '0', 10);
          selectedCandidatePair.requests.totalSent = requestsSent;
          selectedCandidatePair.requests.sent = parsers.tabulateStats(prevStats, raw[keys[i]], 'requestsSent');

          const responsesSent = parseInt(raw[keys[i]].responsesSent || '0', 10);
          selectedCandidatePair.responses.totalSent = responsesSent;
          selectedCandidatePair.responses.sent = parsers.tabulateStats(prevStats, raw[keys[i]], 'responsesSent');

          const responsesReceived = parseInt(raw[keys[i]].responsesReceived || '0', 10);
          selectedCandidatePair.responses.totalReceived = responsesReceived;
          selectedCandidatePair.responses.received = parsers.tabulateStats(prevStats, raw[keys[i]], 'responsesReceived');
        }
      }
    }
  }

  if (selectedLocalCandidateId && selectedRemoteCandidateId) {
    if (raw[prop].type === 'remote-candidate') {
      const remoteCandidateStats = raw[prop];
      if (remoteCandidateStats.id === selectedRemoteCandidateId) {
        selectedCandidatePair.remote.ipAddress = remoteCandidateStats.ip;
        selectedCandidatePair.remote.portNumber = remoteCandidateStats.port;
        selectedCandidatePair.remote.transport = remoteCandidateStats.protocol;
        selectedCandidatePair.remote.priority = remoteCandidateStats.priority;
        selectedCandidatePair.remote.candidateType = formatCanTypeFn(remoteCandidateStats.candidateType);
      }
    }

    if (raw[prop].type === 'local-candidate') {
      const localCandidateStats = raw[prop];
      if (localCandidateStats.id === selectedLocalCandidateId) {
        selectedCandidatePair.local.ipAddress = localCandidateStats.ip;
        selectedCandidatePair.local.portNumber = localCandidateStats.port;
        selectedCandidatePair.local.transport = localCandidateStats.protocol;
        selectedCandidatePair.local.priority = localCandidateStats.priority;
        selectedCandidatePair.local.candidateType = formatCanTypeFn(localCandidateStats.candidateType);
      }
    }
  }

  /**

   TODO: Old stats implementation - remove once tested
  if (raw[prop].type === 'candidate-pair') {
    // Use the nominated pair, else use the one that has succeeded but not yet nominated.
    // This is to handle the case where none of the ICE candidates appear nominated.
    if (raw[prop].state !== 'succeeded' || !raw[prop].nominated || (selectedCandidate.nominated ? true
      : (raw[prop].priority < (selectedCandidate.priority || 0)))) {
      return;
    }

    const prevStats = isAutoBwStats ? peerBandwidth[peerId][prop] : peerStats[peerId][prop];

    // Map the selected ICE candidate pair based on computed priority
    const sending = (peerConnection.localDescription && peerConnection.localDescription.sdp && peerConnection.localDescription.sdp.match(/a=candidate:.*\r\n/gi)) || [];
    const receiving = (peerConnection.remoteDescription && peerConnection.remoteDescription.sdp && peerConnection.remoteDescription.sdp.match(/a=candidate:.*\r\n/gi)) || [];

    for (let s = 0; s < sending.length; s += 1) {
      const sendCanParts = sending[s].split(' ');

      for (let r = 0; r < receiving.length; r += 1) {
        const recvCanParts = receiving[r].split(' ');
        let priority = selectedCandidate.priority;

        if (raw[prop].writable) {
          // Compute the priority since we are the controller
          priority = computePriortyFn(parseInt(sendCanParts[3], 10), parseInt(recvCanParts[3], 10));
        } else {
          // Compute the priority since we are the controlled
          priority = computePriortyFn(parseInt(recvCanParts[3], 10), parseInt(sendCanParts[3], 10));
        }

        if (priority === raw[prop].priority) {
          selectedCandidate.local.ipAddress = sendCanParts[4];
          selectedCandidate.local.portNumber = parseInt(sendCanParts[5], 10);
          selectedCandidate.local.transport = sendCanParts[2];
          selectedCandidate.local.priority = parseInt(sendCanParts[3], 10);
          selectedCandidate.local.candidateType = formatCanTypeFn(sendCanParts[7]);
          selectedCandidate.local.networkType = raw[raw[prop].localCandidateId].networkType;

          selectedCandidate.remote.ipAddress = recvCanParts[4];
          selectedCandidate.remote.portNumber = parseInt(recvCanParts[5], 10);
          selectedCandidate.remote.transport = recvCanParts[2];
          selectedCandidate.remote.priority = parseInt(recvCanParts[3], 10);
          selectedCandidate.remote.candidateType = formatCanTypeFn(recvCanParts[7]);
          break;
        }

        if (isEmptyObj(selectedCandidate.local) && isEmptyObj(selectedCandidate.remote)) {
          break;
        }
      }
    }

    selectedCandidate.writable = raw[prop].writable;
    selectedCandidate.priority = raw[prop].priority;
    selectedCandidate.nominated = raw[prop].nominated;

    // FF has not implemented the following stats
    const totalRoundTripTime = parseInt(raw[prop].totalRoundTripTime || '0', 10);
    selectedCandidate.totalRoundTripTime = totalRoundTripTime;
    selectedCandidate.totalRoundTripTime = parsers.tabulateStats(prevStats, raw[prop], 'totalRoundTripTime');

    const consentRequestsSent = parseInt(raw[prop].consentRequestsSent || '0', 10);
    selectedCandidate.consentRequests.totalSent = consentRequestsSent;
    selectedCandidate.consentRequests.sent = parsers.tabulateStats(prevStats, raw[prop], 'consentRequestsSent');

    const requestsReceived = parseInt(raw[prop].requestsReceived || '0', 10);
    selectedCandidate.requests.totalReceived = requestsReceived;
    selectedCandidate.requests.received = parsers.tabulateStats(prevStats, raw[prop], 'requestsReceived');

    const requestsSent = parseInt(raw[prop].requestsSent || '0', 10);
    selectedCandidate.requests.totalSent = requestsSent;
    selectedCandidate.requests.sent = parsers.tabulateStats(prevStats, raw[prop], 'requestsSent');

    const responsesSent = parseInt(raw[prop].responsesSent || '0', 10);
    selectedCandidate.responses.totalSent = responsesSent;
    selectedCandidate.responses.sent = parsers.tabulateStats(prevStats, raw[prop], 'responsesSent');

    const responsesReceived = parseInt(raw[prop].responsesReceived || '0', 10);
    selectedCandidate.responses.totalReceived = responsesReceived;
    selectedCandidate.responses.received = parsers.tabulateStats(prevStats, raw[prop], 'responsesReceived');
  }

  * */
  // TODO:
  //  // FF has not fully implemented candidate-pair
  //  // test for Plugin
  // else if (raw[prop].type === 'googCandidatePair') {
  //   const prevStats = isAutoBwStats ? self._peerBandwidth[peerId][prop] : self._peerStats[peerId][prop];
  //
  //   selectedCandidate.writable = raw[prop].googWritable === 'true';
  //   selectedCandidate.readable = raw[prop].googReadable === 'true';
  //
  //   var rtt = parseInt(raw[prop].googRtt || '0', 10);
  //   selectedCandidate.totalRtt = rtt;
  //   selectedCandidate.rtt = self._parseConnectionStats(prevStats, raw, 'rtt');
  //
  //   if (raw[prop].consentResponsesReceived) {
  //     var consentResponsesReceived = parseInt(raw[prop].consentResponsesReceived || '0', 10);
  //     selectedCandidate.consentResponses.totalReceived = consentResponsesReceived;
  //     selectedCandidate.consentResponses.received = self._parseConnectionStats(prevStats, raw, 'consentResponsesReceived');
  //   }
  //
  //   if (raw[prop].consentResponsesSent) {
  //     var consentResponsesSent = parseInt(raw[prop].consentResponsesSent || '0', 10);
  //     selectedCandidate.consentResponses.totalSent = consentResponsesSent;
  //     selectedCandidate.consentResponses.sent = self._parseConnectionStats(prevStats, raw, 'consentResponsesSent');
  //   }
  //
  //   if (raw[prop].responsesReceived) {
  //     var responsesReceived = parseInt(raw[prop].responsesReceived || '0', 10);
  //     selectedCandidate.responses.totalReceived = responsesReceived;
  //     selectedCandidate.responses.received = self._parseConnectionStats(prevStats, raw, 'responsesReceived');
  //   }
  //
  //   if (raw[prop].responsesSent) {
  //     var responsesSent = parseInt(raw[prop].responsesSent || '0', 10);
  //     selectedCandidate.responses.totalSent = responsesSent;
  //     selectedCandidate.responses.sent = self._parseConnectionStats(prevStats, raw, 'responsesSent');
  //   }
  //
  //   var localCanItem = raw[raw[prop].localCandidateId || ''] || {};
  //   selectedCandidate.local.ipAddress = localCanItem.ipAddress;
  //   selectedCandidate.local.portNumber = parseInt(localCanItem.portNumber, 10);
  //   selectedCandidate.local.priority = parseInt(localCanItem.priority, 10);
  //   selectedCandidate.local.networkType = localCanItem.networkType;
  //   selectedCandidate.local.transport = localCanItem.transport;
  //   selectedCandidate.local.candidateType = localCanItem.candidateType;
  //
  //   var remoteCanItem = raw[raw[prop].remoteCandidateId || ''] || {};
  //   selectedCandidate.remote.ipAddress = remoteCanItem.ipAddress;
  //   selectedCandidate.remote.portNumber = parseInt(remoteCanItem.portNumber, 10);
  //   selectedCandidate.remote.priority = parseInt(remoteCanItem.priority, 10);
  //   selectedCandidate.remote.transport = remoteCanItem.transport;
  //   selectedCandidate.remote.candidateType = remoteCanItem.candidateType;
  // }
};

export default parseSelectedCandidatePair;