diff --git a/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspHeaders.java b/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspHeaders.java index 62ff519fd8..6b56b7b5ce 100644 --- a/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspHeaders.java +++ b/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspHeaders.java @@ -20,9 +20,8 @@ import androidx.annotation.Nullable; import com.google.android.exoplayer2.util.Util; import com.google.common.base.Ascii; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import java.util.ArrayList; -import java.util.LinkedHashMap; +import com.google.common.collect.ImmutableListMultimap; +import com.google.common.collect.Iterables; import java.util.List; import java.util.Map; @@ -35,45 +34,45 @@ import java.util.Map; */ /* package */ final class RtspHeaders { - public static final String ACCEPT = "Accept"; - public static final String ALLOW = "Allow"; - public static final String AUTHORIZATION = "Authorization"; - public static final String BANDWIDTH = "Bandwidth"; - public static final String BLOCKSIZE = "Blocksize"; - public static final String CACHE_CONTROL = "Cache-Control"; - public static final String CONNECTION = "Connection"; - public static final String CONTENT_BASE = "Content-Base"; - public static final String CONTENT_ENCODING = "Content-Encoding"; - public static final String CONTENT_LANGUAGE = "Content-Language"; - public static final String CONTENT_LENGTH = "Content-Length"; - public static final String CONTENT_LOCATION = "Content-Location"; - public static final String CONTENT_TYPE = "Content-Type"; - public static final String CSEQ = "CSeq"; - public static final String DATE = "Date"; - public static final String EXPIRES = "Expires"; - public static final String PROXY_AUTHENTICATE = "Proxy-Authenticate"; - public static final String PROXY_REQUIRE = "Proxy-Require"; - public static final String PUBLIC = "Public"; - public static final String RANGE = "Range"; - public static final String RTP_INFO = "RTP-Info"; - public static final String RTCP_INTERVAL = "RTCP-Interval"; - public static final String SCALE = "Scale"; - public static final String SESSION = "Session"; - public static final String SPEED = "Speed"; - public static final String SUPPORTED = "Supported"; - public static final String TIMESTAMP = "Timestamp"; - public static final String TRANSPORT = "Transport"; - public static final String USER_AGENT = "User-Agent"; - public static final String VIA = "Via"; - public static final String WWW_AUTHENTICATE = "WWW-Authenticate"; + public static final String ACCEPT = "accept"; + public static final String ALLOW = "allow"; + public static final String AUTHORIZATION = "authorization"; + public static final String BANDWIDTH = "bandwidth"; + public static final String BLOCKSIZE = "blocksize"; + public static final String CACHE_CONTROL = "cache-control"; + public static final String CONNECTION = "connection"; + public static final String CONTENT_BASE = "content-base"; + public static final String CONTENT_ENCODING = "content-encoding"; + public static final String CONTENT_LANGUAGE = "content-language"; + public static final String CONTENT_LENGTH = "content-length"; + public static final String CONTENT_LOCATION = "content-location"; + public static final String CONTENT_TYPE = "content-type"; + public static final String CSEQ = "cseq"; + public static final String DATE = "date"; + public static final String EXPIRES = "expires"; + public static final String PROXY_AUTHENTICATE = "proxy-authenticate"; + public static final String PROXY_REQUIRE = "proxy-require"; + public static final String PUBLIC = "public"; + public static final String RANGE = "range"; + public static final String RTP_INFO = "rtp-info"; + public static final String RTCP_INTERVAL = "rtcp-interval"; + public static final String SCALE = "scale"; + public static final String SESSION = "session"; + public static final String SPEED = "speed"; + public static final String SUPPORTED = "supported"; + public static final String TIMESTAMP = "timestamp"; + public static final String TRANSPORT = "transport"; + public static final String USER_AGENT = "user-agent"; + public static final String VIA = "via"; + public static final String WWW_AUTHENTICATE = "www-authenticate"; /** Builds {@link RtspHeaders} instances. */ public static final class Builder { - private final List namesAndValues; + private final ImmutableListMultimap.Builder namesAndValuesBuilder; /** Creates a new instance. */ public Builder() { - namesAndValues = new ArrayList<>(); + namesAndValuesBuilder = new ImmutableListMultimap.Builder<>(); } /** @@ -84,8 +83,7 @@ import java.util.Map; * @return This builder. */ public Builder add(String headerName, String headerValue) { - namesAndValues.add(headerName.trim()); - namesAndValues.add(headerValue.trim()); + namesAndValuesBuilder.put(Ascii.toLowerCase(headerName.trim()), headerValue.trim()); return this; } @@ -130,37 +128,38 @@ import java.util.Map; } } - private final ImmutableList namesAndValues; + private final ImmutableListMultimap namesAndValues; /** - * Gets the headers as a map, where the keys are the header names and values are the header - * values. - * - * @return The headers as a map. The keys of the map have follows those that are used to build - * this {@link RtspHeaders} instance. + * Returns a map that associates header names to the list of values associated with the + * corresponding header name. */ - public ImmutableMap asMap() { - Map headers = new LinkedHashMap<>(); - for (int i = 0; i < namesAndValues.size(); i += 2) { - headers.put(namesAndValues.get(i), namesAndValues.get(i + 1)); - } - return ImmutableMap.copyOf(headers); + public ImmutableListMultimap asMultiMap() { + return namesAndValues; } /** - * Returns a header value mapped to the argument, {@code null} if the header name is not recorded. + * Returns the most recent header value mapped to the argument, {@code null} if the header name is + * not recorded. */ @Nullable public String get(String headerName) { - for (int i = namesAndValues.size() - 2; i >= 0; i -= 2) { - if (Ascii.equalsIgnoreCase(headerName, namesAndValues.get(i))) { - return namesAndValues.get(i + 1); - } + ImmutableList headerValues = values(headerName); + if (headerValues.isEmpty()) { + return null; } - return null; + return Iterables.getLast(headerValues); + } + + /** + * Returns a list of header values mapped to the argument, in the addition order. The returned + * list is empty if the header name is not recorded. + */ + public ImmutableList values(String headerName) { + return namesAndValues.get(Ascii.toLowerCase(headerName)); } private RtspHeaders(Builder builder) { - this.namesAndValues = ImmutableList.copyOf(builder.namesAndValues); + this.namesAndValues = builder.namesAndValuesBuilder.build(); } } diff --git a/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspMessageUtil.java b/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspMessageUtil.java index d4f10a2080..d644514401 100644 --- a/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspMessageUtil.java +++ b/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspMessageUtil.java @@ -39,7 +39,7 @@ import com.google.android.exoplayer2.ParserException; import com.google.android.exoplayer2.util.Util; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableListMultimap; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -94,11 +94,13 @@ import java.util.regex.Pattern; builder.add( Util.formatInvariant( "%s %s %s", toMethodString(request.method), request.uri, RTSP_VERSION)); - ImmutableMap headers = request.headers.asMap(); + + ImmutableListMultimap headers = request.headers.asMultiMap(); for (String headerName : headers.keySet()) { - builder.add( - Util.formatInvariant( - "%s: %s", headerName, checkNotNull(request.headers.get(headerName)))); + ImmutableList headerValuesForName = headers.get(headerName); + for (int i = 0; i < headerValuesForName.size(); i++) { + builder.add(Util.formatInvariant("%s: %s", headerName, headerValuesForName.get(i))); + } } // Empty line after headers. builder.add(""); @@ -119,11 +121,12 @@ import java.util.regex.Pattern; Util.formatInvariant( "%s %s %s", RTSP_VERSION, response.status, getRtspStatusReasonPhrase(response.status))); - ImmutableMap headers = response.headers.asMap(); + ImmutableListMultimap headers = response.headers.asMultiMap(); for (String headerName : headers.keySet()) { - builder.add( - Util.formatInvariant( - "%s: %s", headerName, checkNotNull(response.headers.get(headerName)))); + ImmutableList headerValuesForName = headers.get(headerName); + for (int i = 0; i < headerValuesForName.size(); i++) { + builder.add(Util.formatInvariant("%s: %s", headerName, headerValuesForName.get(i))); + } } // Empty line after headers. builder.add(""); diff --git a/library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/RtspHeadersTest.java b/library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/RtspHeadersTest.java index b1bd684462..b8b8cbc5dd 100644 --- a/library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/RtspHeadersTest.java +++ b/library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/RtspHeadersTest.java @@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ListMultimap; import org.junit.Test; import org.junit.runner.RunWith; @@ -82,7 +83,47 @@ public final class RtspHeadersTest { } @Test - public void asMap() { + public void get_withMultipleValuesMappedToTheSameName_getsTheMostRecentValue() { + RtspHeaders headers = + new RtspHeaders.Builder() + .addAll( + ImmutableList.of( + "WWW-Authenticate: Digest realm=\"2857be52f47f\"," + + " nonce=\"f4cba07ad14b5bf181ac77c5a92ba65f\", stale=\"FALSE\"", + "WWW-Authenticate: Basic realm=\"2857be52f47f\"")) + .build(); + + assertThat(headers.get("WWW-Authenticate")).isEqualTo("Basic realm=\"2857be52f47f\""); + } + + @Test + public void values_withNoHeaders_returnsAnEmptyList() { + RtspHeaders headers = new RtspHeaders.Builder().build(); + + assertThat(headers.values("WWW-Authenticate")).isEmpty(); + } + + @Test + public void values_withMultipleValuesMappedToTheSameName_returnsAllMappedValues() { + RtspHeaders headers = + new RtspHeaders.Builder() + .addAll( + ImmutableList.of( + "WWW-Authenticate: Digest realm=\"2857be52f47f\"," + + " nonce=\"f4cba07ad14b5bf181ac77c5a92ba65f\", stale=\"FALSE\"", + "WWW-Authenticate: Basic realm=\"2857be52f47f\"")) + .build(); + + assertThat(headers.values("WWW-Authenticate")) + .containsExactly( + "Digest realm=\"2857be52f47f\", nonce=\"f4cba07ad14b5bf181ac77c5a92ba65f\"," + + " stale=\"FALSE\"", + "Basic realm=\"2857be52f47f\"") + .inOrder(); + } + + @Test + public void asMultiMap_withoutValuesMappedToTheSameName_getsTheMappedValuesInAdditionOrder() { RtspHeaders headers = new RtspHeaders.Builder() .addAll( @@ -92,11 +133,39 @@ public final class RtspHeadersTest { "Content-Length: 707", "Transport: RTP/AVP;unicast;client_port=65458-65459\r\n")) .build(); - assertThat(headers.asMap()) + assertThat(headers.asMultiMap()) .containsExactly( - "Accept", "application/sdp", - "CSeq", "3", - "Content-Length", "707", - "Transport", "RTP/AVP;unicast;client_port=65458-65459"); + "accept", "application/sdp", + "cseq", "3", + "content-length", "707", + "transport", "RTP/AVP;unicast;client_port=65458-65459"); + } + + @Test + public void asMap_withMultipleValuesMappedToTheSameName_getsTheMappedValuesInAdditionOrder() { + RtspHeaders headers = + new RtspHeaders.Builder() + .addAll( + ImmutableList.of( + "Accept: application/sdp ", // Extra space after header value. + "Accept: application/sip ", // Extra space after header value. + "CSeq:3", // No space after colon. + "CSeq:5", // No space after colon. + "Transport: RTP/AVP;unicast;client_port=65456-65457", + "Transport: RTP/AVP;unicast;client_port=65458-65459\r\n")) + .build(); + ListMultimap headersMap = headers.asMultiMap(); + + assertThat(headersMap.keySet()).containsExactly("accept", "cseq", "transport").inOrder(); + assertThat(headersMap) + .valuesForKey("accept") + .containsExactly("application/sdp", "application/sip") + .inOrder(); + assertThat(headersMap).valuesForKey("cseq").containsExactly("3", "5").inOrder(); + assertThat(headersMap) + .valuesForKey("transport") + .containsExactly( + "RTP/AVP;unicast;client_port=65456-65457", "RTP/AVP;unicast;client_port=65458-65459") + .inOrder(); } } diff --git a/library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/RtspMessageChannelTest.java b/library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/RtspMessageChannelTest.java index 99b2572434..3f460d2c1c 100644 --- a/library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/RtspMessageChannelTest.java +++ b/library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/RtspMessageChannelTest.java @@ -141,18 +141,18 @@ public final class RtspMessageChannelTest { assertThat(receivedRtspResponses) .containsExactly( /* optionsResponse */ - ImmutableList.of("RTSP/1.0 200 OK", "CSeq: 2", "Public: OPTIONS", ""), + ImmutableList.of("RTSP/1.0 200 OK", "cseq: 2", "public: OPTIONS", ""), /* describeResponse */ ImmutableList.of( "RTSP/1.0 200 OK", - "CSeq: 3", - "Content-Type: application/sdp", - "Content-Length: 28", + "cseq: 3", + "content-type: application/sdp", + "content-length: 28", "", "v=安卓アンドロイド"), /* setupResponse */ ImmutableList.of( - "RTSP/1.0 200 OK", "CSeq: 3", "Transport: RTP/AVP/TCP;unicast;interleaved=0-1", "")) + "RTSP/1.0 200 OK", "cseq: 3", "transport: RTP/AVP/TCP;unicast;interleaved=0-1", "")) .inOrder(); assertThat(receivedInterleavedData) .containsExactly( diff --git a/library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/RtspMessageUtilTest.java b/library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/RtspMessageUtilTest.java index 09d42dc745..c454afdea2 100644 --- a/library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/RtspMessageUtilTest.java +++ b/library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/RtspMessageUtilTest.java @@ -21,6 +21,7 @@ import static com.google.common.truth.Truth.assertThat; import android.net.Uri; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ListMultimap; import java.util.Arrays; import java.util.List; import org.junit.Test; @@ -41,8 +42,10 @@ public final class RtspMessageUtilTest { RtspRequest request = RtspMessageUtil.parseRequest(requestLines); assertThat(request.method).isEqualTo(RtspRequest.METHOD_OPTIONS); - assertThat(request.headers.asMap()) - .containsExactly(RtspHeaders.CSEQ, "2", RtspHeaders.USER_AGENT, "LibVLC/3.0.11"); + assertThat(request.headers.asMultiMap()) + .containsExactly( + RtspHeaders.CSEQ, "2", + RtspHeaders.USER_AGENT, "LibVLC/3.0.11"); assertThat(request.messageBody).isEmpty(); } @@ -57,12 +60,12 @@ public final class RtspMessageUtilTest { RtspResponse response = RtspMessageUtil.parseResponse(responseLines); assertThat(response.status).isEqualTo(200); - assertThat(response.headers.asMap()) + assertThat(response.headers.asMultiMap()) .containsExactly( RtspHeaders.CSEQ, "2", RtspHeaders.PUBLIC, - "OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE, GET_PARAMETER, SET_PARAMETER"); + "OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE, GET_PARAMETER," + " SET_PARAMETER"); assertThat(response.messageBody).isEmpty(); } @@ -78,14 +81,11 @@ public final class RtspMessageUtilTest { RtspRequest request = RtspMessageUtil.parseRequest(requestLines); assertThat(request.method).isEqualTo(RtspRequest.METHOD_DESCRIBE); - assertThat(request.headers.asMap()) + assertThat(request.headers.asMultiMap()) .containsExactly( - RtspHeaders.CSEQ, - "3", - RtspHeaders.USER_AGENT, - "LibVLC/3.0.11", - RtspHeaders.ACCEPT, - "application/sdp"); + RtspHeaders.CSEQ, "3", + RtspHeaders.USER_AGENT, "LibVLC/3.0.11", + RtspHeaders.ACCEPT, "application/sdp"); assertThat(request.messageBody).isEmpty(); } @@ -111,16 +111,12 @@ public final class RtspMessageUtilTest { RtspResponse response = RtspMessageUtil.parseResponse(responseLines); assertThat(response.status).isEqualTo(200); - assertThat(response.headers.asMap()) + assertThat(response.headers.asMultiMap()) .containsExactly( - RtspHeaders.CSEQ, - "3", - RtspHeaders.CONTENT_BASE, - "rtsp://127.0.0.1/test.mkv/", - RtspHeaders.CONTENT_TYPE, - "application/sdp", - RtspHeaders.CONTENT_LENGTH, - "707"); + RtspHeaders.CSEQ, "3", + RtspHeaders.CONTENT_BASE, "rtsp://127.0.0.1/test.mkv/", + RtspHeaders.CONTENT_TYPE, "application/sdp", + RtspHeaders.CONTENT_LENGTH, "707"); assertThat(response.messageBody) .isEqualTo( @@ -135,6 +131,30 @@ public final class RtspMessageUtilTest { + "a=control:track2"); } + @Test + public void parseResponse_with401DescribeResponse_succeeds() { + List responseLines = + Arrays.asList( + "RTSP/1.0 401 Unauthorized", + "CSeq: 3", + "WWW-Authenticate: BASIC realm=\"wow\"", + "WWW-Authenticate: DIGEST realm=\"wow\", nonce=\"nonce\"", + ""); + RtspResponse response = RtspMessageUtil.parseResponse(responseLines); + ListMultimap headersMap = response.headers.asMultiMap(); + + assertThat(response.status).isEqualTo(401); + + assertThat(headersMap.keySet()).containsExactly("cseq", "www-authenticate").inOrder(); + assertThat(headersMap).valuesForKey("cseq").containsExactly("3"); + assertThat(headersMap) + .valuesForKey("www-authenticate") + .containsExactly("BASIC realm=\"wow\"", "DIGEST realm=\"wow\", nonce=\"nonce\"") + .inOrder(); + + assertThat(response.messageBody).isEmpty(); + } + @Test public void parseRequest_withSetParameterRequest_succeeds() { List requestLines = @@ -149,16 +169,12 @@ public final class RtspMessageUtilTest { RtspRequest request = RtspMessageUtil.parseRequest(requestLines); assertThat(request.method).isEqualTo(RtspRequest.METHOD_SET_PARAMETER); - assertThat(request.headers.asMap()) + assertThat(request.headers.asMultiMap()) .containsExactly( - RtspHeaders.CSEQ, - "3", - RtspHeaders.USER_AGENT, - "LibVLC/3.0.11", - RtspHeaders.CONTENT_LENGTH, - "20", - RtspHeaders.CONTENT_TYPE, - "text/parameters"); + RtspHeaders.CSEQ, "3", + RtspHeaders.USER_AGENT, "LibVLC/3.0.11", + RtspHeaders.CONTENT_LENGTH, "20", + RtspHeaders.CONTENT_TYPE, "text/parameters"); assertThat(request.messageBody).isEqualTo("param: stuff"); } @@ -176,14 +192,11 @@ public final class RtspMessageUtilTest { RtspResponse response = RtspMessageUtil.parseResponse(responseLines); assertThat(response.status).isEqualTo(200); - assertThat(response.headers.asMap()) + assertThat(response.headers.asMultiMap()) .containsExactly( - RtspHeaders.CSEQ, - "431", - RtspHeaders.CONTENT_LENGTH, - "46", - RtspHeaders.CONTENT_TYPE, - "text/parameters"); + RtspHeaders.CSEQ, "431", + RtspHeaders.CONTENT_LENGTH, "46", + RtspHeaders.CONTENT_TYPE, "text/parameters"); assertThat(response.messageBody).isEqualTo("packets_received: 10\r\n" + "jitter: 0.3838"); } @@ -206,14 +219,14 @@ public final class RtspMessageUtilTest { List expectedLines = Arrays.asList( "SETUP rtsp://127.0.0.1/test.mkv/track1 RTSP/1.0", - "CSeq: 4", - "Transport: RTP/AVP;unicast;client_port=65458-65459", + "cseq: 4", + "transport: RTP/AVP;unicast;client_port=65458-65459", "", ""); String expectedRtspMessage = "SETUP rtsp://127.0.0.1/test.mkv/track1 RTSP/1.0\r\n" - + "CSeq: 4\r\n" - + "Transport: RTP/AVP;unicast;client_port=65458-65459\r\n" + + "cseq: 4\r\n" + + "transport: RTP/AVP;unicast;client_port=65458-65459\r\n" + "\r\n"; assertThat(messageLines).isEqualTo(expectedLines); @@ -240,14 +253,14 @@ public final class RtspMessageUtilTest { List expectedLines = Arrays.asList( "RTSP/1.0 200 OK", - "CSeq: 4", - "Transport: RTP/AVP;unicast;client_port=65458-65459;server_port=5354-5355", + "cseq: 4", + "transport: RTP/AVP;unicast;client_port=65458-65459;server_port=5354-5355", "", ""); String expectedRtspMessage = "RTSP/1.0 200 OK\r\n" - + "CSeq: 4\r\n" - + "Transport: RTP/AVP;unicast;client_port=65458-65459;server_port=5354-5355\r\n" + + "cseq: 4\r\n" + + "transport: RTP/AVP;unicast;client_port=65458-65459;server_port=5354-5355\r\n" + "\r\n"; assertThat(messageLines).isEqualTo(expectedLines); assertThat(RtspMessageUtil.convertMessageToByteArray(messageLines)) @@ -282,10 +295,10 @@ public final class RtspMessageUtilTest { List expectedLines = Arrays.asList( "RTSP/1.0 200 OK", - "CSeq: 4", - "Content-Base: rtsp://127.0.0.1/test.mkv/", - "Content-Type: application/sdp", - "Content-Length: 707", + "cseq: 4", + "content-base: rtsp://127.0.0.1/test.mkv/", + "content-type: application/sdp", + "content-length: 707", "", "v=0\r\n" + "o=- 1606776316530225 1 IN IP4 192.168.2.176\r\n" @@ -299,10 +312,10 @@ public final class RtspMessageUtilTest { String expectedRtspMessage = "RTSP/1.0 200 OK\r\n" - + "CSeq: 4\r\n" - + "Content-Base: rtsp://127.0.0.1/test.mkv/\r\n" - + "Content-Type: application/sdp\r\n" - + "Content-Length: 707\r\n" + + "cseq: 4\r\n" + + "content-base: rtsp://127.0.0.1/test.mkv/\r\n" + + "content-type: application/sdp\r\n" + + "content-length: 707\r\n" + "\r\n" + "v=0\r\n" + "o=- 1606776316530225 1 IN IP4 192.168.2.176\r\n" @@ -328,8 +341,8 @@ public final class RtspMessageUtilTest { /* messageBody= */ ""); List messageLines = RtspMessageUtil.serializeResponse(response); - List expectedLines = Arrays.asList("RTSP/1.0 454 Session Not Found", "CSeq: 4", "", ""); - String expectedRtspMessage = "RTSP/1.0 454 Session Not Found\r\n" + "CSeq: 4\r\n" + "\r\n"; + List expectedLines = Arrays.asList("RTSP/1.0 454 Session Not Found", "cseq: 4", "", ""); + String expectedRtspMessage = "RTSP/1.0 454 Session Not Found\r\n" + "cseq: 4\r\n" + "\r\n"; assertThat(RtspMessageUtil.serializeResponse(response)).isEqualTo(expectedLines); assertThat(RtspMessageUtil.convertMessageToByteArray(messageLines))