/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you 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 net.jini.discovery;

import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import net.jini.core.lookup.ServiceID;

/**
 * Encapsulate the details of unmarshaling an incoming multicast
 * request packet.
 *
 * 
 *
 * @see OutgoingMulticastRequest
 */
public class IncomingMulticastRequest {
    /**
     * The address to which any responses should go.
     */
    protected InetAddress addr;
    /**
     * The port to which any responses should connect.
     */
    protected int port;
    /**
     * The ServiceIDs from which the requestor has already heard.
     * This set may not be complete.
     */
    protected ServiceID[] serviceIDs;
    /**
     * The groups in which the requestor is interested.  This set may
     * not be complete.
     */
    protected String[] groups;
    /**
     * The current version of the multicast request protocol.
     */
    protected final int protoVersion = 1;

    /**
     * Construct a new object, initialized from the contents of the
     * given multicast request packet.
     *
     * @param dgram the packet to unmarshal
     * @exception IOException a problem occurred while unmarshaling
     * the packet
     */
    public IncomingMulticastRequest(DatagramPacket dgram) throws IOException {
	ByteArrayInputStream bs = new ByteArrayInputStream(dgram.getData());
	DataInputStream ds = new DataInputStream(bs);
	int proto = ds.readInt();
	if (proto != protoVersion)
	    throw new IOException("unsupported protocol version: " + proto);
	port = ds.readInt();
	// Valid port ranges are 1 through 65535 for IPv4.
	if (port <= 0 || port >= 65536)
	    throw new IOException("port number out of range: " + port);
	int sidCount = ds.readInt();
	// We know that a ServiceID marshals to 8 bytes, so ensure
	// that the count can plausibly fit into this packet.
	if (sidCount < 0 || sidCount > bs.available() / 8)
	    throw new IOException("service ID count invalid: " + sidCount);
	serviceIDs = new ServiceID[sidCount];
	for (int i = 0; i < serviceIDs.length; i++) {
	    serviceIDs[i] = new ServiceID(ds);
	}
	int grpCount = ds.readInt();
	// We know that the minimal length of a string written with
	// DataOutput.writeUTF is two bytes (for the empty string), so
	// we make some attempt to see if the count can plausibly fit
	// into this packet.
	if (grpCount < 0 || grpCount > bs.available() / 2)
	    throw new IOException("group count invalid: " + grpCount);
	groups = new String[grpCount];
	for (int i = 0; i < groups.length; i++) {
	    groups[i] = ds.readUTF();
	}
	addr = dgram.getAddress();
    }

    /**
     * Return the address of the host to contact in order to start
     * unicast discovery.
     *
     * @return the address of the host to contact in order to start
     *         unicast discovery
     */
    public InetAddress getAddress() {
	return addr;
    }

    /**
     * Return the number of the port to connect to on the remote host
     * in order to start unicast discovery.
     *
     * @return the number of the port to connect to on the remote host
     *         in order to start unicast discovery
     */
    public int getPort() {
	return port;
    }

    /**
     * Return the set of groups in which the requestor is interested.
     * This set may not be complete, but other incoming packets should
     * contain the rest of the set.
     *
     * @return the set of groups in which the requestor is interested.
     */
    public String[] getGroups() {
	return groups;
    }

    /**
     * Return a set of ServiceIDs from which the requestor has already
     * heard.  This set may not be complete.
     *
     * @return a set of service IDs from which the requestor has already
     *         heard
     */
    public ServiceID[] getServiceIDs() {
	return serviceIDs;
    }

    public int hashCode() {
	byte[] foo = addr.getAddress();
	int code = (foo[0] << 3) ^ (foo[1] << 7) ^ (foo[2] << 5) ^ (foo[3] << 1) ^
	    ~port;
	for (int i = 0; i < groups.length; i++) {
	    code ^= groups[i].hashCode() << i;
	}
	for (int i = 0; i < serviceIDs.length; i++) {
	    code ^= serviceIDs[i].hashCode() << i;
	}
	return code;
    }

    /**
     * Two requests are equal if they have the same address, port,
     * groups, and service IDs.
     */
    public boolean equals(Object o) {
	// We assume here that multiple requests from the same machine
	// will have the same source IP address.  This is not
	// necessarily true.
	if (o instanceof IncomingMulticastRequest) {
	    IncomingMulticastRequest oo = (IncomingMulticastRequest) o;
	    if (oo.addr.equals(addr) && oo.port == port &&
		oo.groups.length == groups.length &&
		oo.serviceIDs.length == serviceIDs.length)
	    {
		for (int i = 0; i < groups.length; i++) {
		    if (!groups[i].equals(oo.groups[i]))
			return false;
		}
		for (int i = 0; i < serviceIDs.length; i++) {
		    if (!serviceIDs[i].equals(oo.serviceIDs[i]))
			return false;
		}
		return true;
	    }
	}
	return false;
    }
}
