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 (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21/*
22 * Copyright 2001-2002 Sun Microsystems, Inc.  All rights reserved.
23 * Use is subject to license terms.
24 *
25 */
26
27//  SrvLocHeader.java: Abstract superclass for SLP Headers
28//  Author:           James Kempf
29//  Created On:       Mon Sep 14 12:47:20 1998
30//  Last Modified By: James Kempf
31//  Last Modified On: Mon Nov 23 14:32:50 1998
32//  Update Count:     55
33//
34
35package com.sun.slp;
36
37import java.util.*;
38import java.net.*;
39import java.io.*;
40
41/**
42 * SrvLocHeader handles different versions of the SLP header. Clients
43 * call the instance methods returned by newInstance(). If no version
44 * specific subclass exists for the version number, null is returned
45 * from newInstance. Parsing of the header and message bodies, and
46 * creation of error replies are handled by the version specific
47 * subclasses. We also let the SrvLocHeader serve as a SrvLocMsg object
48 * to handle the SrvAck, which only has an error code.
49 *
50 * @author James Kempf
51 */
52
53abstract class SrvLocHeader extends Object implements SrvLocMsg, Cloneable {
54
55    // Table of header classes. Keys are the version number.
56
57    private static final Hashtable classTable = new Hashtable();
58
59    // Offset to XID.
60
61    static final int XID_OFFSET = 10;
62
63    // Common constants and instance variables.
64
65    // Number of bytes in the version and function fields.
66
67    static int VERSION_FUNCTION_BYTES = 2;
68
69    // SLP function codes. Even though SAAdvert isn't in all versions,
70    //  we include it here.
71
72    static final int SrvReq  = 1;
73    static final int SrvRply = 2;
74    static final int SrvReg = 3;
75    static final int SrvDereg = 4;
76    static final int SrvAck = 5;
77    static final int AttrRqst = 6;
78    static final int AttrRply = 7;
79    static final int DAAdvert = 8;
80    static final int SrvTypeRqst = 9;
81    static final int SrvTypeRply = 10;
82    static final int SAAdvert = 11;
83
84    static final String[] functionCodeAbbr = {
85	"0",
86	"SrvReq",
87	"SrvRply",
88	"SrvReg",
89	"SrvDereg",
90	"SrvAck",
91	"AttrRqst",
92	"AttrRply",
93	"DAAdvert",
94	"SrvTypeRqst",
95	"SrvTypeRply",
96	"SAAdvert",
97    };
98
99    // Sizes of data items.
100
101    protected static final int BYTE_SIZE = 1;
102    protected static final int SHORT_SIZE = 2;
103    protected static final int INT24_SIZE = 3;
104
105    //
106    // Header instance variables.
107    //
108
109    // Unprotected for less code.
110
111    int    version = 0;			// version number
112    int    functionCode = 0;		// function code
113    int    length = 0;			// packet length
114    short  xid = 0;			// transaction id
115    short  errCode =
116	ServiceLocationException.OK;	// not applicable to start
117    Locale locale = Defaults.locale;	// language locale
118    Vector previousResponders = null;	// list of previous responders
119    Vector scopes = null;		// list of scopes
120    boolean overflow = false;		// Overflow flag
121    boolean fresh = false;		// Fresh flag
122    boolean mcast = false;		// Mulitcast flag.
123    byte[] payload = new byte[0];	// bytes of outgoing payload,
124    int nbytes = 0;			// number of bytes processed
125    int packetLength = 0;		// length of packet.
126    int iNumReplies = 0;		// number of replies.
127
128
129    protected static short uniqueXID = 0;	// outgoing transaction id.
130
131    // Message description.
132
133    private String msgType;
134    private String msgDescription;
135
136
137    SrvLocHeader() {
138
139	packetLength = SLPConfig.getSLPConfig().getMTU();
140
141    }
142
143    //
144    // SrvLocMsg Implementation.
145    //
146
147    public SrvLocHeader getHeader() {
148	return this;
149
150    }
151
152    public short getErrorCode() {
153	return errCode;
154
155    }
156
157    // Return number of replies to this message.
158
159    public int getNumReplies() {
160	return iNumReplies;
161
162    }
163
164    //
165    // SrvLocHeader Interface.
166    //
167
168    // Register a new header class for version. Serious error, causing
169    //  program termination, if we can't find it.
170
171    static void addHeaderClass(String className, int version) {
172
173	try {
174
175	    Class headerClass = Class.forName(className);
176
177	    classTable.put(new Integer(version), headerClass);
178
179	} catch (ClassNotFoundException ex) {
180
181	    Assert.slpassert(false,
182			  "no_class",
183			  new Object[] {className});
184
185	}
186    }
187
188    // Create a version specific instance. We use a naming convention
189    //  to identify the version specific classes used to create the
190    //  instance.
191
192    static SrvLocHeader newInstance(int version) {
193
194	try {
195
196	    // Get header class.
197
198	    Class hdrClass = (Class)classTable.get(new Integer(version));
199
200	    if (hdrClass == null) {
201		return null;
202
203	    }
204
205	    SrvLocHeader hdr = (SrvLocHeader)hdrClass.newInstance();
206
207	    return hdr;
208
209	} catch (Exception ex) {
210
211	    SLPConfig.getSLPConfig().writeLog("slh_creation_exception",
212					      new Object[] {
213		new Integer(version),
214		    ex,
215		    ex.getMessage()});
216	    return null;
217
218	}
219
220    }
221
222    // Parse the incoming stream to obtain the header.
223
224    abstract void parseHeader(int functionCode, DataInputStream dis)
225	throws ServiceLocationException, IOException, IllegalArgumentException;
226
227    // Parse the incoming stream to obtain the message.
228
229    abstract SrvLocMsg parseMsg(DataInputStream dis)
230	throws ServiceLocationException, IOException, IllegalArgumentException;
231
232    // Externalize the message.
233
234    abstract void
235	externalize(ByteArrayOutputStream baos,
236		    boolean multicast,
237		    boolean isTCP)
238	throws ServiceLocationException;
239
240    // Return the appropriately versioned DAAdvert.
241
242    abstract SDAAdvert
243	getDAAdvert(short xid,
244		    long timestamp,
245		    ServiceURL url,
246		    Vector scopes,
247		    Vector attrs)
248	throws ServiceLocationException;
249
250    //
251    // Methods that some subclasses may reimplement.
252    //
253
254    // Parse any options.
255
256    void parseOptions(DataInputStream dis)
257	throws ServiceLocationException,
258	       IOException,
259	       IllegalArgumentException {
260
261    }
262
263    // Create an error reply for this message. This reply will be appropriate
264    //  for the server to send back to the client. Default is to do nothing,
265    //  which is the code for the client.
266
267    SrvLocMsg makeErrorReply(Exception ex) {
268	return null;
269
270    }
271
272    //
273    //  Common utilities for all versions.
274    //
275
276    // Set the packet length to the incoming value.
277
278    void setPacketLength(int newLength) {
279
280	if (newLength > 0) {
281	    packetLength = newLength;
282
283	}
284    }
285
286    // Add an Internet address to the previous responders list.
287
288    void addPreviousResponder(InetAddress addr) {
289
290	String hostAddr = addr.getHostAddress();
291
292	Assert.slpassert((previousResponders != null),
293		      "prev_resp_reply",
294		      new Object[0]);
295
296	if (!previousResponders.contains(hostAddr)) {
297	    previousResponders.addElement(hostAddr);
298
299	}
300    }
301
302    // Get a unique transaction id.
303
304    synchronized static short getUniqueXID() {
305	if (uniqueXID == 0) {
306	    Random r = new Random();
307	    uniqueXID = (short)(r.nextInt() &0xFFFF);
308	}
309	uniqueXID++;
310	return (short)(uniqueXID & 0xFFFF);
311    }
312
313    // Parse 2-byte integer, bump byte count.
314
315    int getInt(DataInputStream dis)
316	throws ServiceLocationException, IOException {
317
318	int ret = getInteger(dis);
319
320	nbytes += SHORT_SIZE;
321
322	return ret;
323    }
324
325
326    // Parse a 2-byte integer from the input stream.
327
328    static int getInteger(DataInputStream dis)
329	throws ServiceLocationException, IOException {
330
331	byte[] b = new byte[2];
332
333	dis.readFully(b, 0, 2);
334
335	int x = (int)((char)b[0] & 0xFF);
336	int y = (int)((char)b[1] & 0xFF);
337	int z = x << 8;
338	z += y;
339	return z;
340    }
341
342    // Parse 2-byte integer, bump byte count.
343
344    void putInt(int z, ByteArrayOutputStream baos) {
345
346	putInteger(z, baos);
347
348	nbytes += SHORT_SIZE;
349
350    }
351
352    // Parse a 2-byte integer to the output stream.
353
354    static void putInteger(int z, ByteArrayOutputStream baos) {
355	baos.write((byte) ((0xFF00 & z)>>8));
356	baos.write((byte) (0xFF & z));
357    }
358
359
360    // Parse a 3-byte integer from the byte input stream.
361
362    protected int getInt24(DataInputStream dis)
363	throws ServiceLocationException, IOException {
364
365	byte[] b = new byte[3];
366
367	dis.readFully(b, 0, 3);
368
369	int w = (int)((char)b[0] & 0xFF);
370	int x = (int)((char)b[1] & 0xFF);
371	int y = (int)((char)b[2] & 0xFF);
372	int z = w << 16;
373	z += x << 8;
374	z += y;
375	nbytes += 3;
376	return z;
377    }
378
379    // Parse a 3-byte integer to the output stream.
380
381    protected void putInt24(int z, ByteArrayOutputStream baos) {
382	baos.write((byte) ((0xFF0000 & z) >> 16));
383	baos.write((byte) ((0xFF00 & z)>>8));
384	baos.write((byte) (0xFF & z));
385
386	nbytes += 3;
387    }
388
389
390    // Parse string, bump byte count. Use UTF8 encoding.
391
392    byte[] getString(StringBuffer buf, DataInputStream dis)
393	throws ServiceLocationException, IOException {
394
395	byte[] ret = getStringField(buf, dis, Defaults.UTF8);
396
397	nbytes += ret.length + SHORT_SIZE;
398
399	return ret;
400    }
401
402    // Parse a string with an initial length from the input stream.
403    //  Convert it to the proper encoding. Return the raw bytes for
404    //  auth block creation.
405
406    static byte[]
407	getStringField(StringBuffer buf, DataInputStream dis, String encoding)
408	throws ServiceLocationException, IOException {
409
410	// Clear out buffer first.
411
412	buf.setLength(0);
413
414	// First get the length.
415
416	int i, n = 0;
417
418	n = getInteger(dis);
419
420	// Now get the bytes.
421
422	byte[] bytes = new byte[n];
423
424	dis.readFully(bytes, 0, n);
425
426	// Convert to string and return.
427
428	buf.append(getBytesString(bytes, encoding));
429
430	return bytes;
431
432    }
433
434    // Parse out string, bump byte count. Use UTF8 encoding.
435
436    byte[] putString(String string, ByteArrayOutputStream baos) {
437
438	byte[] bytes = putStringField(string, baos, Defaults.UTF8);
439
440	nbytes += bytes.length + SHORT_SIZE;
441
442	return bytes;
443
444    }
445
446    // Put a string with an initial length into the byte stream, converting
447    //  into the proper encoding.
448
449    static byte[]
450	putStringField(String string,
451		       ByteArrayOutputStream baos,
452		       String encoding) {
453
454	byte[] bytes = getStringBytes(string, encoding);
455
456	// Put out the string's length in the encoding.
457
458	putInteger(bytes.length, baos);
459
460	// Now really write out the bytes.
461
462	baos.write(bytes, 0, bytes.length);
463
464	return bytes;
465
466    }
467
468    // Convert a Unicode string into encoded bytes.
469
470    static byte[] getStringBytes(String string, String encoding) {
471
472	try {
473	    return string.getBytes(encoding);
474
475	} catch (UnsupportedEncodingException ex) {
476	    return  new byte[0];  // won't happen, hopefully...
477
478	}
479    }
480
481    // Convert bytes into a Unicode string.
482
483    static String getBytesString(byte[] bytes, String encoding) {
484
485	try {
486	    return new String(bytes, encoding);
487
488	} catch (UnsupportedEncodingException ex) {
489	    return "";  // won't happen, hopefully ...
490
491	}
492
493    }
494
495    // Parse a comma separated list of strings from the vector into the
496    //  output stream.
497
498    protected byte[]
499	parseCommaSeparatedListOut(Vector v,
500				   ByteArrayOutputStream baos) {
501
502	return putString(vectorToCommaSeparatedList(v), baos);
503
504    }
505
506    /**
507     * Create a comma separated list of strings out of the vector.
508     *
509     * @param v A Vector of strings.
510     */
511
512    static String
513	vectorToCommaSeparatedList(Vector v) {
514
515	// Construct in a string buffer first.
516
517	int i, n = v.size();
518	StringBuffer buf = new StringBuffer();
519
520
521	for (i = 0; i < n; i++) {
522	    String string = (String)v.elementAt(i);
523
524	    // Add comma for previous one if we need it.
525
526	    if (i != 0) {
527		buf.append(',');
528	    }
529
530	    buf.append(string);
531
532	}
533
534	// Return the bytes.
535
536	return buf.toString();
537    }
538
539    /**
540     * @parameter The string has the format = STRING *("," STRING)
541     * @parameter A boolean indicating whether parens should be ignored or
542     * 		used for grouping.
543     * @return  A vector (of Strings) based upon the (comma delimited) string.
544     */
545    static Vector parseCommaSeparatedListIn(String s, boolean ignoreParens)
546	throws ServiceLocationException {
547
548	if (s == null)
549	    return new Vector();
550	if (s.length() == 0)
551	    return new Vector();
552	StringTokenizer st = new StringTokenizer(s, ",()", true);
553	try {
554	    int level = 0;
555	    String el = "";
556	    Vector v = new Vector();
557
558	    while (st.hasMoreElements()) {
559		String tok = st.nextToken();
560
561		// It's an open paren, so begin collecting.
562
563		if (tok.equals("(")) {
564
565		    // Increment the level if not ignoring parens, add to token
566
567		    if (!ignoreParens) {
568			level++;
569
570		    }
571
572		    el += tok;
573
574		} else if (tok.equals(")")) {
575
576		    // Decrement level if not ignoring parens.
577
578		    if (!ignoreParens) {
579			level--;
580
581		    }
582
583		    el += tok;
584
585		} else if (tok.equals(",")) {
586
587		    // Add if collecting.
588
589		    if (level != 0) {
590			el += tok;
591
592		    } else {
593
594			// Check for empty token.
595
596			if (el.length() <= 0) {
597			    throw
598				new ServiceLocationException(
599					ServiceLocationException.PARSE_ERROR,
600					"csl_syntax_error",
601					new Object[] {s});
602			}
603
604			// If not collecting, then close off the element.
605
606			v.addElement(el);
607			el = "";
608
609		    }
610		} else {
611		    el += tok;
612
613		}
614	    }
615
616	    // Add last token, but check first for empty token.
617
618	    if (el.length() <= 0) {
619		throw
620		    new ServiceLocationException(
621				ServiceLocationException.PARSE_ERROR,
622				"csl_syntax_error",
623				new Object[] {s});
624	    }
625
626	    v.addElement(el);
627
628	    // If we're still collecting on close, then there's a syntax error.
629
630	    if (level != 0) {
631		throw
632		    new ServiceLocationException(
633				ServiceLocationException.PARSE_ERROR,
634				"csl_syntax_error",
635				new Object[] {s});
636	    }
637
638	    return v;
639	} catch (NoSuchElementException nsee) {
640	    throw
641		new ServiceLocationException(
642				ServiceLocationException.PARSE_ERROR,
643				"csl_syntax_error",
644				new Object[] {s});
645
646	}
647    }
648
649    // Allow clients to clone the header.
650
651    public Object clone()
652	throws CloneNotSupportedException {
653	SrvLocHeader hdr = (SrvLocHeader)super.clone();
654
655	// Reinitialize some stuff. Subclasses must reimplement nbytes
656	//  header size calculation.
657
658	hdr.length = 0;
659	hdr.payload = new byte[0];
660	hdr.iNumReplies = 0;
661	// packetlength stays the same, we may be using the same transport.
662
663	return hdr;
664
665    }
666
667    // Construct a description of the header. Messages add individual
668    //  descriptions to this.
669
670    protected void constructDescription(String msgType,
671					String msgDescription) {
672	this.msgType = msgType;
673	this.msgDescription = msgDescription;
674    }
675
676    public String getMsgType() {
677	if (msgType == null) {
678	    if (functionCode > 0 && functionCode < functionCodeAbbr.length) {
679		return functionCodeAbbr[functionCode];
680	    } else {
681		return String.valueOf(functionCode);
682	    }
683	} else {
684	    return msgType;
685	}
686    }
687
688    public String getMsgDescription() {
689	return (msgDescription == null) ? "" : msgDescription;
690    }
691}
692