/** * */ package video.lib; import java.io.BufferedReader; import java.io.IOException; import java.io.StringReader; import java.net.URI; import java.net.URISyntaxException; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.util.regex.Matcher; import org.apache.mina.core.buffer.IoBuffer; import org.apache.mina.core.session.IoSession; import org.apache.mina.filter.codec.ProtocolDecoderOutput; import org.apache.mina.filter.codec.demux.MessageDecoderAdapter; import org.apache.mina.filter.codec.demux.MessageDecoderResult; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * */ public class RtspDecoder extends MessageDecoderAdapter { private static final Logger LOGGER = LoggerFactory.getLogger(RtspDecoder.class); private final CharsetDecoder decoder; private static final byte[] CONTENT_LENGTH = new String("Content-Length:").getBytes(); public RtspDecoder(Charset charset) { decoder = charset.newDecoder(); } /** * Check to see if the message received so far is complete. The IoBuffer will * not change. * * @param in * @return * @throws Exception */ private boolean messageComplete(IoBuffer in) throws Exception { int last = in.remaining() - 1; if (in.remaining() < 4) { return false; } // first the position of the 0x0D 0x0A 0x0D 0x0A bytes // 0x0D 0x0A 0x0D 0x0A is ACSII code for \r\n. int eoh = -1; // End of header for (int i = last; i > 2; i--) { if (in.get(i) == (byte) 0x0A && in.get(i - 1) == (byte) 0x0D && in.get(i - 2) == (byte) 0x0A && in.get(i - 3) == (byte) 0x0D) { eoh = i + 1; break; } } if (eoh == -1) { // There is no CRLF in the message. return false; } // Search for Content-Length. // If the Content-Length is present, the length of the body must equal to // the value specified in the Content-Length. // Else the Content-Length is 0, and the message is considered compete. for (int i = 0; i < last; i++) { boolean found = false; for (int j = 0; j < CONTENT_LENGTH.length; j++) { if (in.get(i + j) != CONTENT_LENGTH[j]) { found = false; break; } found = true; } // If Content-Length is present, we check if the length of body equals to // the value. if (found) { // retrieve value from this position till next 0x0D 0x0A, which will be // the content length of body. StringBuilder contentLength = new StringBuilder(); for (int j = i + CONTENT_LENGTH.length; j < last; j++) { if (in.get(j) == 0x0D) { break; } contentLength.append(new String(new byte[] { in.get(j) })); } // if content-length worth of data has been received then the message is complete return Integer.parseInt(contentLength.toString().trim()) + eoh == in.remaining(); } } // No Content-Length is present, we will assume the length of body is 0 and // simple return true. return true; } /** * Once a message is complete and decodable, then parse RTSP message and save * it into an RTSP object. * * @param in * @return */ private RtspMessage doParse(IoBuffer in) { RtspMessage message = null; try { BufferedReader rdr = new BufferedReader(new StringReader(in.getString(decoder))); String line = null; Matcher matcher = null; // first line. line = rdr.readLine(); // LOGGER.debug("Parsing first line."); if (line.startsWith(RtspMessage.VERSION)) { // It is an RTSP response. // LOGGER.debug("RTSP response."); message = new RtspResponse(); matcher = RtspMessage.RtspResponsePattern.matcher(line); if (!matcher.matches()) { // Request line is ill-formatted. LOGGER.debug("Request line is mal-formatted: " + line); return null; } ((RtspResponse) message).setStatusCode(RtspStatusCode.fromCode(matcher.group(2))); } else { // It is an RTSP request. // LOGGER.debug("RTSP request."); message = new RtspRequest(); matcher = RtspMessage.RtspRequestPattern.matcher(line); if (!matcher.matches()) { // Status line is ill-formatted. LOGGER.debug("Status line is mal-formatted: " + line); return null; } ((RtspRequest) message).setMethod(RtspMethod.fromString(matcher.group(1))); String uriString = matcher.group(2); // ((RtspRequest)message).setRtspVersion(matcher.group(3)); // logger.debug("uri: " + uriString); if (uriString.equals("*")) { ((RtspRequest) message).setRequestUri(null); } else { URI uri = new URI(uriString); ((RtspRequest) message).setRequestUri(uri); } } // LOGGER.debug("Parsing header lines."); while ((line = rdr.readLine()) != null && (!line.equals(""))) { matcher = RtspMessage.RtspHeaderPattern.matcher(line); if (!matcher.matches()) { // Header line is ill-formatted. LOGGER.debug("Header line is mal-formatted: " + line); return null; } // Set each parsed field into message header. message.setField(matcher.group(1), matcher.group(2)); // logger.debug("header group1: " + matcher.group(1)); // logger.debug("header group2: " + matcher.group(2)); } // LOGGER.debug("Parsing body lines."); while ((line = rdr.readLine()) != null && (!line.equals(""))) { message.appendBody(line); message.appendBody(RtspMessage.CRLF); } rdr.close(); LOGGER.debug("RTSP message parse done."); return message; } catch (URISyntaxException ex) { LOGGER.error("RTSP message parsing error: " + ex.toString()); } catch (IOException ex) { LOGGER.error("RTSP message parsing error: " + ex.toString()); } return null; } @Override public MessageDecoderResult decodable(IoSession session, IoBuffer in) { // Return NEED_DATA if the whole header is not read yet. try { return messageComplete(in) ? MessageDecoderResult.OK : MessageDecoderResult.NEED_DATA; } catch (Exception ex) { ex.printStackTrace(); } return MessageDecoderResult.NOT_OK; } @Override public MessageDecoderResult decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception { LOGGER.debug("RTSP message is complete. Start parsing."); RtspMessage m = doParse(in); // Return NEED_DATA if the body is not fully read. if (m == null) { return MessageDecoderResult.NEED_DATA; } out.write(m); LOGGER.debug("Write done."); return MessageDecoderResult.OK; } }