github.com/pion/webrtc/v3@v3.2.24/stats_go_test.go (about)

     1  // SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
     2  // SPDX-License-Identifier: MIT
     3  
     4  //go:build !js
     5  // +build !js
     6  
     7  package webrtc
     8  
     9  import (
    10  	"encoding/json"
    11  	"fmt"
    12  	"sync"
    13  	"testing"
    14  	"time"
    15  
    16  	"github.com/pion/ice/v2"
    17  	"github.com/stretchr/testify/assert"
    18  	"github.com/stretchr/testify/require"
    19  )
    20  
    21  var errReceiveOfferTimeout = fmt.Errorf("timed out waiting to receive offer")
    22  
    23  func TestStatsTimestampTime(t *testing.T) {
    24  	for _, test := range []struct {
    25  		Timestamp StatsTimestamp
    26  		WantTime  time.Time
    27  	}{
    28  		{
    29  			Timestamp: 0,
    30  			WantTime:  time.Unix(0, 0),
    31  		},
    32  		{
    33  			Timestamp: 1,
    34  			WantTime:  time.Unix(0, 1e6),
    35  		},
    36  		{
    37  			Timestamp: 0.001,
    38  			WantTime:  time.Unix(0, 1e3),
    39  		},
    40  	} {
    41  		if got, want := test.Timestamp.Time(), test.WantTime.UTC(); got != want {
    42  			t.Fatalf("StatsTimestamp(%v).Time() = %v, want %v", test.Timestamp, got, want)
    43  		}
    44  	}
    45  }
    46  
    47  type statSample struct {
    48  	name  string
    49  	stats Stats
    50  	json  string
    51  }
    52  
    53  func getStatsSamples() []statSample {
    54  	codecStats := CodecStats{
    55  		Timestamp:      1688978831527.718,
    56  		Type:           StatsTypeCodec,
    57  		ID:             "COT01_111_minptime=10;useinbandfec=1",
    58  		PayloadType:    111,
    59  		CodecType:      CodecTypeEncode,
    60  		TransportID:    "T01",
    61  		MimeType:       "audio/opus",
    62  		ClockRate:      48000,
    63  		Channels:       2,
    64  		SDPFmtpLine:    "minptime=10;useinbandfec=1",
    65  		Implementation: "libvpx",
    66  	}
    67  	codecStatsJSON := `
    68  {
    69  	"timestamp": 1688978831527.718,
    70  	"type": "codec",
    71  	"id": "COT01_111_minptime=10;useinbandfec=1",
    72  	"payloadType": 111,
    73  	"codecType": "encode",
    74  	"transportId": "T01",
    75  	"mimeType": "audio/opus",
    76  	"clockRate": 48000,
    77  	"channels": 2,
    78  	"sdpFmtpLine": "minptime=10;useinbandfec=1",
    79  	"implementation": "libvpx"
    80  }
    81  `
    82  	inboundRTPStreamStats := InboundRTPStreamStats{
    83  		Timestamp:                   1688978831527.718,
    84  		ID:                          "IT01A2184088143",
    85  		Type:                        StatsTypeInboundRTP,
    86  		SSRC:                        2184088143,
    87  		Kind:                        "audio",
    88  		TransportID:                 "T01",
    89  		CodecID:                     "CIT01_111_minptime=10;useinbandfec=1",
    90  		FIRCount:                    1,
    91  		PLICount:                    2,
    92  		NACKCount:                   3,
    93  		SLICount:                    4,
    94  		QPSum:                       5,
    95  		PacketsReceived:             6,
    96  		PacketsLost:                 7,
    97  		Jitter:                      8,
    98  		PacketsDiscarded:            9,
    99  		PacketsRepaired:             10,
   100  		BurstPacketsLost:            11,
   101  		BurstPacketsDiscarded:       12,
   102  		BurstLossCount:              13,
   103  		BurstDiscardCount:           14,
   104  		BurstLossRate:               15,
   105  		BurstDiscardRate:            16,
   106  		GapLossRate:                 17,
   107  		GapDiscardRate:              18,
   108  		TrackID:                     "d57dbc4b-484b-4b40-9088-d3150e3a2010",
   109  		ReceiverID:                  "R01",
   110  		RemoteID:                    "ROA2184088143",
   111  		FramesDecoded:               17,
   112  		LastPacketReceivedTimestamp: 1689668364374.181,
   113  		AverageRTCPInterval:         18,
   114  		FECPacketsReceived:          19,
   115  		BytesReceived:               20,
   116  		PacketsFailedDecryption:     21,
   117  		PacketsDuplicated:           22,
   118  		PerDSCPPacketsReceived: map[string]uint32{
   119  			"123": 23,
   120  		},
   121  	}
   122  	inboundRTPStreamStatsJSON := `
   123  {
   124    "timestamp": 1688978831527.718,
   125    "id": "IT01A2184088143",
   126    "type": "inbound-rtp",
   127    "ssrc": 2184088143,
   128    "kind": "audio",
   129    "transportId": "T01",
   130    "codecId": "CIT01_111_minptime=10;useinbandfec=1",
   131    "firCount": 1,
   132    "pliCount": 2,
   133    "nackCount": 3,
   134    "sliCount": 4,
   135    "qpSum": 5,
   136    "packetsReceived": 6,
   137    "packetsLost": 7,
   138    "jitter": 8,
   139    "packetsDiscarded": 9,
   140    "packetsRepaired": 10,
   141    "burstPacketsLost": 11,
   142    "burstPacketsDiscarded": 12,
   143    "burstLossCount": 13,
   144    "burstDiscardCount": 14,
   145    "burstLossRate": 15,
   146    "burstDiscardRate": 16,
   147    "gapLossRate": 17,
   148    "gapDiscardRate": 18,
   149    "trackId": "d57dbc4b-484b-4b40-9088-d3150e3a2010",
   150    "receiverId": "R01",
   151    "remoteId": "ROA2184088143",
   152    "framesDecoded": 17,
   153    "lastPacketReceivedTimestamp": 1689668364374.181,
   154    "averageRtcpInterval": 18,
   155    "fecPacketsReceived": 19,
   156    "bytesReceived": 20,
   157    "packetsFailedDecryption": 21,
   158    "packetsDuplicated": 22,
   159    "perDscpPacketsReceived": {
   160      "123": 23
   161    }
   162  }
   163  `
   164  	outboundRTPStreamStats := OutboundRTPStreamStats{
   165  		Timestamp:               1688978831527.718,
   166  		Type:                    StatsTypeOutboundRTP,
   167  		ID:                      "OT01A2184088143",
   168  		SSRC:                    2184088143,
   169  		Kind:                    "audio",
   170  		TransportID:             "T01",
   171  		CodecID:                 "COT01_111_minptime=10;useinbandfec=1",
   172  		FIRCount:                1,
   173  		PLICount:                2,
   174  		NACKCount:               3,
   175  		SLICount:                4,
   176  		QPSum:                   5,
   177  		PacketsSent:             6,
   178  		PacketsDiscardedOnSend:  7,
   179  		FECPacketsSent:          8,
   180  		BytesSent:               9,
   181  		BytesDiscardedOnSend:    10,
   182  		TrackID:                 "d57dbc4b-484b-4b40-9088-d3150e3a2010",
   183  		SenderID:                "S01",
   184  		RemoteID:                "ROA2184088143",
   185  		LastPacketSentTimestamp: 11,
   186  		TargetBitrate:           12,
   187  		FramesEncoded:           13,
   188  		TotalEncodeTime:         14,
   189  		AverageRTCPInterval:     15,
   190  		QualityLimitationReason: "cpu",
   191  		QualityLimitationDurations: map[string]float64{
   192  			"none":      16,
   193  			"cpu":       17,
   194  			"bandwidth": 18,
   195  			"other":     19,
   196  		},
   197  		PerDSCPPacketsSent: map[string]uint32{
   198  			"123": 23,
   199  		},
   200  	}
   201  	outboundRTPStreamStatsJSON := `
   202  {
   203    "timestamp": 1688978831527.718,
   204    "type": "outbound-rtp",
   205    "id": "OT01A2184088143",
   206    "ssrc": 2184088143,
   207    "kind": "audio",
   208    "transportId": "T01",
   209    "codecId": "COT01_111_minptime=10;useinbandfec=1",
   210    "firCount": 1,
   211    "pliCount": 2,
   212    "nackCount": 3,
   213    "sliCount": 4,
   214    "qpSum": 5,
   215    "packetsSent": 6,
   216    "packetsDiscardedOnSend": 7,
   217    "fecPacketsSent": 8,
   218    "bytesSent": 9,
   219    "bytesDiscardedOnSend": 10,
   220    "trackId": "d57dbc4b-484b-4b40-9088-d3150e3a2010",
   221    "senderId": "S01",
   222    "remoteId": "ROA2184088143",
   223    "lastPacketSentTimestamp": 11,
   224    "targetBitrate": 12,
   225    "framesEncoded": 13,
   226    "totalEncodeTime": 14,
   227    "averageRtcpInterval": 15,
   228    "qualityLimitationReason": "cpu",
   229    "qualityLimitationDurations": {
   230      "none": 16,
   231      "cpu": 17,
   232      "bandwidth": 18,
   233      "other": 19
   234    },
   235    "perDscpPacketsSent": {
   236      "123": 23
   237    }
   238  }
   239  `
   240  	remoteInboundRTPStreamStats := RemoteInboundRTPStreamStats{
   241  		Timestamp:             1688978831527.718,
   242  		Type:                  StatsTypeRemoteInboundRTP,
   243  		ID:                    "RIA2184088143",
   244  		SSRC:                  2184088143,
   245  		Kind:                  "audio",
   246  		TransportID:           "T01",
   247  		CodecID:               "COT01_111_minptime=10;useinbandfec=1",
   248  		FIRCount:              1,
   249  		PLICount:              2,
   250  		NACKCount:             3,
   251  		SLICount:              4,
   252  		QPSum:                 5,
   253  		PacketsReceived:       6,
   254  		PacketsLost:           7,
   255  		Jitter:                8,
   256  		PacketsDiscarded:      9,
   257  		PacketsRepaired:       10,
   258  		BurstPacketsLost:      11,
   259  		BurstPacketsDiscarded: 12,
   260  		BurstLossCount:        13,
   261  		BurstDiscardCount:     14,
   262  		BurstLossRate:         15,
   263  		BurstDiscardRate:      16,
   264  		GapLossRate:           17,
   265  		GapDiscardRate:        18,
   266  		LocalID:               "RIA2184088143",
   267  		RoundTripTime:         19,
   268  		FractionLost:          20,
   269  	}
   270  	remoteInboundRTPStreamStatsJSON := `
   271  {
   272    "timestamp": 1688978831527.718,
   273    "type": "remote-inbound-rtp",
   274    "id": "RIA2184088143",
   275    "ssrc": 2184088143,
   276    "kind": "audio",
   277    "transportId": "T01",
   278    "codecId": "COT01_111_minptime=10;useinbandfec=1",
   279    "firCount": 1,
   280    "pliCount": 2,
   281    "nackCount": 3,
   282    "sliCount": 4,
   283    "qpSum": 5,
   284    "packetsReceived": 6,
   285    "packetsLost": 7,
   286    "jitter": 8,
   287    "packetsDiscarded": 9,
   288    "packetsRepaired": 10,
   289    "burstPacketsLost": 11,
   290    "burstPacketsDiscarded": 12,
   291    "burstLossCount": 13,
   292    "burstDiscardCount": 14,
   293    "burstLossRate": 15,
   294    "burstDiscardRate": 16,
   295    "gapLossRate": 17,
   296    "gapDiscardRate": 18,
   297    "localId": "RIA2184088143",
   298    "roundTripTime": 19,
   299    "fractionLost": 20
   300  }
   301  `
   302  	remoteOutboundRTPStreamStats := RemoteOutboundRTPStreamStats{
   303  		Timestamp:              1688978831527.718,
   304  		Type:                   StatsTypeRemoteOutboundRTP,
   305  		ID:                     "ROA2184088143",
   306  		SSRC:                   2184088143,
   307  		Kind:                   "audio",
   308  		TransportID:            "T01",
   309  		CodecID:                "CIT01_111_minptime=10;useinbandfec=1",
   310  		FIRCount:               1,
   311  		PLICount:               2,
   312  		NACKCount:              3,
   313  		SLICount:               4,
   314  		QPSum:                  5,
   315  		PacketsSent:            1259,
   316  		PacketsDiscardedOnSend: 6,
   317  		FECPacketsSent:         7,
   318  		BytesSent:              92654,
   319  		BytesDiscardedOnSend:   8,
   320  		LocalID:                "IT01A2184088143",
   321  		RemoteTimestamp:        1689668361298,
   322  	}
   323  	remoteOutboundRTPStreamStatsJSON := `
   324  {
   325    "timestamp": 1688978831527.718,
   326    "type": "remote-outbound-rtp",
   327    "id": "ROA2184088143",
   328    "ssrc": 2184088143,
   329    "kind": "audio",
   330    "transportId": "T01",
   331    "codecId": "CIT01_111_minptime=10;useinbandfec=1",
   332    "firCount": 1,
   333    "pliCount": 2,
   334    "nackCount": 3,
   335    "sliCount": 4,
   336    "qpSum": 5,
   337    "packetsSent": 1259,
   338    "packetsDiscardedOnSend": 6,
   339    "fecPacketsSent": 7,
   340    "bytesSent": 92654,
   341    "bytesDiscardedOnSend": 8,
   342    "localId": "IT01A2184088143",
   343    "remoteTimestamp": 1689668361298
   344  }
   345  `
   346  	csrcStats := RTPContributingSourceStats{
   347  		Timestamp:            1688978831527.718,
   348  		Type:                 StatsTypeCSRC,
   349  		ID:                   "ROA2184088143",
   350  		ContributorSSRC:      2184088143,
   351  		InboundRTPStreamID:   "IT01A2184088143",
   352  		PacketsContributedTo: 5,
   353  		AudioLevel:           0.3,
   354  	}
   355  	csrcStatsJSON := `
   356  {
   357    "timestamp": 1688978831527.718,
   358    "type": "csrc",
   359    "id": "ROA2184088143",
   360    "contributorSsrc": 2184088143,
   361    "inboundRtpStreamId": "IT01A2184088143",
   362    "packetsContributedTo": 5,
   363    "audioLevel": 0.3
   364  }
   365  `
   366  	audioSourceStats := AudioSourceStats{
   367  		Timestamp:                 1689668364374.479,
   368  		Type:                      StatsTypeMediaSource,
   369  		ID:                        "SA5",
   370  		TrackIdentifier:           "d57dbc4b-484b-4b40-9088-d3150e3a2010",
   371  		Kind:                      "audio",
   372  		AudioLevel:                0.0030518509475997192,
   373  		TotalAudioEnergy:          0.0024927631236904358,
   374  		TotalSamplesDuration:      28.360000000001634,
   375  		EchoReturnLoss:            -30,
   376  		EchoReturnLossEnhancement: 0.17551203072071075,
   377  		DroppedSamplesDuration:    0.1,
   378  		DroppedSamplesEvents:      2,
   379  		TotalCaptureDelay:         0.3,
   380  		TotalSamplesCaptured:      4,
   381  	}
   382  	audioSourceStatsJSON := `
   383  {
   384    "timestamp": 1689668364374.479,
   385    "type": "media-source",
   386    "id": "SA5",
   387    "trackIdentifier": "d57dbc4b-484b-4b40-9088-d3150e3a2010",
   388    "kind": "audio",
   389    "audioLevel": 0.0030518509475997192,
   390    "totalAudioEnergy": 0.0024927631236904358,
   391    "totalSamplesDuration": 28.360000000001634,
   392    "echoReturnLoss": -30,
   393    "echoReturnLossEnhancement": 0.17551203072071075,
   394    "droppedSamplesDuration": 0.1,
   395    "droppedSamplesEvents": 2,
   396    "totalCaptureDelay": 0.3,
   397    "totalSamplesCaptured": 4
   398  }
   399  `
   400  	videoSourceStats := VideoSourceStats{
   401  		Timestamp:       1689668364374.479,
   402  		Type:            StatsTypeMediaSource,
   403  		ID:              "SV6",
   404  		TrackIdentifier: "d7f11739-d395-42e9-af87-5dfa1cc10ee0",
   405  		Kind:            "video",
   406  		Width:           640,
   407  		Height:          480,
   408  		Frames:          850,
   409  		FramesPerSecond: 30,
   410  	}
   411  	videoSourceStatsJSON := `
   412  {
   413    "timestamp": 1689668364374.479,
   414    "type": "media-source",
   415    "id": "SV6",
   416    "trackIdentifier": "d7f11739-d395-42e9-af87-5dfa1cc10ee0",
   417    "kind": "video",
   418    "width": 640,
   419    "height": 480,
   420    "frames": 850,
   421    "framesPerSecond": 30
   422  }
   423  `
   424  	audioPlayoutStats := AudioPlayoutStats{
   425  		Timestamp:                  1689668364374.181,
   426  		Type:                       StatsTypeMediaPlayout,
   427  		ID:                         "AP",
   428  		Kind:                       "audio",
   429  		SynthesizedSamplesDuration: 1,
   430  		SynthesizedSamplesEvents:   2,
   431  		TotalSamplesDuration:       593.5,
   432  		TotalPlayoutDelay:          1062194.11536,
   433  		TotalSamplesCount:          28488000,
   434  	}
   435  	audioPlayoutStatsJSON := `
   436  {
   437    "timestamp": 1689668364374.181,
   438    "type": "media-playout",
   439    "id": "AP",
   440    "kind": "audio",
   441    "synthesizedSamplesDuration": 1,
   442    "synthesizedSamplesEvents": 2,
   443    "totalSamplesDuration": 593.5,
   444    "totalPlayoutDelay": 1062194.11536,
   445    "totalSamplesCount": 28488000
   446  }
   447  `
   448  	peerConnectionStats := PeerConnectionStats{
   449  		Timestamp:             1688978831527.718,
   450  		Type:                  StatsTypePeerConnection,
   451  		ID:                    "P",
   452  		DataChannelsOpened:    1,
   453  		DataChannelsClosed:    2,
   454  		DataChannelsRequested: 3,
   455  		DataChannelsAccepted:  4,
   456  	}
   457  	peerConnectionStatsJSON := `
   458  {
   459    "timestamp": 1688978831527.718,
   460    "type": "peer-connection",
   461    "id": "P",
   462    "dataChannelsOpened": 1,
   463    "dataChannelsClosed": 2,
   464    "dataChannelsRequested": 3,
   465    "dataChannelsAccepted": 4
   466  }
   467  `
   468  	dataChannelStats := DataChannelStats{
   469  		Timestamp:             1688978831527.718,
   470  		Type:                  StatsTypeDataChannel,
   471  		ID:                    "D1",
   472  		Label:                 "display",
   473  		Protocol:              "protocol",
   474  		DataChannelIdentifier: 1,
   475  		TransportID:           "T1",
   476  		State:                 DataChannelStateOpen,
   477  		MessagesSent:          1,
   478  		BytesSent:             16,
   479  		MessagesReceived:      2,
   480  		BytesReceived:         20,
   481  	}
   482  	dataChannelStatsJSON := `
   483  {
   484    "timestamp": 1688978831527.718,
   485    "type": "data-channel",
   486    "id": "D1",
   487    "label": "display",
   488    "protocol": "protocol",
   489    "dataChannelIdentifier": 1,
   490    "transportId": "T1",
   491    "state": "open",
   492    "messagesSent": 1,
   493    "bytesSent": 16,
   494    "messagesReceived": 2,
   495    "bytesReceived": 20
   496  }
   497  `
   498  	streamStats := MediaStreamStats{
   499  		Timestamp:        1688978831527.718,
   500  		Type:             StatsTypeStream,
   501  		ID:               "ROA2184088143",
   502  		StreamIdentifier: "S1",
   503  		TrackIDs:         []string{"d57dbc4b-484b-4b40-9088-d3150e3a2010"},
   504  	}
   505  	streamStatsJSON := `
   506  {
   507    "timestamp": 1688978831527.718,
   508    "type": "stream",
   509    "id": "ROA2184088143",
   510    "streamIdentifier": "S1",
   511    "trackIds": [
   512      "d57dbc4b-484b-4b40-9088-d3150e3a2010"
   513    ]
   514  }
   515  `
   516  	senderVideoTrackAttachmentStats := SenderVideoTrackAttachmentStats{
   517  		Timestamp:      1688978831527.718,
   518  		Type:           StatsTypeTrack,
   519  		ID:             "S2",
   520  		Kind:           "video",
   521  		FramesCaptured: 1,
   522  		FramesSent:     2,
   523  		HugeFramesSent: 3,
   524  		KeyFramesSent:  4,
   525  	}
   526  	senderVideoTrackAttachmentStatsJSON := `
   527  {
   528    "timestamp": 1688978831527.718,
   529    "type": "track",
   530    "id": "S2",
   531    "kind": "video",
   532    "framesCaptured": 1,
   533    "framesSent": 2,
   534    "hugeFramesSent": 3,
   535    "keyFramesSent": 4
   536  }
   537  `
   538  	senderAudioTrackAttachmentStats := SenderAudioTrackAttachmentStats{
   539  		Timestamp:                 1688978831527.718,
   540  		Type:                      StatsTypeTrack,
   541  		ID:                        "S1",
   542  		TrackIdentifier:           "audio",
   543  		RemoteSource:              true,
   544  		Ended:                     true,
   545  		Kind:                      "audio",
   546  		AudioLevel:                0.1,
   547  		TotalAudioEnergy:          0.2,
   548  		VoiceActivityFlag:         true,
   549  		TotalSamplesDuration:      0.3,
   550  		EchoReturnLoss:            0.4,
   551  		EchoReturnLossEnhancement: 0.5,
   552  		TotalSamplesSent:          200,
   553  	}
   554  	senderAudioTrackAttachmentStatsJSON := `
   555  {
   556    "timestamp": 1688978831527.718,
   557    "type": "track",
   558    "id": "S1",
   559    "trackIdentifier": "audio",
   560    "remoteSource": true,
   561    "ended": true,
   562    "kind": "audio",
   563    "audioLevel": 0.1,
   564    "totalAudioEnergy": 0.2,
   565    "voiceActivityFlag": true,
   566    "totalSamplesDuration": 0.3,
   567    "echoReturnLoss": 0.4,
   568    "echoReturnLossEnhancement": 0.5,
   569    "totalSamplesSent": 200
   570  }
   571  `
   572  	videoSenderStats := VideoSenderStats{
   573  		Timestamp:      1688978831527.718,
   574  		Type:           StatsTypeSender,
   575  		ID:             "S2",
   576  		Kind:           "video",
   577  		FramesCaptured: 1,
   578  		FramesSent:     2,
   579  		HugeFramesSent: 3,
   580  		KeyFramesSent:  4,
   581  	}
   582  	videoSenderStatsJSON := `
   583  {
   584    "timestamp": 1688978831527.718,
   585    "type": "sender",
   586    "id": "S2",
   587    "kind": "video",
   588    "framesCaptured": 1,
   589    "framesSent": 2,
   590    "hugeFramesSent": 3,
   591    "keyFramesSent": 4
   592  }
   593  `
   594  	audioSenderStats := AudioSenderStats{
   595  		Timestamp:                 1688978831527.718,
   596  		Type:                      StatsTypeSender,
   597  		ID:                        "S1",
   598  		TrackIdentifier:           "audio",
   599  		RemoteSource:              true,
   600  		Ended:                     true,
   601  		Kind:                      "audio",
   602  		AudioLevel:                0.1,
   603  		TotalAudioEnergy:          0.2,
   604  		VoiceActivityFlag:         true,
   605  		TotalSamplesDuration:      0.3,
   606  		EchoReturnLoss:            0.4,
   607  		EchoReturnLossEnhancement: 0.5,
   608  		TotalSamplesSent:          200,
   609  	}
   610  	audioSenderStatsJSON := `
   611  {
   612    "timestamp": 1688978831527.718,
   613    "type": "sender",
   614    "id": "S1",
   615    "trackIdentifier": "audio",
   616    "remoteSource": true,
   617    "ended": true,
   618    "kind": "audio",
   619    "audioLevel": 0.1,
   620    "totalAudioEnergy": 0.2,
   621    "voiceActivityFlag": true,
   622    "totalSamplesDuration": 0.3,
   623    "echoReturnLoss": 0.4,
   624    "echoReturnLossEnhancement": 0.5,
   625    "totalSamplesSent": 200
   626  }
   627  `
   628  	videoReceiverStats := VideoReceiverStats{
   629  		Timestamp:                 1688978831527.718,
   630  		Type:                      StatsTypeReceiver,
   631  		ID:                        "ROA2184088143",
   632  		Kind:                      "video",
   633  		FrameWidth:                720,
   634  		FrameHeight:               480,
   635  		FramesPerSecond:           30.0,
   636  		EstimatedPlayoutTimestamp: 1688978831527.718,
   637  		JitterBufferDelay:         0.1,
   638  		JitterBufferEmittedCount:  1,
   639  		FramesReceived:            79,
   640  		KeyFramesReceived:         10,
   641  		FramesDecoded:             10,
   642  		FramesDropped:             10,
   643  		PartialFramesLost:         5,
   644  		FullFramesLost:            5,
   645  	}
   646  	videoReceiverStatsJSON := `
   647  {
   648    "timestamp": 1688978831527.718,
   649    "type": "receiver",
   650    "id": "ROA2184088143",
   651    "kind": "video",
   652    "frameWidth": 720,
   653    "frameHeight": 480,
   654    "framesPerSecond": 30.0,
   655    "estimatedPlayoutTimestamp": 1688978831527.718,
   656    "jitterBufferDelay": 0.1,
   657    "jitterBufferEmittedCount": 1,
   658    "framesReceived": 79,
   659    "keyFramesReceived": 10,
   660    "framesDecoded": 10,
   661    "framesDropped": 10,
   662    "partialFramesLost": 5,
   663    "fullFramesLost": 5
   664  }
   665  `
   666  	audioReceiverStats := AudioReceiverStats{
   667  		Timestamp:                 1688978831527.718,
   668  		Type:                      StatsTypeReceiver,
   669  		ID:                        "R1",
   670  		Kind:                      "audio",
   671  		AudioLevel:                0.1,
   672  		TotalAudioEnergy:          0.2,
   673  		VoiceActivityFlag:         true,
   674  		TotalSamplesDuration:      0.3,
   675  		EstimatedPlayoutTimestamp: 1688978831527.718,
   676  		JitterBufferDelay:         0.5,
   677  		JitterBufferEmittedCount:  6,
   678  		TotalSamplesReceived:      7,
   679  		ConcealedSamples:          8,
   680  		ConcealmentEvents:         9,
   681  	}
   682  	audioReceiverStatsJSON := `
   683  {
   684    "timestamp": 1688978831527.718,
   685    "type": "receiver",
   686    "id": "R1",
   687    "kind": "audio",
   688    "audioLevel": 0.1,
   689    "totalAudioEnergy": 0.2,
   690    "voiceActivityFlag": true,
   691    "totalSamplesDuration": 0.3,
   692    "estimatedPlayoutTimestamp": 1688978831527.718,
   693    "jitterBufferDelay": 0.5,
   694    "jitterBufferEmittedCount": 6,
   695    "totalSamplesReceived": 7,
   696    "concealedSamples": 8,
   697    "concealmentEvents": 9
   698  }
   699  `
   700  	transportStats := TransportStats{
   701  		Timestamp:               1688978831527.718,
   702  		Type:                    StatsTypeTransport,
   703  		ID:                      "T01",
   704  		PacketsSent:             60,
   705  		PacketsReceived:         8,
   706  		BytesSent:               6517,
   707  		BytesReceived:           1159,
   708  		RTCPTransportStatsID:    "T01",
   709  		ICERole:                 ICERoleControlling,
   710  		DTLSState:               DTLSTransportStateConnected,
   711  		SelectedCandidatePairID: "CPxIhBDNnT_sPDhy1TB",
   712  		LocalCertificateID:      "CFF4:4F:C4:C7:F3:31:6C:B9:D5:AD:19:64:05:9F:2F:E9:00:70:56:1E:BA:92:29:3A:08:CE:1B:27:CF:2D:AB:24",
   713  		RemoteCertificateID:     "CF62:AF:88:F7:F3:0F:D6:C4:93:91:1E:AD:52:F0:A4:12:04:F9:48:E7:06:16:BA:A3:86:26:8F:1E:38:1C:48:49",
   714  		DTLSCipher:              "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
   715  		SRTPCipher:              "AES_CM_128_HMAC_SHA1_80",
   716  	}
   717  	transportStatsJSON := `
   718  {
   719    "timestamp": 1688978831527.718,
   720    "type": "transport",
   721    "id": "T01",
   722    "packetsSent": 60,
   723    "packetsReceived": 8,
   724    "bytesSent": 6517,
   725    "bytesReceived": 1159,
   726    "rtcpTransportStatsId": "T01",
   727    "iceRole": "controlling",
   728    "dtlsState": "connected",
   729    "selectedCandidatePairId": "CPxIhBDNnT_sPDhy1TB",
   730    "localCertificateId": "CFF4:4F:C4:C7:F3:31:6C:B9:D5:AD:19:64:05:9F:2F:E9:00:70:56:1E:BA:92:29:3A:08:CE:1B:27:CF:2D:AB:24",
   731    "remoteCertificateId": "CF62:AF:88:F7:F3:0F:D6:C4:93:91:1E:AD:52:F0:A4:12:04:F9:48:E7:06:16:BA:A3:86:26:8F:1E:38:1C:48:49",
   732    "dtlsCipher": "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
   733    "srtpCipher": "AES_CM_128_HMAC_SHA1_80"
   734  }
   735  `
   736  	iceCandidatePairStats := ICECandidatePairStats{
   737  		Timestamp:                   1688978831527.718,
   738  		Type:                        StatsTypeCandidatePair,
   739  		ID:                          "CPxIhBDNnT_LlMJOnBv",
   740  		TransportID:                 "T01",
   741  		LocalCandidateID:            "IxIhBDNnT",
   742  		RemoteCandidateID:           "ILlMJOnBv",
   743  		State:                       "waiting",
   744  		Nominated:                   true,
   745  		PacketsSent:                 1,
   746  		PacketsReceived:             2,
   747  		BytesSent:                   3,
   748  		BytesReceived:               4,
   749  		LastPacketSentTimestamp:     5,
   750  		LastPacketReceivedTimestamp: 6,
   751  		FirstRequestTimestamp:       7,
   752  		LastRequestTimestamp:        8,
   753  		LastResponseTimestamp:       9,
   754  		TotalRoundTripTime:          10,
   755  		CurrentRoundTripTime:        11,
   756  		AvailableOutgoingBitrate:    12,
   757  		AvailableIncomingBitrate:    13,
   758  		CircuitBreakerTriggerCount:  14,
   759  		RequestsReceived:            15,
   760  		RequestsSent:                16,
   761  		ResponsesReceived:           17,
   762  		ResponsesSent:               18,
   763  		RetransmissionsReceived:     19,
   764  		RetransmissionsSent:         20,
   765  		ConsentRequestsSent:         21,
   766  		ConsentExpiredTimestamp:     22,
   767  	}
   768  	iceCandidatePairStatsJSON := `
   769  {
   770    "timestamp": 1688978831527.718,
   771    "type": "candidate-pair",
   772    "id": "CPxIhBDNnT_LlMJOnBv",
   773    "transportId": "T01",
   774    "localCandidateId": "IxIhBDNnT",
   775    "remoteCandidateId": "ILlMJOnBv",
   776    "state": "waiting",
   777    "nominated": true,
   778    "packetsSent": 1,
   779    "packetsReceived": 2,
   780    "bytesSent": 3,
   781    "bytesReceived": 4,
   782    "lastPacketSentTimestamp": 5,
   783    "lastPacketReceivedTimestamp": 6,
   784    "firstRequestTimestamp": 7,
   785    "lastRequestTimestamp": 8,
   786    "lastResponseTimestamp": 9,
   787    "totalRoundTripTime": 10,
   788    "currentRoundTripTime": 11,
   789    "availableOutgoingBitrate": 12,
   790    "availableIncomingBitrate": 13,
   791    "circuitBreakerTriggerCount": 14,
   792    "requestsReceived": 15,
   793    "requestsSent": 16,
   794    "responsesReceived": 17,
   795    "responsesSent": 18,
   796    "retransmissionsReceived": 19,
   797    "retransmissionsSent": 20,
   798    "consentRequestsSent": 21,
   799    "consentExpiredTimestamp": 22
   800  }
   801  `
   802  	localIceCandidateStats := ICECandidateStats{
   803  		Timestamp:     1688978831527.718,
   804  		Type:          StatsTypeLocalCandidate,
   805  		ID:            "ILO8S8KYr",
   806  		TransportID:   "T01",
   807  		NetworkType:   NetworkTypeUDP4,
   808  		IP:            "192.168.0.36",
   809  		Port:          65400,
   810  		Protocol:      "udp",
   811  		CandidateType: ICECandidateTypeHost,
   812  		Priority:      2122260223,
   813  		URL:           "example.com",
   814  		RelayProtocol: "tcp",
   815  		Deleted:       true,
   816  	}
   817  	localIceCandidateStatsJSON := `
   818  {
   819    "timestamp": 1688978831527.718,
   820    "type": "local-candidate",
   821    "id": "ILO8S8KYr",
   822    "transportId": "T01",
   823    "networkType": 1,
   824    "ip": "192.168.0.36",
   825    "port": 65400,
   826    "protocol": "udp",
   827    "candidateType": "host",
   828    "priority": 2122260223,
   829    "url": "example.com",
   830    "relayProtocol": "tcp",
   831    "deleted": true
   832  }
   833  `
   834  	remoteIceCandidateStats := ICECandidateStats{
   835  		Timestamp:     1689668364374.181,
   836  		Type:          StatsTypeRemoteCandidate,
   837  		ID:            "IGPGeswsH",
   838  		TransportID:   "T01",
   839  		IP:            "10.213.237.226",
   840  		Port:          50618,
   841  		Protocol:      "udp",
   842  		CandidateType: ICECandidateTypeHost,
   843  		Priority:      2122194687,
   844  		URL:           "example.com",
   845  		RelayProtocol: "tcp",
   846  		Deleted:       true,
   847  	}
   848  	remoteIceCandidateStatsJSON := `
   849  {
   850    "timestamp": 1689668364374.181,
   851    "type": "remote-candidate",
   852    "id": "IGPGeswsH",
   853    "transportId": "T01",
   854    "ip": "10.213.237.226",
   855    "port": 50618,
   856    "protocol": "udp",
   857    "candidateType": "host",
   858    "priority": 2122194687,
   859    "url": "example.com",
   860    "relayProtocol": "tcp",
   861    "deleted": true
   862  }
   863  `
   864  	certificateStats := CertificateStats{
   865  		Timestamp:            1689668364374.479,
   866  		Type:                 StatsTypeCertificate,
   867  		ID:                   "CF23:AB:FA:0B:0E:DF:12:34:D3:6C:EA:83:43:BD:79:39:87:39:11:49:41:8A:63:0E:17:B1:3F:94:FA:E3:62:20",
   868  		Fingerprint:          "23:AB:FA:0B:0E:DF:12:34:D3:6C:EA:83:43:BD:79:39:87:39:11:49:41:8A:63:0E:17:B1:3F:94:FA:E3:62:20",
   869  		FingerprintAlgorithm: "sha-256",
   870  		Base64Certificate:    "MIIBFjCBvKADAgECAggAwlrxojpmgTAKBggqhkjOPQQDAjARMQ8wDQYDVQQDDAZXZWJSVEMwHhcNMjMwNzE3MDgxODU2WhcNMjMwODE3MDgxODU2WjARMQ8wDQYDVQQDDAZXZWJSVEMwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARKETeS9qNGe3ltwp+q2KgsYWsJLFCJGap4L2aa862sPijHeuzLgO2bju/mosJN0Li7mXhuKBOsCkCMU7vZHVVVMAoGCCqGSM49BAMCA0kAMEYCIQDXyuyMMrgzd+w3c4h3vPn9AzLcf9CHVHRGYyy5ReI/hgIhALkXfaZ96TQRf5FI2mBJJUX9O/q4Poe3wNZxxWeDcYN+",
   871  		IssuerCertificateID:  "CF62:AF:88:F7:F3:0F:D6:C4:93:91:1E:AD:52:F0:A4:12:04:F9:48:E7:06:16:BA:A3:86:26:8F:1E:38:1C:48:49",
   872  	}
   873  	certificateStatsJSON := `
   874  {
   875    "timestamp": 1689668364374.479,
   876    "type": "certificate",
   877    "id": "CF23:AB:FA:0B:0E:DF:12:34:D3:6C:EA:83:43:BD:79:39:87:39:11:49:41:8A:63:0E:17:B1:3F:94:FA:E3:62:20",
   878    "fingerprint": "23:AB:FA:0B:0E:DF:12:34:D3:6C:EA:83:43:BD:79:39:87:39:11:49:41:8A:63:0E:17:B1:3F:94:FA:E3:62:20",
   879    "fingerprintAlgorithm": "sha-256",
   880    "base64Certificate": "MIIBFjCBvKADAgECAggAwlrxojpmgTAKBggqhkjOPQQDAjARMQ8wDQYDVQQDDAZXZWJSVEMwHhcNMjMwNzE3MDgxODU2WhcNMjMwODE3MDgxODU2WjARMQ8wDQYDVQQDDAZXZWJSVEMwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARKETeS9qNGe3ltwp+q2KgsYWsJLFCJGap4L2aa862sPijHeuzLgO2bju/mosJN0Li7mXhuKBOsCkCMU7vZHVVVMAoGCCqGSM49BAMCA0kAMEYCIQDXyuyMMrgzd+w3c4h3vPn9AzLcf9CHVHRGYyy5ReI/hgIhALkXfaZ96TQRf5FI2mBJJUX9O/q4Poe3wNZxxWeDcYN+",
   881    "issuerCertificateId": "CF62:AF:88:F7:F3:0F:D6:C4:93:91:1E:AD:52:F0:A4:12:04:F9:48:E7:06:16:BA:A3:86:26:8F:1E:38:1C:48:49"
   882  }
   883  `
   884  
   885  	return []statSample{
   886  		{
   887  			name:  "codec_stats",
   888  			stats: codecStats,
   889  			json:  codecStatsJSON,
   890  		},
   891  		{
   892  			name:  "inbound_rtp_stream_stats",
   893  			stats: inboundRTPStreamStats,
   894  			json:  inboundRTPStreamStatsJSON,
   895  		},
   896  		{
   897  			name:  "outbound_rtp_stream_stats",
   898  			stats: outboundRTPStreamStats,
   899  			json:  outboundRTPStreamStatsJSON,
   900  		},
   901  		{
   902  			name:  "remote_inbound_rtp_stream_stats",
   903  			stats: remoteInboundRTPStreamStats,
   904  			json:  remoteInboundRTPStreamStatsJSON,
   905  		},
   906  		{
   907  			name:  "remote_outbound_rtp_stream_stats",
   908  			stats: remoteOutboundRTPStreamStats,
   909  			json:  remoteOutboundRTPStreamStatsJSON,
   910  		},
   911  		{
   912  			name:  "rtp_contributing_source_stats",
   913  			stats: csrcStats,
   914  			json:  csrcStatsJSON,
   915  		},
   916  		{
   917  			name:  "audio_source_stats",
   918  			stats: audioSourceStats,
   919  			json:  audioSourceStatsJSON,
   920  		},
   921  		{
   922  			name:  "video_source_stats",
   923  			stats: videoSourceStats,
   924  			json:  videoSourceStatsJSON,
   925  		},
   926  		{
   927  			name:  "audio_playout_stats",
   928  			stats: audioPlayoutStats,
   929  			json:  audioPlayoutStatsJSON,
   930  		},
   931  		{
   932  			name:  "peer_connection_stats",
   933  			stats: peerConnectionStats,
   934  			json:  peerConnectionStatsJSON,
   935  		},
   936  		{
   937  			name:  "data_channel_stats",
   938  			stats: dataChannelStats,
   939  			json:  dataChannelStatsJSON,
   940  		},
   941  		{
   942  			name:  "media_stream_stats",
   943  			stats: streamStats,
   944  			json:  streamStatsJSON,
   945  		},
   946  		{
   947  			name:  "sender_video_track_stats",
   948  			stats: senderVideoTrackAttachmentStats,
   949  			json:  senderVideoTrackAttachmentStatsJSON,
   950  		},
   951  		{
   952  			name:  "sender_audio_track_stats",
   953  			stats: senderAudioTrackAttachmentStats,
   954  			json:  senderAudioTrackAttachmentStatsJSON,
   955  		},
   956  		{
   957  			name:  "receiver_video_track_stats",
   958  			stats: videoSenderStats,
   959  			json:  videoSenderStatsJSON,
   960  		},
   961  		{
   962  			name:  "receiver_audio_track_stats",
   963  			stats: audioSenderStats,
   964  			json:  audioSenderStatsJSON,
   965  		},
   966  		{
   967  			name:  "receiver_video_track_stats",
   968  			stats: videoReceiverStats,
   969  			json:  videoReceiverStatsJSON,
   970  		},
   971  		{
   972  			name:  "receiver_audio_track_stats",
   973  			stats: audioReceiverStats,
   974  			json:  audioReceiverStatsJSON,
   975  		},
   976  		{
   977  			name:  "transport_stats",
   978  			stats: transportStats,
   979  			json:  transportStatsJSON,
   980  		},
   981  		{
   982  			name:  "ice_candidate_pair_stats",
   983  			stats: iceCandidatePairStats,
   984  			json:  iceCandidatePairStatsJSON,
   985  		},
   986  		{
   987  			name:  "local_ice_candidate_stats",
   988  			stats: localIceCandidateStats,
   989  			json:  localIceCandidateStatsJSON,
   990  		},
   991  		{
   992  			name:  "remote_ice_candidate_stats",
   993  			stats: remoteIceCandidateStats,
   994  			json:  remoteIceCandidateStatsJSON,
   995  		},
   996  		{
   997  			name:  "certificate_stats",
   998  			stats: certificateStats,
   999  			json:  certificateStatsJSON,
  1000  		},
  1001  	}
  1002  }
  1003  
  1004  func TestStatsMarshal(t *testing.T) {
  1005  	for _, test := range getStatsSamples() {
  1006  		t.Run(test.name+"_marshal", func(t *testing.T) {
  1007  			actualJSON, err := json.Marshal(test.stats)
  1008  			require.NoError(t, err)
  1009  
  1010  			assert.JSONEq(t, test.json, string(actualJSON))
  1011  		})
  1012  	}
  1013  }
  1014  
  1015  func TestStatsUnmarshal(t *testing.T) {
  1016  	for _, test := range getStatsSamples() {
  1017  		t.Run(test.name+"_unmarshal", func(t *testing.T) {
  1018  			actualStats, err := UnmarshalStatsJSON([]byte(test.json))
  1019  			require.NoError(t, err)
  1020  
  1021  			assert.Equal(t, test.stats, actualStats)
  1022  		})
  1023  	}
  1024  }
  1025  
  1026  func waitWithTimeout(t *testing.T, wg *sync.WaitGroup) {
  1027  	// Wait for all of the event handlers to be triggered.
  1028  	done := make(chan struct{})
  1029  	go func() {
  1030  		wg.Wait()
  1031  		done <- struct{}{}
  1032  	}()
  1033  	timeout := time.After(5 * time.Second)
  1034  	select {
  1035  	case <-done:
  1036  		break
  1037  	case <-timeout:
  1038  		t.Fatal("timed out waiting for waitgroup")
  1039  	}
  1040  }
  1041  
  1042  func getConnectionStats(t *testing.T, report StatsReport, pc *PeerConnection) PeerConnectionStats {
  1043  	stats, ok := report.GetConnectionStats(pc)
  1044  	assert.True(t, ok)
  1045  	assert.Equal(t, stats.Type, StatsTypePeerConnection)
  1046  	return stats
  1047  }
  1048  
  1049  func getDataChannelStats(t *testing.T, report StatsReport, dc *DataChannel) DataChannelStats {
  1050  	stats, ok := report.GetDataChannelStats(dc)
  1051  	assert.True(t, ok)
  1052  	assert.Equal(t, stats.Type, StatsTypeDataChannel)
  1053  	return stats
  1054  }
  1055  
  1056  func getCodecStats(t *testing.T, report StatsReport, c *RTPCodecParameters) CodecStats {
  1057  	stats, ok := report.GetCodecStats(c)
  1058  	assert.True(t, ok)
  1059  	assert.Equal(t, stats.Type, StatsTypeCodec)
  1060  	return stats
  1061  }
  1062  
  1063  func getTransportStats(t *testing.T, report StatsReport, statsID string) TransportStats {
  1064  	stats, ok := report[statsID]
  1065  	assert.True(t, ok)
  1066  	transportStats, ok := stats.(TransportStats)
  1067  	assert.True(t, ok)
  1068  	assert.Equal(t, transportStats.Type, StatsTypeTransport)
  1069  	return transportStats
  1070  }
  1071  
  1072  func getSctpTransportStats(t *testing.T, report StatsReport) SCTPTransportStats {
  1073  	stats, ok := report["sctpTransport"]
  1074  	assert.True(t, ok)
  1075  	transportStats, ok := stats.(SCTPTransportStats)
  1076  	assert.True(t, ok)
  1077  	assert.Equal(t, transportStats.Type, StatsTypeSCTPTransport)
  1078  	return transportStats
  1079  }
  1080  
  1081  func getCertificateStats(t *testing.T, report StatsReport, certificate *Certificate) CertificateStats {
  1082  	certificateStats, ok := report.GetCertificateStats(certificate)
  1083  	assert.True(t, ok)
  1084  	assert.Equal(t, certificateStats.Type, StatsTypeCertificate)
  1085  	return certificateStats
  1086  }
  1087  
  1088  func findLocalCandidateStats(report StatsReport) []ICECandidateStats {
  1089  	result := []ICECandidateStats{}
  1090  	for _, s := range report {
  1091  		stats, ok := s.(ICECandidateStats)
  1092  		if ok && stats.Type == StatsTypeLocalCandidate {
  1093  			result = append(result, stats)
  1094  		}
  1095  	}
  1096  	return result
  1097  }
  1098  
  1099  func findRemoteCandidateStats(report StatsReport) []ICECandidateStats {
  1100  	result := []ICECandidateStats{}
  1101  	for _, s := range report {
  1102  		stats, ok := s.(ICECandidateStats)
  1103  		if ok && stats.Type == StatsTypeRemoteCandidate {
  1104  			result = append(result, stats)
  1105  		}
  1106  	}
  1107  	return result
  1108  }
  1109  
  1110  func findCandidatePairStats(t *testing.T, report StatsReport) []ICECandidatePairStats {
  1111  	result := []ICECandidatePairStats{}
  1112  	for _, s := range report {
  1113  		stats, ok := s.(ICECandidatePairStats)
  1114  		if ok {
  1115  			assert.Equal(t, StatsTypeCandidatePair, stats.Type)
  1116  			result = append(result, stats)
  1117  		}
  1118  	}
  1119  	return result
  1120  }
  1121  
  1122  func signalPairForStats(pcOffer *PeerConnection, pcAnswer *PeerConnection) error {
  1123  	offerChan := make(chan SessionDescription)
  1124  	pcOffer.OnICECandidate(func(candidate *ICECandidate) {
  1125  		if candidate == nil {
  1126  			offerChan <- *pcOffer.PendingLocalDescription()
  1127  		}
  1128  	})
  1129  
  1130  	offer, err := pcOffer.CreateOffer(nil)
  1131  	if err != nil {
  1132  		return err
  1133  	}
  1134  	if err := pcOffer.SetLocalDescription(offer); err != nil {
  1135  		return err
  1136  	}
  1137  
  1138  	timeout := time.After(3 * time.Second)
  1139  	select {
  1140  	case <-timeout:
  1141  		return errReceiveOfferTimeout
  1142  	case offer := <-offerChan:
  1143  		if err := pcAnswer.SetRemoteDescription(offer); err != nil {
  1144  			return err
  1145  		}
  1146  
  1147  		answer, err := pcAnswer.CreateAnswer(nil)
  1148  		if err != nil {
  1149  			return err
  1150  		}
  1151  
  1152  		if err = pcAnswer.SetLocalDescription(answer); err != nil {
  1153  			return err
  1154  		}
  1155  
  1156  		err = pcOffer.SetRemoteDescription(answer)
  1157  		if err != nil {
  1158  			return err
  1159  		}
  1160  		return nil
  1161  	}
  1162  }
  1163  
  1164  func TestStatsConvertState(t *testing.T) {
  1165  	testCases := []struct {
  1166  		ice   ice.CandidatePairState
  1167  		stats StatsICECandidatePairState
  1168  	}{
  1169  		{
  1170  			ice.CandidatePairStateWaiting,
  1171  			StatsICECandidatePairStateWaiting,
  1172  		},
  1173  		{
  1174  			ice.CandidatePairStateInProgress,
  1175  			StatsICECandidatePairStateInProgress,
  1176  		},
  1177  		{
  1178  			ice.CandidatePairStateFailed,
  1179  			StatsICECandidatePairStateFailed,
  1180  		},
  1181  		{
  1182  			ice.CandidatePairStateSucceeded,
  1183  			StatsICECandidatePairStateSucceeded,
  1184  		},
  1185  	}
  1186  
  1187  	s, err := toStatsICECandidatePairState(ice.CandidatePairState(42))
  1188  
  1189  	assert.Error(t, err)
  1190  	assert.Equal(t,
  1191  		StatsICECandidatePairState("Unknown"),
  1192  		s)
  1193  	for i, testCase := range testCases {
  1194  		s, err := toStatsICECandidatePairState(testCase.ice)
  1195  		assert.NoError(t, err)
  1196  		assert.Equal(t,
  1197  			testCase.stats,
  1198  			s,
  1199  			"testCase: %d %v", i, testCase,
  1200  		)
  1201  	}
  1202  }
  1203  
  1204  func TestPeerConnection_GetStats(t *testing.T) {
  1205  	offerPC, answerPC, err := newPair()
  1206  	assert.NoError(t, err)
  1207  
  1208  	track1, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion1")
  1209  	require.NoError(t, err)
  1210  
  1211  	_, err = offerPC.AddTrack(track1)
  1212  	require.NoError(t, err)
  1213  
  1214  	baseLineReportPCOffer := offerPC.GetStats()
  1215  	baseLineReportPCAnswer := answerPC.GetStats()
  1216  
  1217  	connStatsOffer := getConnectionStats(t, baseLineReportPCOffer, offerPC)
  1218  	connStatsAnswer := getConnectionStats(t, baseLineReportPCAnswer, answerPC)
  1219  
  1220  	for _, connStats := range []PeerConnectionStats{connStatsOffer, connStatsAnswer} {
  1221  		assert.Equal(t, uint32(0), connStats.DataChannelsOpened)
  1222  		assert.Equal(t, uint32(0), connStats.DataChannelsClosed)
  1223  		assert.Equal(t, uint32(0), connStats.DataChannelsRequested)
  1224  		assert.Equal(t, uint32(0), connStats.DataChannelsAccepted)
  1225  	}
  1226  
  1227  	// Create a DC, open it and send a message
  1228  	offerDC, err := offerPC.CreateDataChannel("offerDC", nil)
  1229  	assert.NoError(t, err)
  1230  
  1231  	msg := []byte("a classic test message")
  1232  	offerDC.OnOpen(func() {
  1233  		assert.NoError(t, offerDC.Send(msg))
  1234  	})
  1235  
  1236  	dcWait := sync.WaitGroup{}
  1237  	dcWait.Add(1)
  1238  
  1239  	answerDCChan := make(chan *DataChannel)
  1240  	answerPC.OnDataChannel(func(d *DataChannel) {
  1241  		d.OnOpen(func() {
  1242  			answerDCChan <- d
  1243  		})
  1244  		d.OnMessage(func(m DataChannelMessage) {
  1245  			dcWait.Done()
  1246  		})
  1247  	})
  1248  
  1249  	assert.NoError(t, signalPairForStats(offerPC, answerPC))
  1250  	waitWithTimeout(t, &dcWait)
  1251  
  1252  	answerDC := <-answerDCChan
  1253  
  1254  	reportPCOffer := offerPC.GetStats()
  1255  	reportPCAnswer := answerPC.GetStats()
  1256  
  1257  	connStatsOffer = getConnectionStats(t, reportPCOffer, offerPC)
  1258  	assert.Equal(t, uint32(1), connStatsOffer.DataChannelsOpened)
  1259  	assert.Equal(t, uint32(0), connStatsOffer.DataChannelsClosed)
  1260  	assert.Equal(t, uint32(1), connStatsOffer.DataChannelsRequested)
  1261  	assert.Equal(t, uint32(0), connStatsOffer.DataChannelsAccepted)
  1262  	dcStatsOffer := getDataChannelStats(t, reportPCOffer, offerDC)
  1263  	assert.Equal(t, DataChannelStateOpen, dcStatsOffer.State)
  1264  	assert.Equal(t, uint32(1), dcStatsOffer.MessagesSent)
  1265  	assert.Equal(t, uint64(len(msg)), dcStatsOffer.BytesSent)
  1266  	assert.NotEmpty(t, findLocalCandidateStats(reportPCOffer))
  1267  	assert.NotEmpty(t, findRemoteCandidateStats(reportPCOffer))
  1268  	assert.NotEmpty(t, findCandidatePairStats(t, reportPCOffer))
  1269  
  1270  	connStatsAnswer = getConnectionStats(t, reportPCAnswer, answerPC)
  1271  	assert.Equal(t, uint32(1), connStatsAnswer.DataChannelsOpened)
  1272  	assert.Equal(t, uint32(0), connStatsAnswer.DataChannelsClosed)
  1273  	assert.Equal(t, uint32(0), connStatsAnswer.DataChannelsRequested)
  1274  	assert.Equal(t, uint32(1), connStatsAnswer.DataChannelsAccepted)
  1275  	dcStatsAnswer := getDataChannelStats(t, reportPCAnswer, answerDC)
  1276  	assert.Equal(t, DataChannelStateOpen, dcStatsAnswer.State)
  1277  	assert.Equal(t, uint32(1), dcStatsAnswer.MessagesReceived)
  1278  	assert.Equal(t, uint64(len(msg)), dcStatsAnswer.BytesReceived)
  1279  	assert.NotEmpty(t, findLocalCandidateStats(reportPCAnswer))
  1280  	assert.NotEmpty(t, findRemoteCandidateStats(reportPCAnswer))
  1281  	assert.NotEmpty(t, findCandidatePairStats(t, reportPCAnswer))
  1282  	assert.NoError(t, err)
  1283  	for i := range offerPC.api.mediaEngine.videoCodecs {
  1284  		codecStat := getCodecStats(t, reportPCOffer, &(offerPC.api.mediaEngine.videoCodecs[i]))
  1285  		assert.NotEmpty(t, codecStat)
  1286  	}
  1287  	for i := range offerPC.api.mediaEngine.audioCodecs {
  1288  		codecStat := getCodecStats(t, reportPCOffer, &(offerPC.api.mediaEngine.audioCodecs[i]))
  1289  		assert.NotEmpty(t, codecStat)
  1290  	}
  1291  
  1292  	// Close answer DC now
  1293  	dcWait = sync.WaitGroup{}
  1294  	dcWait.Add(1)
  1295  	offerDC.OnClose(func() {
  1296  		dcWait.Done()
  1297  	})
  1298  	assert.NoError(t, answerDC.Close())
  1299  	waitWithTimeout(t, &dcWait)
  1300  	time.Sleep(10 * time.Millisecond)
  1301  
  1302  	reportPCOffer = offerPC.GetStats()
  1303  	reportPCAnswer = answerPC.GetStats()
  1304  
  1305  	connStatsOffer = getConnectionStats(t, reportPCOffer, offerPC)
  1306  	assert.Equal(t, uint32(1), connStatsOffer.DataChannelsOpened)
  1307  	assert.Equal(t, uint32(1), connStatsOffer.DataChannelsClosed)
  1308  	assert.Equal(t, uint32(1), connStatsOffer.DataChannelsRequested)
  1309  	assert.Equal(t, uint32(0), connStatsOffer.DataChannelsAccepted)
  1310  	dcStatsOffer = getDataChannelStats(t, reportPCOffer, offerDC)
  1311  	assert.Equal(t, DataChannelStateClosed, dcStatsOffer.State)
  1312  
  1313  	connStatsAnswer = getConnectionStats(t, reportPCAnswer, answerPC)
  1314  	assert.Equal(t, uint32(1), connStatsAnswer.DataChannelsOpened)
  1315  	assert.Equal(t, uint32(1), connStatsAnswer.DataChannelsClosed)
  1316  	assert.Equal(t, uint32(0), connStatsAnswer.DataChannelsRequested)
  1317  	assert.Equal(t, uint32(1), connStatsAnswer.DataChannelsAccepted)
  1318  	dcStatsAnswer = getDataChannelStats(t, reportPCAnswer, answerDC)
  1319  	assert.Equal(t, DataChannelStateClosed, dcStatsAnswer.State)
  1320  
  1321  	answerICETransportStats := getTransportStats(t, reportPCAnswer, "iceTransport")
  1322  	offerICETransportStats := getTransportStats(t, reportPCOffer, "iceTransport")
  1323  	assert.GreaterOrEqual(t, offerICETransportStats.BytesSent, answerICETransportStats.BytesReceived)
  1324  	assert.GreaterOrEqual(t, answerICETransportStats.BytesSent, offerICETransportStats.BytesReceived)
  1325  
  1326  	answerSCTPTransportStats := getSctpTransportStats(t, reportPCAnswer)
  1327  	offerSCTPTransportStats := getSctpTransportStats(t, reportPCOffer)
  1328  	assert.GreaterOrEqual(t, offerSCTPTransportStats.BytesSent, answerSCTPTransportStats.BytesReceived)
  1329  	assert.GreaterOrEqual(t, answerSCTPTransportStats.BytesSent, offerSCTPTransportStats.BytesReceived)
  1330  
  1331  	certificates := offerPC.configuration.Certificates
  1332  
  1333  	for i := range certificates {
  1334  		assert.NotEmpty(t, getCertificateStats(t, reportPCOffer, &certificates[i]))
  1335  	}
  1336  
  1337  	closePairNow(t, offerPC, answerPC)
  1338  }
  1339  
  1340  func TestPeerConnection_GetStats_Closed(t *testing.T) {
  1341  	pc, err := NewPeerConnection(Configuration{})
  1342  	assert.NoError(t, err)
  1343  
  1344  	assert.NoError(t, pc.Close())
  1345  
  1346  	pc.GetStats()
  1347  }