-
Notifications
You must be signed in to change notification settings - Fork 0
/
RtcpData.cs
942 lines (759 loc) · 29 KB
/
RtcpData.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
using System;
using System.Net;
using System.Diagnostics;
using System.Collections;
using System.Reflection;
namespace Vs.Provider.RtpLib.MSR.LST.Net.Rtp
{
internal interface IRtcpData
{
void WriteDataToBuffer(BufferChunk buffer);
void ReadDataFromBuffer(BufferChunk buffer);
int Size {get;}
}
/// <summary>
/// Structure containing a SenderReport, See RFC 3550 for details of the data it contains
/// </summary>
public class SenderReport : IRtcpData
{
#region Statics
// ntpTS(8), rtpTS(4), packets(4), octets(4)
public const int SIZE = 20;
#endregion Statics
#region Members
private ulong ntpTS; // 8 bytes
private uint rtpTS;
private uint packets;
private uint octets;
#endregion Members
#region Methods
public ulong Time
{
get{return ntpTS;}
set{ntpTS = value;}
}
public uint TimeStamp
{
get{return rtpTS;}
set{rtpTS = value;}
}
public uint PacketCount
{
get{return packets;}
set{packets = value;}
}
public uint BytesSent
{
get{return octets;}
set{octets = value;}
}
public override string ToString()
{
return "SenderReport {" +
" Time := " + Time +
" TimeStamp := " + TimeStamp +
" PacketCount := " + PacketCount +
" BytesSent := " + BytesSent +
" }";
}
public void ReadDataFromBuffer(BufferChunk buffer)
{
Time = buffer.NextUInt64();
TimeStamp = buffer.NextUInt32();
PacketCount = buffer.NextUInt32();
BytesSent = buffer.NextUInt32();
}
public void WriteDataToBuffer(BufferChunk buffer)
{
buffer += Time;
buffer += TimeStamp;
buffer += PacketCount;
buffer += BytesSent;
}
public int Size
{
get{return SIZE;}
}
#endregion Methods
}
/// <summary>
/// Structure containing a ReceiverReport, see RFC 3550 for details on the data it contains
/// </summary>
public class ReceiverReport : IRtcpData
{
#region Statics
// ssrc(4) + fractionLost(1) + packetsLost(3) + seq(4) + jitter(4) + lsr(4) + dlsr(4)
public const int SIZE = 24;
private const int MIN_PACKETS_LOST = -8388607; // 0xFF800001
private const int MAX_PACKETS_LOST = 8388607; // 0x007FFFFF
#endregion Statics
#region Members
private uint ssrc;
private byte fractionLost;
private int packetsLost;
private uint seq;
private uint jitter;
private uint lsr;
private uint dlsr;
#endregion Members
#region Methods
#region Public
public uint SSRC
{
get{return ssrc;}
set{ssrc = value;}
}
public byte FractionLost
{
get{return fractionLost;}
set{fractionLost = value;}
}
/// <summary>
/// A 24 bit signed integer - we handle conversion between 24 and 32 bits
///
/// According to Colin's book, pg 102 - "The field saturates at the maximum positive value
/// of 0x7FFFFF if more packets than that are lost during the session"
/// </summary>
public int PacketsLost
{
get{return packetsLost;}
set
{
// Make sure the 32 bit value fits within the 24 bit container
if(value < MIN_PACKETS_LOST)
{
value = MIN_PACKETS_LOST;
}
else if(value > MAX_PACKETS_LOST)
{
value = MAX_PACKETS_LOST;
}
packetsLost = value;
}
}
public uint ExtendedHighestSequence
{
get{return seq;}
set{seq = value;}
}
public uint Jitter
{
get{return jitter;}
set{jitter = value;}
}
public uint LastSenderReport
{
get{return lsr;}
set{lsr = value;}
}
public uint DelaySinceLastSenderReport
{
get{return dlsr;}
set{dlsr = value;}
}
public override string ToString()
{
return "ReceiverReport {" +
" SSRC := " + SSRC +
" FractionLost := " + FractionLost +
" PacketsLost := " + PacketsLost +
" SequenceReceiver := " + ExtendedHighestSequence +
" Jitter := " + Jitter +
" LastSenderReport := " + LastSenderReport +
" DelaySinceLastSenderReport := " + DelaySinceLastSenderReport +
" }";
}
public void ReadDataFromBuffer(BufferChunk buffer)
{
SSRC = buffer.NextUInt32();
FractionLost = buffer.NextByte();
PacketsLost = ThreeBytesToInt(buffer.NextBufferChunk(3));
ExtendedHighestSequence = buffer.NextUInt32();
Jitter = buffer.NextUInt32();
LastSenderReport = buffer.NextUInt32();
DelaySinceLastSenderReport = buffer.NextUInt32();
}
public void WriteDataToBuffer(BufferChunk buffer)
{
buffer += SSRC;
buffer += FractionLost;
buffer += IntToThreeBytes(PacketsLost);
buffer += ExtendedHighestSequence;
buffer += Jitter;
buffer += LastSenderReport;
buffer += DelaySinceLastSenderReport;
}
public int Size
{
get{return SIZE;}
}
#endregion Public
#region Private
/// <summary>
/// Converts the least significant 3 bytes of an integer into a byte[]
/// </summary>
/// <param name="data">Must be in big-endian format!</param>
/// <returns></returns>
private static BufferChunk IntToThreeBytes(int data)
{
BufferChunk ret = new BufferChunk(3);
// We don't want the most significant byte, which due to the big-endianness conversion
// now occupies byte 1 (of our little endian architecture)
ret += (byte)(data >> 1 * 8);
ret += (byte)(data >> 2 * 8);
ret += (byte)(data >> 3 * 8);
return ret;
}
/// <summary>
/// Converts 3 bytes into a big-endian integer
/// </summary>
/// <param name="data"></param>
/// <returns>Big-endian formatted integer</returns>
private static int ThreeBytesToInt(BufferChunk data)
{
int ret = 0;
ret += (data.NextByte() << 1 * 8);
ret += (data.NextByte() << 2 * 8);
ret += (data.NextByte() << 3 * 8);
// If the 8th bit of the second byte (what will be the 24th bit)
// is turned on, the value is signed, so sign extend our integer
if(((byte)(ret >> 1 * 8) & 0x80) == 0x80)
{
ret += 0xFF;
}
return ret;
}
#endregion Private
#endregion Methods
}
/// <summary>
/// SdesData structure, used extensively amoung Rtcp & Rtp objects to describe the common properties associated with the Rtp data / Rtcp Participant.
/// See RFC 3550 for definitions on the data it contains.
/// </summary>
public class SdesData : IRtcpData
{
#region Statics
private const int MAX_PROPERTY_LENGTH = 255;
private const int MAX_PRIV_PROPERTY_LENGTH = 254;
private static System.Text.UTF8Encoding utf8 = new System.Text.UTF8Encoding();
#endregion Statics
#region Members
/// <summary>
/// These properties are stored as byte[] instead of string because they will be sent
/// across the wire in byte[] format more often than they will be updated. It is also
/// helpful to know how many bytes of storage (size) a particular instance requires.
/// And they can be manipulated more efficiently in loops.
///
/// We use the SDESType as the index. Unfortunately, the SDESType starts with End = 0. In
/// order to avoid a bunch of -1 math, we just allocate the extra storage (4 bytes) at 0
/// and never use it.
/// </summary>
private byte[][] data = new byte[(int)Rtcp.SDESType.NOTE + 1][];
/// <summary>
/// A place to store private SDES extensions
///
/// According to a close reading of RFC 3550 6.5.8 and emails exchanged with Colin Perkins
/// Private extensions are to be transmitted as UTF8 strings to remain consistent with the
/// rest of the SDES items, even though that may not be the most efficient storage.
///
/// It is also desirable to not risk breaking other implementations by packing the data
/// efficiently as bytes (1 == True, and 0 == False) and producing an invalid UTF8 string
/// </summary>
private SdesPrivateExtensionHashtable privs = new SdesPrivateExtensionHashtable();
#endregion Members
#region Constructors
/// <summary>
/// Most common constructor, setting the most relevant data
/// </summary>
/// <param name="cName">Canonical name - Unique network identifier for this participant</param>
/// <param name="name">Friendly name for this participant</param>
public SdesData(string cName, string name)
{
SetCName(cName);
Name = name;
}
/// <summary>
/// Constructs an SdesData instance by reading its properties from another instance
/// </summary>
/// <param name="data"></param>
public SdesData(SdesData sdes)
{
// Skip the first index - see comments for member variable 'data'
for(int i = 1; i < sdes.data.Length; i++)
{
byte[] data = sdes.data[i];
if(data != null)
{
byte[] copy = new byte[data.Length];
data.CopyTo(copy, 0);
data = copy;
}
this.data[i] = data;
}
foreach(DictionaryEntry de in sdes.privs)
{
SetPrivateExtension((byte[])de.Key, (byte[])de.Value);
}
}
/// <summary>
/// Constructs an SdesData instance by reading its properties from a BufferChunk
/// </summary>
/// <param name="buffer"></param>
public SdesData(BufferChunk buffer)
{
ReadDataFromBuffer(buffer);
}
#endregion Constructors
#region Methods
#region Public
/// <summary>
/// Serializes this object into the provided buffer
/// </summary>
/// <param name="buffer"></param>
public void WriteDataToBuffer(BufferChunk buffer)
{
// TODO - we don't scale well, because we write every property, every time
// Colin's book recommends Name 7/8 times, and alternating the others (1/8)
// CName is a given of course JVE
// Well-known properties
for(int id = (int)Rtcp.SDESType.CNAME; id <= (int)Rtcp.SDESType.NOTE; id++)
{
WritePropertyToBuffer((Rtcp.SDESType)id, data[id], buffer);
}
// Write private properties
foreach (DictionaryEntry de in privs)
{
WritePrivatePropertyToBuffer((byte[])de.Key, (byte[])de.Value, buffer);
}
// Indicate the list is finished
buffer += (byte)Rtcp.SDESType.END;
}
/// <summary>
/// Deserializes the provided buffer into this object
/// </summary>
/// <param name="buffer"></param>
public void ReadDataFromBuffer(BufferChunk buffer)
{
Rtcp.SDESType type;
while((type = (Rtcp.SDESType)buffer.NextByte()) != Rtcp.SDESType.END)
{
switch(type)
{
case Rtcp.SDESType.CNAME:
case Rtcp.SDESType.EMAIL:
case Rtcp.SDESType.LOC:
case Rtcp.SDESType.NAME:
case Rtcp.SDESType.NOTE:
case Rtcp.SDESType.PHONE:
case Rtcp.SDESType.TOOL:
ReadPropertyFromBuffer((int)type, buffer);
break;
case Rtcp.SDESType.PRIV:
ReadPrivatePropertyFromBuffer(buffer);
break;
default:
throw new ApplicationException("Unexpected SDES type: " + type);
}
}
}
/// <summary>
/// Return the size in bytes of this SdesData instance
/// Used to find out how much buffer space it will take to serialize this instance
/// </summary>
public int Size
{
get
{
int ret = 0;
// Well-known properties
for(int id = (int)Rtcp.SDESType.CNAME; id <= (int)Rtcp.SDESType.NOTE; id++)
{
if(data[id] != null) // We don't write null data
{
ret += data[id].Length + 2; // Type + Length byte
}
}
// Private properties
foreach(DictionaryEntry de in privs)
{
ret += 3; // Type + Length byte + Prefix Length byte
// Hashtables don't allow a null key, so no need to check for null
ret += ((byte[])de.Key).Length;
// Value can be null though
if(de.Value != null)
{
ret += ((byte[])de.Value).Length;
}
}
// +1 for the SDESType.END marker
return ++ret;
}
}
/// <summary>
/// Canonical name - Unique network identifier for this participant
/// Usually an email address, but a domain name domain\user or GUID would work as well
/// </summary>
public string CName
{
get{return GetProperty(Rtcp.SDESType.CNAME);}
}
/// <summary>
/// Friendly name of the participant, e.g. "Dogbert"
/// </summary>
public string Name
{
get{return GetProperty(Rtcp.SDESType.NAME);}
set{SetProperty(value, Rtcp.SDESType.NAME);}
}
/// <summary>
/// Email address
/// </summary>
public string Email
{
get{return GetProperty(Rtcp.SDESType.EMAIL);}
set{SetProperty(value, Rtcp.SDESType.EMAIL);}
}
/// <summary>
/// Phone number
/// </summary>
public string Phone
{
get{return GetProperty(Rtcp.SDESType.PHONE);}
set{SetProperty(value, Rtcp.SDESType.PHONE);}
}
/// <summary>
/// Physical Location. AKA Microsoft Campus, 112/2379, Redmond, WA, USA, Third Rock from the Sun
/// </summary>
public string Location
{
get{return GetProperty(Rtcp.SDESType.LOC);}
set{SetProperty(value, Rtcp.SDESType.LOC);}
}
/// <summary>
/// Name and Version of tool sending streams, e.g. 'ConferenceXP v1.0'
/// </summary>
public string Tool
{
get{return GetProperty(Rtcp.SDESType.TOOL);}
}
/// <summary>
/// Whatever the user wants to communicate to others on a periodic basis
/// Similar to Instant Messenger status
/// This isn't real time chat :-)
/// </summary>
public string Note
{
get{return GetProperty(Rtcp.SDESType.NOTE);}
set{SetProperty(value, Rtcp.SDESType.NOTE);}
}
/// <summary>
/// Flag indicating whether you want the tool field to be set or not
/// </summary>
/// <param name="val"></param>
public void SetTool(bool val)
{
string tool = null;
if(val)
{
AssemblyName exe = Assembly.GetEntryAssembly().GetName();
AssemblyName rtp = Assembly.GetExecutingAssembly().GetName();
tool = exe.Name + " v" + exe.Version + ", ";
tool += rtp.Name + " v" + rtp.Version;
}
SetProperty(tool, Rtcp.SDESType.TOOL);
}
/// <summary>
/// Add a private extension to the SDES data
///
/// This method validates the data will fit in the 255 bytes (length = (1 Byte) = 255 max)
/// available when converted to UTF8 for transmission across the wire and then stores it
///
/// ---------------------------------------------------------------------------------------
/// Structure:
/// ---------------------------------------------------------------------------------------
/// 0 1 2 3
/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// | PRIV=8 | length | prefix length |prefix string...
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// ... | value string ...
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// </summary>
/// <param name="prefix">Name of the extension</param>
/// <param name="data">Value of the extension</param>
public void SetPrivateExtension(string prefix, string data)
{
// Prefix cannot be null, it is the hashtable key
if(prefix == null)
{
throw new ArgumentNullException("prefix");
}
byte[] prefixBytes;
lock(utf8)
{
prefixBytes = utf8.GetBytes(prefix);
}
// Data can be null though, if the prefix communicates enough data
byte[] dataBytes = null;
int dataLength = 0;
if(data != null)
{
lock(utf8)
{
dataBytes = utf8.GetBytes(data);
dataLength = dataBytes.Length;
}
}
// Check to see if it is too long
if (prefixBytes.Length + dataLength > MAX_PRIV_PROPERTY_LENGTH)
{
throw new ArgumentException(string.Format("A PRIV item's prefix + data can not exceed {0} UTF8 bytes",
MAX_PRIV_PROPERTY_LENGTH));
}
SetPrivateExtension(prefixBytes, dataBytes);
}
public string GetPrivateExtension(string prefix)
{
string data = null;
if(privs != null)
{
lock(utf8)
{
byte[] prefixBytes = utf8.GetBytes(prefix);
if(privs.ContainsKey(prefixBytes))
{
data = utf8.GetString(privs[prefixBytes]);
}
}
}
return data;
}
/// <summary>
/// Gets the private extensions for this stream in a Hashtable with (string, string) key/value pairs.
/// </summary>
public Hashtable GetPrivateExtensions()
{
Hashtable priExns = new Hashtable();
ICollection keys = privs.Keys;
foreach( byte[] key in keys )
{
byte[] val = privs[key];
priExns.Add( utf8.GetString(key), utf8.GetString(val) );
}
return priExns;
}
public override string ToString()
{
string str =
"SdesData { " +
"CName: " + CName + ", " +
"Name: " + Name + ", " +
"Email: " + Email + ", " +
"Phone: " + Phone + ", " +
"Location: " + Location + ", " +
"Tool: " + Tool + ", " +
"Note: " + Note;
if(privs != null)
{
foreach (DictionaryEntry de in privs)
{
string prefix, data = null;
lock(utf8)
{
prefix = utf8.GetString((byte[])de.Key);
if(de.Value != null)
{
data = utf8.GetString((byte[])de.Value);
}
}
str += ", " + prefix + ": " + data;
}
}
str += " }";
return str;
}
#endregion Public
#region Internal
/// <summary>
/// This method is called to update the local data from another SdesData
/// </summary>
/// <param name="data">what we want to update our data to</param>
/// <returns>true if the local data was updated, otherwise false</returns>
internal bool UpdateData(SdesData sdes)
{
bool ret = false;
// Well-known properties
// CName can never be updated, so start with Name
for(int id = (int)Rtcp.SDESType.NAME; id <= (int)Rtcp.SDESType.NOTE; id++)
{
if(!BufferChunk.Compare(data[id], sdes.data[id]))
{
data[id] = BufferChunk.Copy(sdes.data[id]);
ret = true;
}
}
// Write private properties
foreach(DictionaryEntry de in sdes.privs)
{
byte[] key = (byte[])de.Key;
byte[] data = (byte[])de.Value;
if(!privs.Contains(key) || !BufferChunk.Compare(data, privs[key]))
{
privs[BufferChunk.Copy(key)] = BufferChunk.Copy(data);
ret = true;
}
}
return ret;
}
#endregion Internal
#region Private
private void SetCName(string cName)
{
// CName must be something valid
if(cName == null || cName == string.Empty)
{
throw new ApplicationException("cName must be a valid string, preferrably something unique, like an email address");
}
SetProperty(cName, Rtcp.SDESType.CNAME);
}
/// <summary>
/// ---------------------------------------------------------------------------------------
/// Purpose:
/// ---------------------------------------------------------------------------------------
/// Make sure the data will fit in the 255 bytes (length == 1 byte == byte.MaxValue)
/// available to it when converted to UTF8 for transmission across the wire
///
/// ---------------------------------------------------------------------------------------
/// General structure of an SDES property:
/// ---------------------------------------------------------------------------------------
/// 0 1 2 3
/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// | SDES=N | length | data ...
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// </summary>
/// <param name="data"></param>
private void SetProperty(string data, Rtcp.SDESType type)
{
byte[] bytes = null;
if(data != null)
{
lock(utf8)
{
bytes = utf8.GetBytes(data);
}
// Check to see if it is too long
if (bytes.Length > MAX_PROPERTY_LENGTH)
{
throw new ArgumentException(string.Format("An SDES item's data can not exceed " +
"{0} UTF8 bytes: {1}, {2}",
MAX_PROPERTY_LENGTH, bytes.Length, data));
}
}
this.data[(int)type] = bytes;
}
private string GetProperty(Rtcp.SDESType type)
{
string ret = null;
if(data[(int)type] != null)
{
lock(utf8)
{
ret = utf8.GetString(data[(int)type]);
}
}
return ret;
}
private void SetPrivateExtension(byte[] prefix, byte[] data)
{
// If the collection does not exist, create it
if (privs == null)
{
privs = new SdesPrivateExtensionHashtable();
}
// Set the value (this will add if it does not exist)
privs[prefix] = data;
}
private void WritePropertyToBuffer(Rtcp.SDESType type, byte[] data, BufferChunk buffer)
{
if(data != null)
{
// Type
buffer += (byte)type;
// Length
buffer += (byte)data.Length;
// Data
if(data.Length != 0)
{
buffer += data;
}
}
}
private void ReadPropertyFromBuffer(int type, BufferChunk buffer)
{
int dataLength = buffer.NextByte();
if(dataLength != 0)
{
data[type] = (byte[])buffer.NextBufferChunk(dataLength);
}
else // Clear value
{
data[type] = null;
}
}
private void WritePrivatePropertyToBuffer(byte[] prefix, byte[] data, BufferChunk buffer)
{
int prefixLength = prefix.Length;
int dataLength = 0;
if(data != null)
{
dataLength = data.Length;
}
// This should have already been validated as the property was added/set
Debug.Assert(prefixLength + dataLength <= SdesData.MAX_PRIV_PROPERTY_LENGTH);
// Write data
buffer += (byte)Rtcp.SDESType.PRIV;
buffer += (byte)(prefixLength + dataLength + 1); // +1 = prefix length
buffer += (byte)prefixLength;
buffer += prefix;
if(data != null)
{
buffer += data;
}
}
private void ReadPrivatePropertyFromBuffer(BufferChunk buffer)
{
byte totalLength = buffer.NextByte();
byte prefixLength = buffer.NextByte();
int dataLength = totalLength - prefixLength - 1;
// The cast to byte[] does a copy
byte[] prefix = (byte[])buffer.NextBufferChunk(prefixLength);
byte[] data = null;
if(dataLength > 0)
{
// The cast to byte[] does a copy
data = (byte[])buffer.NextBufferChunk(dataLength);
}
privs[prefix] = data;
}
#endregion Private
#endregion Methods
}
internal class SdesReport
{
internal uint ssrc;
internal SdesData sdes;
internal SdesReport(uint ssrc, SdesData sdes)
{
this.ssrc = ssrc;
this.sdes = sdes;
}
internal int Size
{
get
{
return Rtp.SSRC_SIZE + sdes.Size;
}
}
}
}