package de.debugco.jairport;
import org.jboss.netty.channel.*;
import org.jboss.netty.handler.codec.http.*;
import static org.jboss.netty.handler.codec.http.HttpHeaders.*;
import org.jboss.netty.handler.codec.rtsp.RtspMethods;
import org.jboss.netty.handler.codec.rtsp.RtspVersions;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.charset.Charset;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RtspRequestHandler extends SimpleChannelUpstreamHandler {
private static final String RSAAESKEY = "a=rsaaeskey:";
private static final String AESIV = "a=aesiv:";
private static final String FMTP = "a=fmtp:";
private void setPorts(RaopSession session, String transport) {
int controlPort = 0;
int timingPort = 0;
Pattern controlPortPattern = Pattern.compile(".*control_port=(\\d+).*");
Matcher controlPortMatcher = controlPortPattern.matcher(transport);
if(controlPortMatcher.matches()) {
controlPort = Integer.parseInt(controlPortMatcher.group(1));
}
Pattern timingPortPattern = Pattern.compile(".*timing_port=(\\d+).*");
Matcher timingPortMatcher = timingPortPattern.matcher(transport);
if(timingPortMatcher.matches()) {
timingPort = Integer.parseInt(timingPortMatcher.group(1));
}
if(controlPort == 0) {
throw new RuntimeException("no control port");
} else if(timingPort == 0) {
throw new RuntimeException("no timing port");
}
session.setControlPort(controlPort);
session.setTimingPort(timingPort);
}
@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
HttpRequest request = (HttpRequest) e.getMessage();
System.out.println(request.toString());
if(!RtspVersions.RTSP_1_0.equals(request.getProtocolVersion())) {
HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.INTERNAL_SERVER_ERROR);
response.addHeader("Connection", "close");
e.getChannel().write(response);
e.getChannel().disconnect();
return;
}
HttpResponse response = new DefaultHttpResponse(RtspVersions.RTSP_1_0, HttpResponseStatus.OK);
response.clearHeaders();
String cSeq = request.getHeader("CSeq");
if(cSeq != null) {
response.addHeader("CSeq", cSeq);
}
response.addHeader("Audio-Jack-Status", "connected; type=analog");
String challenge = request.getHeader("Apple-Challenge");
if(challenge != null) {
SocketAddress remoteAddress = e.getRemoteAddress();
response.addHeader("Apple-Response", Utils.getChallengeResponse(challenge, ((InetSocketAddress) remoteAddress).getAddress(), Configuration.getHardwareAddress()));
}
String clientInstance = request.getHeader("Client-Instance");
if (Utils.isNullOrEmpty(clientInstance)) {
throw new RuntimeException("No Client Instance given");
}
HttpMethod method = request.getMethod();
if(RtspMethods.OPTIONS.equals(method)) {
response.addHeader("Public", "ANNOUNCE, SETUP, RECORD, PAUSE, FLUSH, TEARDOWN, OPTIONS, GET_PARAMETER, SET_PARAMETER");
} else if(RtspMethods.ANNOUNCE.equals(method)) {
String content = request.getContent().toString(Charset.forName("UTF-8"));
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
request.getContent().readBytes(out, request.getContent().readableBytes());
BufferedReader reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(out.toByteArray())));
String line;
String rsaAesKey = null;
String aesIv = null;
String fmtp = null;
// Get attributes of Service-Discovery-Protocol
while ((line = reader.readLine()) != null) {
if (line.startsWith(RSAAESKEY)) {
rsaAesKey = line.substring(RSAAESKEY.length());
} else if (line.startsWith(AESIV)) {
aesIv = line.substring(AESIV.length());
} else if (line.startsWith(FMTP)) {
fmtp = line.substring(FMTP.length());
}
}
if(Utils.isNullOrEmpty(aesIv)) {
throw new RuntimeException("No AES Iv");
}
if(Utils.isNullOrEmpty(rsaAesKey)) {
throw new RuntimeException("No RSA AES Key");
}
RaopSession session = RaopSessionManager.getSession(clientInstance);
if(session == null) {
session = new RaopSession(clientInstance, Utils.decodeBase64(aesIv), Utils.decryptBase64(rsaAesKey), fmtp);
RaopSessionManager.addSession(clientInstance, session);
} else {
session.setAesIv(Utils.decodeBase64(aesIv));
session.setAesKey(Utils.decryptBase64(rsaAesKey));
session.setFmtp(fmtp);
}
} finally {
out.close();
}
} else if(RtspMethods.SETUP.equals(method)) {
RaopSession session = RaopSessionManager.getSession(clientInstance);
if(session == null) {
throw new RuntimeException("No Session " + clientInstance);
}
String transport = request.getHeader("Transport");
setPorts(session, transport);
RaopServer server = new RaopServer(session);
server.setDaemon(true);
server.start();
session.setServer(server);
response.setHeader("Transport", request.getHeader("Transport") + ";server_port=" + server.getPort());
response.setHeader("Session", request.getHeader("Client-Instance"));
} else if(RtspMethods.RECORD.equals(method)) {
// ignore
} else if("FLUSH".equalsIgnoreCase(method.getName())) {
System.out.println("FLUSH");
} else if(RtspMethods.TEARDOWN.equals(method)) {
response.setHeader("Connection", "close");
e.getChannel().write(response);
RaopSessionManager.shutdownSession(clientInstance);
e.getChannel().disconnect();
return;
} else if(RtspMethods.SET_PARAMETER.equals(method)) {
// TODO set volume
} else if(RtspMethods.GET_PARAMETER.equals(method)) {
// ignore
} else if("DENIED".equalsIgnoreCase(method.getName())) {
// ignore
} else {
throw new RuntimeException("Unknown Method: " + method.getName());
}
boolean keepAlive = isKeepAlive(request);
if(keepAlive) {
response.setHeader("Content-Length", response.getContent().readableBytes());
}
ChannelFuture future = e.getChannel().write(response);
if(!keepAlive) {
future.addListener(ChannelFutureListener.CLOSE);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
if(e.getCause() != null) {
e.getCause().printStackTrace();
}
e.getChannel().close();
}
}