RTP/RTCP – Realtime Transport/Control Protocol

Ponieważ ostatnio musiałem odświeżyc swoją wiedzę na temat RTP ponizej powtórka z przeszlosci. Poniższy post oparty jest bezpośrednio na dokumencie IETF RFC 3550 specyfikujacym bazowy protokol RTP.

RTP/RTCP są to protokóły przeznaczone do transmisji end2end sygnałów cyfrowych o charakterystyce ‘realtime’, takich jak dzwięk czy video. Został zaprojektowany w celu oddzielenia mechanizmów transmiscji danych i kontroli sesji. Z każdym strumieniem danych skojarzony jest oddzielny kanał RTP/RTCP zawierajaca po jednym porcie RTP i RTCP. RTP jest protokołem odpowiedzialnym za transmisje strumieni danych tak zwanych ‘RTP payload’. RTP samo w sobie nie zapewnia mechanizmów kontorli opoźnień czy stratności ale bazuje na wykorzystywanym protokole transportowym ktorym najcześciej jest to UDP. RTCP skolei jest odpowiedzialne za kontrole jakości swiadczonych uslug poprzez RTP (informowanie o ilosci gubionych pakietow, opoznieniach czy parametrach wykorzystywanych kodekow adaptacyjnych). Opcjonalnie dostarcza możliwość kontroli uczestnikow sesji, ale to najcześciej jest realizowane przez skojarzony z RTP protokół sygnalizacyjny tak jak np SIP.

RTP

Struktura pakietu

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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|V=2|P|X|  CC   |M|     PT      |       sequence number         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                           timestamp                           |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|           synchronization source (SSRC) identifier            |
+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
|            contributing source (CSRC) identifiers             |
|                             ....                              |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Pierwsze 12 oktetow wystepuje zawsze, opcjonalne pola CSRC wystepuja gdy na drodze danych jest mixer.

  • version (V): 2 bity – wersja obecnie 2
  • padding (P): 1 bit – wskazuje czy koniec pakietu jest uzupelniony zerami, jesli tak ostatni oktet wskazuje na liczbe oktetow do pominiecia
  • extension (X): 1 bit – wskazuje ze do pakietu dolaczony jest naglowek z rozszerzeniem
  • CSRC count (CC): 4 bity – wskazuje na liczbe identyfikatorw CSRC na koncu naglowka
  • marker (M): 1 bit – umozliwia wskazanie ze jest znaczacy pakiet, wykorzystywane przez profile RTP
  • payload type (PT): 7 bitów – określa format danych
  • sequence number: 16 bitów – numer sekwencyjny pakietu RTP, zwiekszany o jeden za każdym razem
  • timestamp: 32 bity – wartosc bezwgledna informujace o przedziale pomiedzy przesylanymi probkami danych
  • SSRC: 32 bity – identyfikuje zrodlo synchronizacji
  • CSRC list: od 0 tdo 15 , 32 bity każde – listaidentyfikatorow CSRC ktorych dane sa mixowane

RTCP

Zasada dzialanie RTCP polega na cyklicznej wymianie wiadomosci kontrolnych w oparciu o te same mechanizmy dystrubucji co RTP np. uzywajac innego portu udp. RTCP realizuje 4 podstawowe funkcje:
  • informowanie o jakosci dystrybucji danych oraz mozliwosciach adaptacji na poziomie kodowania
  • przenosi parametr CNAME odpowiedzialny za persystentna identyfikacje sesji RTP
  • ustala czestotliowsc wymiany pakietow w zaleznosci od liczby uczestnikow
  • opcjonalnie pozawala przenosic minimalna ilosc informacji identyfikujaca strony

Struktura pakietu

RFC 3550 definiuje pieć rodzajów pakietów, które przenoszą różne informacje kontrolne

  • SR (Sender Report) – statystyki transmisji i odbioru danych od aktywnych uczestnikow
  • RR (Receiver Report ) – statystyki odbioru dla nieaktywnych uczestnikow
  • SDES (Source Description) – parametry informacyjne zródła np CNAME
  • BYE – informuje o razlaczeniu
  • APP – informacje specyficzne dla danej aplikacji
Kazdy pakiet RTCP składa sie ze stałej części oraz następującej po niej czesci o zmiennej długosci, w zależności od typu pakietu wyrówanej do 32 bitów. Kilka pakietów może być szeregowo łączonych tworząc tak zwany złożony pakiet RTP, umieszczany w pakiecie warstwy niższej. Nie ma żadnego konkretnego wymogu na wielkosc pakietu złożonego, gdyż jest ona kontrolowana przez protokół transportowy. Każdy pakiet wchodzący w skład pakietu złożonego jest analizowany niezależnie od innych, stad kolejność i kombinacja pakietów nie są istotne. Tym niemniej aby spełnić wymaganie realizowane przez protokół na strukturę pakietu złożonego zostały nałożone następujące ograniczenia:
  • SR lub RR musza byc wysylane w kazdym pakiecie zlozonym, tak aby statystyki odbioru byly jak najbardziej dokladne
  • SDES z parametrem CNAME musi być wysylany w kazym pakieci zlozonym, tak aby odbiorca jak najszyciej otrzymal informacje o nadawcy
  • liczba pakietów wyslana w pierwszym pakiecie zlozonym powinna byc jak najmniejsza (2) tak aby liczba stalych bitów byla jak najwieksza i prawdopodobienstwo walidacji pakietu najwieksze

Stad tez struktura pakietu zlozonego musi zawierac conajmniej dwa pakiety o nastepujacej formie:

random encryption prefix: losowy 32-bitowy integer
|
|[--------- packet --------][---------- packet ----------][-packet-]
|
|                receiver            chunk        chunk
V                reports           item  item   item  item
--------------------------------------------------------------------
R[SR #sendinfo #site1#site2][SDES #CNAME PHONE #CNAME LOC][BYE##why]
--------------------------------------------------------------------
|                                                                  |
|<-----------------------  compound packet ----------------------->|
|<--------------------------  UDP packet ------------------------->|

#: SSRC/CSRC identifier
  • Encryption prefix – jesli pakiet zlozony jest szyforowany jest poprzedzany 32 bitową wartościa calkowita
  • SR lub RR – zawsze pierwszy pakiet w pakiecie zlozonym to SR lub RR nawet jesli zadne dane nie byly jeszcze wyslane
  • Dodatkowe RR – jesli liczba zrodel dla ktorych generowane sa statystyki przewyzsza 31 i nie moga byc umieszczone w jednym RR lub SS sa one umieszczane w dodatkowych pakietach RR
  • SDES – w kazdym pakiecie zlozonym musi byc dolaczony pakiet zawierajacy parametr CNAME inne parametry sa umieszczane w zalezności od aplikacji
  • BYE lub APP – pozostale pakiety moga sie pojawiac w dowolnej ilosci i kolejnosci z tym wyjatkiem ze pakiet BYE zawierajacy SSRC/CSRC musi byc ostatni
Kazdy uczestnik powinnien wysylac tylko jeden pakiet zlozony w trakcie okresu raportowania aby oszacowanie pasma bylo precyzyjniejsze. Jesli ilosc dodatkowych pakietow RR nie miesci sie w MTU nalezy ograniczyc ich ilosc i przeslac w nastepnej turze, tak by wszystkie zrodla byly raportowane.

Czestotliwosci RTCP

RTP zostalo tak zaprojektowane aby umozliwiac regulowanie ruchu kontrolnego w zaleznosci o ilosci uczestnikow i przyjetej charakterystyki lacza. Z kazda sesja danych RTP zwiazane jest maksymalne dopuszczalne pasmo sesji (session bandwidth) bedace agregacja danych poszczegolnych uczestnikow. Mechanizm doboru pasma sesji moze byc praktycznie dowolny ale najczesniej przyjmuje sie jego wartosc jako nominalna sume pasm zajmowanych przez maksymalna liczbe jednoczesnie aktywnych uczestnikow. Wartosc pasma sesji najczesciej ustalana jest przez warstwe aplikacji odpowiedzialna za zarzadzanie sesja przy czym najczesciej wartosc domyslna ustalana jest jako pasmo odpowiadajace jednemu aktywnemu uzytkownikowi. Wszyscy uczestnicy sesji musza uzywac tego samego pasma tak aby okres retransmisji RTCP byl taki sam. Warto pamietac ze w trakcie obliczania utylizacji dostepnego pasma brane sa pod uwage tez protokoly transportowe (UDP i IP) ale warstwa lacza danych juz nie gdyz te sie od siebie różnia. Ruch kontrolny jest ograniczany zarówno z góry jak i z dołu. Z góry jako czastkowa wartość calkowitego dostepnego pasma (norma 5%) lub jako wartość ilościowa. Z dolu natomiast ustala sie minimalna wartość tak aby nie generować nadmiernego ruchu (norma 5s), istnieja przypadki kiedy ta wartosc moze byc jeszcze bardziej zredukowana i odwrotnie proporcjonalna do dostepnego pasma. Zaleca sie rowniez aby z posrod calego ruchu RTCP, 1/4 byla przypisana do aktywnych uczestnikow, tak aby nowo dolaczajacy sie uzytkownicy szybko dowiadywali sie aktywnych CNAME. Algorytm oblicza czestotliowsci wysylania pakietów zlozonych tak aby dostępne pasmo na ruch kontrolny było równie rozdzielone pomiedzy uczestników. Wyznaczona czestotliwosc skaluje sie liniowe w stosunku do liczby uczestników, co zapewnia stała wartość ruchu kontrolnego. Aby uniknąc pelnej synchronizacji kazdy z uczestnikow posluguje sie lekka wariacja okresu wysylania RTCP oraz losowym opoznienieniem dla pierwszego wysylanego pakietu zlozonego. Dodatkowo obslugiwane sa mechanizmy obslugujace sytuacje wyjatkowe kiedy wielu uczestnikow jednoczesnie dolacza lub opuszcza sesje.

Ilosc Uczestnikow

Wyznaczanie czestotliwosci RTCP bazuje na znajomosci oszacowanej liczby uczestnikow. Uczestnik okreslany jest jako nowy gdy w sesji pojawi sie ruch z nowym identyfikatorem SSRC lub CSRC. Istnieje możliwosc przyjecia ze musi byc zarejstrowanych kilka pakietow by uznac ze pojawil sie nowy uczestnik lub ze musi zostac odebrany pakiet SDES z nowym CNAME. Uczestnika uwaza sie za usunietego gdy wysyla pakiet BYE lub przez okreslony czas nie wysyla pakietow.

Zasady Wysylania i Odbierania pakietow RTCP

Aby zrealizowac powyzsze zalozenia kazdy uzytkownik musi lokalnie przechowywac szereg informacji zwiazanych z realizowana sesja:

  • tp – czas ostatniej transmisji
  • tc- obecny czas
  • tn – czas nastepnej transmisji
  • pmembers – oszacowana liczba uczestnikow podczas podczas ostatniej transmisji
  • members – aktualna oszacowana liczba uczestnikow
  • senders – aktualna oszacowana liczba aktywnych uczestnikow
  • rtcp_bw – pasmo przydzielone dla calego ruchu RTCP wszystkich uczestnikow
  • we_sent – flaga informujaca czy od ostatniego raportu uczestnik wyslal dane
  • avg_rtcp_size – sredni rozmiar wyslanych i odebranych pakietow przez uzytkownika
  • initial – ustawiona na true gdy uzytkownik nie wyslal jeszcze zadnego pakietu RTCP
W trakcie inicjalizacji aplikacji parametry ustawiane sa na wartosci domyslne. Wartosc okresu nadawania wiadomosci kontrolnych jest obliczana na podstawie powyzej wymienionych parameterow. Procedura w efekcie daje przedzial ktory jest losowy i przydziela minimum 1/4 calego dostepnego pasma uzytkownikom aktywnym. Jesli uzytkoników aktywnych jest wiecej niż 1/4 wszystkich uzytkownikow dostepne pasmo jest dytrybuowane po rowno do wszystkich uczestników. Po otrzymaniu pakietu RTP lub RTCP od uczesnika, ktorego SSRC nie jest obecne w tablicy uczestników, jest on dodawany do listy i liczba uczestnikow jest aktualizowana. Kiedy pakiet RTP jest od uczestnika ktory nie znajduje sie na liscie aktywnych uczestnikow jest on do niej dodawany i ich liczba jest aktualizowana. Jak zawsze przy kazdym odebranym i wyslanym pakiecie wartosc avg_rtcp_size jest aktualizowana. Gdy uczestnik odbiera pakiet BYE sprawdza czy na liscie uczestników lub aktywnych uczestników znajduje nadawca pakietu, jesli tak, jest on z niej usuwany, aktualizowane sa parametry oraz czas wyslania nastepnego zlozonego pakietu RTCP. Przynajmniej raz na jeden okres przesylania pakietu kontrolnego uczestnik weryfikuje czy na którejś z list nie nastapil timeout dla danego SSRC. Kiedy uczestnik chce opuscic sesje moze ale nie musi wyslac pakiet BYE, jesli tego nie zrobi nastapi timeout. Jesli liczba uzytkownikow jest mala (zalecane 50) moze wyslac pakiet od razu, w przeciwnym wypadku stosuje mechanizm zapobiegajacy masowemu opuszczaniu sesji przez duza liczbe uczestnikow.

Pakiety SR i RR

W oparciu o pakiety RR odbiorcy informuja o jakosci odbieranych danych, jeśli odbiorca jest uczestnikiem aktywnym i wysyłał dane od ostatniego raportu wykorzystuje pakiet SR zawierajacy dodatkowe informacje o nadawcy. W kazdym pakiecie SR i RR znajduje sie po jednym bloku raportujacym skojarzonym z jednym źródłem synchronizacji. Jeśli zródeł jest wiecej niż 31 powinny zostać umieszczone w kolejnych pakietach RR.
SR składa sie z trzech sekcji obowiazkowych: nagłówka, informacji o nadawcy, listy bloków raportujacych i czwartej opcjonalnej dedykowanej dla konkretnego profilu. opcjonalna cześć jest wykorzystywana gdy profil RTP wymaga przesylania dodatkowych informacji pomiedzy stronami.
        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
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
header |V=2|P|    RC   |   PT=SR=200   |             length            |
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
       |                         SSRC of sender                        |
       +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
sender |              NTP timestamp, most significant word             |
info   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
       |             NTP timestamp, least significant word             |
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
       |                         RTP timestamp                         |
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
       |                     sender's packet count                     |
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
       |                      sender's octet count                     |
       +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
report |                 SSRC_1 (SSRC of first source)                 |
block  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  1    | fraction lost |       cumulative number of packets lost       |
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
       |           extended highest sequence number received           |
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
       |                      interarrival jitter                      |
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
       |                         last SR (LSR)                         |
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
       |                   delay since last SR (DLSR)                  |
       +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
report |                 SSRC_2 (SSRC of second source)                |
block  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  2    :                               ...                             :
       +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
       |                  profile-specific extensions                  |
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  • wersja (V): 2 bity – identyfikuje wersje, tak samo jak RTP 2
  • padding (P): 1 bit – wskazuje czy koniec pakietu jest uzupelniony zerami, jesli tak ostatni oktet wskazuje na liczbe oktetow do pominiecia
  • reception report count (RC): 5 bitów – liczba blokow raportujacych w tym pakiecie
  • packet type (PT): 8 bitów – indentyfikuej pakiet RTCP SR (stala wartosc 200)
  • length: 16 bitów – dlugosc pakietu w 32 bitowych słowach właczając nagłówek i wyrównanie
  • SSRC: 32 bity – identyfikator SSRC zródla pakietu SR
  • NTP timestamp: 64 bity – zegarowy czas wyslania pakietu
  • RTP timestamp: 32 bity – okresowy czas wyslania pakietu
  • sender’s packet count: 32 bity – calkowita liczba pakietow RTP wyslanych przez uczest
  • SSRC_n (source identifier): 32 bity – identyfikator SSRC dla zrodla ktorego dotyczy raport
  • fraction lost: 8 bitów – stosunek pakietow odebranych do pakietow spodziewanych RTP
  • cumulative number of packets lost: 24 bity – calkowita liczba wszystkich zgóbionych pakietów RTP
  • xtended highest sequence number received: 32 bity – najwieszky numer sekwencyjny odebranego pakietu
  • interarrival jitter: 32 bity – roznica pomiedzy odstepem w wysylaniu kolejnych pakietow
  • last SR timestamp (LSR): 32 bity – srodkowe 32 bity otrzymane w SR od nadawcy
  • delay since last SR (DLSR): 32 bity – czas pomiedzy odbiorem pakietu SR od nadawcy a nadaniem tego bloku raportujacego
Struktura pakietu RR jest taka sama jak pakietu SR, z tą różnicą że pakiet RR nie zawiera czesci informacyjnej o nadawcy a pole typu pakietu zawiera wartosc 201:
        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
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
header |V=2|P|    RC   |   PT=RR=201   |             length            |
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
       |                     SSRC of packet sender                     |
       +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
report |                 SSRC_1 (SSRC of first source)                 |
block  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  1    | fraction lost |       cumulative number of packets lost       |
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
       |           extended highest sequence number received           |
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
       |                      interarrival jitter                      |
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
       |                         last SR (LSR)                         |
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
       |                   delay since last SR (DLSR)                  |
       +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
report |                 SSRC_2 (SSRC of second source)                |
block  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  2    :                               ...                             :
       +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
       |                  profile-specific extensions                  |
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Po otrzymaniu raportu w postaci pakietu SR lub RR nadawca moze zmodyfikować na jego podstawie charakterysytke sesji, określić zakres wystepujących problemów, określić skutecznosc w dostarczaniu raportow itp. Dane raportujace moga byc rowniez agregowane przez aplikacje monitorujace nadzorujace wydajnosc sieci.

Pakiety SDES

Pakiet SDES posiada trzy poziomową strukture, w której skład wchodzi nagłówek, zero lub wiecej fragmentów zawierających atrybuty opisujące zródło identyfikowane w danym fragmencie. Każdy fragment zawiera indentyfikator SSRC/CSRC oraz listę atrybótów. Każdy atrybut zawiera 2 8-śmio bitowe pola wskazujace na jego typ oraz dlugość oraz sam tekst, gdzie tekst nie może być dłuższy niż 255 oktetów
        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
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
header |V=2|P|    SC   |  PT=SDES=202  |             length            |
       +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
chunk  |                          SSRC/CSRC_1                          |
  1    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
       |                           SDES items                          |
       |                              ...                              |
       +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
chunk  |                          SSRC/CSRC_2                          |
  2    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
       |                           SDES items                          |
       |                              ...                              |
       +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
  • version (V) – wersja, padding (P) – dopełnienie, length – dlugość
  • packet type (PT): 8 bitów – typ pakietu (202)
  • source count (SC): 5 bitów – liczba fragmentów w pakiecie

autor: Tomasz Zieleniewski

You May Also Like

Spock basics

Spock (homepage) is like its authors say 'testing and specification framework'. Spock combines very elegant and natural syntax with the powerful capabilities. And what is most important it is easy to use.

One note at the very beginning: I assume that you are already familiar with principles of Test Driven Development and you know how to use testing framework like for example JUnit.

So how can I start?


Writing spock specifications is very easy. We need basic configuration of Spock and Groovy dependencies (if you are using mavenized project with Eclipse look to my previous post: Spock, Java and Maven). Once we have everything set up and running smooth we can write our first specs (spec or specification is equivalent for test class in other frameworks like JUnit of TestNG).

What is great with Spock is fact that we can use it to test both Groovy projects and pure Java projects or even mixed projects.


Let's go!


Every spec class must inherit from spock.lang.Specification class. Only then test runner will recognize it as test class and start tests. We will write few specs for this simple class: User class and few tests not connected with this particular class.

We start with defining our class:
import spock.lang.*

class UserSpec extends Specification {

}
Now we can proceed to defining test fixtures and test methods.

All activites we want to perform before each test method, are to be put in def setup() {...} method and everything we want to be run after each test should be put in def cleanup() {...} method (they are equivalents for JUnit methods with @Before and @After annotations).

It can look like this:
class UserSpec extends Specification {
User user
Document document

def setup() {
user = new User()
document = DocumentTestFactory.createDocumentWithTitle("doc1")
}

def cleanup() {

}
}
Of course we can use field initialization for instantiating test objects:
class UserSpec extends Specification {
User user = new User()
Document document = DocumentTestFactory.createDocumentWithTitle("doc1")

def setup() {

}

def cleanup() {

}
}

What is more readable or preferred? It is just a matter of taste because according to Spock docs behaviour is the same in these two cases.

It is worth mentioning that JUnit @BeforeClass/@AfterClass are also present in Spock as def setupSpec() {...} and def cleanupSpec() {...}. They will be runned before first test and after last test method.


First tests


In Spock every method in specification class, expect setup/cleanup, is treated by runner as a test method (unless you annotate it with @Ignore).

Very interesting feature of Spock and Groovy is ability to name methods with full sentences just like regular strings:
class UserSpec extends Specification {
// ...

def "should assign coment to user"() {
// ...
}
}
With such naming convention we can write real specification and include details about specified behaviour in method name, what is very convenient when reading test reports and analyzing errors.

Test method (also called feature method) is logically divided into few blocks, each with its own purpose. Blocks are defined like labels in Java (but they are transformed with Groovy AST transform features) and some of them must be put in code in specific order.

Most basic and common schema for Spock test is:
class UserSpec extends Specification {
// ...

def "should assign coment to user"() {
given:
// do initialization of test objects
when:
// perform actions to be tested
then:
// collect and analyze results
}
}

But there are more blocks like:
  • setup
  • expect
  • where
  • cleanup
In next section I am going to describe each block shortly with little examples.

given block

This block is used to setup test objects and their state. It has to be first block in test and cannot be repeated. Below is little example how can it be used:
class UserSpec extends Specification {
// ...

def "should add project to user and mark user as project's owner"() {
given:
User user = new User()
Project project = ProjectTestFactory.createProjectWithName("simple project")
// ...
}
}

In this code given block contains initialization of test objects and nothing more. We create simple user without any specified attributes and project with given name. In case when some of these objects could be reused in more feature methods, it could be worth putting initialization in setup method.

when and then blocks

When block contains action we want to test (Spock documentation calls it 'stimulus'). This block always occurs in pair with then block, where we are verifying response for satisfying certain conditions. Assume we have this simple test case:
class UserSpec extends Specification {
// ...

def "should assign user to comment when adding comment to user"() {
given:
User user = new User()
Comment comment = new Comment()
when:
user.addComment(comment)
then:
comment.getUserWhoCreatedComment().equals(user)
}

// ...
}

In when block there is a call of tested method and nothing more. After we are sure our action was performed, we can check for desired conditions in then block.

Then block is very well structured and its every line is treated by Spock as boolean statement. That means, Spock expects that we write instructions containing comparisons and expressions returning true or false, so we can create then block with such statements:
user.getName() == "John"
user.getAge() == 40
!user.isEnabled()
Each of lines will be treated as single assertion and will be evaluated by Spock.

Sometimes we expect that our method throws an exception under given circumstances. We can write test for it with use of thrown method:
class CommentSpec extends Specification {
def "should throw exception when adding null document to comment"() {
given:
Comment comment = new Comment()
when:
comment.setCommentedDocument(null)
then:
thrown(RuntimeException)
}
}

In this test we want to make sure that passing incorrect parameters is correctly handled by tested method and that method throws an exception in response. In case you want to be certain that method does not throw particular exception, simply use notThrown method.


expect block

Expect block is primarily used when we do not want to separate when and then blocks because it is unnatural. It is especially useful for simple test (and according to TDD rules all test should be simple and short) with only one condition to check, like in this example (it is simple but should show the idea):
def "should create user with given name"() {
given:
User user = UserTestFactory.createUser("john doe")
expect:
user.getName() == "john doe"
}



More blocks!


That were very simple tests with standard Spock test layout and canonical divide into given/when/then parts. But Spock offers more possibilities in writing tests and provides more blocks.


setup/cleanup blocks

These two blocks have the very same functionality as the def setup and def cleanup methods in specification. They allow to perform some actions before test and after test. But unlike these methods (which are shared between all tests) blocks work only in methods they are defined in. 


where - easy way to create readable parameterized tests

Very often when we create unit tests there is a need to "feed" them with sample data to test various cases and border values. With Spock this task is very easy and straighforward. To provide test data to feature method, we need to use where block. Let's take a look at little the piece of code:

def "should successfully validate emails with valid syntax"() {
expect:
emailValidator.validate(email) == true
where:
email }

In this example, Spock creates variable called email which is used when calling method being tested. Internally feature method is called once, but framework iterates over given values and calls expect/when block as many times as there are values (however, if we use @Unroll annotation Spock can create separate run for each of given values, more about it in one of next examples).

Now, lets assume that we want our feature method to test both successful and failure validations. To achieve that goal we can create few 
parameterized variables for both input parameter and expected result. Here is a little example:

def "should perform validation of email addresses"() {
expect:
emailValidator.validate(email) == result
where:
email result }
Well, it looks nice, but Spock can do much better. It offers tabular format of defining parameters for test what is much more readable and natural. Lets take a look:
def "should perform validation of email addresses"() {
expect:
emailValidator.validate(email) == result
where:
email | result
"WTF" | false
"@domain" | false
"foo@bar.com" | true
"a@test" | false
}
In this code, each column of our "table" is treated as a separate variable and rows are values for subsequent test iterations.

Another useful feature of Spock during parameterizing test is its ability to "unroll" each parameterized test. Feature method from previous example could be defined as (the body stays the same, so I do not repeat it):
@Unroll("should validate email #email")
def "should perform validation of email addresses"() {
// ...
}
With that annotation, Spock generate few methods each with its own name and run them separately. We can use symbols from where blocks in @Unroll argument by preceding it with '#' sign what is a signal to Spock to use it in generated method name.


What next?


Well, that was just quick and short journey  through Spock and its capabilities. However, with that basic tutorial you are ready to write many unit tests. In one of my future posts I am going to describe more features of Spock focusing especially on its mocking abilities.