Browse Source

ref: store SSRCs as numbers (#485)

* ref: store SSRCs as a number

Converts all the places where SSRCs where stored as string to use
numbers.

* doc(RTPStatsCollector): getNonNegativeStat

Fixes invalid description about returning NaN.

* ref(JitsiConf...EventManager): simplify for..of

* ref(JitsiRemoteTrack): throw TypeError

Will throw a TypeError when 'ssrc' is not a number.

* fix(RTPStatsCollector): invalid reference

* doc(RTPStatsCollector): getNonNegativeStat private

* ref(RTPStatsCollector): simplify for..of

* fix(SSRCs): check for negative value

Will not accept negative SSRCs, since those are supposed to be unsigned.
tags/v0.0.2
Paweł Domas 8 years ago
parent
commit
d03204a0c0

+ 18
- 35
JitsiConferenceEventManager.js View File

@@ -53,32 +53,24 @@ export default function JitsiConferenceEventManager(conference) {
53 53
  * @param resolutions map of resolutions by ssrc
54 54
  */
55 55
 function mapResolutionsByUserId(conference, resolutions) {
56
-
57 56
     const id2resolution = {};
58 57
 
59 58
     // preprocess resolutions: group by user id, skip incorrect
60 59
     // resolutions etc.
61
-    Object.keys(resolutions).forEach(ssrc => {
62
-        const resolution = resolutions[ssrc];
63
-
64
-        if (!resolution.width || !resolution.height
65
-            || resolution.width === -1 || resolution.height === -1) {
66
-            return;
67
-        }
68
-
60
+    for (const [ ssrc, resolution ] of resolutions) {
69 61
         const id = conference.rtc.getResourceBySSRC(ssrc);
70 62
 
71
-        if (!id) {
72
-            return;
73
-        }
74
-
75
-        // ssrc to resolution map for user id
76
-        const idResolutions = id2resolution[id] || {};
63
+        if (id
64
+            && resolution.width && resolution.height
65
+            && resolution.width !== -1 && resolution.height !== -1) {
66
+            // ssrc to resolution map for user id
67
+            const idResolutions = id2resolution[id] || {};
77 68
 
78
-        idResolutions[ssrc] = resolution;
69
+            idResolutions[ssrc] = resolution;
79 70
 
80
-        id2resolution[id] = idResolutions;
81
-    });
71
+            id2resolution[id] = idResolutions;
72
+        }
73
+    }
82 74
 
83 75
     return id2resolution;
84 76
 }
@@ -89,30 +81,21 @@ function mapResolutionsByUserId(conference, resolutions) {
89 81
  * @param framerates map of framerates by ssrc
90 82
  */
91 83
 function mapFrameratesByUserId(conference, framerates) {
92
-
93 84
     const id2framerate = {};
94 85
 
95 86
     // preprocess framerates: group by user id
96
-    Object.keys(framerates).forEach(ssrc => {
97
-        const framerate = framerates[ssrc];
98
-
99
-        if (framerate === 0) {
100
-            return;
101
-        }
102
-
87
+    for (const [ ssrc, framerate ] of framerates) {
103 88
         const id = conference.rtc.getResourceBySSRC(ssrc);
104 89
 
105
-        if (!id) {
106
-            return;
107
-        }
108
-
109
-        // ssrc to framerate map for user id
110
-        const id2framerates = id2framerate[id] || {};
90
+        if (framerate !== 0 && id) {
91
+            // ssrc to framerate map for user id
92
+            const id2framerates = id2framerate[id] || {};
111 93
 
112
-        id2framerates[ssrc] = framerate;
94
+            id2framerates[ssrc] = framerate;
113 95
 
114
-        id2framerate[id] = id2framerates;
115
-    });
96
+            id2framerate[id] = id2framerates;
97
+        }
98
+    }
116 99
 
117 100
     return id2framerate;
118 101
 }

+ 8
- 2
modules/RTC/JitsiRemoteTrack.js View File

@@ -22,10 +22,11 @@ let ttfmTrackerVideoAttached = false;
22 22
  *        the new JitsiRemoteTrack
23 23
  * @param {MediaType} mediaType the type of the media
24 24
  * @param {VideoType} videoType the type of the video if applicable
25
- * @param {string} ssrc the SSRC number of the Media Stream
25
+ * @param {number} ssrc the SSRC number of the Media Stream
26 26
  * @param {boolean} muted the initial muted state
27 27
  * @param {boolean} isP2P indicates whether or not this track belongs to a P2P
28 28
  * session
29
+ * @throws {TypeError} if <tt>ssrc</tt> is not a number.
29 30
  * @constructor
30 31
  */
31 32
 export default function JitsiRemoteTrack(
@@ -50,6 +51,11 @@ export default function JitsiRemoteTrack(
50 51
         mediaType,
51 52
         videoType);
52 53
     this.rtc = rtc;
54
+
55
+    // Prevent from mixing up type of SSRC which should be a number
56
+    if (typeof ssrc !== 'number') {
57
+        throw new TypeError(`SSRC ${ssrc} is not a number`);
58
+    }
53 59
     this.ssrc = ssrc;
54 60
     this.ownerEndpointId = ownerEndpointId;
55 61
     this.muted = muted;
@@ -147,7 +153,7 @@ JitsiRemoteTrack.prototype.isLocal = function() {
147 153
 
148 154
 /**
149 155
  * Returns the synchronization source identifier (SSRC) of this remote track.
150
- * @returns {string} the SSRC of this remote track
156
+ * @returns {number} the SSRC of this remote track
151 157
  */
152 158
 JitsiRemoteTrack.prototype.getSSRC = function() {
153 159
     return this.ssrc;

+ 5
- 16
modules/RTC/RTC.js View File

@@ -670,7 +670,7 @@ export default class RTC extends Listenable {
670 670
     /**
671 671
      * Searches in localTracks(session stores ssrc for audio and video) and
672 672
      * remoteTracks for the ssrc and returns the corresponding resource.
673
-     * @param ssrc the ssrc to check.
673
+     * @param {number} ssrc the ssrc to check.
674 674
      */
675 675
     getResourceBySSRC(ssrc) {
676 676
         const track = this._getTrackBySSRC(ssrc);
@@ -680,22 +680,16 @@ export default class RTC extends Listenable {
680 680
 
681 681
     /**
682 682
      * Finds a track (either local or remote) which runs on the given SSRC.
683
-     * @param {string|number} ssrc
683
+     * @param {number} ssrc
684 684
      * @return {JitsiTrack|undefined}
685
-     *
686
-     * FIXME figure out where SSRC is stored as a string and convert to number
687 685
      * @private
688 686
      */
689 687
     _getTrackBySSRC(ssrc) {
690 688
         let track
691 689
             = this.getLocalTracks().find(
692 690
                 localTrack =>
693
-
694
-                    // It is important that SSRC is not compared with ===,
695
-                    // because the code calling this method is inconsistent
696
-                    // about string vs number types
697 691
                     Array.from(this.peerConnections.values())
698
-                         .find(pc => pc.getLocalSSRC(localTrack) == ssrc) // eslint-disable-line eqeqeq, max-len
692
+                         .find(pc => pc.getLocalSSRC(localTrack) === ssrc)
699 693
                 );
700 694
 
701 695
         if (!track) {
@@ -708,19 +702,14 @@ export default class RTC extends Listenable {
708 702
     /**
709 703
      * Searches in remoteTracks for the ssrc and returns the corresponding
710 704
      * track.
711
-     * @param ssrc the ssrc to check.
705
+     * @param {number} ssrc the ssrc to check.
712 706
      * @return {JitsiRemoteTrack|undefined} return the first remote track that
713 707
      * matches given SSRC or <tt>undefined</tt> if no such track was found.
714 708
      * @private
715 709
      */
716 710
     _getRemoteTrackBySSRC(ssrc) {
717
-        /* eslint-disable eqeqeq */
718
-        // FIXME: Convert the SSRCs in whole project to use the same type.
719
-        // Now we are using number and string.
720 711
         return this.getRemoteTracks().find(
721
-            remoteTrack => ssrc == remoteTrack.getSSRC());
722
-
723
-        /* eslint-enable eqeqeq */
712
+            remoteTrack => ssrc === remoteTrack.getSSRC());
724 713
     }
725 714
 
726 715
     /**

+ 13
- 3
modules/RTC/TraceablePeerConnection.js View File

@@ -575,10 +575,20 @@ TraceablePeerConnection.prototype._remoteTrackAdded = function(stream, track) {
575 575
 
576 576
     // FIXME the length of ssrcLines[0] not verified, but it will fail
577 577
     // with global error handler anyway
578
-    const trackSsrc = ssrcLines[0].substring(7).split(' ')[0];
578
+    const ssrcStr = ssrcLines[0].substring(7).split(' ')[0];
579
+    const trackSsrc = Number(ssrcStr);
579 580
     const ownerEndpointId = this.signalingLayer.getSSRCOwner(trackSsrc);
580 581
 
581
-    if (!ownerEndpointId) {
582
+    if (isNaN(trackSsrc) || trackSsrc < 0) {
583
+        GlobalOnErrorHandler.callErrorHandler(
584
+            new Error(
585
+                `Invalid SSRC: ${ssrcStr
586
+                    } for remote track, msid: ${streamId
587
+                    } media type: ${mediaType}`));
588
+
589
+        // Abort
590
+        return;
591
+    } else if (!ownerEndpointId) {
582 592
         GlobalOnErrorHandler.callErrorHandler(
583 593
             new Error(
584 594
                 `No SSRC owner known for: ${trackSsrc
@@ -622,7 +632,7 @@ TraceablePeerConnection.prototype._remoteTrackAdded = function(stream, track) {
622 632
  * @param {MediaStreamTrack} track the WebRTC track instance
623 633
  * @param {MediaType} mediaType the track's type of the media
624 634
  * @param {VideoType} [videoType] the track's type of the video (if applicable)
625
- * @param {string} ssrc the track's main SSRC number
635
+ * @param {number} ssrc the track's main SSRC number
626 636
  * @param {boolean} muted the initial muted status
627 637
  */
628 638
 TraceablePeerConnection.prototype._createRemoteTrack

+ 63
- 54
modules/statistics/RTPStatsCollector.js View File

@@ -231,8 +231,11 @@ export default function StatsCollector(
231 231
     this.statsIntervalId = null;
232 232
     this.statsIntervalMilis = statsInterval;
233 233
 
234
-    // Map of ssrcs to SsrcStats
235
-    this.ssrc2stats = {};
234
+    /**
235
+     * Maps SSRC numbers to {@link SsrcStats}.
236
+     * @type {Map<number,SsrcStats}
237
+     */
238
+    this.ssrc2stats = new Map();
236 239
 }
237 240
 
238 241
 /* eslint-enable max-params */
@@ -401,6 +404,28 @@ StatsCollector.prototype._defineGetStatValueMethod = function(keys) {
401 404
     return (item, name) => itemStatByKey(item, keyFromName(name));
402 405
 };
403 406
 
407
+/**
408
+ * Obtains a stat value from given stat and converts it to a non-negative
409
+ * number. If the value is either invalid or negative then 0 will be returned.
410
+ * @param report
411
+ * @param {string} name
412
+ * @return {number}
413
+ * @private
414
+ */
415
+StatsCollector.prototype.getNonNegativeStat = function(report, name) {
416
+    let value = this._getStatValue(report, name);
417
+
418
+    if (typeof value !== 'number') {
419
+        value = Number(value);
420
+    }
421
+
422
+    if (isNaN(value)) {
423
+        return 0;
424
+    }
425
+
426
+    return Math.max(0, value);
427
+};
428
+
404 429
 /* eslint-disable no-continue */
405 430
 
406 431
 /**
@@ -412,25 +437,6 @@ StatsCollector.prototype.processStatsReport = function() {
412 437
     }
413 438
 
414 439
     const getStatValue = this._getStatValue;
415
-
416
-    /**
417
-     *
418
-     * @param report
419
-     * @param name
420
-     */
421
-    function getNonNegativeStat(report, name) {
422
-        let value = getStatValue(report, name);
423
-
424
-        if (typeof value !== 'number') {
425
-            value = Number(value);
426
-        }
427
-
428
-        if (isNaN(value)) {
429
-            return 0;
430
-        }
431
-
432
-        return Math.max(0, value);
433
-    }
434 440
     const byteSentStats = {};
435 441
 
436 442
     for (const idx in this.currentStatsReport) {
@@ -464,7 +470,7 @@ StatsCollector.prototype.processStatsReport = function() {
464 470
                 type = getStatValue(now, 'transportType');
465 471
                 localip = getStatValue(now, 'localAddress');
466 472
                 active = getStatValue(now, 'activeConnection');
467
-                rtt = getNonNegativeStat(now, 'currentRoundTripTime');
473
+                rtt = this.getNonNegativeStat(now, 'currentRoundTripTime');
468 474
             } catch (e) { /* not supported*/ }
469 475
             if (!ip || !type || !localip || active !== 'true') {
470 476
                 continue;
@@ -512,7 +518,7 @@ StatsCollector.prototype.processStatsReport = function() {
512 518
         }
513 519
 
514 520
         const before = this.previousStatsReport[idx];
515
-        const ssrc = getStatValue(now, 'ssrc');
521
+        const ssrc = this.getNonNegativeStat(now, 'ssrc');
516 522
 
517 523
         if (!before || !ssrc) {
518 524
             continue;
@@ -529,8 +535,12 @@ StatsCollector.prototype.processStatsReport = function() {
529 535
             continue;
530 536
         }
531 537
 
532
-        const ssrcStats
533
-          = this.ssrc2stats[ssrc] || (this.ssrc2stats[ssrc] = new SsrcStats());
538
+        let ssrcStats = this.ssrc2stats.get(ssrc);
539
+
540
+        if (!ssrcStats) {
541
+            ssrcStats = new SsrcStats();
542
+            this.ssrc2stats.set(ssrc, ssrcStats);
543
+        }
534 544
 
535 545
         let isDownloadStream = true;
536 546
         let key = 'packetsReceived';
@@ -550,11 +560,13 @@ StatsCollector.prototype.processStatsReport = function() {
550 560
             packetsNow = 0;
551 561
         }
552 562
 
553
-        const packetsBefore = getNonNegativeStat(before, key);
563
+        const packetsBefore = this.getNonNegativeStat(before, key);
554 564
         const packetsDiff = Math.max(0, packetsNow - packetsBefore);
555 565
 
556
-        const packetsLostNow = getNonNegativeStat(now, 'packetsLost');
557
-        const packetsLostBefore = getNonNegativeStat(before, 'packetsLost');
566
+        const packetsLostNow
567
+            = this.getNonNegativeStat(now, 'packetsLost');
568
+        const packetsLostBefore
569
+            = this.getNonNegativeStat(before, 'packetsLost');
558 570
         const packetsLostDiff = Math.max(0, packetsLostNow - packetsLostBefore);
559 571
 
560 572
         ssrcStats.setLoss({
@@ -563,8 +575,10 @@ StatsCollector.prototype.processStatsReport = function() {
563 575
             isDownloadStream
564 576
         });
565 577
 
566
-        const bytesReceivedNow = getNonNegativeStat(now, 'bytesReceived');
567
-        const bytesReceivedBefore = getNonNegativeStat(before, 'bytesReceived');
578
+        const bytesReceivedNow
579
+            = this.getNonNegativeStat(now, 'bytesReceived');
580
+        const bytesReceivedBefore
581
+            = this.getNonNegativeStat(before, 'bytesReceived');
568 582
         const bytesReceived
569 583
             = Math.max(0, bytesReceivedNow - bytesReceivedBefore);
570 584
 
@@ -628,7 +642,7 @@ StatsCollector.prototype.processStatsReport = function() {
628 642
             // let's try with another one (FF)
629 643
             try {
630 644
                 ssrcStats.setFramerate(Math.round(
631
-                    getNonNegativeStat(now, 'framerateMean')));
645
+                    this.getNonNegativeStat(now, 'framerateMean')));
632 646
             } catch (err) { /* not supported*/ }
633 647
         }
634 648
 
@@ -650,34 +664,29 @@ StatsCollector.prototype.processStatsReport = function() {
650 664
     };
651 665
     let bitrateDownload = 0;
652 666
     let bitrateUpload = 0;
653
-    const resolutions = {};
654
-    const framerates = {};
655
-
656
-    Object.keys(this.ssrc2stats).forEach(
657
-        function(ssrc) {
658
-            const ssrcStats = this.ssrc2stats[ssrc];
667
+    const resolutions = new Map();
668
+    const framerates = new Map();
659 669
 
660
-            // process packet loss stats
661
-            const loss = ssrcStats.loss;
662
-            const type = loss.isDownloadStream ? 'download' : 'upload';
670
+    for (const [ ssrc, ssrcStats ] of this.ssrc2stats) {
671
+        // process packet loss stats
672
+        const loss = ssrcStats.loss;
673
+        const type = loss.isDownloadStream ? 'download' : 'upload';
663 674
 
664
-            totalPackets[type] += loss.packetsTotal;
665
-            lostPackets[type] += loss.packetsLost;
675
+        totalPackets[type] += loss.packetsTotal;
676
+        lostPackets[type] += loss.packetsLost;
666 677
 
667
-            // process bitrate stats
668
-            bitrateDownload += ssrcStats.bitrate.download;
669
-            bitrateUpload += ssrcStats.bitrate.upload;
678
+        // process bitrate stats
679
+        bitrateDownload += ssrcStats.bitrate.download;
680
+        bitrateUpload += ssrcStats.bitrate.upload;
670 681
 
671
-            ssrcStats.resetBitrate();
682
+        ssrcStats.resetBitrate();
672 683
 
673
-            // collect resolutions
674
-            resolutions[ssrc] = ssrcStats.resolution;
684
+        // collect resolutions
685
+        resolutions.set(ssrc, ssrcStats.resolution);
675 686
 
676
-            // collect framerates
677
-            framerates[ssrc] = ssrcStats.framerate;
678
-        },
679
-        this
680
-    );
687
+        // collect framerates
688
+        framerates.set(ssrc, ssrcStats.framerate);
689
+    }
681 690
 
682 691
     this.eventEmitter.emit(
683 692
         StatisticsEvents.BYTE_SENT_STATS, this.peerconnection, byteSentStats);
@@ -728,7 +737,7 @@ StatsCollector.prototype.processAudioLevelReport = function() {
728 737
         }
729 738
 
730 739
         const before = this.baselineAudioLevelsReport[idx];
731
-        const ssrc = getStatValue(now, 'ssrc');
740
+        const ssrc = this.getNonNegativeStat(now, 'ssrc');
732 741
 
733 742
         if (!before) {
734 743
             logger.warn(`${ssrc} not enough data`);

+ 9
- 3
modules/xmpp/JingleSessionPC.js View File

@@ -600,7 +600,7 @@ export default class JingleSessionPC extends JingleSession {
600 600
                         + 'source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]');
601 601
 
602 602
             ssrcs.each((i2, ssrcElement) => {
603
-                const ssrc = ssrcElement.getAttribute('ssrc');
603
+                const ssrc = Number(ssrcElement.getAttribute('ssrc'));
604 604
 
605 605
                 $(ssrcElement)
606 606
                     .find('>ssrc-info[xmlns="http://jitsi.org/jitmeet"]')
@@ -608,8 +608,14 @@ export default class JingleSessionPC extends JingleSession {
608 608
                         const owner = ssrcInfoElement.getAttribute('owner');
609 609
 
610 610
                         if (owner && owner.length) {
611
-                            this.signalingLayer.setSSRCOwner(
612
-                                ssrc, Strophe.getResourceFromJid(owner));
611
+                            if (isNaN(ssrc) || ssrc < 0) {
612
+                                logger.warn(
613
+                                    `Invalid SSRC ${ssrc} value received`
614
+                                        + ` for ${owner}`);
615
+                            } else {
616
+                                this.signalingLayer.setSSRCOwner(
617
+                                    ssrc, Strophe.getResourceFromJid(owner));
618
+                            }
613 619
                         }
614 620
                     }
615 621
                 );

+ 6
- 2
modules/xmpp/SignalingLayerImpl.js View File

@@ -24,7 +24,7 @@ export default class SignalingLayerImpl extends SignalingLayer {
24 24
          * onaddstream webrtc event where we have only the ssrc
25 25
          * FIXME: This map got filled and never cleaned and can grow during long
26 26
          * conference
27
-         * @type {Map<string, string>} maps SSRC number to jid
27
+         * @type {Map<number, string>} maps SSRC number to jid
28 28
          */
29 29
         this.ssrcOwners = new Map();
30 30
 
@@ -95,10 +95,14 @@ export default class SignalingLayerImpl extends SignalingLayer {
95 95
 
96 96
     /**
97 97
      * Set an SSRC owner.
98
-     * @param {string} ssrc an SSRC to be owned
98
+     * @param {number} ssrc an SSRC to be owned
99 99
      * @param {string} endpointId owner's ID (MUC nickname)
100
+     * @throws TypeError if <tt>ssrc</tt> is not a number
100 101
      */
101 102
     setSSRCOwner(ssrc, endpointId) {
103
+        if (typeof ssrc !== 'number') {
104
+            throw new TypeError(`SSRC(${ssrc}) must be a number`);
105
+        }
102 106
         this.ssrcOwners.set(ssrc, endpointId);
103 107
     }
104 108
 }

+ 1
- 1
service/RTC/SignalingLayer.js View File

@@ -19,7 +19,7 @@ export default class SignalingLayer extends Listenable {
19 19
 
20 20
     /**
21 21
      * Obtains the endpoint ID for given SSRC.
22
-     * @param {string} ssrc a string representation of the SSRC number.
22
+     * @param {number} ssrc the SSRC number.
23 23
      * @return {string|null} the endpoint ID for given media SSRC.
24 24
      */
25 25
     getSSRCOwner(ssrc) { // eslint-disable-line no-unused-vars

Loading…
Cancel
Save