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;
import static com.google.android.exoplayer2.util.Assertions.checkState;
import static com.google.android.exoplayer2.util.Assertions.checkStateNotNull;
import static com.google.common.base.Strings.nullToEmpty;
import static java.lang.Math.max;
import android.net.Uri;
import android.os.Handler;
......@@ -376,18 +377,23 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
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(
@RtspRequest.Method int method,
@Nullable String sessionId,
Map<String, String> additionalHeaders,
Uri uri) {
RtspHeaders.Builder headersBuilder = new RtspHeaders.Builder();
headersBuilder.add(RtspHeaders.CSEQ, String.valueOf(cSeq++));
headersBuilder.add(RtspHeaders.USER_AGENT, userAgent);
if (sessionId != null) {
headersBuilder.add(RtspHeaders.SESSION, sessionId);
}
RtspHeaders.Builder headersBuilder = new RtspHeaders.Builder(userAgent, sessionId, cSeq++);
if (rtspAuthenticationInfo != null) {
checkStateNotNull(rtspAuthUserInfo);
......@@ -413,6 +419,12 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
messageChannel.send(message);
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 {
......@@ -436,6 +448,23 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private void handleRtspMessage(List<String> 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);
int cSeq = Integer.parseInt(checkNotNull(response.headers.get(RtspHeaders.CSEQ)));
......
......@@ -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}.
*
* @param namesAndValuesBuilder A {@link ImmutableListMultimap.Builder} that this builder builds
......
......@@ -114,10 +114,15 @@ import java.util.regex.Pattern;
/**
* 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}.
* @return A list of the lines of the {@link RtspRequest}, without line terminators (CRLF).
*/
public static ImmutableList<String> serializeRequest(RtspRequest request) {
checkArgument(request.headers.get(RtspHeaders.CSEQ) != null);
ImmutableList.Builder<String> builder = new ImmutableList.Builder<>();
// Request line.
builder.add(
......@@ -140,10 +145,15 @@ import java.util.regex.Pattern;
/**
* 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}.
* @return A list of the lines of the {@link RtspResponse}, without line terminators (CRLF).
*/
public static ImmutableList<String> serializeResponse(RtspResponse response) {
checkArgument(response.headers.get(RtspHeaders.CSEQ) != null);
ImmutableList.Builder<String> builder = new ImmutableList.Builder<>();
// Request line.
builder.add(
......@@ -327,6 +337,16 @@ import java.util.regex.Pattern;
|| 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. */
public static String[] splitRtspMessageBody(String body) {
return Util.split(body, body.contains(CRLF) ? CRLF : LF);
......
......@@ -17,6 +17,7 @@
package com.google.android.exoplayer2.source.rtsp;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import android.net.Uri;
import androidx.annotation.Nullable;
......@@ -336,6 +337,52 @@ public final class RtspMessageUtilTest {
}
@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() {
RtspResponse response =
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