Commit e088cb43 by claincly Committed by Ian Baker

Handle RTSP request by replying Method Not Allowed.

PiperOrigin-RevId: 395438728
parent d9bc2231
...@@ -34,6 +34,7 @@ import static com.google.android.exoplayer2.util.Assertions.checkNotNull; ...@@ -34,6 +34,7 @@ import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import static com.google.android.exoplayer2.util.Assertions.checkState; import static com.google.android.exoplayer2.util.Assertions.checkState;
import static com.google.android.exoplayer2.util.Assertions.checkStateNotNull; import static com.google.android.exoplayer2.util.Assertions.checkStateNotNull;
import static com.google.common.base.Strings.nullToEmpty; import static com.google.common.base.Strings.nullToEmpty;
import static java.lang.Math.max;
import android.net.Uri; import android.net.Uri;
import android.os.Handler; import android.os.Handler;
...@@ -376,18 +377,23 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -376,18 +377,23 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
lastRequest.method, sessionId, lastRequestHeaders, lastRequest.uri)); lastRequest.method, sessionId, lastRequestHeaders, lastRequest.uri));
} }
public void sendMethodNotAllowedResponse(int cSeq) {
// RTSP status code 405: Method Not Allowed (RFC2326 Section 7.1.1).
sendResponse(
new RtspResponse(
/* status= */ 405, new RtspHeaders.Builder(userAgent, sessionId, cSeq).build()));
// The server could send a cSeq that is larger than the current stored cSeq. To maintain a
// monotonically increasing cSeq number, this.cSeq needs to be reset to server's cSeq + 1.
this.cSeq = max(this.cSeq, cSeq + 1);
}
private RtspRequest getRequestWithCommonHeaders( private RtspRequest getRequestWithCommonHeaders(
@RtspRequest.Method int method, @RtspRequest.Method int method,
@Nullable String sessionId, @Nullable String sessionId,
Map<String, String> additionalHeaders, Map<String, String> additionalHeaders,
Uri uri) { Uri uri) {
RtspHeaders.Builder headersBuilder = new RtspHeaders.Builder(); RtspHeaders.Builder headersBuilder = new RtspHeaders.Builder(userAgent, sessionId, cSeq++);
headersBuilder.add(RtspHeaders.CSEQ, String.valueOf(cSeq++));
headersBuilder.add(RtspHeaders.USER_AGENT, userAgent);
if (sessionId != null) {
headersBuilder.add(RtspHeaders.SESSION, sessionId);
}
if (rtspAuthenticationInfo != null) { if (rtspAuthenticationInfo != null) {
checkStateNotNull(rtspAuthUserInfo); checkStateNotNull(rtspAuthUserInfo);
...@@ -413,6 +419,12 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -413,6 +419,12 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
messageChannel.send(message); messageChannel.send(message);
lastRequest = request; lastRequest = request;
} }
private void sendResponse(RtspResponse response) {
List<String> message = RtspMessageUtil.serializeResponse(response);
maybeLogMessage(message);
messageChannel.send(message);
}
} }
private final class MessageListener implements RtspMessageChannel.MessageListener { private final class MessageListener implements RtspMessageChannel.MessageListener {
...@@ -436,6 +448,23 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -436,6 +448,23 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private void handleRtspMessage(List<String> message) { private void handleRtspMessage(List<String> message) {
maybeLogMessage(message); maybeLogMessage(message);
if (RtspMessageUtil.isRtspResponse(message)) {
handleRtspResponse(message);
} else {
handleRtspRequest(message);
}
}
private void handleRtspRequest(List<String> message) {
// Handling RTSP requests on the client is optional (RFC2326 Section 10). Decline all
// requests with 'Method Not Allowed'.
messageSender.sendMethodNotAllowedResponse(
Integer.parseInt(
checkNotNull(RtspMessageUtil.parseRequest(message).headers.get(RtspHeaders.CSEQ))));
}
private void handleRtspResponse(List<String> message) {
RtspResponse response = RtspMessageUtil.parseResponse(message); RtspResponse response = RtspMessageUtil.parseResponse(message);
int cSeq = Integer.parseInt(checkNotNull(response.headers.get(RtspHeaders.CSEQ))); int cSeq = Integer.parseInt(checkNotNull(response.headers.get(RtspHeaders.CSEQ)));
......
...@@ -79,6 +79,23 @@ import java.util.Map; ...@@ -79,6 +79,23 @@ import java.util.Map;
} }
/** /**
* Creates a new instance with common header values.
*
* @param userAgent The user agent string.
* @param sessionId The RTSP session ID; use {@code null} when the session is not yet set up.
* @param cSeq The RTSP cSeq sequence number.
*/
public Builder(String userAgent, @Nullable String sessionId, int cSeq) {
this();
add(USER_AGENT, userAgent);
add(CSEQ, String.valueOf(cSeq));
if (sessionId != null) {
add(SESSION, sessionId);
}
}
/**
* Creates a new instance to build upon the provided {@link RtspHeaders}. * Creates a new instance to build upon the provided {@link RtspHeaders}.
* *
* @param namesAndValuesBuilder A {@link ImmutableListMultimap.Builder} that this builder builds * @param namesAndValuesBuilder A {@link ImmutableListMultimap.Builder} that this builder builds
......
...@@ -114,10 +114,15 @@ import java.util.regex.Pattern; ...@@ -114,10 +114,15 @@ import java.util.regex.Pattern;
/** /**
* Serializes an {@link RtspRequest} to an {@link ImmutableList} of strings. * Serializes an {@link RtspRequest} to an {@link ImmutableList} of strings.
* *
* <p>The {@link RtspRequest} must include the {@link RtspHeaders#CSEQ} header, or this method
* throws {@link IllegalArgumentException}.
*
* @param request The {@link RtspRequest}. * @param request The {@link RtspRequest}.
* @return A list of the lines of the {@link RtspRequest}, without line terminators (CRLF). * @return A list of the lines of the {@link RtspRequest}, without line terminators (CRLF).
*/ */
public static ImmutableList<String> serializeRequest(RtspRequest request) { public static ImmutableList<String> serializeRequest(RtspRequest request) {
checkArgument(request.headers.get(RtspHeaders.CSEQ) != null);
ImmutableList.Builder<String> builder = new ImmutableList.Builder<>(); ImmutableList.Builder<String> builder = new ImmutableList.Builder<>();
// Request line. // Request line.
builder.add( builder.add(
...@@ -140,10 +145,15 @@ import java.util.regex.Pattern; ...@@ -140,10 +145,15 @@ import java.util.regex.Pattern;
/** /**
* Serializes an {@link RtspResponse} to an {@link ImmutableList} of strings. * Serializes an {@link RtspResponse} to an {@link ImmutableList} of strings.
* *
* <p>The {@link RtspResponse} must include the {@link RtspHeaders#CSEQ} header, or this method
* throws {@link IllegalArgumentException}.
*
* @param response The {@link RtspResponse}. * @param response The {@link RtspResponse}.
* @return A list of the lines of the {@link RtspResponse}, without line terminators (CRLF). * @return A list of the lines of the {@link RtspResponse}, without line terminators (CRLF).
*/ */
public static ImmutableList<String> serializeResponse(RtspResponse response) { public static ImmutableList<String> serializeResponse(RtspResponse response) {
checkArgument(response.headers.get(RtspHeaders.CSEQ) != null);
ImmutableList.Builder<String> builder = new ImmutableList.Builder<>(); ImmutableList.Builder<String> builder = new ImmutableList.Builder<>();
// Request line. // Request line.
builder.add( builder.add(
...@@ -327,6 +337,16 @@ import java.util.regex.Pattern; ...@@ -327,6 +337,16 @@ import java.util.regex.Pattern;
|| STATUS_LINE_PATTERN.matcher(line).matches(); || STATUS_LINE_PATTERN.matcher(line).matches();
} }
/**
* Returns whether the RTSP message is an RTSP response.
*
* @param lines The non-empty list of received lines, with line terminators removed.
* @return Whether the lines represent an RTSP response.
*/
public static boolean isRtspResponse(List<String> lines) {
return STATUS_LINE_PATTERN.matcher(lines.get(0)).matches();
}
/** Returns the lines in an RTSP message body split by the line terminator used in body. */ /** Returns the lines in an RTSP message body split by the line terminator used in body. */
public static String[] splitRtspMessageBody(String body) { public static String[] splitRtspMessageBody(String body) {
return Util.split(body, body.contains(CRLF) ? CRLF : LF); return Util.split(body, body.contains(CRLF) ? CRLF : LF);
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
package com.google.android.exoplayer2.source.rtsp; package com.google.android.exoplayer2.source.rtsp;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import android.net.Uri; import android.net.Uri;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
...@@ -336,6 +337,52 @@ public final class RtspMessageUtilTest { ...@@ -336,6 +337,52 @@ public final class RtspMessageUtilTest {
} }
@Test @Test
public void serialize_requestWithoutCseqHeader_throwsIllegalArgumentException() {
RtspRequest request =
new RtspRequest(
Uri.parse("rtsp://127.0.0.1/test.mkv/track1"),
RtspRequest.METHOD_OPTIONS,
RtspHeaders.EMPTY,
/* messageBody= */ "");
assertThrows(IllegalArgumentException.class, () -> RtspMessageUtil.serializeRequest(request));
}
@Test
public void serialize_responseWithoutCseqHeader_throwsIllegalArgumentException() {
RtspResponse response = new RtspResponse(/* status= */ 200, RtspHeaders.EMPTY);
assertThrows(IllegalArgumentException.class, () -> RtspMessageUtil.serializeResponse(response));
}
@Test
public void isRtspResponse_withSuccessfulRtspResponse_returnsTrue() {
List<String> responseLines =
Arrays.asList(
"RTSP/1.0 200 OK",
"CSeq: 2",
"Public: OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE, GET_PARAMETER, SET_PARAMETER",
"");
assertThat(RtspMessageUtil.isRtspResponse(responseLines)).isTrue();
}
@Test
public void isRtspResponse_withUnsuccessfulRtspResponse_returnsTrue() {
List<String> responseLines = Arrays.asList("RTSP/1.0 405 Method Not Allowed", "CSeq: 2", "");
assertThat(RtspMessageUtil.isRtspResponse(responseLines)).isTrue();
}
@Test
public void isRtspResponse_withRtspRequest_returnsFalse() {
List<String> requestLines =
Arrays.asList("OPTIONS rtsp://localhost:554/foo.bar RTSP/1.0", "CSeq: 2", "");
assertThat(RtspMessageUtil.isRtspResponse(requestLines)).isFalse();
}
@Test
public void serialize_failedResponse_succeeds() { public void serialize_failedResponse_succeeds() {
RtspResponse response = RtspResponse response =
new RtspResponse( new RtspResponse(
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment