2 * Copyright (c) 2001-2007 Sun Microsystems, Inc. All rights reserved.
4 * The Sun Project JXTA(TM) Software License
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met:
9 * 1. Redistributions of source code must retain the above copyright notice,
10 * this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright notice,
13 * this list of conditions and the following disclaimer in the documentation
14 * and/or other materials provided with the distribution.
16 * 3. The end-user documentation included with the redistribution, if any, must
17 * include the following acknowledgment: "This product includes software
18 * developed by Sun Microsystems, Inc. for JXTA(TM) technology."
19 * Alternately, this acknowledgment may appear in the software itself, if
20 * and wherever such third-party acknowledgments normally appear.
22 * 4. The names "Sun", "Sun Microsystems, Inc.", "JXTA" and "Project JXTA" must
23 * not be used to endorse or promote products derived from this software
24 * without prior written permission. For written permission, please contact
25 * Project JXTA at http://www.jxta.org.
27 * 5. Products derived from this software may not be called "JXTA", nor may
28 * "JXTA" appear in their name, without prior written permission of Sun.
30 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
31 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
32 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SUN
33 * MICROSYSTEMS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
34 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
35 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
36 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
37 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
38 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
39 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
41 * JXTA is a registered trademark of Sun Microsystems, Inc. in the United
42 * States and other countries.
44 * Please see the license information page at :
45 * <http://www.jxta.org/project/www/license.html> for instructions on use of
46 * the license in source files.
48 * ====================================================================
50 * This software consists of voluntary contributions made by many individuals
51 * on behalf of Project JXTA. For more information on Project JXTA, please see
52 * http://www.jxta.org.
54 * This license is based on the BSD license adopted by the Apache Foundation.
57 package net.jxta.impl.endpoint.msgframing;
60 import java.io.EOFException;
61 import java.io.InputStream;
62 import java.io.IOException;
63 import java.io.OutputStream;
64 import java.io.UnsupportedEncodingException;
66 import java.net.URISyntaxException;
67 import java.nio.ByteBuffer;
68 import java.nio.BufferUnderflowException;
69 import java.util.Arrays;
70 import java.util.List;
71 import java.text.MessageFormat;
73 import net.jxta.endpoint.EndpointAddress;
74 import net.jxta.id.ID;
75 import net.jxta.id.IDFactory;
77 import java.util.logging.Level;
78 import net.jxta.logging.Logging;
79 import java.util.logging.Logger;
83 * Contains a JXTA connection Welcome Message. The Welcome Message is sent by
84 * both participant peers as the first interchange on newly opened connections.
86 * <p/>The Welcome Message contains the following information:
88 * <li>The address to which the local peer believes it is connected.</li>
89 * <li>The local peer's return address, the source address.</li>
90 * <li>The local peer's peer id.</li>
91 * <li>A flag which controls propagation behaviour for this conneciton.</li>
94 * @see <a href="https://jxta-spec.dev.java.net/nonav/JXTAProtocols.html#trans-tcpipt"
95 * target="_blank">JXTA Protocols Specification : TCP/IP Message Transport</a>
97 public class WelcomeMessage {
102 private static final Logger LOG = Logger.getLogger(WelcomeMessage.class.getName());
105 * The Welcome Message Signature/Preamble
107 private final static String GREETING = "JXTAHELLO";
110 * A space for separating elements of the welcome message.
112 private final static String SPACE = " ";
115 * Version string for Welcome Message Version 1.1
117 private final static String WELCOME_VERSION_1_1 = "1.1";
120 * Version string for Welcome Message Version 3.0
122 private final static String WELCOME_VERSION_3_0 = "3.0";
125 * The current welcome message version. This is the only version we will emit.
127 private final static String CURRENTVERSION = WELCOME_VERSION_1_1;
130 * The destination address that we believe we are connecting to.
132 private EndpointAddress destinationAddress;
135 * Our return address, the purported source address of this connection.
137 private EndpointAddress publicAddress;
140 * Our peerid, the logical return address.
145 * This connection does not wish to receive any propagation/broadcast/notifications.
147 private boolean noPropagate;
150 * The preferred binary wire message format version.
152 private int preferredMessageVersion;
155 * The welcome message version we are supporting
157 private String versionString;
160 * The welcome message as a text string.
162 private String welcomeString;
165 * The welcome message as UTF-8 byte stream.
167 private byte[] welcomeBytes;
168 private final int MAX_LEN = 4096;
171 * Default constructor
173 public WelcomeMessage() {}
176 * Creates a new instance of WelcomeMessage for our Welcome Message.
178 * @param destAddr The destination address that we believe we are connecting to.
179 * @param publicaddress Our return address, the purported source address of this connection.
180 * @param peerid Our peerid, the logical return address.
181 * @param dontPropagate If <tt>true</tt> this connection does not wish to receive any propagation/broadcast/notifications.
183 public WelcomeMessage(EndpointAddress destAddr, EndpointAddress publicaddress, ID peerid, boolean dontPropagate) {
184 this(destAddr, publicaddress, peerid, dontPropagate, 0);
188 * Creates a new instance of WelcomeMessage for our Welcome Message.
190 * @param destAddr The destination address that we believe we are connecting to.
191 * @param publicaddress Our return address, the purported source address of this connection.
192 * @param peerid Our peerid, the logical return address.
193 * @param dontPropagate If <tt>true</tt> this connection does not wish to receive any propagation/broadcast/notifications.
194 * @param preferredMsgVersion Binary Wire Messsage format we prefer.
196 public WelcomeMessage(EndpointAddress destAddr, EndpointAddress publicaddress, ID peerid, boolean dontPropagate, int preferredMsgVersion) {
197 destinationAddress = destAddr;
198 publicAddress = publicaddress;
200 noPropagate = dontPropagate;
201 versionString = CURRENTVERSION;
202 preferredMessageVersion = preferredMsgVersion;
204 welcomeString = GREETING + SPACE + destAddr.toString() + SPACE + publicAddress.toString() + SPACE + peerID.toString()
205 + SPACE + (noPropagate ? "1" : "0") + SPACE + versionString;
208 welcomeBytes = welcomeString.getBytes("UTF-8");
209 } catch (UnsupportedEncodingException never) {// all implementations must support utf-8
214 * Creates a new instance of WelcomeMessage for another peer's Welcome Message
216 * @param in The InputStream to read the welcome message from.
217 * @throws IOException If there is a problem reading the welcome header.
219 public WelcomeMessage(InputStream in) throws IOException {
220 welcomeBytes = new byte[MAX_LEN];
222 boolean sawCR = false;
223 boolean sawCRLF = false;
225 // read the welcome message
231 throw new EOFException("Stream terminated before end of welcome message");
235 welcomeBytes[readAt++] = (byte) 0x0D;
244 welcomeBytes[readAt++] = (byte) 0x0A;
249 welcomeBytes[readAt++] = (byte) c;
253 if (readAt == welcomeBytes.length) {
254 throw new IOException("Invalid welcome message, too long");
259 byte[] truncatedBytes = new byte[readAt];
261 System.arraycopy(welcomeBytes, 0, truncatedBytes, 0, readAt);
262 welcomeBytes = truncatedBytes;
263 welcomeString = new String(welcomeBytes, "UTF-8");
264 parseWelcome(welcomeString);
268 * Attempts to init a welcome object from a socketChannel
270 * @param buffer the data buffer
271 * @return null if incomplete welcome was received
272 * @throws IOException if an io error occurs
274 public boolean read(ByteBuffer buffer) throws IOException {
275 int limit = buffer.limit();
277 if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
278 LOG.fine(MessageFormat.format("Reading a buffer of size :{0}", limit));
281 throw new IOException(MessageFormat.format("Invalid welcome message. Invalid length {0}", limit));
283 int eomPos = findEom(buffer, 0, limit);
285 if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
286 LOG.fine(MessageFormat.format("Buffer size :{0} Welcome End-Of-Message pos :{1}", limit, eomPos));
291 welcomeBytes = new byte[eomPos];
293 buffer.get(welcomeBytes, 0, eomPos);
295 buffer.position(eomPos + 2);
296 if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
297 LOG.fine(MessageFormat.format("buffer stats :{0}", buffer.toString()));
299 } catch (BufferUnderflowException buf) {
300 // not enough data, signal for another read
303 welcomeString = new String(welcomeBytes, "UTF-8");
304 parseWelcome(welcomeString);
309 * returns position of <cr><lf> position in buffer, otherwise -1
311 * @param buffer the byte buffer
312 * @param offset The offset within the buffer array
313 * @param length the length
314 * @return terminating position, or -1 if none found
316 private int findEom(ByteBuffer buffer, int offset, int length) {
318 int lastOffset = length - 2; // we are looking for 2 chars.
320 for (int j = offset; j <= lastOffset; j++) {
321 byte c = buffer.get(j);
324 c = buffer.get(j + 1);
335 private void parseWelcome(String welcomeString) throws IOException {
336 List<String> thePieces = Arrays.asList(welcomeString.split("\\s"));
338 if (0 == thePieces.size()) {
339 throw new IOException("Invalid welcome message, did not contain any tokens.");
342 if (thePieces.size() < 5) {
343 throw new IOException("Invalid welcome message, did not contain enough tokens.");
346 if (!GREETING.equals(thePieces.get(0))) {
347 throw new IOException("Invalid welcome message, did not start with greeting");
351 destinationAddress = new EndpointAddress(thePieces.get(1));
352 } catch (IllegalArgumentException badAddress) {
353 IOException failed = new IOException("Invalid welcome message, bad destination address");
355 failed.initCause(badAddress);
360 publicAddress = new EndpointAddress(thePieces.get(2));
361 } catch (IllegalArgumentException badAddress) {
362 IOException failed = new IOException("Invalid welcome message, bad publicAddress address");
364 failed.initCause(badAddress);
369 URI peerURI = new URI(thePieces.get(3));
371 peerID = IDFactory.fromURI(peerURI);
372 } catch (URISyntaxException badURI) {
373 IOException failed = new IOException("Invalid welcome message, bad peer id");
375 failed.initCause(badURI);
379 versionString = thePieces.get(thePieces.size() - 1);
381 if (WELCOME_VERSION_1_1.equals(versionString)) {
382 if (6 != thePieces.size()) {
383 throw new IOException("Invalid welcome message, incorrect number of tokens.");
386 String noPropagateStr = thePieces.get(4);
388 if (noPropagateStr.equals("1")) {
390 } else if (noPropagateStr.equals("0")) {
393 throw new IOException("Invalid welcome message, illegal value for propagate flag");
396 // preferred message version is not set in
397 preferredMessageVersion = 0;
398 } else if (WELCOME_VERSION_3_0.equals(versionString)) {
399 if (7 != thePieces.size()) {
400 throw new IOException("Invalid welcome message, incorrect number of tokens.");
403 String noPropagateStr = thePieces.get(4);
405 if (noPropagateStr.equals("1")) {
407 } else if (noPropagateStr.equals("0")) {
410 throw new IOException("Invalid welcome message, illegal value for propagate flag");
413 String preferredVersionStr = thePieces.get(5);
416 preferredMessageVersion = Integer.valueOf(preferredVersionStr);
417 } catch (IllegalArgumentException failed) {
418 IOException failure = new IOException("Invalid welcome message, illegal value for preferred message version");
420 failure.initCause(failed);
425 // Unrecognized Welcome message version. Use default values.
427 preferredMessageVersion = 0;
430 if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
431 LOG.fine("Successfuly parsed a welcome message :" + getWelcomeString());
436 * Write the welcome message to the provided stream.
438 * @param theStream The OutputStream to which to write the welcome message.
439 * @throws IOException If there is a problem writing the welcome message.
441 public void sendToStream(OutputStream theStream) throws IOException {
442 theStream.write(welcomeBytes);
443 theStream.write('\r');
444 theStream.write('\n');
448 * Write the welcome to a socket channel
450 * @return A ByteBuffer of the welcome message
451 * @throws java.io.IOException if an io error occurs
453 public ByteBuffer getByteBuffer() throws IOException {
454 if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
455 LOG.fine(MessageFormat.format("Sending welcome message of size:{0}", welcomeBytes.length + 2));
457 ByteBuffer buffer = ByteBuffer.allocate(welcomeBytes.length + 2);
459 buffer.put(welcomeBytes);
460 buffer.put((byte) '\r');
461 buffer.put((byte) '\n');
467 * Return the peerid associated with the Welcome Message.
469 * @return The peer ID from the Welcome Message.
471 public ID getPeerID() {
476 * Return the source address associated with the Welcome Message.
478 * @return The source address from the Welcome Message.
480 public EndpointAddress getPublicAddress() {
481 return publicAddress;
485 * Return the destination address associated with the Welcome Message.
487 * @return The destination address from the Welcome Message.
489 public EndpointAddress getDestinationAddress() {
490 return destinationAddress;
494 * Return the propagation preference from the Welcome Message.
496 * @return <tt>true</tt> if <strong>no</strong> propagation is desired
497 * otherwise <tt>false</tt>
499 public boolean dontPropagate() {
504 * Return the preferred message version from the Welcome Message.
506 * @return The preferred Message Version.
508 public int getPreferredMessageVersion() {
509 return preferredMessageVersion;
513 * Return the version associated with the Welcome Message.
515 * @return The version from the Welcome Message.
517 public String getWelcomeVersion() {
518 return versionString;
522 * Return a String containing the Welcome Message.
524 * @return a String containing the Welcome Message.
526 public String getWelcomeString() {
527 return welcomeString;