Package hamsam.protocol.yahoo

Source Code of hamsam.protocol.yahoo.Packet

/*
* Hamsam - Instant Messaging API
* Copyright (C) 2003 Raghu K
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

package hamsam.protocol.yahoo;

import java.io.IOException;
import java.util.Vector;
import java.util.Enumeration;
import hamsam.net.Connection;
import hamsam.exception.IllegalArgumentException;

/**
* A packet is the unit of communication between Yahoo server and
* client.
*
* <p>
* A packet consists of a header and a data section. The header is fixed length
* (20 bytes) while the data can have any length between 0 to 2^16 - 1 bytes.
* The header has the following format.
*
* <p>
* <table border="1">
*   <tr>
*     <th>Field Name</th>
*     <th>Meaning</th>
*     <th>Length</th>
*   </tr>
*   <tr>
*     <td>Protocol ID</td>
*     <td>
*       This is an identifier used to identify Yahoo packets. It is always
*       four ASCII characters - "YMSG"
*     </td>
*     <td>4 bytes</td>
*   </tr>
*   <tr>
*     <td>Protocol Version</td>
*     <td>
*       This is used to identify the version of the protocol used. The version
*       used here is 0x000b0000. This value is present only in packets that are
*       sent from client to server, in packets received by the client from server,
*       this is always 0.
*     </td>
*     <td>4 bytes</td>
*   </tr>
*   <tr>
*     <td>Data Length</td>
*     <td>
*       This is the length of the data section in bytes, in big-endian order.
*     </td>
*     <td>2 bytes</td>
*   </tr>
*   <tr>
*     <td>Service Type</td>
*     <td>
*       To distinguish different operations, Yahoo uses different service codes,
*       all these are defined in the {@link Constants Constants} interface.
*     </td>
*     <td>2 bytes</td>
*   </tr>
*   <tr>
*     <td>Status Indicator</td>
*     <td>
*       In case of a response from the server, indicates the status
*       of the request (success/failure/etc.).  For a request, it is 0
*       in most cases, except for packets that set the user's status
*       (set status, typing notify, etc.). All these values are defined in
*       the {@link Constants Constants} interface.
*     </td>
*     <td>4 bytes</td>
*   </tr>
*   <tr>
*     <td>Session ID</td>
*     <td>
*       The session identifier is a unique number to identify the protocol
*       session. When the client sends the first packet, this is set to 0.
*       The server responds with a session identifier. This value is used
*       for all further packets, by the client as well as the server.
*     </td>
*     <td>4 bytes</td>
*   </tr>
* </table>
*
* <p>
* The header is followed by a data section that is a collection of key value pairs
* A key may have multiple values associated. A key is a numeric string. Packet stores
* a key as a numeric value in ASCII character set. A key is terminated by the
* special byte sequence 0xc0 0x80. This is followed by a value, which is again
* terminated by 0xc0 0x80.
*
* @author Raghu
*/
public class Packet
{
  private long version;

  /**
   * The service type of this packet.
   */
  private int service;

  /**
   * The status indicator of this packet.
   */
  private long status;

  /**
   * Session identifier for this packet.
   */
  private long sessionID;

  /**
   * The keys and values in the data section is put in this
   * vector. The first element is a key, the second is its
   * value, the third is the second key, while fourth is
   * the second key's value and so on.
   */
  private Vector data;

  /**
   * Construct a packet with the specified parameters.
   *
   * @param service the service type of this packet.
   * @param status the status indicator of this packet.
   * @param sessionID the sessionID for this packet.
   */
  public Packet(int service, long status, long sessionID)
  {
    this.service = service;
    this.status = status;
    this.sessionID = sessionID;
    this.version = 0x000b0000;
    data = new Vector();
  }

  /**
   * Construct a packet from an array of characters that represents the
   * packet.
   *
   * @param data the array of characters that form the packet.
   * @throws IllegalArgumentException if the data does not form a valid packet.
   */
  public Packet(char[] data) throws IllegalArgumentException
  {
    if(data.length < 20)
      throw new IllegalArgumentException("Yahoo packet is too small, size = " + data.length);

    /* header starts with 'YMSG' */
    if(data[0] != 'Y' || data[1] != 'M' || data[2] != 'S' || data[3] != 'G')
      throw new IllegalArgumentException("Invalid yahoo packet header (No YMSG found)");

    /* get the version */
    version = ((long)toUnsigned(data[4]) << 24) +
          ((long)toUnsigned(data[5]) << 16) +
          ((long)toUnsigned(data[6]) << 8) +
          toUnsigned(data[7]);

    /* get the length of the data portion */
    int dataLength = (toUnsigned(data[8]) << 8) + toUnsigned(data[9]);

    if(data.length != dataLength + 20)
      throw new IllegalArgumentException("Yahoo packet size is not correct, header specifies a packet size of " + (dataLength + 20) + ", but actual packet size is " + data.length);

    /* get the service type */
    service = (toUnsigned(data[10]) << 8) + toUnsigned(data[11]);

    /* get the status */
    status = ((long)toUnsigned(data[12]) << 24) +
          ((long)toUnsigned(data[13]) << 16) +
          ((long)toUnsigned(data[14]) << 8) +
          toUnsigned(data[15]);

    /* get the status */
    sessionID = ((long)toUnsigned(data[16]) << 24) +
          ((long)toUnsigned(data[17]) << 16) +
          ((long)toUnsigned(data[18]) << 8) +
          toUnsigned(data[19]);

    /* create the hashtable */
    boolean gettingKey = true;
    int key = 0, valueStart = 20;
    String value;

    int i = 20;
    while(i < data.length - 1)
    {
      int dataCurrent = toUnsigned(data[i]);
      int dataNext = toUnsigned(data[i + 1]);

      if(gettingKey)
      {
        // check for a key
        if(dataCurrent != 0xc0)
          key = key * 10 + dataCurrent - '0';
        else if(dataNext == 0x80)
        {
          gettingKey = false;
          valueStart = i + 2;
          i++;
        }
        else
          throw new IllegalArgumentException("Invalid packet (No end signature found)");
      }
      else
      {
        // get value
        if(dataCurrent == 0xc0 && dataNext == 0x80)
        {
          value = new String(data, valueStart, i - valueStart);
          this.data.add(new Integer(key));
          this.data.add(value);
          gettingKey = true;
          key = 0;
          i++;
        }
      }

      i++;
    }
  }

  /**
   * Build a packet from the bytes read from a connection.
   *
   * @param conn the connection from which the packet is to be read.
   * @throws IllegalArgumentException if the data read does not form a valid packet.
   * @throws IOException if an I/O error occurs.
   */
  public Packet(Connection conn) throws IllegalArgumentException, IOException
  {
    data = new Vector();

    byte[] header = new byte[20];
    int count = 0;

    /* Read the header */
    while(count != header.length)
    {
      int ret = conn.read(header, count, header.length - count);
      if(ret == -1)
        throw new IllegalArgumentException("Stream closed without enough data");
      count += ret;
    }

    /* header starts with 'YMSG' */
    if(header[0] != 'Y' || header[1] != 'M' || header[2] != 'S' || header[3] != 'G')
      throw new IllegalArgumentException("Invalid yahoo packet header (No YMSG found)");

    /* get the version */
    version = ((long)toUnsigned(header[4]) << 24) +
          ((long)toUnsigned(header[5]) << 16) +
          ((long)toUnsigned(header[6]) << 8) +
          toUnsigned(header[7]);

    /* get the length of the data portion */
    int dataLength = (toUnsigned(header[8]) << 8) + toUnsigned(header[9]);

    /* get the service type */
    service = (toUnsigned(header[10]) << 8) + toUnsigned(header[11]);

    /* get the status */
    status = ((long)toUnsigned(header[12]) << 24) +
          ((long)toUnsigned(header[13]) << 16) +
          ((long)toUnsigned(header[14]) << 8) +
          toUnsigned(header[15]);

    /* get the status */
    sessionID = ((long)toUnsigned(header[16]) << 24) +
          ((long)toUnsigned(header[17]) << 16) +
          ((long)toUnsigned(header[18]) << 8) +
          toUnsigned(header[19]);

    /* now load the data portion */
    byte[] dataArray = new byte[dataLength];
    count = 0;

    while(count != dataArray.length)
    {
      count += conn.read(dataArray, count, dataArray.length - count);
    }

    /* create the hashtable */
    boolean gettingKey = true;
    int key = 0, valueStart = 0;
    String value;

    int i = 0;
    while(i < count - 1)
    {
      int data = toUnsigned(dataArray[i]);
      int dataNext = toUnsigned(dataArray[i + 1]);

      if(gettingKey)
      {
        // check for a key
        if(data != 0xc0)
          key = key * 10 + data - '0';
        else if(dataNext == 0x80)
        {
          gettingKey = false;
          valueStart = i + 2;
          i++;
        }
        else
          throw new IllegalArgumentException("Invalid packet (No end signature found)");
      }
      else
      {
        // get value
        if(data == 0xc0 && dataNext == 0x80)
        {
          value = new String(dataArray, valueStart, i - valueStart);
          this.data.add(new Integer(key));
          this.data.add(value);
          gettingKey = true;
          key = 0;
          i++;
        }
      }

      i++;
    }
  }

  /**
   * Convert this packet to a byte array which can be sent to
   * Yahoo server.
   *
   * @return a byte array representing this packet.
   */
  public byte[] toBytes()
  {
    // compute length of data portion
    int dataLength = 0;
    Enumeration all = data.elements();

    while(all.hasMoreElements())
    {
      Object elem = all.nextElement();
      dataLength += elem.toString().length() + 2;
    }

    byte[] ret = new byte[dataLength + 20];

    ret[0] = 'Y'; ret[1] = 'M'; ret[2] = 'S'; ret[3] = 'G';

    /* version */
    ret[4] = (byte)((version >> 24) & 0xff);
    ret[5] = (byte)((version >> 16) & 0xff);
    ret[6] = (byte)((version >> 8) & 0xff);
    ret[7] = (byte)(version & 0xff);

    /* data length */
    ret[8] = (byte)((dataLength >> 8) & 0xff);
    ret[9] = (byte)(dataLength & 0xff);

    /* service type */
    ret[10] = (byte)((service >> 8) & 0xff);
    ret[11] = (byte)(service & 0xff);

    /* status */
    ret[12] = (byte)((status >> 24) & 0xff);
    ret[13] = (byte)((status >> 16) & 0xff);
    ret[14] = (byte)((status >> 8) & 0xff);
    ret[15] = (byte)(status & 0xff);

    /* session id */
    ret[16] = (byte)((sessionID >> 24) & 0xff);
    ret[17] = (byte)((sessionID >> 16) & 0xff);
    ret[18] = (byte)((sessionID >> 8) & 0xff);
    ret[19] = (byte)(sessionID & 0xff);

    /* data */
    int index = 20;
    all = data.elements();

    while(all.hasMoreElements())
    {
      Object elem = all.nextElement();
      byte[] elemData = elem.toString().getBytes();
      for(int i = 0; i < elemData.length; i++)
        ret[index++] = elemData[i];

      ret[index++] = (byte)0xc0;
      ret[index++] = (byte)0x80;
    }

    return ret;
  }

  /**
   * Add a key value pair to the data section of this
   * packet.
   *
   * @param key the key to be added.
   * @param val the value to be added.
   */
  public synchronized void putData(int key, String val)
  {
    data.add(new Integer(key));
    data.add(new String(val));
  }

  /**
   * Get the number of key value pairs present in this packet.
   *
   * @return the number of key value pairs.
   */
  public int getDataSize()
  {
    return data.size() / 2;
  }

  /**
   * Get the key from the key value pair at a specified index.
   *
   * @return the key at the specified index.
   * @throws ArrayIndexOutOfBoundsException if the index is out of range.
   */
  public int getKey(int index) throws ArrayIndexOutOfBoundsException
  {
    return ((Integer) data.elementAt(index * 2)).intValue();
  }

  /**
   * Get the value from the key value pair at a specified index.
   *
   * @return the value at the specified index.
   * @throws ArrayIndexOutOfBoundsException if the index is out of range.
   */
  public String getValue(int index)
  {
    return new String((String) data.elementAt(index * 2 + 1));
  }

  /**
   * Returns the service type of this packet.
   *
   * @return service type of this packet.
   */
  public int getService()
  {
    return service;
  }

  /**
   * Returns the status indicator of this packet.
   *
   * @return status indicator of this packet.
   */
  public long getStatus()
  {
    return status;
  }

  /**
   * Returns the session identifier of this packet.
   *
   * @return the session identiier of thsi packet.
   */
  public long getSessionID()
  {
    return sessionID;
  }

  /**
   * Returns a string representation of this packet.
   *
   * @return a string representation of this packet.
   */
  public String toString()
  {
    String ret = new String("YahooPacket { ");

    ret += "version = " + version + ", service = " + service + ", status = " + status + ", session id = " + sessionID;
    ret += ", data = \n";

    Enumeration all = data.elements();
    while(all.hasMoreElements())
    {
      Object key = all.nextElement();
      ret += key.toString() + " = ";
      Object value = all.nextElement();
      ret += value.toString() + "\n";
    }

    ret += " }";

    return ret;
  }

  /**
   * Convert a signed byte to unsigned byte.
   *
   * @param val the signed byte to be converted.
   * @return the equivalent unsigned value.
   */
  private int toUnsigned(int val)
  {
    return val < 0 ? 256 + val : val;
  }
}
TOP

Related Classes of hamsam.protocol.yahoo.Packet

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.