/*
* Copyright (C) 2009 Bradley Austin Davis.
*
* This file is part of serket.
*
* serket 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 3 of the License, or (at your option) any later
* version.
*
* serket 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* serket. If not, see <http://www.gnu.org/licenses/>.
*/
package org.saintandreas.serket.ssdp;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.DatagramPacket;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.apache.http.Header;
import org.apache.http.HttpException;
import org.apache.http.impl.io.AbstractMessageParser;
import org.apache.http.impl.io.AbstractSessionInputBuffer;
import org.apache.http.io.SessionInputBuffer;
import org.apache.http.message.BasicLineParser;
import org.apache.http.message.LineParser;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpParams;
import com.google.common.base.Charsets;
public class Message {
public final static String NOTIFY_START = "NOTIFY * HTTP/1.1\r\n";
public final static String SEARCH_START = "M-SEARCH * HTTP/1.1\r\n";
public final static String HTTP_OK_START = "HTTP/1.1 200 OK\r\n";
private final static String NOTIFY_TYPE_TEMPLATE = "NTS: %s\r\n";
private final static String CACHE_CONTROL_TEMPLATE = "CACHE-CONTROL: max-age=%d\r\n";
private final static String SEARCH_DISCOVER = "MAN: \"ssdp:discover\"\r\n";
private final static String SYSTEM_IDENTIFIER = System.getProperty("os.name") + "/" + System.getProperty("os.version");
private final static String NOTIFICATION_TYPE_TEMPLATE = "NT: %s\r\n";
private final static String UNIQUE_SERVICE_NAME_TEMPLATE = "USN: %s\r\n";
private final static String SERVER_TEMPLATE = "SERVER: " + SYSTEM_IDENTIFIER + " UPnP/1.1 %s\r\n";
private final static String LOCATION_TEMPLATE = "LOCATION: %s\r\n";
private final static String USER_AGENT_TEMPLATE = "USER-AGENT: " + SYSTEM_IDENTIFIER + " UPnP/1.1 %s\r\n";
private final static String MULTICAST_HOST = "HOST: 239.255.255.250:1900\r\n";
private final static String SEARCH_TARGET_TEMPLATE = "ST: %s\r\n";
public static enum Type {
NOTIFY_ALIVE, NOTIFY_BYEBYE, NOTIFY_UPDATE, SEARCH, RESPONSE
}
private final static String NOTIFY_TEMPLATE =
NOTIFY_START +
MULTICAST_HOST +
NOTIFY_TYPE_TEMPLATE +
NOTIFICATION_TYPE_TEMPLATE +
UNIQUE_SERVICE_NAME_TEMPLATE;
private final static String NOTIFY_ALIVE_TEMPLATE =
LOCATION_TEMPLATE +
CACHE_CONTROL_TEMPLATE +
SERVER_TEMPLATE +
"\r\n";
// private final static String NOTIFY_UPDATE_TEMPLATE =
// NOTIFY_START +
// MULTICAST_HOST +
// NOTIFY_UPDATE +
// NOTIFICATION_TYPE_TEMPLATE +
// UNIQUE_SERVICE_NAME_TEMPLATE;
//
// private final static String NOTIFY_BYEBYE_TEMPLATE =
// NOTIFY_START +
// MULTICAST_HOST +
// NOTIFY_BYEBYE +
// NOTIFICATION_TYPE_TEMPLATE +
// UNIQUE_SERVICE_NAME_TEMPLATE;
private final static String NOTIFY_ALIVE = "ssdp:alive";
private final static String NOTIFY_UPDATE = "ssdp:update";
private final static String NOTIFY_BYEBYE = "ssdp:byebyte";
private static String buildNotifyMessage(String notificationMessageType, String notificationType, String uniqueServiceName) {
return String.format(NOTIFY_TEMPLATE, notificationMessageType, notificationType, uniqueServiceName);
}
public static String buildNotifyAliveMessage(String notificationType, String uniqueServiceName, String location, long expireSeconds, String serverSuffix) {
return buildNotifyMessage(NOTIFY_ALIVE, notificationType, uniqueServiceName) + String.format(NOTIFY_ALIVE_TEMPLATE, location, expireSeconds, serverSuffix) + "\r\n";
}
public static String buildNotifyUpdateMessage(String notificationType, String uniqueServiceName, String location, long expireSeconds, String serverSuffix) {
return buildNotifyMessage(NOTIFY_UPDATE, notificationType, uniqueServiceName) + "\r\n";
}
public static String buildNotifyByeByeMessage(String notificationType, String uniqueServiceName, String location, long expireSeconds, String serverSuffix) {
return buildNotifyMessage(NOTIFY_BYEBYE, notificationType, uniqueServiceName) + "\r\n";
}
private final static String SEARCH_TEAMPLTE =
SEARCH_START +
MULTICAST_HOST +
SEARCH_DISCOVER +
USER_AGENT_TEMPLATE +
SEARCH_TARGET_TEMPLATE +
"MX: %d\r\n" +
"\r\n";
public static String buildSearchMessage(String searchType, int seconds) {
return String.format(SEARCH_TEAMPLTE, "serket/1.0", searchType, seconds);
}
private final static String SEARCH_RESPONSE_TEAMPLTE =
HTTP_OK_START +
UNIQUE_SERVICE_NAME_TEMPLATE +
SEARCH_TARGET_TEMPLATE +
LOCATION_TEMPLATE +
CACHE_CONTROL_TEMPLATE +
SERVER_TEMPLATE +
"EXT:\r\n" +
"\r\n";
public static String buildSearchResponseMessage(String uniqueServiceName, String searchTarget, String location, long expireSeconds, String serverSuffix) {
return String.format(SEARCH_RESPONSE_TEAMPLTE, uniqueServiceName, searchTarget, location, expireSeconds, serverSuffix);
}
public final Type type;
public final Map<String, Header> headers;
public final String usn;
public final DatagramPacket original;
public final String originalString;
public Message(Type type, Map<String, Header> headers, String usn, DatagramPacket original, String originalString) {
this.type = type;
this.headers = Collections.unmodifiableMap(headers);
this.usn = usn;
this.original = original;
this.originalString = originalString;
}
public static class ByteArrayInputBuffer extends AbstractSessionInputBuffer {
private final ByteArrayInputStream is;
public ByteArrayInputBuffer(byte[] buffer) {
this(buffer, new BasicHttpParams());
}
public ByteArrayInputBuffer(byte[] buffer, HttpParams params) {
this(buffer, 8192, params);
}
public ByteArrayInputBuffer(byte[] buffer, int bufferSize, HttpParams params) {
is = new ByteArrayInputStream(buffer);
this.init(is, bufferSize, params);
}
@Override
public boolean isDataAvailable(int timeout) throws IOException {
boolean result = hasBufferedData();
if (!result) {
fillBuffer();
result = hasBufferedData();
}
return result;
}
}
public static Message parseMessage(DatagramPacket packet) throws IOException, HttpException {
Type type;
Map<String, Header> headers = new HashMap<String, Header>();
String usn = null;
LineParser parser = new BasicLineParser();
SessionInputBuffer inBuffer = new ByteArrayInputBuffer(packet.getData());
String command = inBuffer.readLine();
Header[] hs = AbstractMessageParser.parseHeaders(inBuffer, 64, 1024, parser);
for (Header h : hs) {
headers.put(h.getName(), h);
}
if (Message.NOTIFY_START.startsWith(command)) {
Header h = headers.get("NTS");
usn = headers.get("USN").getValue();
if ("ssdp:alive".equals(h.getValue())) {
// LogFactory.getLog(SSDPTests.class).debug("Notify message alive " + usn);
type = Type.NOTIFY_ALIVE;
} else if ("ssdp:update".equals(h.getValue())) {
// LogFactory.getLog(SSDPTests.class).debug("Notify message update " + usn);
type = Type.NOTIFY_UPDATE;
} else if ("ssdp:byebye".equals(h.getValue())) {
// LogFactory.getLog(SSDPTests.class).debug("Notify message byebye " + usn);
type = Type.NOTIFY_BYEBYE;
} else throw new RuntimeException("unknown type");
} else if (Message.SEARCH_START.startsWith(command)) {
usn = headers.get("ST").getValue();
// LogFactory.getLog(SSDPTests.class).debug("Search message " + usn);
type = Type.SEARCH;
} else if (Message.HTTP_OK_START.startsWith(command)) {
// LogFactory.getLog(SSDPTests.class).debug("Response message");
type = Type.RESPONSE;
} else throw new RuntimeException("unknown type");
return new Message(type, headers, usn, packet, new String(packet.getData(), 0, packet.getLength(), Charsets.US_ASCII));
}
}