package org.red5.server.net.rtsp.codec; /* * RED5 Open Source Flash Server - http://www.osflash.org/red5 * * Copyright (c) 2006-2008 by respective authors (see below). All rights reserved. * * This library is free software; you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free Software * Foundation; either version 2.1 of the License, or (at your option) any later * version. * * This library is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License along * with this library; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * Copyright (C) 2005 - Matteo Merli - matteo.merli@gmail.com * * * ***************************************************************************/ import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; import java.net.URL; import java.nio.CharBuffer; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.mina.core.buffer.IoBuffer; import org.apache.mina.core.session.IoSession; import org.apache.mina.filter.codec.ProtocolDecoder; import org.apache.mina.filter.codec.ProtocolDecoderException; import org.apache.mina.filter.codec.ProtocolDecoderOutput; import org.red5.server.net.rtsp.RTSPCode; import org.red5.server.net.rtsp.RTSPRequest; import org.red5.server.net.rtsp.RTSPResponse; import org.red5.server.net.rtsp.messages.RTSPMessage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @author Matteo Merli (matteo.merli@gmail.com) */ public class RTSPDecoder implements ProtocolDecoder { /** * State enumerator that indicates the reached state in the RTSP message * decoding process. */ public enum ReadState { /** Unrecoverable error occurred */ Failed, /** Trying to resync */ Sync, /** Waiting for a command */ Ready, /** Reading interleaved packet */ Packet, /** Reading command (request or command line) */ Command, /** Reading headers */ Header, /** Reading body (entity) */ Body, /** Fully formed message */ Dispatch } private static Logger log = LoggerFactory.getLogger(RTSPDecoder.class); private static final Pattern rtspRequestPattern = Pattern .compile("([A-Z_]+) ([^ ]+) RTSP/1.0"); private static final Pattern rtspResponsePattern = Pattern .compile("RTSP/1.0 ([0-9]+) .+"); private static final Pattern rtspHeaderPattern = Pattern .compile("([a-zA-Z\\-]+[0-9]?):\\s?(.*)"); /** * Do the parsing on the incoming stream. If the stream does not contain the * entire RTSP message wait for other data to arrive, before dispatching the * message. * */ public void decode(IoSession session, IoBuffer buffer, ProtocolDecoderOutput out) throws ProtocolDecoderException { BufferedReader reader = null; try { reader = new BufferedReader(new InputStreamReader(buffer .asInputStream(), "US-ASCII")); } catch (UnsupportedEncodingException e1) { } // Retrieve status from session ReadState state = (ReadState) session.getAttribute("state"); if (state == null) state = ReadState.Command; RTSPMessage rtspMessage = (RTSPMessage) session .getAttribute("rtspMessage"); try { while (true) { if (state != ReadState.Command && state != ReadState.Header) // the "while" loop is only used to read commands and // headers break; String line = reader.readLine(); if (line == null) // there's no more data in the buffer break; if (line.length() == 0) { // This is the empty line that marks the end // of the headers section state = ReadState.Body; break; } switch (state) { case Command: // log.debug( "Command line: " + line ); if (line.startsWith("RTSP")) { // this is a RTSP response Matcher m = rtspResponsePattern.matcher(line); if (!m.matches()) throw new ProtocolDecoderException( "Malformed response line: " + line); RTSPCode code = RTSPCode.fromString(m.group(1)); rtspMessage = new RTSPResponse(); ((RTSPResponse) (rtspMessage)).setCode(code); RTSPRequest.Verb verb = (RTSPRequest.Verb) session .getAttribute("lastRequestVerb"); ((RTSPResponse) (rtspMessage)).setRequestVerb(verb); } else { // this is a RTSP request Matcher m = rtspRequestPattern.matcher(line); if (!m.matches()) throw new ProtocolDecoderException( "Malformed request line: " + line); String verb = m.group(1); String strUrl = m.group(2); URL url = null; if (!strUrl.equalsIgnoreCase("*")) { try { url = new URL(strUrl); } catch (MalformedURLException e) { log.error("", e); url = null; session.setAttribute("state", ReadState.Failed); throw new ProtocolDecoderException( "Invalid URL"); } } rtspMessage = new RTSPRequest(); ((RTSPRequest) rtspMessage).setVerb(verb); if (((RTSPRequest) rtspMessage).getVerb() == RTSPRequest.Verb.None) { session.setAttribute("state", ReadState.Failed); throw new ProtocolDecoderException( "Invalid method: " + verb); } ((RTSPRequest) rtspMessage).setUrl(url); } state = ReadState.Header; break; case Header: // this is an header Matcher m = rtspHeaderPattern.matcher(line); if (!m.matches()) throw new ProtocolDecoderException( "RTSP header not valid"); rtspMessage.setHeader(m.group(1), m.group(2)); break; } } if (state == ReadState.Body) { // Read the message body int bufferLen = Integer.parseInt(rtspMessage.getHeader( "Content-Length", "0")); if (bufferLen == 0) { // there's no buffer to be read state = ReadState.Dispatch; } else { // we have a content buffer to read int bytesToRead = bufferLen - rtspMessage.getBufferSize(); // if ( bytesToRead < reader. decodeBuf.length() ) { // log.warn( "We are reading more bytes than // Content-Length." ); // } // read the content buffer CharBuffer bufferContent = CharBuffer.allocate(bytesToRead); reader.read(bufferContent); bufferContent.flip(); rtspMessage.appendToBuffer(bufferContent); if (rtspMessage.getBufferSize() >= bufferLen) { // The RTSP message parsing is completed state = ReadState.Dispatch; } } } } catch (IOException e) { /* * error on input stream should not happen since the input stream is * coming from a bytebuffer. */ log.error("", e); return; } finally { try { reader.close(); } catch (Exception e) { } } if (state == ReadState.Dispatch) { // The message is already formed // send it session.removeAttribute("state"); session.removeAttribute("rtspMessage"); out.write(rtspMessage); return; } // log.debug( "INCOMPLETE MESSAGE \n" + rtspMessage ); // Save attributes in session session.setAttribute("state", state); session.setAttribute("rtspMessage", rtspMessage); } /* * (non-Javadoc) * * @see org.apache.mina.filter.codec.ProtocolDecoder#dispose(org.apache.mina.common.IoSession) */ public void dispose(IoSession session) throws Exception { // Do nothing } /** {@inheritDoc} */ public void finishDecode(IoSession session, ProtocolDecoderOutput out) throws Exception { // TODO Auto-generated method stub } }