package org.red5.server.net.rtmp;
/*
* RED5 Open Source Flash Server - http://www.osflash.org/red5
*
* Copyright (c) 2006-2009 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
*/
import org.apache.mina.common.ByteBuffer;
import org.apache.mina.common.IoHandlerAdapter;
import org.apache.mina.common.IoSession;
import org.apache.mina.filter.LoggingFilter;
import org.apache.mina.filter.codec.ProtocolCodecFactory;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.red5.server.api.Red5;
import org.red5.server.net.protocol.ProtocolState;
import org.red5.server.net.rtmp.codec.RTMP;
import org.red5.server.net.rtmp.message.Constants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
/**
* Handles all RTMP protocol events fired by the MINA framework.
*/
public class RTMPMinaIoHandler extends IoHandlerAdapter implements
ApplicationContextAware {
/**
* Logger
*/
private static Logger log = LoggerFactory
.getLogger(RTMPMinaIoHandler.class);
/**
* RTMP events handler
*/
private IRTMPHandler handler;
/**
* Mode
*/
private boolean mode = RTMP.MODE_SERVER;
/**
* Application context
*/
@SuppressWarnings("unused")
private ApplicationContext appCtx;
/**
* RTMP protocol codec factory
*/
private ProtocolCodecFactory codecFactory = null;
private IRTMPConnManager rtmpConnManager;
/**
* Setter for handler.
*
* @param handler RTMP events handler
*/
public void setHandler(IRTMPHandler handler) {
this.handler = handler;
}
/**
* Setter for mode.
*
* @param mode <code>true</code> if handler should work in server mode,
* <code>false</code> otherwise
*/
public void setMode(boolean mode) {
this.mode = mode;
}
/**
* Setter for codec factory.
*
* @param codecFactory RTMP protocol codec factory
*/
public void setCodecFactory(ProtocolCodecFactory codecFactory) {
this.codecFactory = codecFactory;
}
public void setRtmpConnManager(IRTMPConnManager rtmpConnManager) {
this.rtmpConnManager = rtmpConnManager;
}
protected IRTMPConnManager getRtmpConnManager() {
return rtmpConnManager;
}
/** {@inheritDoc} */
@Override
public void exceptionCaught(IoSession session, Throwable cause)
throws Exception {
log.error("Exception caught", cause);
}
/** {@inheritDoc} */
@Override
public void messageReceived(IoSession session, Object in) throws Exception {
log.debug("messageRecieved");
final ProtocolState state = (ProtocolState) session
.getAttribute(ProtocolState.SESSION_KEY);
if (in instanceof ByteBuffer) {
rawBufferRecieved(state, (ByteBuffer) in, session);
return;
}
final RTMPMinaConnection conn = (RTMPMinaConnection) session
.getAttachment();
handler.messageReceived(conn, state, in);
}
/**
* Handle raw buffer receiving event.
*
* @param state
* Protocol state
* @param in
* Data buffer
* @param session
* I/O session, that is, connection between two endpoints
*/
protected void rawBufferRecieved(ProtocolState state, ByteBuffer in,
IoSession session) {
final RTMP rtmp = (RTMP) state;
ByteBuffer out = null;
final RTMPMinaConnection conn = (RTMPMinaConnection) session
.getAttachment();
conn.getWriteLock().lock();
try {
if (rtmp.getMode() == RTMP.MODE_SERVER) {
if (rtmp.getState() != RTMP.STATE_HANDSHAKE) {
log.warn("Raw buffer after handshake, something odd going on");
}
log.debug("Handshake 2nd phase - size: {}", in.remaining());
//if the 5th byte is 0 then dont generate new-style handshake
//byte[] bIn = in.array();
//log.debug("First few bytes (in): {},{},{},{},{},{},{},{},{},{}", new Object[]{bIn[0],bIn[1],bIn[2],bIn[3],bIn[4],bIn[5],bIn[6],bIn[7],bIn[8],bIn[9]});
if (in.get(4) == 0) {
log.debug("Using old style handshake");
out = ByteBuffer.allocate((Constants.HANDSHAKE_SIZE * 2) + 1);
out.put((byte) 0x03);
// set server uptime in seconds
out.putInt((int) Red5.getUpTime() / 1000); //0x01
out.put(RTMPHandshake.HANDSHAKE_PAD_BYTES).put(in).flip();
} else {
log.debug("Using new style handshake");
RTMPHandshake shake = new RTMPHandshake();
out = shake.generateResponse(in);
}
//byte[] bOut = out.array();
//log.debug("First few bytes (out): {},{},{},{},{},{},{},{},{},{}", new Object[]{bOut[0],bOut[1],bOut[2],bOut[3],bOut[4],bOut[5],bOut[6],bOut[7],bOut[8],bOut[9]});
// Skip first 8 bytes when comparing the handshake, they seem to
// be changed when connecting from a Mac client.
rtmp.setHandshake(out, 9, Constants.HANDSHAKE_SIZE-8);
} else {
log.debug("Handshake 3d phase - size: {}", in.remaining());
in.skip(1);
out = ByteBuffer.allocate(Constants.HANDSHAKE_SIZE);
int limit = in.limit();
in.limit(in.position() + Constants.HANDSHAKE_SIZE);
out.put(in);
out.flip();
in.limit(limit);
in.skip(Constants.HANDSHAKE_SIZE);
}
} finally {
conn.getWriteLock().unlock();
}
if (out != null) {
session.write(out);
}
}
/** {@inheritDoc} */
@Override
public void messageSent(IoSession session, Object message) throws Exception {
log.debug("messageSent");
session.getAttribute(ProtocolState.SESSION_KEY);
final RTMPMinaConnection conn = (RTMPMinaConnection) session
.getAttachment();
handler.messageSent(conn, message);
if (mode == RTMP.MODE_CLIENT) {
if (message instanceof ByteBuffer) {
if (((ByteBuffer) message).limit() == Constants.HANDSHAKE_SIZE) {
handler.connectionOpened(conn, (RTMP) session
.getAttribute(ProtocolState.SESSION_KEY));
}
}
}
}
/** {@inheritDoc} */
@Override
public void sessionOpened(IoSession session) throws Exception {
super.sessionOpened(session);
RTMP rtmp = (RTMP) session.getAttribute(ProtocolState.SESSION_KEY);
if (rtmp.getMode() == RTMP.MODE_CLIENT) {
log.debug("Handshake 1st phase");
ByteBuffer out = ByteBuffer.allocate(Constants.HANDSHAKE_SIZE + 1);
out.put((byte) 0x03);
out.put(RTMPHandshake.getHandshakeBytes());
out.flip();
session.write(out);
} else {
final RTMPMinaConnection conn = (RTMPMinaConnection) session
.getAttachment();
handler.connectionOpened(conn, rtmp);
}
}
/** {@inheritDoc} */
@Override
public void sessionClosed(IoSession session) throws Exception {
final RTMP rtmp = (RTMP) session
.getAttribute(ProtocolState.SESSION_KEY);
final RTMPMinaConnection conn = (RTMPMinaConnection) session
.getAttachment();
this.handler.connectionClosed(conn, rtmp);
session.removeAttribute(ProtocolState.SESSION_KEY);
session.setAttachment(null);
rtmpConnManager.removeConnection(conn.getId());
}
/** {@inheritDoc} */
@Override
public void sessionCreated(IoSession session) throws Exception {
log.debug("Session created");
// moved protocol state from connection object to RTMP object
RTMP rtmp = new RTMP(mode);
session.setAttribute(ProtocolState.SESSION_KEY, rtmp);
session.getFilterChain().addFirst("protocolFilter",
new ProtocolCodecFilter(codecFactory));
if (log.isDebugEnabled()) {
session.getFilterChain().addLast("logger", new LoggingFilter());
}
RTMPMinaConnection conn = createRTMPMinaConnection();
conn.setIoSession(session);
conn.setState(rtmp);
session.setAttachment(conn);
}
/** {@inheritDoc} */
public void setApplicationContext(ApplicationContext appCtx)
throws BeansException {
this.appCtx = appCtx;
}
protected RTMPMinaConnection createRTMPMinaConnection() {
return (RTMPMinaConnection) rtmpConnManager
.createConnection(RTMPMinaConnection.class);
}
}