/* * 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.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.IOException; import java.net.DatagramPacket; import java.net.InetAddress; import java.util.Collection; import java.util.Iterator; import java.util.Vector; import java.security.AccessController; import java.security.PrivilegedAction; import net.jini.core.lookup.ServiceID; /** * Encapsulate the details of marshaling a multicast request into one or * more packets. * * @author Sun Microsystems, Inc. * * @see IncomingMulticastRequest */ public class OutgoingMulticastRequest { /** * The minimum size we allow for an outgoing packet. */ protected static final int minMaxPacketSize = 512; /** * The current version of the multicast announcement protocol. */ protected static final int protocolVersion = 1; /** * Using the default maximum packet size, marshal a multicast request * into one or more datagram packets. These packets are guaranteed * to contain, between them, all of the groups in which the requestor * is interested. However, the set of ServiceIDs from which the * requestor has heard may be incomplete. * <p> * The datagram packets returned will have been initialized for * sending to the appropriate multicast address and UDP port. * * @param responsePort the port to which respondents should * connect in order to start unicast discovery * @param groups the set of groups in which the requestor is * interested * @param heard the set of ServiceIDs from which the requestor has * already heard * * @return an array of datagram packets, which will always * contain at least one member * * @exception IOException an error occurred during marshalling. * * @exception IllegalArgumentException when the number and length of the * group names to marshal, relative * to the value of the default packet * size maximum, is too large. */ public static DatagramPacket[] marshal(int responsePort, String[] groups, ServiceID[] heard) throws IOException { return marshal(responsePort, groups, heard, minMaxPacketSize ); } /** * Using the given maximum packet size, marshal a multicast request * into one or more datagram packets. These packets are guaranteed * to contain, between them, all of the groups in which the requestor * is interested. However, the set of ServiceIDs from which the * requestor has heard may be incomplete. * <p> * The datagram packets returned will have been initialized for * sending to the appropriate multicast address and UDP port. * * @param responsePort the port to which respondents should * connect in order to start unicast discovery * @param groups the set of groups in which the requestor is * interested * @param heard the set of ServiceIDs from which the requestor has * already heard * @param maxPacketSize the maximum size to allow for an outgoing packet * * @return an array of datagram packets, which will always * contains at least one member * * @exception IOException an error occurred during marshalling * * @exception IllegalArgumentException when the value of the * <code>maxPacketSize</code> argument * is less than the default packet * size maximum; or when the number * and length of the group names to * marshal, relative to the value of * the <code>maxPacketSize</code> * argument, is too large. */ public static DatagramPacket[] marshal(int responsePort, String[] groups, ServiceID[] heard, int maxPacketSize) throws IOException { if (maxPacketSize < minMaxPacketSize) { throw new IllegalArgumentException("maxPacketSize (" +maxPacketSize+") is " +"less than minMaxPacketSize (" +minMaxPacketSize+")"); }//endif // Marshal the fixed header stuff. byte[] marshaledHeader; { ByteArrayOutputStream hbs = new ByteArrayOutputStream(); DataOutputStream hos = new DataOutputStream(hbs); // Write out the relatively fixed stuff first. hos.writeInt(protocolVersion); hos.writeInt(responsePort); marshaledHeader = hbs.toByteArray(); } // Marshal all group names, and find the length of the longest // marshaled representation as we go. byte[][] marshaledGroups = new byte[groups.length][]; // Length of the longest group. int longestGroup = -1; for (int i = 0; i < groups.length; i++) { ByteArrayOutputStream gbs = new ByteArrayOutputStream(); DataOutputStream gos = new DataOutputStream(gbs); gos.writeUTF(groups[i]); gos.flush(); marshaledGroups[i] = gbs.toByteArray(); if (marshaledHeader.length + 4 /* heard count */ + /* assume no service IDs heard */ 4 /* group count */ + marshaledGroups[i].length > maxPacketSize) throw new IllegalArgumentException("group name marshals too " +"large (" + marshaledGroups[i].length + " bytes)"); if (marshaledGroups[i].length > longestGroup) longestGroup = marshaledGroups[i].length; } // Marshal the set of service IDs we've heard from. We have // to make sure that we can fit at least one group name into a // request, so if we have heard from too many lookup services, // we have to truncate this set. This is messy. byte[] marshaledHeard; { int prevTotal = 0; int total = 0; int count; ByteArrayOutputStream dbs = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(dbs); // Write out heard service IDs. We keep writing until we // have one too many. for (count = 0; count < heard.length; count++) { heard[count].writeBytes(dos); prevTotal = total; total = dbs.size(); // @@@ We are hard-coding knowledge about the packet // format here. This can't be avoided. if (marshaledHeader.length + 4 /* group count */ + longestGroup + 4 /* heard count */ + total > maxPacketSize) break; } dos.flush(); dbs.flush(); byte[] tmp = dbs.toByteArray(); ByteArrayOutputStream hbs = new ByteArrayOutputStream(); DataOutputStream hos = new DataOutputStream(hbs); hos.writeInt(count); hos.write(tmp, 0, count == heard.length ? total : prevTotal); hos.flush(); marshaledHeard = hbs.toByteArray(); } // We now know the size of the header and the set of marshaled // service IDs. Fill in the requests using the groups. // Build up a collection of requests. Collection reqs = new Vector(); InetAddress addr = Constants.getRequestAddress(); if (groups.length == 0) { ByteArrayOutputStream bs = new ByteArrayOutputStream(maxPacketSize); DataOutputStream os = new DataOutputStream(bs); os.write(marshaledHeader); os.write(marshaledHeard); os.writeInt(0); // no groups to write os.flush(); byte[] payload = bs.toByteArray(); reqs.add(new DatagramPacket(payload, payload.length, addr, Constants.discoveryPort)); } else { for (int curr = 0; curr < marshaledGroups.length; ) { ByteArrayOutputStream bs = new ByteArrayOutputStream(maxPacketSize); DataOutputStream os = new DataOutputStream(bs); os.write(marshaledHeader); os.write(marshaledHeard); os.flush(); int bytes = bs.size() + 4 /* for group count */; int last = curr; while (last < marshaledGroups.length && bytes + marshaledGroups[last].length <= maxPacketSize) { bytes += marshaledGroups[last].length; last += 1; } os.writeInt(last - curr); while (curr < last) { os.write(marshaledGroups[curr++]); } os.flush(); byte[] payload = bs.toByteArray(); reqs.add(new DatagramPacket(payload, payload.length, addr, Constants.discoveryPort)); } } Iterator iter = reqs.iterator(); DatagramPacket[] ary = new DatagramPacket[reqs.size()]; for (int i = 0; iter.hasNext(); i++) { ary[i] = (DatagramPacket) iter.next(); } return ary; } }