| /**************************************************************************** |
| * Copyright (c) 2005, 2010 Jan S. Rellermeyer, Systems Group, |
| * Department of Computer Science, ETH Zurich and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Jan S. Rellermeyer - initial API and implementation |
| * Markus Alexander Kuppe - enhancements and bug fixes |
| * |
| *****************************************************************************/ |
| package ch.ethz.iks.slp.impl; |
| |
| import java.io.ByteArrayOutputStream; |
| import java.io.DataInputStream; |
| import java.io.DataOutputStream; |
| import java.io.IOException; |
| import java.net.InetAddress; |
| import java.net.ProtocolException; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.StringTokenizer; |
| |
| import ch.ethz.iks.slp.ServiceLocationException; |
| import ch.ethz.iks.slp.impl.attr.AttributeListVisitor; |
| import ch.ethz.iks.slp.impl.attr.gen.Parser; |
| import ch.ethz.iks.slp.impl.attr.gen.ParserException; |
| import ch.ethz.iks.slp.impl.attr.gen.Rule; |
| |
| /** |
| * base class for all messages that the SLP framework uses. |
| * |
| * @author Jan S. Rellermeyer, ETH Zurich |
| * @since 0.1 |
| */ |
| public abstract class SLPMessage { |
| |
| /** |
| * the <code>Locale</code> of the message. |
| */ |
| Locale locale; |
| |
| /** |
| * the funcID encodes the message type. |
| */ |
| byte funcID; |
| |
| /** |
| * the transaction ID. |
| */ |
| short xid; |
| |
| /** |
| * the sender or receiver address. |
| */ |
| InetAddress address; |
| |
| /** |
| * the sender or receiver port. |
| */ |
| int port; |
| |
| /** |
| * true if the message was processed or will be sent via TCP |
| */ |
| boolean tcp; |
| |
| /** |
| * true if the message came in or will go out by multicast. |
| */ |
| boolean multicast; |
| |
| |
| /** |
| * the message version according to RFC 2608, Version = 2. |
| */ |
| public static final byte VERSION = 2; |
| |
| /** |
| * the message funcID values according to RFC 2608, Service Request = 1. |
| */ |
| public static final byte SRVRQST = 1; |
| |
| /** |
| * the message funcID values according to RFC 2608, Service Reply = 2. |
| */ |
| public static final byte SRVRPLY = 2; |
| |
| /** |
| * the message funcID values according to RFC 2608, Service Registration = |
| * 3. |
| */ |
| public static final byte SRVREG = 3; |
| |
| /** |
| * the message funcID values according to RFC 2608, Service Deregistration = |
| * 4. |
| */ |
| public static final byte SRVDEREG = 4; |
| |
| /** |
| * the message funcID values according to RFC 2608, Service Acknowledgement = |
| * 5. |
| */ |
| public static final byte SRVACK = 5; |
| |
| /** |
| * the message funcID values according to RFC 2608, Attribute Request = 6. |
| */ |
| public static final byte ATTRRQST = 6; |
| |
| /** |
| * the message funcID values according to RFC 2608, Attribute Reply = 7. |
| */ |
| public static final byte ATTRRPLY = 7; |
| |
| /** |
| * the message funcID values according to RFC 2608, DA Advertisement = 8. |
| */ |
| public static final byte DAADVERT = 8; |
| |
| /** |
| * the message funcID values according to RFC 2608, Service Type Request = |
| * 9. |
| */ |
| public static final byte SRVTYPERQST = 9; |
| |
| /** |
| * the message funcID values according to RFC 2608, Service Type Reply = 10. |
| */ |
| public static final byte SRVTYPERPLY = 10; |
| |
| /** |
| * the message funcID values according to RFC 2608, SA Advertisement = 11. |
| */ |
| public static final byte SAADVERT = 11; |
| |
| /** |
| * used for reverse lookup of funcID values to have nicer debug messages. |
| */ |
| private static final String[] TYPES = { "NULL", "SRVRQST", "SRVPLY", |
| "SRVREG", "SRVDEREG", "SRVACK", "ATTRRQST", "ATTRRPLY", "DAADVERT", |
| "SRVTYPERQST", "SRVTYPERPLY", "SAADVERT" }; |
| |
| /** |
| * get the bytes from a SLPMessage. Processes the header and then calls the |
| * getBody() method of the implementing subclass. |
| * |
| * @return an array of bytes encoding the SLPMessage. |
| * @throws IOException |
| * @throws ServiceLocationException |
| * in case of IOExceptions. |
| */ |
| private void writeHeader(final DataOutputStream out) |
| throws IOException { |
| byte flags = 0; |
| if (funcID == SRVREG) { |
| flags |= 0x40; |
| } |
| if (multicast) { |
| flags |= 0x20; |
| } |
| int msgSize = getSize(); |
| if (!tcp && msgSize > SLPCore.CONFIG.getMTU()) { |
| flags |= 0x80; |
| } |
| out.write(VERSION); |
| out.write(funcID); |
| out.write((byte) ((msgSize) >> 16)); |
| out.write((byte) (((msgSize) >> 8) & 0xFF)); |
| out.write((byte) ((msgSize) & 0xFF)); |
| out.write(flags); |
| out.write(0); |
| out.write(0); |
| out.write(0); |
| out.write(0); |
| out.writeShort(xid); |
| out.writeUTF(locale.getLanguage()); |
| } |
| |
| /** |
| * |
| */ |
| protected abstract void writeTo(final DataOutputStream out) throws IOException; |
| |
| /** |
| * |
| */ |
| byte[] getBytes() throws IOException { |
| final ByteArrayOutputStream bytes = new ByteArrayOutputStream(); |
| final DataOutputStream out = new DataOutputStream(bytes); |
| writeHeader(out); |
| writeTo(out); |
| return bytes.toByteArray(); |
| } |
| |
| /** |
| * The RFC 2608 SLP message header: |
| * |
| * <pre> |
| * 0 1 2 3 |
| * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 |
| * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| * | Version | Function-ID | Length | |
| * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| * | Length, contd.|O|F|R| reserved |Next Ext Offset| |
| * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| * | Next Extension Offset, contd.| XID | |
| * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| * | Language Tag Length | Language Tag \ |
| * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| * </pre> |
| * |
| * This method parses the header and then delegates the creation of the |
| * corresponding SLPMessage to the subclass that matches the funcID. |
| * |
| * @param senderAddr |
| * the address of the message sender. |
| * @param senderPort |
| * the port of the message sender. |
| * @param data |
| * the raw bytes of the message |
| * @param len |
| * the length of the byte array. |
| * @param tcp |
| * true if the message was received via TCP, false otherwise. |
| * @return a SLPMessage of the matching subtype. |
| * @throws ServiceLocationException |
| * in case of any parsing errors. |
| */ |
| static SLPMessage parse(final InetAddress senderAddr, final int senderPort, |
| final DataInputStream in, final boolean tcp) |
| throws ServiceLocationException, ProtocolException { |
| try { |
| final int version = in.readByte(); // version |
| if (version != VERSION) { |
| in.readByte(); // funcID |
| final int length = in.readShort(); |
| byte[] drop = new byte[length - 4]; |
| in.readFully(drop); |
| SLPCore.platform.logWarning("Dropped SLPv" + version + " message from " |
| + senderAddr + ":" + senderPort); |
| } |
| final byte funcID = in.readByte(); // funcID |
| final int length = readInt(in, 3); |
| |
| // slpFlags |
| final byte flags = (byte) (in.readShort() >> 8); |
| |
| if (!tcp && (flags & 0x80) != 0) { |
| throw new ProtocolException(); |
| } |
| |
| // we don't process extensions, we simply ignore them |
| readInt(in, 3); // extOffset |
| final short xid = in.readShort(); // XID |
| final Locale locale = new Locale(in.readUTF(), ""); // Locale |
| |
| final SLPMessage msg; |
| |
| // decide on the type of the message |
| switch (funcID) { |
| case DAADVERT: |
| msg = new DAAdvertisement(in); |
| break; |
| case SRVRQST: |
| msg = new ServiceRequest(in); |
| break; |
| case SRVRPLY: |
| msg = new ServiceReply(in); |
| break; |
| case ATTRRQST: |
| msg = new AttributeRequest(in); |
| break; |
| case ATTRRPLY: |
| msg = new AttributeReply(in); |
| break; |
| case SRVREG: |
| msg = new ServiceRegistration(in); |
| break; |
| case SRVDEREG: |
| msg = new ServiceDeregistration(in); |
| break; |
| case SRVACK: |
| msg = new ServiceAcknowledgement(in); |
| break; |
| case SRVTYPERQST: |
| msg = new ServiceTypeRequest(in); |
| break; |
| case SRVTYPERPLY: |
| msg = new ServiceTypeReply(in); |
| break; |
| default: |
| throw new ServiceLocationException( |
| ServiceLocationException.PARSE_ERROR, "Message type " |
| + getType(funcID) + " not supported"); |
| } |
| |
| // set the fields |
| msg.address = senderAddr; |
| msg.port = senderPort; |
| msg.tcp = tcp; |
| msg.multicast = ((flags & 0x2000) >> 13) == 1 ? true : false; |
| msg.xid = xid; |
| msg.funcID = funcID; |
| msg.locale = locale; |
| if (msg.getSize() != length) { |
| SLPCore.platform.logError("Length of " + msg + " should be " + length + ", read " |
| + msg.getSize()); |
| // throw new ServiceLocationException( |
| // ServiceLocationException.INTERNAL_SYSTEM_ERROR, |
| // "Length of " + msg + " should be " + length + ", read " |
| // + msg.getSize()); |
| } |
| return msg; |
| } catch (ProtocolException pe) { |
| throw pe; |
| } catch (IOException ioe) { |
| SLPCore.platform.logError("Network Error", ioe); |
| throw new ServiceLocationException( |
| ServiceLocationException.NETWORK_ERROR, ioe.getMessage()); |
| } |
| } |
| |
| /** |
| * |
| * @return |
| */ |
| protected int getHeaderSize() { |
| return 14 + locale.getLanguage().length(); |
| } |
| |
| /** |
| * |
| * @return |
| */ |
| protected abstract int getSize(); |
| |
| /** |
| * Get a string representation of the message. Overridden by message |
| * subtypes. |
| * |
| * @return a String. |
| */ |
| public String toString() { |
| final StringBuffer buffer = new StringBuffer(); |
| buffer.append(getType(funcID) + " - "); |
| buffer.append("xid=" + xid); |
| buffer.append(", locale=" + locale); |
| return buffer.toString(); |
| } |
| |
| /** |
| * returns the string value of the message type, catches the case where an |
| * unsupported message has been received. |
| * |
| * @param type |
| * the type. |
| * @return the type as String. |
| */ |
| static String getType(final int type) { |
| if (type > -1 && type < 12) { |
| return TYPES[type]; |
| } |
| return String.valueOf(type + " - UNSUPPORTED"); |
| } |
| |
| /** |
| * parse a numerical value that can be spanned over multiple bytes. |
| * |
| * @param input |
| * the data input stream. |
| * @param len |
| * the number of bytes to read. |
| * @return the int value. |
| * @throws ServiceLocationException |
| * in case of IO errors. |
| */ |
| private static int readInt(final DataInputStream input, final int len) |
| throws ServiceLocationException { |
| try { |
| int value = 0; |
| for (int i = 0; i < len; i++) { |
| value <<= 8; |
| value += input.readByte() & 0xff; |
| } |
| return value; |
| } catch (IOException ioe) { |
| throw new ServiceLocationException( |
| ServiceLocationException.PARSE_ERROR, ioe.getMessage()); |
| } |
| } |
| |
| /** |
| * transforms a Java list to string list. |
| * |
| * @param list |
| * the list |
| * @param delim |
| * the delimiter |
| * @return the String list. |
| */ |
| static String listToString(final List list, final String delim) { |
| if (list == null || list.size() == 0) { |
| return ""; |
| } else if (list.size() == 1) { |
| return list.get(0).toString(); |
| } else { |
| final StringBuffer buffer = new StringBuffer(); |
| final Object[] elements = list.toArray(); |
| for (int i = 0; i < elements.length - 1; i++) { |
| buffer.append(elements[i]); |
| buffer.append(delim); |
| } |
| buffer.append(elements[elements.length - 1]); |
| return buffer.toString(); |
| } |
| } |
| |
| /** |
| * transforms a string list to Java List. |
| * |
| * @param str |
| * the String list |
| * @param delim |
| * the delimiter |
| * @return the List. |
| */ |
| static List stringToList(final String str, final String delim) { |
| List result = new ArrayList(); |
| StringTokenizer tokenizer = new StringTokenizer(str, delim); |
| while (tokenizer.hasMoreTokens()) { |
| result.add(tokenizer.nextToken()); |
| } |
| return result; |
| } |
| |
| /** |
| * |
| * @param input |
| * @return |
| * @throws ServiceLocationException |
| * |
| * @author Markus Alexander Kuppe |
| * @since 1.1 |
| */ |
| protected List attributeStringToList(String input) throws ServiceLocationException { |
| if("".equals(input)) { |
| return new ArrayList(); |
| } |
| Parser parser = new Parser(); |
| try { |
| Rule parse = parser.parse("attr-list", input); |
| AttributeListVisitor visitor = new AttributeListVisitor(); |
| parse.visit(visitor); |
| return visitor.getAttributes(); |
| } catch (IllegalArgumentException e) { |
| throw new ServiceLocationException(ServiceLocationException.PARSE_ERROR, e.getMessage()); |
| } catch (ParserException e) { |
| throw new ServiceLocationException(ServiceLocationException.PARSE_ERROR, e.getMessage()); |
| } |
| } |
| |
| /** |
| * |
| * @param input |
| * @return |
| * @throws ServiceLocationException |
| * |
| * @author Markus Alexander Kuppe |
| * @since 1.1 |
| */ |
| protected List attributeStringToListLiberal(String input) { |
| if("".equals(input)) { |
| return new ArrayList(); |
| } |
| Parser parser = new Parser(); |
| Rule rule = null; |
| try { |
| rule = parser.parse("attr-list", input); |
| } catch (IllegalArgumentException e) { |
| SLPCore.platform.logError(e.getMessage(), e); |
| return new ArrayList(); |
| // may never happen!!! |
| } catch (ParserException e) { |
| SLPCore.platform.logTraceDrop(e.getMessage()); |
| rule = e.getRule(); |
| } |
| AttributeListVisitor visitor = new AttributeListVisitor(); |
| rule.visit(visitor); |
| return visitor.getAttributes(); |
| } |
| } |