costin      00/12/04 22:30:16

  Modified:    src/share/org/apache/tomcat/modules/server Ajp13.java
  Removed:     src/share/org/apache/tomcat/util BuffTool.java
  Log:
  Check in the excelent patch submited by Dan Milstein.
  
  Remove the BufTool - it's no longer needed, all the encoding that was
  specific to ajp13 is now part of Ajp13Packet.
  
  ( the general purpose byte parsing will show up in a different tool,  to be used
  by all components that are working on the byte[] - I have it almost
  done as a rewrite of MessageBytes  )
  
  Submitted by: Dan Milstein <[EMAIL PROTECTED]>
  
  Revision  Changes    Path
  1.6       +400 -187  
jakarta-tomcat/src/share/org/apache/tomcat/modules/server/Ajp13.java
  
  Index: Ajp13.java
  ===================================================================
  RCS file: 
/home/cvs/jakarta-tomcat/src/share/org/apache/tomcat/modules/server/Ajp13.java,v
  retrieving revision 1.5
  retrieving revision 1.6
  diff -u -r1.5 -r1.6
  --- Ajp13.java        2000/11/30 04:58:45     1.5
  +++ Ajp13.java        2000/12/05 06:30:15     1.6
  @@ -59,27 +59,51 @@
   
   package org.apache.tomcat.modules.server;
   
  -import java.io.*;
  -import java.net.*;
  -import java.util.*;
  -import org.apache.tomcat.core.*;
  -import org.apache.tomcat.util.*;
  -
  +import java.io.IOException;
  +import java.io.UnsupportedEncodingException;
  +import java.io.InputStream;
  +import java.io.OutputStream;
  +import java.net.Socket;
  +import java.util.Enumeration;
  +
  +import org.apache.tomcat.core.Request;
  +import org.apache.tomcat.util.MimeHeaders;
  +import org.apache.tomcat.util.MessageBytes;
  +
  +/**
  + * Represents a single, persistent connection between the web server and
  + * the servlet container.  Uses the Apache JServ Protocol version 1.3 for
  + * communication.  Because this protocal does not multiplex requests, this
  + * connection can only be associated with a single request-handling cycle
  + * at a time.<P>
  + *
  + * This class contains knowledge about how an individual packet is laid out
  + * (via the internal <CODE>Ajp13Packet</CODE> class), and also about the
  + * stages of communicaton between the server and the servlet container.  It
  + * translates from Tomcat's internal servlet support methods
  + * (e.g. doWrite) to the correct packets to send to the web server.
  + *
  + * @see Ajp13Interceptor 
  + */
   public class Ajp13
   {
       public static final int MAX_PACKET_SIZE=8192;
  -    public static final int H_SIZE=4;
  +    public static final int H_SIZE=4;  // Size of basic packet header
   
       public static final int  MAX_READ_SIZE = MAX_PACKET_SIZE - H_SIZE - 2;
       public static final int  MAX_SEND_SIZE = MAX_PACKET_SIZE - H_SIZE - 4;
   
  +    // Prefix codes for message types from server to container
       public static final byte JK_AJP13_FORWARD_REQUEST   = 2;
       public static final byte JK_AJP13_SHUTDOWN          = 7;
        
  +    // Prefix codes for message types from container to server
       public static final byte JK_AJP13_SEND_BODY_CHUNK   = 3;
       public static final byte JK_AJP13_SEND_HEADERS      = 4;
       public static final byte JK_AJP13_END_RESPONSE      = 5;
  +    public static final byte JK_AJP13_GET_BODY_CHUNK    = 6;
       
  +    // Integer codes for common response header strings
       public static final int SC_RESP_CONTENT_TYPE        = 0xA001;
       public static final int SC_RESP_CONTENT_LANGUAGE    = 0xA002;
       public static final int SC_RESP_CONTENT_LENGTH      = 0xA003;
  @@ -91,11 +115,10 @@
       public static final int SC_RESP_SERVLET_ENGINE      = 0xA009;
       public static final int SC_RESP_STATUS              = 0xA00A;
       public static final int SC_RESP_WWW_AUTHENTICATE    = 0xA00B;
  -    
  -    public static final byte JK_AJP13_GET_BODY_CHUNK = 6;
        
  -    public static final byte SC_A_CONTEXT       = 1;
  -    public static final byte SC_A_SERVLET_PATH  = 2;
  +    // Integer codes for common (optional) request attribute names
  +    public static final byte SC_A_CONTEXT       = 1;  // XXX Unused
  +    public static final byte SC_A_SERVLET_PATH  = 2;  // XXX Unused
       public static final byte SC_A_REMOTE_USER   = 3;
       public static final byte SC_A_AUTH_TYPE     = 4;
       public static final byte SC_A_QUERY_STRING  = 5;
  @@ -103,9 +126,14 @@
       public static final byte SC_A_SSL_CERT      = 7;
       public static final byte SC_A_SSL_CIPHER    = 8;
       public static final byte SC_A_SSL_SESSION   = 9;
  -    public static final byte SC_A_REQ_ATTRIBUTE = 10;
  +
  +    // Used for attributes which are not in the list above
  +    public static final byte SC_A_REQ_ATTRIBUTE = 10; 
  +
  +    // Terminates list of attributes
       public static final byte SC_A_ARE_DONE      = (byte)0xFF;
   
  +    // Translates integer codes to names of HTTP methods
       public static final String []methodTransArray = {
           "OPTIONS",
           "GET",
  @@ -116,6 +144,7 @@
           "TRACE"
       };
       
  +    // Translates integer codes to request header names
       public static final String []headerTransArray = {
           "accept",
           "accept-charset",
  @@ -133,86 +162,118 @@
           "user-agent"
       };
   
  +
  +    // ============ Instance Properties ====================
  +
       OutputStream out;
       InputStream in;
        
  -    Ajp13Packet outBuf=new Ajp13Packet( MAX_PACKET_SIZE );;
  -    Ajp13Packet inBuf=new Ajp13Packet( MAX_PACKET_SIZE );;
  +    Ajp13Packet outBuf = new Ajp13Packet( MAX_PACKET_SIZE );
  +    Ajp13Packet inBuf  = new Ajp13Packet( MAX_PACKET_SIZE );
   
  +    // Holds incoming reads of request body data (*not* header data)
       byte []bodyBuff = new byte[MAX_READ_SIZE];
  -    int blen;
  -    int pos;
  +    
  +    int blen;  // Length of current chunk of body data in buffer
  +    int pos;   // Current read position within that buffer
   
       public Ajp13() 
       {
           super();
       }
   
  +    public void recycle() 
  +    {
  +    }
  +    
  +    /**
  +     * Associate an open socket with this instance.
  +     */
       public void setSocket( Socket socket ) throws IOException {
        socket.setSoLinger( true, 100);
        out = socket.getOutputStream();
  -     in = socket.getInputStream();
  -     pos=0;
  +     in  = socket.getInputStream();
  +     pos = 0;
       }
   
  +    /**
  +     * Read a new packet from the web server and decode it.  If it's a
  +     * forwarded request, store its properties in the passed-in Request
  +     * object.
  +     *
  +     * @param req An empty (newly-recycled) request object.
  +     * 
  +     * @return 200 in case of a successful read of a forwarded request, 500
  +     * if there were errors in the reading of the request, and -2 if the
  +     * server is asking the container to shut itself down.  
  +     */
       public int receiveNextRequest(Request req) throws IOException 
       {
  +     // XXX The return values are awful.
  +
        int err = receive(inBuf);
        if(err < 0) {
            return 500;
        }
        
  -     // XXX right now the only incoming packet is "new request"
  -     // We need to deal with arbitrary calls
        int type = (int)inBuf.getByte();
        switch(type) {
            
        case JK_AJP13_FORWARD_REQUEST:
            return decodeRequest(req, inBuf);
  -
  +         
        case JK_AJP13_SHUTDOWN:
            return -2;
        }
  -     return 200;
  +     return 200; // XXX This is actually an error condition 
       }
   
  +    /**
  +     * Parse a FORWARD_REQUEST packet from the web server and store its
  +     * properties in the passed-in request object.
  +     *
  +     * @param req An empty (newly-recycled) request object.
  +     * @param msg Holds the packet which has just been sent by the web
  +     * server, with its read position just past the packet header (which in
  +     * this case includes the prefix code for FORWARD_REQUEST).
  +     *
  +     * @return 200 in case of a successful decoduing, 500 in case of error.  
  +     */
       private int decodeRequest( Request req, Ajp13Packet msg ) throws IOException
       {
  -     
  +     // XXX Awful return values
  +
           boolean isSSL = false;
  -        byte bsc;
  -        int  hCount = 0;
  +
  +        // Translate the HTTP method code to a String.
  +        byte methodCode = msg.getByte();
  +        req.method().setString( methodTransArray[(int)methodCode - 1] );
   
  -        /*
  -         * Read the method and translate it to a String
  -         */
  -        bsc        = msg.getByte();
  -        req.method().setString( methodTransArray[(int)bsc - 1] );
  -        req.setProtocol( msg.getString());
  -        req.requestURI().setString(  msg.getString());
  +        req.setProtocol(            msg.getString());
  +        req.requestURI().setString( msg.getString());
   
  -        req.setRemoteAddr( msg.getString());
  -        req.setRemoteHost( msg.getString());
  +        req.setRemoteAddr(          msg.getString());
  +        req.setRemoteHost(          msg.getString());
           req.serverName().setString( msg.getString());
  -        req.setServerPort( msg.getInt());
  +        req.setServerPort(          msg.getInt());
   
  -     bsc        = msg.getByte();
  -        if(bsc != 0) {
  -            isSSL = true;
  -        }
  +     isSSL = (msg.getByte() != 0);
   
        // Decode headers
  -     MimeHeaders headers=req.getMimeHeaders();
  -     hCount     = msg.getInt();
  +     MimeHeaders headers = req.getMimeHeaders();
  +     int hCount = msg.getInt();
           for(int i = 0 ; i < hCount ; i++) {
               String hName = null;
   
  +         // Header names are encoded as either an integer code starting
  +         // with 0xA0, or as a normal string (in which case the first
  +         // two bytes are the length).
               int isc = msg.peekInt();
  -            int hId = isc & 0x000000FF;
  +            int hId = isc & 0xFF;
   
  -            isc &= 0x0000FF00;
  -            if(0x0000A000 == isc) {
  -                msg.getInt();
  +            isc &= 0xFF00;
  +            if(0xA000 == isc) {
  +                msg.getInt(); // To advance the read position
                   hName = headerTransArray[hId - 1];
               } else {
                   hName = msg.getString().toLowerCase();
  @@ -222,10 +283,11 @@
               headers.addValue( hName ).setString( hValue );
           }
   
  -        for(bsc = msg.getByte() ;
  -            bsc != SC_A_ARE_DONE ;
  -            bsc = msg.getByte()) {
  -            switch(bsc) {
  +     byte attributeCode;
  +        for(attributeCode = msg.getByte() ;
  +            attributeCode != SC_A_ARE_DONE ;
  +            attributeCode = msg.getByte()) {
  +            switch(attributeCode) {
            case SC_A_CONTEXT      :
                //              contextPath = msg.getString();
                   break;
  @@ -270,11 +332,12 @@
                
            case SC_A_REQ_ATTRIBUTE :
                isSSL = true;
  -             req.setAttribute(msg.getString(), msg.getString());
  +             req.setAttribute(msg.getString(), 
  +                              msg.getString());
                   break;
   
            default:
  -             return 500;
  +             return 500; // Error
               }
           }
   
  @@ -282,6 +345,8 @@
               req.scheme().setString("https");
           }
   
  +     // Check to see if there should be a body packet coming along
  +     // immediately after
        MessageBytes clB=headers.getValue("content-length");
           int contentLength = (clB==null) ? -1 : clB.getInt();
        if(contentLength > 0) {
  @@ -296,20 +361,38 @@
            msg.getBytes(bodyBuff);
        }
       
  -        return 200;
  +        return 200; // Success
       }
  +
  +    // ==================== Servlet Input Support =================
       
  +    /**
  +     * Return the next byte of request body data (to a servlet).
  +     *
  +     * @see Ajp13Request#doRead
  +     */
       public int doRead() throws IOException 
       {
           if(pos >= blen) {
  -            refeelReadBuffer();
  +            refillReadBuffer();
           }
           return bodyBuff[pos++];
       }
       
  +    /**
  +     * Store a chunk of request data into the passed-in byte buffer.
  +     *
  +     * @param b A buffer to fill with data from the request.
  +     * @param off The offset in the buffer at which to start filling.
  +     * @param len The number of bytes to copy into the buffer.
  +     *
  +     * @return The number of bytes actually copied into the buffer.
  +     *
  +     * @see Ajp13Request#doRead
  +     */
       public int doRead(byte[] b, int off, int len) throws IOException 
       {
  -        // XXXXXX Stupid, but the whole thing must be rewriten ( see super()! )
  +        // XXX Rewrite to use System.arrayCopy (please!)
           for(int i = off ; i < (len + off) ; i++) {
               int a = doRead();
               if(-1 == a) {
  @@ -320,71 +403,71 @@
           
           return len;
       }
  -    
  -    public void recycle() 
  -    {
  -    }
       
  -    public void refeelReadBuffer() throws IOException 
  +    /**
  +     * Get more request body data from the web server and store it in the 
  +     * internal buffer.
  +     */
  +    private void refillReadBuffer() throws IOException 
       {
  +     // Why not use outBuf??
        inBuf.reset();
  -     Ajp13Packet msg = inBuf;
  -     msg.appendByte(JK_AJP13_GET_BODY_CHUNK);
  -     msg.appendInt(MAX_READ_SIZE);
  -     send(msg);
  +     inBuf.appendByte(JK_AJP13_GET_BODY_CHUNK);
  +     inBuf.appendInt(MAX_READ_SIZE);
  +     send(inBuf);
        
  -     int err = receive(msg);
  +     int err = receive(inBuf);
           if(err < 0) {
            throw new IOException();
        }
        
  -     blen = msg.peekInt();
  +     blen = inBuf.peekInt();
        pos = 0;
  -     msg.getBytes(bodyBuff);
  +     inBuf.getBytes(bodyBuff);
       }    
   
  -    // ==================== Output ====================
  +    // ==================== Servlet Output Support =================
       
  -    // XXX if more headers that MAX_SIZE, send 2 packets!   
  +    /**
  +     * Send the HTTP headers back to the web server and on to the browser.
  +     *
  +     * @param status The HTTP status code to send.
  +     * @param headers The set of all headers.
  +     */
       public void sendHeaders(int status, MimeHeaders headers) throws IOException 
       {
  +     // XXX if more headers that MAX_SIZE, send 2 packets!   
        outBuf.reset();
  -        Ajp13Packet msg=outBuf;
  -        msg.reset();
  -
  -        msg.appendByte(JK_AJP13_SEND_HEADERS);
  -        msg.appendInt(status);
  -        msg.appendString("");
  +        outBuf.appendByte(JK_AJP13_SEND_HEADERS);
  +        outBuf.appendInt(status);
  +        outBuf.appendString(""); // Http Status Message -- broken.
           
  -        msg.appendInt(headers.size());
  +        outBuf.appendInt(headers.size());
           
           Enumeration e = headers.names();
           while(e.hasMoreElements()) {
               String headerName = (String)e.nextElement();            
               int sc = headerNameToSc(headerName);
               if(-1 != sc) {
  -                msg.appendInt(sc);
  +                outBuf.appendInt(sc);
               } else {
  -                msg.appendString(headerName);
  +                outBuf.appendString(headerName);
               }
  -            msg.appendString(headers.getHeader(headerName));
  +            outBuf.appendString(headers.getHeader(headerName));
           }
   
  -        msg.end();
  -        send(msg);
  +        outBuf.end();
  +        send(outBuf);
       } 
  -         
  -    public void finish() throws IOException 
  -    {
  -     outBuf.reset();
  -        Ajp13Packet msg = outBuf;
  -        msg.reset();
  -        msg.appendByte(JK_AJP13_END_RESPONSE);
  -        msg.appendByte((byte)1);        
  -        msg.end();
  -        send(msg);
  -    }
  -    
  +
  +    /**
  +     * Translate an HTTP response header name to an integer code if
  +     * possible.  Case is ignored.
  +     * 
  +     * @param name The name of the response header to translate.
  +     *
  +     * @return The code for that header name, or -1 if no code exists.
  +     */
       protected int headerNameToSc(String name)
       {       
           switch(name.charAt(0)) {
  @@ -426,7 +509,7 @@
               
        case 'w':
        case 'W':
  -         if(name.equalsIgnoreCase("WWW-Autheticate")) {
  +         if(name.equalsIgnoreCase("WWW-Authenticate")) {
                return SC_RESP_WWW_AUTHENTICATE;
            }
               break;          
  @@ -435,6 +518,27 @@
           return -1;
       }
   
  +    /**
  +     * Signal the web server that the servlet has finished handling this
  +     * request.  
  +     */
  +    public void finish() throws IOException 
  +    {
  +     outBuf.reset();
  +        outBuf.appendByte(JK_AJP13_END_RESPONSE);
  +        outBuf.appendByte((byte)1);        
  +        outBuf.end();
  +        send(outBuf);
  +    }
  +
  +    /**
  +     * Send a chunk of response body data to the web server and on to the
  +     * browser.
  +     *
  +     * @param b A huffer of bytes to send.
  +     * @param off The offset into the buffer from which to start sending.
  +     * @param len The number of bytes to send.
  +     */    
       public void doWrite(  byte b[], int off, int len) throws IOException 
       {
        int sent = 0;
  @@ -443,48 +547,73 @@
            to_send = to_send > MAX_SEND_SIZE ? MAX_SEND_SIZE : to_send;
   
            outBuf.reset();
  -         Ajp13Packet buf = outBuf;
  -         buf.reset();
  -         buf.appendByte(JK_AJP13_SEND_BODY_CHUNK);                   
  -         buf.appendBytes(b, off + sent, to_send);            
  -         send(buf);
  +         outBuf.appendByte(JK_AJP13_SEND_BODY_CHUNK);                        
  +         outBuf.appendBytes(b, off + sent, to_send);         
  +         send(outBuf);
            sent += to_send;
        }
       }
       
   
  -    public int receive(Ajp13Packet msg) throws IOException {
  -     byte b[]=msg.getBuff();
  +    // ========= Internal Packet-Handling Methods =================
  +
  +    /**
  +     * Read in a packet from the web server and store it in the passed-in
  +     * <CODE>Ajp13Packet</CODE> object.
  +     *
  +     * @param msg The object into which to store the incoming packet -- any
  +     * current contents will be overwritten.
  +     *
  +     * @return The number of bytes read on a successful read or -1 if there 
  +     * was an error.
  +     **/
  +    private int receive(Ajp13Packet msg) throws IOException {
  +     // XXX If the length in the packet header doesn't agree with the
  +     // actual number of bytes read, it should probably return an error
  +     // value.  Also, callers of this method never use the length
  +     // returned -- should probably return true/false instead.
  +     byte b[] = msg.getBuff();
        
  -     int rd=in.read( b, 0, H_SIZE );
  -     if( rd<=0 ) {
  -         //      System.out.println("Rd header returned: " + rd );
  +     int rd = in.read( b, 0, H_SIZE );
  +     if(rd <= 0) {
            return rd;
        }
        
  -     int len=msg.checkIn();
  +     int len = msg.checkIn();
        
        // XXX check if enough space - it's assert()-ed !!!
        // Can we have only one read ( with unblocking, it can read all at once - but 
maybe more ) ?
  -     //???   len-=4; // header
        
  -     rd=in.read( b, 4, len );
  +     rd = in.read( b, 4, len );
        if( rd != len ) {
            System.out.println( "Incomplete read, deal with it " + len + " " + rd);
  -         // ??? log
  +         // XXX log
  +         // XXX Return an error code?
        }
        //      msg.dump( "Incoming");
        return rd;
  -     //    System.out.println( "Incoming Packet len=" + len);
       }
  -     
  -    public void send( Ajp13Packet msg ) throws IOException {
  -     msg.end();
  -     byte b[]=msg.getBuff();
  -     int len=msg.getLen();
  +
  +    /**
  +     * Send a packet to the web server.  Works for any type of message.
  +     *
  +     * @param msg A packet with accumulated data to send to the server --
  +     * this method will write out the length in the header.  
  +     */
  +    private void send( Ajp13Packet msg ) throws IOException {
  +     msg.end(); // Write the packet header
  +     byte b[] = msg.getBuff();
  +     int len  = msg.getLen();
        out.write( b, 0, len );
       }
        
  +    /**
  +     * Close the socket connection to the web server.  In general, sockets
  +     * are maintained across many requests, so this will not be called
  +     * after finish().  
  +     *
  +     * @see Ajp13Interceptor#processConnection
  +     */
       public void close() throws IOException {
        if(null != out) {        
            out.close();
  @@ -494,49 +623,53 @@
        }
       }
   
  -    /** Encapsulated messages passed between Tomcat and Web servers
  +    /**
  +     * A single packet for communication between the web server and the
  +     * container.  Designed to be reused many times with no creation of
  +     * garbage.  Understands the format of data types for these packets.
  +     * Can be used (somewhat confusingly) for both incoming and outgoing
  +     * packets.  
        */
       public static class Ajp13Packet {
  -     // previous name: MsgBuff
  -     byte buff[];
  -     int len;
  -     int pos;
  -     int maxlen;
  -     
  +     byte buff[]; // Holds the bytes of the packet
  +     int pos;     // The current read or write position in the buffer
  +
  +     int len; 
  +     // This actually means different things depending on whether the
  +     // packet is read or write.  For read, it's the length of the
  +     // payload (excluding the header).  For write, it's the length of
  +     // the packet as a whole (counting the header).  Oh, well.
  +
  +     /**
  +      * Create a new packet with an internal buffer of given size.
  +      */
        public Ajp13Packet( int size ) {
  -         buff=new byte[size];
  -         maxlen=size;
  +         buff = new byte[size];
        }
  -     
  -     public Ajp13Packet( byte b[] ) {
  -         buff=b;
  -         maxlen=b.length;
  -     }
  -     
  +
        public byte[] getBuff() {
            return buff;
        }
        
  -     public void setBuff(byte[] b) {
  -         buff=b;
  -         maxlen = b.length;
  -     }
  -     
        public int getLen() {
            return len;
        }
        
  -     public int getMaxLen() {
  -         return maxlen;
  -     }
  -     
  -     /** Verify the buffer and read the len
  +     /** 
  +      * Parse the packet header for a packet sent from the web server to
  +      * the container.  Set the read position to immediately after
  +      * the header.
  +      *
  +      * @return The length of the packet payload, as encoded in the
  +      * header, or -1 if the packet doesn't have a valid header.  
         */
        public int checkIn() {
  -         pos=4;
  -         int mark=BuffTool.getInt( buff,0 );
  -         len=BuffTool.getInt( buff,2 );
  +         pos = 0;
  +         int mark = getInt();
  +         len      = getInt();
  +         
            if( mark != 0x1234 ) {
  +             // XXX Logging
                System.out.println("BAD packet " + mark);
                dump( "In: " );
                return -1;
  @@ -544,56 +677,122 @@
            return len;
        }
        
  +     /**
  +      * Prepare this packet for accumulating a message from the container to
  +      * the web server.  Set the write position to just after the header
  +      * (but leave the length unwritten, because it is as yet unknown).  
  +      */
        public void reset() {
  -         len=4;
  -         pos=4;
  -         buff[0]=(byte)'A';
  -         buff[1]=(byte)'B';
  +         len = 4;
  +         pos = 4;
  +         buff[0] = (byte)'A';
  +         buff[1] = (byte)'B';
        }
        
  +     /**
  +      * For a packet to be sent to the web server, finish the process of
  +      * accumulating data and write the length of the data payload into
  +      * the header.  
  +      */
        public void end() {
  -         len=pos;
  +         len = pos;
            setInt( 2, len-4 );
        }
        
  -     public void setInt(int bpos, int val ) {
  -         BuffTool.addInt( buff, bpos, val );
  +     // ============ Data Writing Methods ===================
  +
  +     /**
  +      * Write an integer at an arbitrary position in the packet, but don't
  +      * change the write position.
  +      *
  +      * @param bpos The 0-indexed position within the buffer at which to
  +      * write the integer (where 0 is the beginning of the header).
  +      * @param val The integer to write.
  +      */
  +     private void setInt( int bPos, int val ) {
  +         buff[bPos]   = (byte) ((val >>>  8) & 0xFF);
  +         buff[bPos+1] = (byte) (val & 0xFF);
        }
  -     
  -     public void appendByte( byte val ) {
  -         buff[pos] = val;
  -         pos++;
  +
  +     public void appendInt( int val ) {
  +         setInt( pos, val );
  +         pos += 2;
        }
        
  -     public void appendInt( int val ) {
  -         BuffTool.addInt( buff, pos, val );
  -         pos+=2;
  +     public void appendByte( byte val ) {
  +         buff[pos++] = val;
        }
        
  -     public void appendString( String val ) {
  -         pos=BuffTool.addString( buff, pos, val );
  +     /**
  +      * Write a String out at the current write position.  Strings are
  +      * encoded with the length in two bytes first, then the string, and
  +      * then a terminating \0 (which is <B>not</B> included in the
  +      * encoded length).  The terminator is for the convenience of the C
  +      * code, where it saves a round of copying.  A null string is
  +      * encoded as a string with length 0.  
  +      */
  +     public void appendString( String str ) {
  +         if(str != null) {
  +             int strLen = str.length();
  +             setInt( pos, strLen );
  +             System.arraycopy( str.getBytes(), 0, buff, pos+2, strLen);
  +             buff[pos + strLen + 2] = 0; // The \0 terminator
  +             pos += strLen + 3;
  +         }
  +         else {
  +             setInt( pos, 0);
  +             buff[pos + 2] = 0;
  +             pos += 3;
  +         }     
  +
        }
        
  -     public void appendBytes( byte b[], int off, int len ) {
  -         BuffTool.addInt( buff, pos, len );
  -         pos+=2;
  -         if( pos + len > buff.length ) {
  -             System.out.println("Buffer overflow " + buff.length + " " + pos + " " 
+ len );
  +     /** 
  +      * Copy a chunk of bytes into the packet, starting at the current
  +      * write position.  The chunk of bytes is encoded with the length
  +      * in two bytes first, then the data itself, and finally a
  +      * terminating \0 (which is <B>not</B> included in the encoded
  +      * length).
  +      *
  +      * @param b The array from whcih to copy bytes.
  +      * @param off The offset into the array at which to start copying
  +      * @param len The number of bytes to copy.  
  +      */
  +     public void appendBytes( byte b[], int off, int numBytes ) {
  +         appendInt( numBytes );
  +         if( pos + numBytes > buff.length ) {
  +             System.out.println("Buffer overflow " + buff.length + " " + pos + " " 
+ numBytes );
  +             // XXX Log
            }
  -         System.arraycopy( b, off, buff, pos, len);
  -         buff[pos+len]=0;
  -         pos+=len;
  -         pos++;
  +         System.arraycopy( b, off, buff, pos, numBytes);
  +         buff[pos + numBytes] = 0; // Terminating \0
  +         pos += numBytes + 1;
        }
   
  +     
  +     // ============ Data Reading Methods ===================
  +
  +     /**
  +      * Read an integer from packet, and advance the read position past
  +      * it.  Integers are encoded as two unsigned bytes with the
  +      * high-order byte first, and, as far as I can tell, in
  +      * little-endian order within each byte.  
  +      */
        public int getInt() {
  -         int res=BuffTool.getInt( buff, pos );
  -         pos+=2;
  -         return res;
  +         int result = peekInt();
  +         pos += 2;
  +         return result;
        }
   
  +     /**
  +      * Read an integer from the packet, but don't advance the read
  +      * position past it.  
  +      */
        public int peekInt() {
  -         return BuffTool.getInt( buff, pos );
  +         int b1 = buff[pos] & 0xFF;  // No swap, Java order
  +         int b2 = buff[pos + 1] & 0xFF;
  +
  +         return  (b1<<8) + b2;
        }
   
        public byte getByte() {
  @@ -606,35 +805,51 @@
            return buff[pos];
        }
   
  +     public static final String DEFAULT_CHAR_ENCODING = "8859_1";
  +
  +     /**
  +      * Read a String from the packet, and advance the read position
  +      * past it.  See appendString for details on string encoding.
  +      **/
        public String getString() throws java.io.UnsupportedEncodingException {
  -         int ll= getInt();
  -         if( (ll == 0xFFFF) || (ll==-1) ) {
  -             //          System.out.println("null string " + ll);
  +         int length = getInt();
  +         if( (length == 0xFFFF) || (length == -1) ) {
  +             //          System.out.println("null string " + length);
                return null;
            }
  -         String s=BuffTool.getString( buff, pos, ll );
  -         pos +=ll;
  -         pos++;
  +         String s = new String( buff, pos, length, DEFAULT_CHAR_ENCODING );
  +
  +         pos += length;
  +         pos++; // Skip the terminating \0
            return s;
        }
   
  +     /**
  +      * Copy a chunk of bytes from the packet into an array and advance
  +      * the read position past the chunk.  See appendBytes() for details
  +      * on the encoding.
  +      *
  +      * @return The number of bytes copied.
  +      */
        public int getBytes(byte dest[]) {
  -         int ll= getInt();
  -         if( ll > buff.length ) {
  +         int length = getInt();
  +         if( length > buff.length ) {
  +             // XXX Should be if(pos + length > buff.legth)?
                System.out.println("XXX Assert failed, buff too small ");
            }
        
  -         if( (ll == 0xFFFF) || (ll==-1) ) {
  -             System.out.println("null string " + ll);
  +         if( (length == 0xFFFF) || (length == -1) ) {
  +             System.out.println("null string " + length);
                return 0;
            }
   
  -         System.arraycopy( buff, pos,  dest, 0, ll );
  -         pos +=ll;
  -         pos++; // ??? 
  -         return ll;
  +         System.arraycopy( buff, pos,  dest, 0, length );
  +         pos += length;
  +         pos++; // Skip terminating \0  XXX I believe this is wrong but harmless
  +         return length;
        }
   
  +     // ============== Debugging code =========================
        private String hex( int x ) {
            //      if( x < 0) x=256 + x;
            String h=Integer.toHexString( x );
  @@ -657,7 +872,7 @@
        }
       
        public void dump(String msg) {
  -         System.out.println( msg + ": " + buff + " " + pos +"/" + len + "/" + 
maxlen );
  +         System.out.println( msg + ": " + buff + " " + pos +"/" + len);
   
            for( int j=0; j<len + 16; j+=16 )
                hexLine( j );
  @@ -665,6 +880,4 @@
            System.out.println();
        }
       }
  -
  -
   }
  
  
  

Reply via email to