xref: /illumos-gate/usr/src/lib/libslp/javalib/com/sun/slp/SLPHeaderV2.java (revision 8548bf79039833dba8615afdf63258b2cb122121)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * ident	"%Z%%M%	%I%	%E% SMI"
24  *
25  * Copyright (c) 2001 by Sun Microsystems, Inc.
26  * All rights reserved.
27  *
28  */
29 
30 //  SCCS Status:       %W%	%G%
31 //  SLPHeaderV2.java:   Base class for Service Location Messages
32 //  Author:           James Kempf
33 //  Created On:       Thu Oct  9 08:50:31 1997
34 //  Last Modified By: James Kempf
35 //  Last Modified On: Wed Jan  6 15:24:26 1999
36 //  Update Count:     472
37 //
38 
39 package com.sun.slp;
40 
41 import java.util.*;
42 
43 import java.net.*;
44 import java.io.*;
45 
46 /**
47  * The SLPHeaderV2 class serves as the header class for all SLPv2 messages.
48  * It contains instance variables for SLP message header contents,
49  * and implements the SLPHeaderV2 interface.
50  *
51  * @version %R%.%L% %D%
52  * @author James Kempf
53  */
54 
55 class SLPHeaderV2 extends SrvLocHeader implements Cloneable {
56 
57     // Support for options.
58 
59     private int optOff = 0;
60     Hashtable optTable = new Hashtable();
61 
62     // Maximum message size (24 bits).
63 
64     private final static int MAX_MESSAGE_LENGTH = 0xffffff;
65 
66     // Location of flag byte.
67 
68     static final int FLAG_BYTE = 4;
69 
70     // Various header flags.
71 
72     protected static final int NOFLAG   = 0x00;
73     static final int           OVERFLOW = 0x80;  // needed by Transact.
74     protected static final int FRESH    = 0x40;
75     protected static final int MCAST    = 0x20;
76 
77     // Header sizes. Note that this doesn't include the language tag,
78     //  which is variable.
79 
80     protected static final int REST_HEADER_BYTES = 12;
81     protected static final int HEADER_BYTES =
82 	VERSION_FUNCTION_BYTES + REST_HEADER_BYTES;
83 
84     // Maximum protected scopes allowed.
85 
86     protected static final int MAX_PROTECTED_SCOPES = 255;
87 
88     // Registered option classes.
89 
90     protected static Hashtable optClasses = new Hashtable();
91 
92     // Manditory option range.
93 
94     protected static int MANDATORY_OPTION_LOW = 0x4000;
95     protected static int MANDATORY_OPTION_HIGH = 0x7fff;
96 
97     // Sizes of option id and extension fields (in bytes).
98 
99     protected static int OPT_ID_SIZE = 2;
100     protected static int OPT_OFF_SIZE = 2;
101 
102     // Interfaces for options to use.
103 
104     interface OptionParser {
105 
106 	// Parse the option from the data stream. We include the header also,
107 	//  in case it is needed.
108 
109 	abstract SLPOption parse(SLPHeaderV2 hdr, DataInputStream dsr)
110 	    throws ServiceLocationException, IOException;
111 
112     }
113 
114     interface SLPOption {
115 
116 	// Externalize the option to the byte array stream. We include the
117 	//  header also, in case it is needed.
118 
119 	abstract void externalize(SLPHeaderV2 hdr, ByteArrayOutputStream baos)
120 	    throws ServiceLocationException;
121 
122     }
123 
124     // Register an option parsing class.
125 
126     static void registerOptionClass(int id, Class optClass) {
127 
128 	Integer key = new Integer(id);
129 
130 	// We should probably check if it implements SLPOption.OptionParser,
131 	//  but...
132 
133 	optClasses.put(key, optClass);
134 
135     }
136 
137     //
138     // Header instance variables.
139     //
140 
141     // For the incoming message side.
142 
143     SLPHeaderV2() {
144 	super();
145 
146 	version = Defaults.version;
147 
148     }
149 
150     // Initialize the new SLPHeaderV2 from the input stream. Version and
151     //  function code have already been removed from the stream.
152 
153     void parseHeader(int functionCode, DataInputStream dis)
154 	throws ServiceLocationException, IOException {
155 
156 	this.functionCode = functionCode;
157 
158 	nbytes += 2;  // for version and function code...
159 
160 	// Get length.
161 
162 	length = getInt24(dis);
163 
164 	// Get flags.
165 
166 	byte[] b = new byte[2];
167 
168 	dis.readFully(b, 0, 2);
169 
170 	nbytes += 2;
171 
172 	byte flags   = (byte) ((char)b[0] & 0xFF);
173 
174 	overflow = ((flags & OVERFLOW) != NOFLAG);
175 	fresh = ((flags & FRESH) != NOFLAG);
176 	mcast = ((flags & MCAST) != NOFLAG);
177 
178 	// We could check for null on reserved part of flags field, but
179 	//  in the spirit of "be liberal in what you receive" we don't.
180 
181 	// Get option offset.
182 
183 	optOff = getInt24(dis);
184 
185 	// Check option offset for sanity.
186 
187 	if (optOff > length) {
188 	    throw
189 		new ServiceLocationException(
190 				ServiceLocationException.PARSE_ERROR,
191 				"option_error",
192 				new Object[] {
193 		    new Integer(optOff), new Integer(length)});
194 
195 	}
196 
197 	// Get transaction id.
198 
199 	xid = (short)getInt(dis);
200 
201 	// Get language code.
202 
203 	StringBuffer buf = new StringBuffer();
204 
205 	getString(buf, dis);
206 
207 	locale = SLPConfig.langTagToLocale(buf.toString());
208 
209 	// Everything went OK coming in, so set the error code.
210 
211 	errCode = ServiceLocationException.OK;
212     }
213 
214     // By default, the header parses the client side message. A server
215     //  side subclass must replace this. We do this so that the amount of code
216     //  in the client is minimized, since this class must be in both.
217 
218     SrvLocMsg parseMsg(DataInputStream dis)
219 	throws ServiceLocationException,
220 	       IOException,
221 	       IllegalArgumentException {
222 
223 	SrvLocMsg rply = null;
224 
225 	// Get the error code, if not SAAdvert.
226 
227 	if (functionCode != SrvLocHeader.SAAdvert) {
228 	    errCode = (short)getInt(dis);
229 
230 	}
231 
232 	// Switch and convert according to function code.
233 
234 	switch (functionCode) {
235 
236 	case SrvLocHeader.SrvRply:
237 	    rply = new CSrvMsg(this, dis);
238 	    break;
239 
240 	case SrvLocHeader.AttrRply:
241 	    rply = new CAttrMsg(this, dis);
242 	    break;
243 
244 	case SrvLocHeader.SrvTypeRply:
245 	    rply = new CSrvTypeMsg(this, dis);
246 	    break;
247 
248 	case SrvLocHeader.DAAdvert:
249 	    rply = new CDAAdvert(this, dis);
250 	    break;
251 
252 	case SrvLocHeader.SrvAck:
253 
254 	    // We act as a SrvAck.
255 
256 	    rply = this;
257 	    iNumReplies = 1;
258 	    break;
259 
260 	case SrvLocHeader.SAAdvert:
261 	    rply = new CSAAdvert(this, dis);
262 	    break;
263 
264 	default:
265 	    throw
266 		new ServiceLocationException(
267 				ServiceLocationException.PARSE_ERROR,
268 				"function_code_error",
269 				new Object[] {
270 		    new Integer(functionCode)});
271 
272 	}
273 
274 	// Check for size overflow.
275 
276 	if (nbytes > length) {
277 	    throw
278 		new ServiceLocationException(
279 				ServiceLocationException.PARSE_ERROR,
280 				"length_overflow",
281 				new Object[] {
282 		    new Integer(nbytes), new Integer(length)});
283 
284 	}
285 
286 	return rply;
287     }
288 
289     // Construct a header for output. Used by the client side code to
290     //  construct an initial request and the server side code to construct
291     //  a reply.
292 
293     SLPHeaderV2(int functionCode, boolean fresh, Locale locale)
294 	throws ServiceLocationException {
295 
296 	// Check for proper function code and nonnull locale.
297 
298 	Assert.slpassert(((functionCode <= SAAdvert) &&
299 	    (functionCode >= SrvReq)),
300 		      "function_code_error",
301 		      new Object[] {new Integer(functionCode)});
302 
303 	Assert.slpassert((locale != null),
304 		      "null_locale_error",
305 		      new Object[0]);
306 
307 	this.version = Defaults.version;
308 	this.functionCode = functionCode;
309 	this.locale = locale;
310 	this.xid = getUniqueXID();  // client can change it later if they want.
311 	this.fresh = fresh;
312 
313 	// If there's not enough for the error code (if any), then signal
314 	//  an error. The assumption here is that the message is going
315 	//  via UDP or multicast.
316 
317 	byte[] ltag =
318 	    getStringBytes(SLPConfig.localeToLangTag(locale), Defaults.UTF8);
319 	int headerLen = ltag.length + HEADER_BYTES;
320 	int payLen =  packetLength - headerLen;
321 
322 	if (payLen < SHORT_SIZE) {
323 	    throw
324 		new ServiceLocationException(
325 				ServiceLocationException.BUFFER_OVERFLOW,
326 				"buffer_overflow",
327 				new Object[] {
328 		    new Integer(headerLen + SHORT_SIZE),
329 			new Integer(packetLength)});
330 	}
331     }
332 
333     // Externalize the message by converting it to bytes and writing
334     //  it to the output stream.
335 
336     public void
337 	externalize(ByteArrayOutputStream baos, boolean mcast, boolean isTCP)
338 	throws ServiceLocationException {
339 
340 	// Convert the locale to a tag. We need the length.
341 
342 	byte[] ltagBytes =
343 	    getStringBytes(SLPConfig.localeToLangTag(locale), Defaults.UTF8);
344 	int ltagLen = ltagBytes.length;
345 
346 	// Set the multicast flag.
347 
348 	this.mcast = mcast;
349 
350 	// We need to get stuff into another stream first, so we can correctly
351 	//  calculate the length.
352 
353 	ByteArrayOutputStream bbaos = new ByteArrayOutputStream();
354 
355 	// Need to put in the error code. There will only be an error code
356 	//  if error codes are applicable for this message type. Note
357 	//  that room for the error code was reserved in the initial
358 	//  calculation of the header, so there should always be room
359 	//  for it, even if the packet overflowed otherwise.
360 
361 	if (functionCode == SrvLocHeader.SrvAck ||
362 	    functionCode == SrvLocHeader.SrvTypeRply ||
363 	    functionCode == SrvLocHeader.SrvRply ||
364 	    functionCode == SrvLocHeader.AttrRply ||
365 	    functionCode == SrvLocHeader.DAAdvert) {
366 	    putInt(errCode, bbaos);
367 
368 	}
369 
370 	// Put in the previous responders, if there are any. Note that
371 	//  there may be only when the error code is not put out.
372 	//  We check against the packet size during parsing so that
373 	//  we don't overflow the packet and throw a special exception
374 	//  if an overflow happens. We only put out the previous
375 	//  responders list if the request is going by multicast, but
376 	//  we need to put out an empty vector for unicast requests.
377 
378 	int prevResLen =
379 	    packetLength - (payload.length + HEADER_BYTES + ltagLen);
380 	Vector resp = previousResponders;
381 
382 	if (resp != null) {
383 	    resp = (mcast ? resp:new Vector());
384 
385 	    parsePreviousRespondersOut(resp, bbaos, prevResLen);
386 
387 	}
388 
389 	// If the error code is OK, then insert the rest of the message
390 	//  and parse the options. If there was an error,
391 	//  this step is skipped because the data isn't relevant.
392 
393 	if (errCode == ServiceLocationException.OK) {
394 	    bbaos.write(payload, 0, payload.length);
395 
396 	    // Externalize any options.
397 
398 	    optOff = externalizeOptions(bbaos, ltagLen);
399 	}
400 
401 	byte[] payloadBytes = bbaos.toByteArray();
402 
403 	// Set the length here to the actual length of the packet.
404 
405 	length = HEADER_BYTES + ltagLen + payloadBytes.length;
406 
407 	// If we exceed the 24 bit length size, we are hosed.
408 
409 	if (length > MAX_MESSAGE_LENGTH) {
410 	    throw
411 		new ServiceLocationException(
412 				ServiceLocationException.PARSE_ERROR,
413 				"max_msg_size_exceeded",
414 				new Object[0]);
415 
416 	}
417 
418 	// Truncate if necessary. We will always have room for a header
419 	//  and error code because we check when creating the object.
420 	//  Note that no URL block will be truncated because the spec
421 	//  says it can't be.
422 
423 	if (!isTCP && (length > packetLength)) {
424 	    overflow = true;
425 	    length = packetLength;
426 	    byte[] newBytes = new byte[packetLength];
427 	    System.arraycopy(payloadBytes, 0, newBytes, 0,
428 			     length - (HEADER_BYTES + ltagLen));
429 	    payloadBytes = newBytes;
430 
431 	}
432 
433 	//
434 	// Write out the header.
435 	//
436 
437 	// Write version and function code.
438 
439 	baos.write((byte) (0xFF & version));
440 	baos.write((byte) (0xFF & functionCode));
441 
442 	// Put length in.
443 
444 	putInt24(length, baos);
445 
446 	// Put in flags.
447 
448 	byte flags = (byte)NOFLAG;
449 
450 	if (overflow) {
451 	    flags = (byte)(flags | OVERFLOW);
452 	} else {
453 	    flags = (byte)(flags & ~OVERFLOW);
454 	}
455 
456 	if (fresh) {
457 	    flags = (byte)((flags | FRESH) & 0xFF);
458 	} else {
459 	    flags = (byte)((flags & ~FRESH) & 0xFF);
460 	}
461 
462 	if (mcast) {
463 	    flags = (byte)((flags | MCAST) & 0xFF);
464 	} else {
465 	    flags = (byte)((flags & ~MCAST) & 0xFF);
466 	}
467 
468 	// Write out flags.
469 
470 	baos.write((byte) (0xFF & flags));
471 	baos.write((byte)0);
472 
473 	putInt24(optOff, baos);  // write option offset,  if any.
474 
475 	putInt(xid, baos);  // write xid.
476 
477 	putInt(ltagLen, baos);  // write lang size.
478 	baos.write(ltagBytes, 0, ltagBytes.length);  // write lang tag.
479 
480 	//
481 	// Write the body.
482 	//
483 
484 	baos.write(payloadBytes, 0, payloadBytes.length);
485 
486     }
487 
488     //
489     // Option handling.
490     //
491 
492     // Parse any options.
493 
494     void parseOptions(DataInputStream dsr)
495 	throws ServiceLocationException,
496 	       IOException,
497 	       IllegalArgumentException {
498 
499 	// If no options return.
500 
501 	if (optOff == 0) {
502 	    return;
503 
504 	}
505 
506 	int optNext = 0;
507 
508 	// Parse any options in the data stream.
509 
510 	do {
511 
512 	    // Parse extension id.
513 
514 	    int optId = getInt(dsr);
515 
516 	    // Parse extension offset.
517 
518 	    optNext = getInt(dsr);
519 
520 	    // Lookup an option parser.
521 
522 	    Integer key = new Integer(optId);
523 
524 	    Class optClass = (Class)optClasses.get(key);
525 
526 	    // May be an exception if class is null.
527 
528 	    if (optClass == null) {
529 
530 		// In mandatory range. Throw an exception.
531 
532 		if ((optId >= MANDATORY_OPTION_LOW) &&
533 		    (optId <= MANDATORY_OPTION_HIGH)) {
534 		    throw
535 			new ServiceLocationException(
536 				ServiceLocationException.OPTION_NOT_SUPPORTED,
537 				"v2_unsup_option",
538 				new Object[] {key});
539 
540 		}
541 
542 		// Skip the rest of the option.
543 
544 		int skipStart = length;
545 
546 		if (optNext != 0) {
547 		    skipStart = optNext;
548 
549 		}
550 
551 		dsr.skipBytes(skipStart - nbytes);
552 
553 
554 	    } else {
555 
556 		try {
557 
558 		    // Parse the option.
559 
560 		    OptionParser optParser =
561 			(OptionParser)optClass.newInstance();
562 
563 		    SLPOption opt = optParser.parse(this, dsr);
564 
565 		    // Insert option into option table.
566 
567 		    optTable.put(key, opt);
568 
569 		} catch (InstantiationException ex) {
570 
571 		    throw
572 			new ServiceLocationException(
573 				ServiceLocationException.INTERNAL_SYSTEM_ERROR,
574 				"v2_option_inst",
575 				new Object[] {key, ex});
576 
577 		} catch (IllegalAccessException ex) {
578 
579 		    throw
580 			new ServiceLocationException(
581 				ServiceLocationException.INTERNAL_SYSTEM_ERROR,
582 				"v2_option_sec",
583 				new Object[] {key, ex});
584 		}
585 	    }
586 	} while (optNext != 0);
587     }
588 
589     // Externalize any options.
590 
591     private int externalizeOptions(ByteArrayOutputStream baos, int langTagLen)
592 	throws ServiceLocationException {
593 
594 	// Calculate offset to options, if any.
595 
596 	int toOpt  = 0;
597 
598 	if (optTable.size() <= 0) {
599 	    return toOpt;
600 
601 	}
602 
603 	toOpt = HEADER_BYTES + langTagLen + baos.size();
604 
605 	// For all options in the table, parse them out.
606 
607 	Enumeration en = optTable.keys();
608 	int nextOpt = toOpt;
609 
610 	while (en.hasMoreElements()) {
611 	    Integer id = (Integer)en.nextElement();
612 	    SLPOption opt = (SLPOption)optTable.get(id);
613 	    ByteArrayOutputStream obaos = new ByteArrayOutputStream();
614 
615 	    // Linearize option object.
616 
617 	    opt.externalize(this, obaos);
618 
619 	    // Calculate offset to next options.
620 
621 	    nextOpt += obaos.size() + OPT_ID_SIZE + OPT_OFF_SIZE;
622 
623 	    // Plop it into the output stream.
624 
625 	    putInt(id.intValue(), baos);
626 
627 
628 	    // Check whether there are more options first. If so, then
629 	    //  the next offset is zero.
630 
631 	    if (en.hasMoreElements()) {
632 		putInt(nextOpt, baos);
633 
634 	    } else {
635 		putInt(0, baos);
636 
637 	    }
638 
639 	    byte[] bytes = obaos.toByteArray();
640 
641 	    baos.write(bytes, 0, bytes.length);
642 
643 	}
644 
645 	return toOpt;
646     }
647 
648     // Parse the previous responder list out, being sure to truncate
649     //  so the list is syntactically correct if there is an overflow.
650     //  This duplicates the comma separated list code to a certain
651     //  extent.
652 
653     private void
654 	parsePreviousRespondersOut(Vector resp,
655 				   ByteArrayOutputStream baos,
656 				   int available)
657 	throws ServiceLocationException {
658 
659 	ByteArrayOutputStream bbaos = new ByteArrayOutputStream();
660 	int i, n = resp.size();
661 
662 	for (i = 0; i < n; i++) {
663 	    String address = (String)resp.elementAt(i);
664 
665 	    // Add comma if necessary.
666 
667 	    if (i > 0) {
668 		address = "," + address;
669 
670 	    }
671 
672 	    // Convert to UTF8 bytes.
673 
674 	    byte[] bytes = getStringBytes(address, Defaults.UTF8);
675 
676 	    // Write bytes to stream if there's room.
677 
678 	    if (bytes.length <= available) {
679 		bbaos.write(bytes, 0, bytes.length);
680 		available = available - bytes.length;
681 
682 	    } else {
683 
684 		// Throw exception, upper layers need to break off multicast.
685 		//  This exception should *never* be surfaced.
686 
687 		throw
688 		    new ServiceLocationException(
689 			ServiceLocationException.PREVIOUS_RESPONDER_OVERFLOW,
690 			"v2_prev_resp_overflow",
691 			new Object[] {});
692 	    }
693 	}
694 
695 	// Now write to the real stream.
696 
697 	byte[] out = bbaos.toByteArray();
698 	putInt(out.length, baos);
699 	baos.write(out, 0, out.length);
700 
701 	nbytes += out.length;
702 
703     }
704 
705     //
706     //  Utilities for parsing service URL's and attribute lists, with
707     //  authentication information.
708     //
709 
710     // Parse in a service URL including lifetime if necessary.
711 
712     ServiceURL
713 	parseServiceURLIn(DataInputStream dis,
714 			  Hashtable authTable,
715 			  short err)
716 	throws ServiceLocationException, IOException {
717 
718 	// Ignore reserved byte.
719 
720 	byte[] b = new byte[1];
721 
722 	dis.readFully(b, 0, 1);
723 
724 	nbytes += 1;
725 
726 	// Get URL lifetime.
727 
728 	int lifetime = getInt(dis);
729 
730 	// Get URL.
731 
732 	StringBuffer buf = new StringBuffer();
733 
734 	byte[] rawBytes = getString(buf, dis);
735 
736 	// Get auth block, if any.
737 
738 	Hashtable auth = null;
739 
740 	// Get number of auth blocks.
741 
742 	b = new byte[1];
743 
744 	dis.readFully(b, 0, 1);
745 
746 	nbytes += 1;
747 
748 	byte nauths = (byte)(b[0] & 0xFF);
749 
750 	if (nauths > 0) {
751 	    ByteArrayOutputStream abaos = new ByteArrayOutputStream();
752 	    putInteger(rawBytes.length, abaos);
753 	    Object[] message = new Object[2];
754 	    message[0] = abaos.toByteArray();
755 	    message[1] = rawBytes;
756 	    auth = getCheckedAuthBlockList(message, nauths, dis);
757 
758 	    lifetime = AuthBlock.getShortestLifetime(auth);
759 
760 	}
761 
762 	String ssurl = buf.toString();
763 	ServiceURL url = null;
764 
765 	try {
766 
767 	    url = new ServiceURL(ssurl, lifetime);
768 
769 	} catch (IllegalArgumentException ex) {
770 
771 	    throw
772 		new ServiceLocationException(err,
773 					     "malformed_url",
774 					     new Object[] {ex.getMessage()});
775 
776 	}
777 
778 	if (auth != null) {
779 
780 	    // Put it in the auth block for this URL.
781 
782 	    authTable.put(url, auth);
783 
784 	}
785 
786 	return url;
787     }
788 
789     // Parse out a service URL, create authentication blocks if necessary.
790     //  Return true if the URL was output. Check that we don't overflow
791     //  packet size in the middle.
792 
793     boolean
794 	parseServiceURLOut(ServiceURL surl,
795 			   boolean urlAuth,
796 			   Hashtable auth,
797 			   ByteArrayOutputStream baos,
798 			   boolean checkOverflow)
799 	throws ServiceLocationException {
800 
801 	// We need to keep track of size, so we don't overflow packet length.
802 
803 	ByteArrayOutputStream bbaos = new ByteArrayOutputStream();
804 
805 	int mbytes = nbytes;
806 
807 	// Convert the URL to bytes.
808 
809 	byte[] bytes =  getStringBytes(surl.toString(), Defaults.UTF8);
810 
811 	// Parse out reserved.
812 
813 	bbaos.write((byte)(0xFF & 0));
814 
815 	nbytes += 1;
816 
817 	// Parse out the lifetime.
818 
819 	putInt(surl.getLifetime(), bbaos);
820 
821 	byte bs = (byte)0;
822 
823 	// Process auth block list if required.
824 
825 	if (urlAuth) {
826 
827 	    // Create an auth block if necessary.
828 
829 	    if (auth == null) {
830 		ByteArrayOutputStream abaos = new ByteArrayOutputStream();
831 		putInteger(bytes.length, abaos);
832 		Object[] message = new Object[2];
833 		message[0] = abaos.toByteArray();
834 		message[1] = bytes;
835 		auth = getCheckedAuthBlockList(message, surl.getLifetime());
836 
837 	    }
838 
839 	    bs = (byte) auth.size();
840 	    Object[] bytesArray = AuthBlock.getContents(auth);
841 	    bytes = (byte[]) bytesArray[1];
842 
843 	}
844 
845 	// Put out the URL bytes.
846 
847 	putInt(bytes.length, bbaos);
848 	bbaos.write(bytes, 0, bytes.length);
849 
850 	nbytes += bytes.length;
851 
852 	// Write auth block size.
853 
854 	bbaos.write((byte)(0xFF & bs));
855 
856 	nbytes += 1;
857 
858 	// If there are auth blocks required, put them out now.
859 
860 	if (bs > (byte)0) {
861 	    AuthBlock.externalizeAll(this, auth, bbaos);
862 
863 	}
864 
865 	// If we can, write it out.
866 
867 	bytes = bbaos.toByteArray();
868 
869 	if (!checkOverflow || nbytes <= packetLength) {
870 	    baos.write(bytes, 0, bytes.length);
871 	    return true; // nbytes already set...
872 
873 	} else {
874 	    nbytes = mbytes; // truncate...
875 	    return false;
876 
877 	}
878     }
879 
880     // Parse in a potentially authenticated attribute list.
881 
882     Hashtable
883 	parseAuthenticatedAttributeVectorIn(Vector attrs,
884 					    DataInputStream dis,
885 					    boolean allowMultiValuedBooleans)
886 	throws ServiceLocationException, IOException {
887 
888 	// First, parse in the attribute vector.
889 
890 	byte[] rawBytes =
891 	    parseAttributeVectorIn(attrs, dis, allowMultiValuedBooleans);
892 
893 	ByteArrayOutputStream abaos = new ByteArrayOutputStream();
894 	putInteger(rawBytes.length, abaos);
895 	Object[] message = new Object[2];
896 	message[0] = abaos.toByteArray();
897 	message[1] = rawBytes;
898 
899 	// Get the attribute list signature, if necessary.
900 
901 	return parseSignatureIn(message, dis);
902 
903     }
904 
905     // Parse in a list of attributes into attrs, returing raw bytes.
906     //  ServiceLocationAttribute objects. Clients take care of auth blocks.
907 
908     byte[]
909 	parseAttributeVectorIn(Vector attrs,
910 			       DataInputStream dis,
911 			       boolean allowMultiValuedBooleans)
912 	throws ServiceLocationException, IOException {
913 
914 	StringBuffer buf = new StringBuffer();
915 
916 	byte[] rawBytes  = getString(buf, dis);
917 
918 	// Parse the list into ServiceLocationAttribute objects.
919 
920 	Vector attrForms = parseCommaSeparatedListIn(buf.toString(), false);
921 
922 	int i, n = attrForms.size();
923 
924 	for (i = 0; i < n; i++) {
925 	    String attrForm =
926 		(String)attrForms.elementAt(i);
927 
928 	    attrs.addElement(
929 		new ServiceLocationAttribute(
930 			attrForm, allowMultiValuedBooleans));
931 	}
932 
933 	return rawBytes;
934     }
935 
936     // Parse out a vector of ServiceLocationAttributes. Includes escaping
937     //  characters.
938     byte[]
939 	parseAttributeVectorOut(Vector v,
940 				int lifetime,
941 				boolean attrAuth,
942 				Hashtable auth,
943 				ByteArrayOutputStream baos,
944 				boolean writeAuthCount)
945 	throws ServiceLocationException {
946 
947 	byte[] bytes = null;
948 	int nBlocks = 0;
949 
950 	// Convert attribute vector to comma separated list.
951 
952 	if (!attrAuth || auth == null) {
953 	    Vector strings = new Vector();
954 	    Enumeration en = v.elements();
955 
956 	    // Convert the attributes to strings, escaping characters to
957 	    //  escape.
958 
959 	    while (en.hasMoreElements()) {
960 		ServiceLocationAttribute attr =
961 		    (ServiceLocationAttribute)en.nextElement();
962 
963 		strings.addElement(attr.externalize());
964 
965 	    }
966 
967 	    // Create the comma separated list.
968 
969 	    String clist = vectorToCommaSeparatedList(strings);
970 	    bytes = getStringBytes(clist, Defaults.UTF8);
971 
972 	    if (attrAuth) {
973 		ByteArrayOutputStream abaos = new ByteArrayOutputStream();
974 		putInteger(bytes.length, abaos);
975 		Object[] message = new Object[2];
976 		message[0] = abaos.toByteArray();
977 		message[1] = bytes;
978 		auth = getCheckedAuthBlockList(message, lifetime);
979 	    }
980 	} else {
981 	    Object[] bytesArray = AuthBlock.getContents(auth);
982 	    bytes = (byte[]) bytesArray[1];
983 
984 	}
985 
986 	// Get number of blocks if authentication.
987 
988 	if (auth != null) {
989 	    nBlocks = auth.size();
990 
991 	}
992 
993 
994 	// Write out the bytes.
995 
996 	putInt(bytes.length, baos);
997 	baos.write(bytes, 0, bytes.length);
998 	nbytes += bytes.length;
999 
1000 	// Write out number of auth blocks.
1001 
1002 	if (writeAuthCount) {
1003 	    baos.write((byte)(nBlocks & 0xFF));
1004 	    nbytes += 1;
1005 	}
1006 
1007 	// Write out the attribute authentication blocks.
1008 
1009 	if (attrAuth && nBlocks > 0) {
1010 	    AuthBlock.externalizeAll(this, auth, baos);
1011 	}
1012 
1013 	return bytes;
1014     }
1015 
1016     // Get an auth block list, checking first for security.
1017 
1018     Hashtable getCheckedAuthBlockList(Object[] message, int lifetime)
1019 	throws ServiceLocationException {
1020 
1021 	if (!SLPConfig.getSLPConfig().getHasSecurity()) {
1022 	    throw
1023 		new ServiceLocationException(
1024 				ServiceLocationException.AUTHENTICATION_ABSENT,
1025 				"auth_classes_missing",
1026 				new Object[0]);
1027 	}
1028 
1029 	return AuthBlock.makeAuthBlocks(message, lifetime);
1030     }
1031 
1032     // Get an SLPAuthBlockList, checking first if security is enabled.
1033 
1034     Hashtable getCheckedAuthBlockList(Object[] message,
1035 				      byte nauth,
1036 				      DataInputStream dis)
1037 	throws ServiceLocationException, IOException {
1038 
1039 	if (!SLPConfig.getSLPConfig().getHasSecurity()) {
1040 	    throw
1041 		new ServiceLocationException(
1042 				ServiceLocationException.AUTHENTICATION_ABSENT,
1043 				"auth_classes_missing",
1044 				new Object[0]);
1045 	}
1046 
1047 	return AuthBlock.makeAuthBlocks(this, message, dis, nauth);
1048     }
1049 
1050     // Parse in an attribute signature.
1051 
1052     Hashtable parseSignatureIn(Object[] message, DataInputStream dis)
1053 	throws ServiceLocationException, IOException {
1054 
1055 	Hashtable auth = null;
1056 
1057 	byte[] b = new byte[1];
1058 
1059 	dis.readFully(b, 0, 1);
1060 	nbytes += 1;
1061 
1062 	byte nauths = (byte)(b[0] & 0xFF);
1063 
1064 	if (nauths > 0) {
1065 	    auth = getCheckedAuthBlockList(message, nauths, dis);
1066 
1067 	}
1068 
1069 	return auth;
1070     }
1071 
1072     //
1073     // Utility functions to help with verification of data.
1074     //
1075 
1076     // Escape tags, check for strings. Trim trailing, leading whitespace,
1077     //  since it is ignored for matching purposes and tags are only
1078     //  used for matching.
1079 
1080     void escapeTags(Vector t)
1081 	throws ServiceLocationException {
1082 
1083 	int i, n = t.size();
1084 
1085 	for (i = 0; i < n; i++) {
1086 	    Object o = t.elementAt(i);
1087 
1088 	    if (o instanceof String) {
1089 
1090 		// Escape tag.
1091 
1092 		String tag =
1093 		    ServiceLocationAttribute.escapeAttributeString((String)o,
1094 								   false);
1095 
1096 		t.setElementAt(tag.trim(), i);
1097 
1098 	    } else {
1099 		throw
1100 		    new IllegalArgumentException(
1101 		SLPConfig.getSLPConfig().formatMessage("nonstring_tag",
1102 						       new Object[0]));
1103 	    }
1104 	}
1105     }
1106 
1107     // Unescape tags. Trim trailing and leading whitespace since it is
1108     //  ignored for matching purposes and tags are only used for matching.
1109 
1110     void unescapeTags(Vector t)
1111 	throws ServiceLocationException {
1112 
1113 	int i, n = t.size();
1114 
1115 	for (i = 0; i < n; i++) {
1116 	    String tag = (String)t.elementAt(i);
1117 
1118 	    tag =
1119 		ServiceLocationAttribute.unescapeAttributeString(tag, false);
1120 
1121 	    t.setElementAt(tag.trim(), i);
1122 	}
1123     }
1124 
1125     // Escape vector of scope strings.
1126 
1127     static void escapeScopeStrings(Vector scopes)
1128 	throws ServiceLocationException {
1129 
1130 	int i, n = scopes.size();
1131 	Vector ret = new Vector();
1132 
1133 	for (i = 0; i < n; i++) {
1134 	    String scope = (String)scopes.elementAt(i);
1135 
1136 	    scopes.setElementAt(
1137 		ServiceLocationAttribute.escapeAttributeString(scope, false),
1138 		i);
1139 	}
1140     }
1141 
1142     // Unescape vector of scope strings.
1143 
1144     static void unescapeScopeStrings(Vector scopes)
1145 	throws ServiceLocationException {
1146 
1147 	int i, n = scopes.size();
1148 	Vector ret = new Vector();
1149 
1150 	for (i = 0; i < n; i++) {
1151 	    String scope = (String)scopes.elementAt(i);
1152 
1153 	    scopes.setElementAt(
1154 		ServiceLocationAttribute.unescapeAttributeString(scope, false),
1155 		i);
1156 	}
1157     }
1158 
1159     // Error if somebody tries to do this client side.
1160 
1161     SDAAdvert
1162 	getDAAdvert(short xid,
1163 		    long timestamp,
1164 		    ServiceURL url,
1165 		    Vector scopes,
1166 		    Vector attrs)
1167 	throws ServiceLocationException {
1168 
1169 	Assert.slpassert(false,
1170 		      "v2_daadvert_client_side",
1171 		      new Object[0]);
1172 
1173 	return null;  // never get here...
1174     }
1175 
1176     // Reimplement clone() to get the header size right.
1177 
1178     public Object clone()
1179 	throws CloneNotSupportedException {
1180 	SLPHeaderV2 hdr = (SLPHeaderV2)super.clone();
1181 
1182 	byte[] langBytes = getStringBytes(locale.toString(),
1183 					  Defaults.UTF8);
1184 
1185 	hdr.nbytes = HEADER_BYTES + langBytes.length + 2;  // for error code...
1186 
1187 	return hdr;
1188     }
1189 }
1190