/* * 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.mrtmp; import java.util.Map; import org.apache.mina.core.buffer.IoBuffer; import org.apache.mina.core.session.IoSession; import org.red5.io.object.StreamAction; import org.red5.server.api.IConnection.Encoding; import org.red5.server.api.service.IPendingServiceCall; import org.red5.server.api.service.IServiceCall; import org.red5.server.net.rtmp.Channel; import org.red5.server.net.rtmp.RTMPConnection; import org.red5.server.net.rtmp.RTMPHandler; import org.red5.server.net.rtmp.codec.RTMP; import org.red5.server.net.rtmp.event.BytesRead; import org.red5.server.net.rtmp.event.IRTMPEvent; import org.red5.server.net.rtmp.event.Invoke; import org.red5.server.net.rtmp.event.Ping; import org.red5.server.net.rtmp.event.Unknown; import org.red5.server.net.rtmp.message.Header; import org.red5.server.net.rtmp.message.Packet; import org.red5.server.service.Call; public class EdgeRTMPHandler extends RTMPHandler { private IMRTMPManager mrtmpManager; public void setMRTMPManager(IMRTMPManager mrtmpManager) { this.mrtmpManager = mrtmpManager; } @Override public void messageReceived(RTMPConnection conn, Packet packet) throws Exception { // RTMPConnection conn = (RTMPConnection) session.getAttribute(RTMPConnection.RTMP_CONNECTION_KEY); // RTMP state = (RTMP) session.getAttribute(ProtocolState.SESSION_KEY); RTMP state = conn.getState(); IRTMPEvent message = null; // final Packet packet = (Packet) in; message = packet.getMessage(); final Header header = packet.getHeader(); final Channel channel = conn.getChannel(header.getChannelId()); // Increase number of received messages conn.messageReceived(); if (header.getDataType() == TYPE_BYTES_READ) { // TODO need to sync the bytes read on edge and origin onStreamBytesRead(conn, channel, header, (BytesRead) message); } if (header.getDataType() == TYPE_INVOKE) { final IServiceCall call = ((Invoke) message).getCall(); final String action = call.getServiceMethodName(); if (call.getServiceName() == null && !conn.isConnected() && StreamAction.valueOf(action).equals(StreamAction.CONNECT)) { handleConnect(conn, channel, header, (Invoke) message, (RTMP) state); return; } } switch (header.getDataType()) { case TYPE_CHUNK_SIZE: case TYPE_INVOKE: case TYPE_FLEX_MESSAGE: case TYPE_NOTIFY: case TYPE_AUDIO_DATA: case TYPE_VIDEO_DATA: case TYPE_FLEX_SHARED_OBJECT: case TYPE_FLEX_STREAM_SEND: case TYPE_SHARED_OBJECT: case TYPE_BYTES_READ: forwardPacket(conn, packet); break; case TYPE_PING: onPing(conn, channel, header, (Ping) message); break; default: if (log.isDebugEnabled()) { log.debug("Unknown type: {}", header.getDataType()); } } if (message instanceof Unknown) { log.info(message.toString()); } if (message != null) { message.release(); } } public void messageSent(RTMPConnection conn, Object message) { log.debug("Message sent"); if (message instanceof IoBuffer) { return; } // Increase number of sent messages conn.messageSent((Packet) message); } /** * Pass through all Ping events to origin except ping/pong */ protected void onPing(RTMPConnection conn, Channel channel, Header source, Ping ping) { switch (ping.getEventType()) { case Ping.PONG_SERVER: // This is the response to an IConnection.ping request conn.pingReceived(ping); break; default: // forward other to origin Packet p = new Packet(source); p.setMessage(ping); forwardPacket(conn, p); } } protected void handleConnect(RTMPConnection conn, Channel channel, Header header, Invoke invoke, RTMP rtmp) { final IPendingServiceCall call = invoke.getCall(); // Get parameters passed from client to NetConnection#connection final Map<String, Object> params = invoke.getConnectionParams(); // Get hostname String host = getHostname((String) params.get("tcUrl")); // App name as path, but without query string if there is one String path = (String) params.get("app"); if (path.indexOf("?") != -1) { int idx = path.indexOf("?"); params.put("queryString", path.substring(idx)); path = path.substring(0, idx); } params.put("path", path); final String sessionId = null; conn.setup(host, path, params); // check the security constraints // send back "ConnectionRejected" if fails. if (!checkPermission(conn)) { call.setStatus(Call.STATUS_ACCESS_DENIED); call.setResult(getStatus(NC_CONNECT_REJECTED)); Invoke reply = new Invoke(); reply.setCall(call); reply.setTransactionId(invoke.getTransactionId()); channel.write(reply); conn.close(); } else { synchronized (rtmp) { // connect the origin sendConnectMessage(conn); rtmp.setState(RTMP.STATE_EDGE_CONNECT_ORIGIN_SENT); Packet packet = new Packet(header); packet.setMessage(invoke); forwardPacket(conn, packet); rtmp.setState(RTMP.STATE_ORIGIN_CONNECT_FORWARDED); // Evaluate request for AMF3 encoding if (Integer.valueOf(3).equals(params.get("objectEncoding"))) { rtmp.setEncoding(Encoding.AMF3); } } } } protected boolean checkPermission(RTMPConnection conn) { // TODO check permission per some rules return true; } protected void sendConnectMessage(RTMPConnection conn) { IMRTMPConnection mrtmpConn = mrtmpManager.lookupMRTMPConnection(conn); if (mrtmpConn != null) { mrtmpConn.connect(conn.getId()); } } protected void forwardPacket(RTMPConnection conn, Packet packet) { IMRTMPConnection mrtmpConn = mrtmpManager.lookupMRTMPConnection(conn); if (mrtmpConn != null) { mrtmpManager.lookupMRTMPConnection(conn).write(conn.getId(), packet); } } @Override public void connectionClosed(RTMPConnection conn) { // the state change will be maintained inside connection object. conn.close(); } }