/*
* RED5 Open Source Flash Server - http://code.google.com/p/red5/
*
* Copyright 2006-2012 by respective authors (see below). All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.red5.server.net.rtmp;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFactory;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.logging.LoggingFilter;
import org.red5.server.api.IConnection;
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.rtmpe.RTMPEIoFilter;
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 {
private static Logger log = LoggerFactory.getLogger(RTMPMinaIoHandler.class);
/**
* RTMP events handler
*/
protected IRTMPHandler handler;
/**
* Application context
*/
protected ApplicationContext appCtx;
protected IRTMPConnManager rtmpConnManager;
/** {@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();
session.setAttribute(ProtocolState.SESSION_KEY, rtmp);
//add rtmpe filter
session.getFilterChain().addFirst("rtmpeFilter", new RTMPEIoFilter());
//add protocol filter next
ProtocolCodecFactory codecFactory = (ProtocolCodecFactory) appCtx.getBean("rtmpCodecFactory");
session.getFilterChain().addLast("protocolFilter", new ProtocolCodecFilter(codecFactory));
if (log.isTraceEnabled()) {
session.getFilterChain().addLast("logger", new LoggingFilter());
}
//create a connection
RTMPMinaConnection conn = createRTMPMinaConnection();
conn.setIoSession(session);
conn.setState(rtmp);
//add the connection
session.setAttribute(RTMPConnection.RTMP_CONNECTION_KEY, conn);
//add the inbound handshake
session.setAttribute(RTMPConnection.RTMP_HANDSHAKE, new InboundHandshake());
}
/** {@inheritDoc} */
@Override
public void sessionOpened(IoSession session) throws Exception {
log.debug("Session opened");
super.sessionOpened(session);
// get protocol state
RTMP rtmp = (RTMP) session.getAttribute(ProtocolState.SESSION_KEY);
handler.connectionOpened((RTMPMinaConnection) session.getAttribute(RTMPConnection.RTMP_CONNECTION_KEY), rtmp);
}
/** {@inheritDoc} */
@Override
public void sessionClosed(IoSession session) throws Exception {
log.debug("Session closed");
RTMP rtmp = (RTMP) session.removeAttribute(ProtocolState.SESSION_KEY);
log.debug("RTMP state: {}", rtmp);
RTMPMinaConnection conn = (RTMPMinaConnection) session.removeAttribute(RTMPConnection.RTMP_CONNECTION_KEY);
try {
conn.sendPendingServiceCallsCloseError();
// fire-off closed event
handler.connectionClosed(conn, rtmp);
// clear any session attributes we may have previously set
// TODO: verify this cleanup code is necessary. The session is over and will be garbage collected surely?
session.removeAttribute(RTMPConnection.RTMP_HANDSHAKE);
session.removeAttribute(RTMPConnection.RTMPE_CIPHER_IN);
session.removeAttribute(RTMPConnection.RTMPE_CIPHER_OUT);
} finally {
// DW we *always* remove the connection from the RTMP manager even if unexpected exception gets thrown e.g. by handler.connectionClosed
// Otherwise connection stays around forever, and everything it references e.g. Client, ...
rtmpConnManager.removeConnection(conn.getId());
}
}
/**
* Handle raw buffer receiving event.
*
* @param in
* Data buffer
* @param session
* I/O session, that is, connection between two endpoints
*/
protected void rawBufferRecieved(IoBuffer in, IoSession session) {
log.debug("rawBufferRecieved: {}", in);
final RTMP rtmp = (RTMP) session.getAttribute(ProtocolState.SESSION_KEY);
log.debug("state: {}", rtmp);
final RTMPMinaConnection conn = (RTMPMinaConnection) session.getAttribute(RTMPConnection.RTMP_CONNECTION_KEY);
RTMPHandshake handshake = (RTMPHandshake) session.getAttribute(RTMPConnection.RTMP_HANDSHAKE);
if (handshake != null) {
if (rtmp.getState() != RTMP.STATE_HANDSHAKE) {
log.warn("Raw buffer after handshake, something odd going on");
}
log.debug("Handshake - server phase 1 - size: {}", in.remaining());
IoBuffer out = handshake.doHandshake(in);
if (out != null) {
log.debug("Output: {}", out);
session.write(out);
//if we are connected and doing encryption, add the ciphers
if (rtmp.getState() == RTMP.STATE_CONNECTED) {
// remove handshake from session now that we are connected
// if we are using encryption then put the ciphers in the session
if (handshake.getHandshakeType() == RTMPConnection.RTMP_ENCRYPTED) {
log.debug("Adding ciphers to the session");
session.setAttribute(RTMPConnection.RTMPE_CIPHER_IN, handshake.getCipherIn());
session.setAttribute(RTMPConnection.RTMPE_CIPHER_OUT, handshake.getCipherOut());
}
}
}
} else {
log.warn("Handshake was not found for this connection: {}", conn);
log.debug("RTMP state: {} Session: {}", rtmp, session);
}
}
/** {@inheritDoc} */
@Override
public void messageReceived(IoSession session, Object message) throws Exception {
log.trace("messageReceived");
if (message instanceof IoBuffer) {
rawBufferRecieved((IoBuffer) message, session);
} else {
log.trace("Setting connection local");
Red5.setConnectionLocal((IConnection) session.getAttribute(RTMPConnection.RTMP_CONNECTION_KEY));
handler.messageReceived(message, session);
log.trace("Removing connection local");
Red5.setConnectionLocal(null);
}
}
/** {@inheritDoc} */
@Override
public void messageSent(IoSession session, Object message) throws Exception {
log.debug("messageSent");
final RTMPMinaConnection conn = (RTMPMinaConnection) session.getAttribute(RTMPConnection.RTMP_CONNECTION_KEY);
handler.messageSent(conn, message);
}
/** {@inheritDoc} */
@Override
public void exceptionCaught(IoSession session, Throwable cause) throws Exception {
log.warn("Exception caught {}", cause.getMessage());
if (log.isDebugEnabled()) {
log.error("Exception detail", cause);
}
}
/**
* Setter for handler.
*
* @param handler RTMP events handler
*/
public void setHandler(IRTMPHandler handler) {
this.handler = handler;
}
public void setRtmpConnManager(IRTMPConnManager rtmpConnManager) {
this.rtmpConnManager = rtmpConnManager;
}
protected IRTMPConnManager getRtmpConnManager() {
return rtmpConnManager;
}
/** {@inheritDoc} */
public void setApplicationContext(ApplicationContext appCtx) throws BeansException {
log.debug("Setting application context: {} {}", appCtx.getDisplayName(), appCtx);
this.appCtx = appCtx;
}
protected RTMPMinaConnection createRTMPMinaConnection() {
return (RTMPMinaConnection) rtmpConnManager.createConnection(RTMPMinaConnection.class);
}
}