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

Touki na Confiturze 2013 – ciąg dalszy

Kolejna partia nagrań z konferencji została udostępniona. Wśród nich m.in. prezentacja Michała Trzaskowskiego o rozpoznawaniu oczekiwań klienta podczas prowadzenia projektu z wykorzystaniem ”zwinnych” metodyk projektowych oraz wystąpienie Macieja Próchniaka o możliwościach Slick, jednej z bibliotek w Scali.

Using WsLite in practice

TL;DR

There is a example working GitHub project which covers unit testing and request/response logging when using WsLite.

Why Groovy WsLite ?

I’m a huge fan of Groovy WsLite project for calling SOAP web services. Yes, in a real world you have to deal with those - big companies have huge amount of “legacy” code and are crazy about homogeneous architecture - only SOAP, Java, Oracle, AIX…

But I also never been comfortable with XFire/CXF approach of web service client code generation. I wrote a bit about other posibilites in this post. With JAXB you can also experience some freaky classloading errors - as Tomek described on his blog. In a large commercial project the “the less code the better” principle is significant. And the code generated from XSD could look kinda ugly - especially more complicated structures like sequences, choices, anys etc.

Using WsLite with native Groovy concepts like XmlSlurper could be a great choice. But since it’s a dynamic approach you have to be really careful - write good unit tests and log requests. Below are my few hints for using WsLite in practice.

Unit testing

Suppose you have some invocation of WsLite SOAPClient (original WsLite example):

def getMothersDay(long _year) {
    def response = client.send(SOAPAction: action) {
       body {
           GetMothersDay('xmlns':'http://www.27seconds.com/Holidays/US/Dates/') {
              year(_year)
           }
       }
    }
    response.GetMothersDayResponse.GetMothersDayResult.text()
}

How can the unit test like? My suggestion is to mock SOAPClient and write a simple helper to test that builded XML is correct. Example using great SpockFramework:

void setup() {
   client = Mock(SOAPClient)
   service.client = client
}

def "should pass year to GetMothersDay and return date"() {
  given:
      def year = 2013
  when:
      def date = service.getMothersDay(year)
  then:
      1 * client.send(_, _) >> { Map params, Closure requestBuilder ->
            Document doc = buildAndParseXml(requestBuilder)
            assertXpathEvaluatesTo("$year", '//ns:GetMothersDay/ns:year', doc)
            return mockResponse(Responses.mothersDay)
      }
      date == "2013-05-12T00:00:00"
}

This uses a real cool feature of Spock - even when you mock the invocation with “any mark” (_), you are able to get actual arguments. So we can build XML that would be passed to SOAPClient's send method and check that specific XPaths are correct:

void setup() {
    engine = XMLUnit.newXpathEngine()
    engine.setNamespaceContext(new SimpleNamespaceContext(namespaces()))
}

protected Document buildAndParseXml(Closure xmlBuilder) {
    def writer = new StringWriter()
    def builder = new MarkupBuilder(writer)
    builder.xml(xmlBuilder)
    return XMLUnit.buildControlDocument(writer.toString())
}

protected void assertXpathEvaluatesTo(String expectedValue,
                                      String xpathExpression, Document doc) throws XpathException {
    Assert.assertEquals(expectedValue,
            engine.evaluate(xpathExpression, doc))
}

protected Map namespaces() {
    return [ns: 'http://www.27seconds.com/Holidays/US/Dates/']
}

The XMLUnit library is used just for XpathEngine, but it is much more powerful for comparing XML documents. The NamespaceContext is needed to use correct prefixes (e.g. ns:GetMothersDay) in your Xpath expressions.

Finally - the mock returns SOAPResponse instance filled with envelope parsed from some constant XML:

protected SOAPResponse mockResponse(String resp) {
    def envelope = new XmlSlurper().parseText(resp)
    new SOAPResponse(envelope: envelope)
}

Request and response logging

The WsLite itself doesn’t use any logging framework. We usually handle it by adding own sendWithLogging method:

private SOAPResponse sendWithLogging(String action, Closure cl) {
    SOAPResponse response = client.send(SOAPAction: action, cl)
    log(response?.httpRequest, response?.httpResponse)
    return response
}

private void log(HTTPRequest request, HTTPResponse response) {
    log.debug("HTTPRequest $request with content:\n${request?.contentAsString}")
    log.debug("HTTPResponse $response with content:\n${response?.contentAsString}")
}

This logs the actual request and response send through SOAPClient. But it logs only when invocation is successful and errors are much more interesting… So here goes withExceptionHandler method:

private SOAPResponse withExceptionHandler(Closure cl) {
    try {
        cl.call()
    } catch (SOAPFaultException soapEx) {
        log(soapEx.httpRequest, soapEx.httpResponse)
        def message = soapEx.hasFault() ? soapEx.fault.text() : soapEx.message
        throw new InfrastructureException(message)
    } catch (HTTPClientException httpEx) {
        log(httpEx.request, httpEx.response)
        throw new InfrastructureException(httpEx.message)
    }
}
def send(String action, Closure cl) {
    withExceptionHandler {
        sendWithLogging(action, cl)
    }
}

XmlSlurper gotchas

Working with XML document with XmlSlurper is generally great fun, but is some cases could introduce some problems. A trivial example is parsing an id with a number to Long value:

def id = Long.valueOf(edit.'@id' as String)

The Attribute class (which edit.'@id' evaluates to) can be converted to String using as operator, but converting to Long requires using valueOf.

The second example is a bit more complicated. Consider following XML fragment:

<edit id="3">
   <params>
      <param value="label1" name="label"/>
      <param value="2" name="param2"/>
   </params>
   <value>123</value>
</edit>
<edit id="6">
   <params>
      <param value="label2" name="label"/>
      <param value="2" name="param2"/>
   </params>
   <value>456</value>
</edit>

We want to find id of edit whose label is label1. The simplest solution seems to be:

def param = doc.edit.params.param.find { it['@value'] == 'label1' }
def edit = params.parent().parent()

But it doesn’t work! The parent method returns multiple edits, not only the one that is parent of given param

Here’s the correct solution:

doc.edit.find { edit ->
    edit.params.param.find { it['@value'] == 'label1' }
}

Example

The example working project covering those hints could be found on GitHub.