[Tinyos-contrib-commits] CVS: tinyos-1.x/contrib/shockfish/tools/java_tc65/src/com/shockfish/tinyos/net CldcFtpClient.java, NONE, 1.1 CldcFtpSClient.java, NONE, 1.1 CldcNtpMessage.java, NONE, 1.1 CldcSntpClient.java, NONE, 1.1

rogmeier rogmeier at users.sourceforge.net
Mon Sep 11 06:40:05 PDT 2006


Update of /cvsroot/tinyos/tinyos-1.x/contrib/shockfish/tools/java_tc65/src/com/shockfish/tinyos/net
In directory sc8-pr-cvs10.sourceforge.net:/tmp/cvs-serv28926/src/com/shockfish/tinyos/net

Added Files:
	CldcFtpClient.java CldcFtpSClient.java CldcNtpMessage.java 
	CldcSntpClient.java 
Log Message:
TC65/TinyNode foundations.

--- NEW FILE: CldcFtpClient.java ---
package com.shockfish.tinyos.net;

import javax.microedition.io.*;

import com.shockfish.tinyos.tools.CldcLogger;

import java.io.*;

/**
 * 
 * @author Pierre Metrailler, Shockfish
 * @author Raphael KOENG, HEIG-VD
 */

public class CldcFtpClient {

	protected SocketConnection sc = null;

	protected DataInputStream is;

	protected DataOutputStream os;

	protected String sockOpts;
  
	protected final int numDebug = CldcLogger.SRC_3;
  
	public final static int FTP_COMMAND_MAX_LENGTH = 400;
	
  
  public CldcFtpClient() {
	  System.out.println("##### New instance of FTPclient created.");
    if (!CldcLogger.setSourceLabel(numDebug, "CldcFtpClient"))
    CldcLogger.devDebug(numDebug, "this source will also print debug for " +
        "CldcFtpClient");
  }

	public boolean ascii() throws IOException {
		sendLine("TYPE A");
		String response = readLine();

		return (response.startsWith("200 "));
	}

	public boolean bin() throws IOException {
		sendLine("TYPE I");
		String response = readLine();
		return (response.startsWith("200 "));
	}

	/**
	 * 
	 * @param host ftp IP or domaine name
	 * @param port port to connect to
	 * @param user ftp user name
	 * @param pass ftp password of the user
	 * @param sockOpts 
	 * @throws IOException
	 */

	public void connect(String host, int port, String user, String pass,
			String sockOpts) throws IOException {
    CldcLogger.devDebug(numDebug, "connect()\tBegin connection");
		this.sockOpts = sockOpts;
		if (sc != null) {
			return;
		}

		sc = (SocketConnection) Connector.open("socket://" + host + ":" + port
				+ sockOpts,Connector.READ_WRITE,true);
    CldcLogger.devDebug(numDebug, "connect()\tConnection open");

		// FIXME according to javax.microedition.io: Interface SocketConnection for
		// tc65: "The TC65 does not support the socket options KEEPALIVE and LINGER" 
		sc.setSocketOption(SocketConnection.LINGER, 5);

		is = sc.openDataInputStream();
		os = sc.openDataOutputStream();

		String response = readLine();
		if (!response.startsWith("220 ")) {
      CldcLogger.devDebug(numDebug, "connect()\tUnexpected response");
			return;
		}
		sendLine("USER " + user);
		response = readLine();
		if (!response.startsWith("331 ")) {
      CldcLogger.devDebug(numDebug, "connect()\tUnexpected response");
			return;
		}
		sendLine("PASS " + pass);
		response = readLine();

		if (response.startsWith("530 ")) {
			// we are automaticly disconnected!
			try {
				os.close();
				is.close();
				sc.close();
			} finally {
				os = null;
				is = null;
				sc = null;
			}
			throw (new IOException("Not logged in (ftp response : " + response + ")."));
		}

		if (!response.startsWith("230 ")) {
      CldcLogger.devDebug(numDebug, "connect()\tUnexpected response");
			return;
		}
    CldcLogger.devDebug(numDebug, "connect()\tConnected");
	}

	public boolean changeDir(String dir) throws IOException {
  	sendLine("CWD " + dir); // Change Working Directory
  	String response = readLine();
  	return (response.startsWith("250 "));
  }
  
  /* fileName can be dir/file.txt */
  public boolean delete (String fileName) throws IOException {
    sendLine("dele " + fileName);
    // String response = readLine();

    // TODO ok like that?
    if (!checkResponse(250)) {
      CldcLogger.devDebug(numDebug, "delete()\tdele failed.");
      //           " Server response = " + response);
      return false;
    }
    
    return true;
  }

  public void disconnect() throws IOException {
    CldcLogger.devDebug(numDebug, "disconnect()\tBegin disconnection");
		try {
			if (os != null) {
				sendLine("QUIT");
			}

			if (os != null)
				os.close();

			if (is != null)
				is.close();

			if (sc != null)
				sc.close();
				
		} finally {
			os = null;
			is = null;
			sc = null;
		}
    CldcLogger.devDebug(numDebug, "disconnect()\tDisconnected");
	}

	/**
   * 
   * @param filename
   * @return the file content, null if the file does not exist.
   * @throws IOException 
   */
  public String getData(String filename) throws IOException {
  	CldcLogger.devDebug(numDebug, "getData()\tBegin upload"
  			+ filename);
  	sendLine("PASV");
  	String response = readLine();
  	if (!response.startsWith("227 ")) {
  		return null;
  	}
  
  	String dataCon = parseDataCon(response);
  	sendLine("RETR " + filename);
  
  	// open
  	SocketConnection dataSc = null;
  	dataSc = (SocketConnection) Connector.open("socket://" + dataCon
  			+ sockOpts,Connector.READ_WRITE,true);
  	dataSc.setSocketOption(SocketConnection.LINGER, 5);
  	DataInputStream dataIn = dataSc.openDataInputStream();
  
  	response = readLine();
  	if (response.startsWith("550 ")) {
  		throw (new IOException("Cannot open ftp data connexion ("
  				+ response + ")"));
  	}
  	if (!response.startsWith("150 ")) {
  		return null;
  	}
  
  	// read
  	StringBuffer buf = new StringBuffer();
  	int c;
  	boolean readMore = true;
  	do {
  		c = dataIn.read();
  		switch (c) {
  		case -1:
  			readMore = false;
  			break;
  		default:
  			buf.append((char) c);
  		}
  	} while (readMore);
  
  	// close
  	dataIn.close();
  	dataSc.close();
  
  	response = readLine();
  	if (!response.startsWith("226 ")) {
  		return null;
  	}
  
  	CldcLogger.info("file " + filename + " downloaded.");
  
  	return buf.toString();
  }
  
  // juste to test multiline response
  public String getServerInfo() throws IOException {
    sendLine("HELP");
    return readResponse();
  }

  /* dir can be folder or folder/folder/... (end / optional) */
	public boolean makeDir(String dir) throws IOException {
		sendLine("MKD " + dir);
		String response = readLine();

		if (response.startsWith("550 "))
			throw new IOException("Dir already exist (FTP server response: "
					+ response + ")");

		return (response.startsWith("250 "));
	}
  
  /*
   * cant also be used to move a file.
   */
  public boolean renameFile(String actualName, String newName) throws IOException {
    // FIXME is it necessary to inroduice a LOCK? So the commande rnfr et rnto
    // will be execute one after the other (no concurrence).
    
    sendLine("rnfr " + actualName);
    String response = readLine();
    
    if (!response.startsWith("350 ")) {
      CldcLogger.devDebug(numDebug, "renameFile()\trnfr failed." +
          " Server response = " + response);
      return false;
    }
    
    sendLine("rnto " + newName);
    response = readLine();
    
    if (!response.startsWith("250 ")) {
      CldcLogger.devDebug(numDebug, "renameFile()\trnto failed." +
          " Server response = " + response);
      return false;
    }
    
    CldcLogger.info("file " + actualName + " renamed to " + newName + ".");
    
    return true;
  }

	public boolean putData(String data, String filename) throws IOException {

		sendLine("PASV");
		String response = readLine();
		if (!response.startsWith("227 ")) {
			return false;
		}

		CldcLogger.devDebug(numDebug,
				"putData()\t 1 first readLine passed \n(" + response + ")");
    
		String dataCon = parseDataCon(response);
		sendLine("STOR " + filename);

		SocketConnection dataSc = null;
		try {
			dataSc = (SocketConnection) Connector.open("socket://" + dataCon
					+ sockOpts,Connector.READ_WRITE,true);
      CldcLogger.devDebug(numDebug,
          "putData()\t 2 second readLine passed \n(" + response + ")");
		} catch (IOException e) {
			dataSc = null;
			throw ((IOException) e);
		}
		CldcLogger.devDebug(numDebug, " 3 putData()\t data stream opened");
		dataSc.setSocketOption(SocketConnection.LINGER, 5);
		
    CldcLogger.devDebug(numDebug, " 4 putData()\t socket option set");
    
		response = readLine();
    
    CldcLogger.devDebug(numDebug,
        "putData()\t 5 second readline \n(" + response + ")");
    
		if (!response.startsWith("150 ")) {
			return false;
		}

    DataOutputStream output = dataSc.openDataOutputStream();
	InputStream input = new ByteArrayInputStream(data.getBytes());
    CldcLogger.devDebug(numDebug, "putData()\t streams opened");

		byte[] buffer = new byte[4096];
		int bytesRead = 0;
		while ((bytesRead = input.read(buffer)) != -1) {
			output.write(buffer, 0, bytesRead);
		}
    CldcLogger.devDebug(numDebug, "putData()\t data sended");

		output.flush();
		output.close();
		input.close();

		// rkg
		dataSc.close();
    CldcLogger.devDebug(numDebug, "putData()\t streams closed...");

		response = readLine();

		CldcLogger.info("file " + filename + " uploaded. (" + response + ")");
		return response.startsWith("226 ");
	}

	// parse the response of a PASV command
	protected String parseDataCon(String rawCon) {
		// exemple of a rawCon "227 Entering Passive Mode (15,16,17,18,140,240)."
		// 4 first num for IP, 2 last for port.
		int portNum = 0;
		String ip = "";
		int par1idx = rawCon.indexOf('(');
		int par2idx = rawCon.indexOf(')', par1idx + 1);

		if (par2idx > 0) {
			String dc = rawCon.substring(par1idx + 1, par2idx);
			// begin of Least Significant Byte of the port
			int p2idx = dc.lastIndexOf(',');
			// begin of Most Significant Byte of the port
			int p1idx = dc.lastIndexOf(',', (p2idx - 1)); //
			try {
				portNum = ((Integer.parseInt(dc.substring(p1idx + 1, p2idx))) * 256)
						+ (Integer.parseInt(dc.substring(p2idx + 1)));
			} catch (NumberFormatException nfe) {
        CldcLogger.warning("Number format doomed");
			}
			ip = (dc.substring(0, p1idx)).replace(',', '.');
		}
		return ip + ":" + portNum;
	}

  protected boolean checkResponse (int expectedResponseCode) throws IOException {
    String response = readResponse();
    return response.startsWith(Integer.toString(expectedResponseCode));
  }
  
  /**
   * Read response of the ftp server (on the command channel)
   * 
   * @return the response (1 or more lines)
   * @throws IOException when connection problem occure or when response is not
   * valid (acording to the rfc 959)
   */
  protected String readResponse() throws IOException {
    /* response always start with a 3 digits code
     * Can be 1 line, but also multiline.
     * first line of a multiline response begins with the 3 digits response
     * code follow by '-'.
     * Last line (in both case, 1 line or multiline) begins with the 3 digits
     * response code follow by a white space.
     * Attention: first line code and last line code are equal and between may
     * also start with a number!
     */
    /* multiline response ("valid") exemple:
     * 214-The following commands are recognized:
     *    USER   PASS   QUIT   CWD    PWD    PORT   PASV   TYPE
     *    ...
     *    120 commands are accepted
     * 214 Have a nice day.
     */
    String responseLine = readLine();
    
    // check that the 3 firts digit are number
    if (!isAValidCode(responseLine.substring(0, 3)))
      throw (new IOException("FTP response format is not valid"));
    
    
    if (responseLine.charAt(3) == ' ') {
      // one line response
      return responseLine;
      
    } else if (responseLine.charAt(3) == '-') {
      // multine response
      String beginLastLine = responseLine.substring(0, 3) + " ";
      String allResponse = responseLine;
      responseLine = readLine();
      while (!responseLine.startsWith(beginLastLine)) {
        allResponse += responseLine;
        responseLine = readLine();
      }
      allResponse += responseLine;
      return allResponse;
      
    } else {
      // does not respect rfc 959
      throw (new IOException("FTP response format is not valid"));
    }
  }
  
  // TODO for the moment, only check if code is a number, but not all the numbers
  // between 0 and 999 are acceptable which should be also check!
  private boolean isAValidCode (String code) {
    if (code.length() != 3)
      return false;
    
    try {
      Integer.parseInt(code);
    }  catch (NumberFormatException e) {
      return false;
    }
      return true;
  }
  
	protected String readLine() throws IOException {
		int b;
		int bytesRead = 0;
		String buf = "";
		while (true) {
			b = is.read();
			bytesRead++;
			
			// Experimental results have shown that closing the connection on
			// the server side does not throw any exception on the client side.
			// Even worse, the read() method always returns 0 (a valid value) and
			// the thread enters an infinite loop. As a workaround we set an upper
			// bound on the number of char we expected from a readline() and throw
			// an exception if that figure is exceeded.
			 
			if (bytesRead > FTP_COMMAND_MAX_LENGTH) {
				throw (new IOException("FTP_COMMAND_MAX_LENGTH exceeded, probably due to broken pipe."));
			}
			
			if (b == -1) {
				// Mainly due to a reset connection from the server / server has
				// closed connection
				CldcLogger.devDebug(numDebug, "Readline read returned  -1");
				throw (new IOException(
						"Cannot read in the input stream (return " + b + ")"));
			}
			if (((char) b) == '\n') {
				return buf;
			}
			buf = buf + ((char) b);
		}
	}

	protected void sendLine(String line) throws IOException {
		if (sc == null) {
			System.out.println("no socket found");
			return;
		}
		try {
			line = line + "\r\n";
			os.write(line.getBytes());
			os.flush();
		} catch (IOException e) {
			CldcLogger.devDebug(numDebug, "Error in sendline, exception follows.)");
			// if (e.getMessage().startsWith("Socket was closed"))
				// procedureAlert();
			sc = null;
			throw e;
		}
	}
  

}

--- NEW FILE: CldcFtpSClient.java ---
package com.shockfish.tinyos.net;

import javax.microedition.io.*;

import com.shockfish.tinyos.tools.CldcLogger;

import java.io.*;

/**
 * 
 * @author Pierre Metrailler, Shockfish
 */

public class CldcFtpSClient extends CldcFtpClient {

  public void connect(String host, int port, String user, String pass,
      String sockOpts) throws IOException {
    this.sockOpts = sockOpts;
    if (sc != null) {
      System.out.println("[cldc]\tsc != null OK");
      return;
    }
    String connectStr = "ssl://" + host + ":" + port + sockOpts;
    CldcLogger.devDebug(numDebug, "[cldc]\tConnect str =" + connectStr); // 1
    sc = (SecureConnection) Connector.open(connectStr,Connector.READ_WRITE,true);

    CldcLogger.devDebug(numDebug, "[cldc]\tConnector.open OK"); // 2
    SecurityInfo info = ((SecureConnection) sc).getSecurityInfo();
    CldcLogger.devDebug(numDebug, "[cldc]\tSecureConnection:" + info.getProtocolName());

    sc.setSocketOption(SocketConnection.LINGER, 5);

    is = sc.openDataInputStream();
    os = sc.openDataOutputStream();

    String response = readLine();
    // FIXME : banner can actually be spread over several 200-lines
    if (!response.startsWith("220 ")) {
      CldcLogger.devDebug(numDebug, "Unexpected response");
      return;
    }
    sendLine("USER " + user);
    response = readLine();
    if (!response.startsWith("331 ")) {
      CldcLogger.devDebug(numDebug, "Unexpected response");
      return;
    }
    sendLine("PASS " + pass);
    response = readLine();

    if (response.startsWith("530 ")) {
      // we are automaticly disconnected!
      try {
        os.close();
        is.close();
        sc.close();
      } finally {
        os = null;
        is = null;
        sc = null;
      }
      throw (new IOException("Not logged in (" + response + ")."));
    } // FIXME Est-ce qu'on lance une déconnexion?

    CldcLogger.devDebug(numDebug, "[cldc]\tUser and password accepted.");
    if (!response.startsWith("230 ")) {
      CldcLogger.devDebug(numDebug, "Unexpected response");
      return;
    }

    // TODO CLNT, OPTS

    // TODO have a method for 200-check
    sendLine("PBSZ 0");
    response = readLine();
    if (!response.startsWith("200 ")) {
      CldcLogger.devDebug(numDebug, "Unexpected response");
      return;
    }

    CldcLogger.devDebug(numDebug, "[cldc]\tafter PBSZ 0");
    sendLine("PROT P");
    response = readLine();
    if (!response.startsWith("200 ")) {
      CldcLogger.devDebug(numDebug, "Unexpected response");
      return;
    }
    

    CldcLogger.devDebug(numDebug, "[cldc]\tFIN Connect()");
  }

  public boolean putData(String data, String filename) throws IOException {
    /*if (!isAlive()) {
     throw new IOException("connection died"); 
    }*/
    
    sendLine("PASV");
    String response = readLine();
    if (!response.startsWith("227 ")) {
      return false;
    }

    CldcLogger.devDebug(numDebug,
        "putData()\t 1 first readLine passed \n(" + response + ")");
    
    // bug 1-3 (sf 29.05 nuit)

    String dataCon = parseDataCon(response);
    sendLine("STOR " + filename);
    CldcLogger.devDebug(numDebug, "putData()\t 1.5 sent STOR command");

    SocketConnection dataSc = null;
    try {
      dataSc = (SecureConnection) Connector.open("ssl://" + dataCon
          + sockOpts,Connector.READ_WRITE,true);
      CldcLogger.devDebug(numDebug, "putData()\t 2 data connection opened");
    } catch (IOException e) {
      CldcLogger.devDebug(numDebug, "putData()\t 2 data connection failed");
      try {
        dataSc.close();
      } catch (Exception e2) {
        // most of the time a IOException but can also be a NullPointerException
        CldcLogger.devDebug(numDebug, "putData()\t dataSC.close failed," +
            " but dont worry. " + e2);
      }
      dataSc = null;
      
      throw ((IOException) e);
    } catch (Exception e) {
      CldcLogger.severe("Not an IOException throw!");
      e.printStackTrace();
    }
    
    dataSc.setSocketOption(SocketConnection.LINGER, 5);
    CldcLogger.devDebug(numDebug, "putData()\t 4 socket option set");
    
    response = readLine();
    CldcLogger.devDebug(numDebug,
        "putData()\t 5 second readline \n(" + response + ")");
    
    if (!response.startsWith("150 ")) {
      return false;
    }

    DataOutputStream output = dataSc.openDataOutputStream();
    InputStream input = new ByteArrayInputStream(data.getBytes());
    CldcLogger.devDebug(numDebug, "putData()\t 5 streams opened");

    byte[] buffer = new byte[4096];
    int bytesRead = 0;
    while ((bytesRead = input.read(buffer)) != -1) {
      output.write(buffer, 0, bytesRead);
    }
    CldcLogger.devDebug(numDebug, "putData()\t 6 data sended");

    output.flush();
    output.close();
    input.close();

    dataSc.close();
    CldcLogger.devDebug(numDebug, "putData()\t 7 streams closed...");

    response = readLine();

    CldcLogger.info("file " + filename + " uploaded. (" + response + ")");
    return response.startsWith("226 ");
  }

}

--- NEW FILE: CldcNtpMessage.java ---
package com.shockfish.tinyos.net;
//import java.text.DecimalFormat;
//import java.text.SimpleDateFormat;
import java.util.Date;
import com.shockfish.tinyos.util.CldcMath;
import java.util.Random;

/**
 * This class represents a NTP message, as specified in RFC 2030.  The message
 * format is compatible with all versions of NTP and SNTP.
 *
 * This class does not support the optional authentication protocol, and
 * ignores the key ID and message digest fields.
 * 
 * For convenience, this class exposes message values as native Java types, not
 * the NTP-specified data formats.  For example, timestamps are
 * stored as doubles (as opposed to the NTP unsigned 64-bit fixed point
 * format).
 * 
 * However, the contructor NtpMessage(byte[]) and the method toByteArray()
 * allow the import and export of the raw NTP message format.
 * 
 * 
 * Usage example
 * 
 * // Send message
 * DatagramSocket socket = new DatagramSocket();
 * InetAddress address = InetAddress.getByName("ntp.cais.rnp.br");
 * byte[] buf = new NtpMessage().toByteArray();
 * DatagramPacket packet = new DatagramPacket(buf, buf.length, address, 123);
 * socket.send(packet);
 * 
 * // Get response
 * socket.receive(packet);
 * System.out.println(msg.toString());
 * 
 *  
 * This code is copyright (c) Adam Buckley 2004
 *
 * This program is free software; you can redistribute it and/or modify it 
 * under the terms of the GNU General Public License as published by the Free 
 * Software Foundation; either version 2 of the License, or (at your option) 
 * any later version.  A HTML version of the GNU General Public License can be
 * seen at http://www.gnu.org/licenses/gpl.html
 *
 * This program 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 General Public License for 
 * more details.
 * 
 * 
 * Comments for member variables are taken from RFC2030 by David Mills,
 * University of Delaware.
 * 
 * Number format conversion code in NtpMessage(byte[] array) and toByteArray()
 * inspired by http://www.pps.jussieu.fr/~jch/enseignement/reseaux/
 * NTPMessage.java which is copyright (c) 2003 by Juliusz Chroboczek
 * 
 * @author Adam Buckley
 * @author Pierre Metrailler, Shockfish SA, for the CLDC port.
 */
public class CldcNtpMessage
{
	/**
	 * This is a two-bit code warning of an impending leap second to be
	 * inserted/deleted in the last minute of the current day.  It's values
	 * may be as follows:
	 * 
	 * Value     Meaning
	 * -----     -------
	 * 0         no warning
	 * 1         last minute has 61 seconds
	 * 2         last minute has 59 seconds)
	 * 3         alarm condition (clock not synchronized)
	 */
	public byte leapIndicator = 0;
	
	
	/**
	 * This value indicates the NTP/SNTP version number.  The version number
	 * is 3 for Version 3 (IPv4 only) and 4 for Version 4 (IPv4, IPv6 and OSI).
	 * If necessary to distinguish between IPv4, IPv6 and OSI, the
	 * encapsulating context must be inspected.
	 */
	public byte version = 3;
	
	
	/**
	 * This value indicates the mode, with values defined as follows:
	 * 
	 * Mode     Meaning
	 * ----     -------
	 * 0        reserved
	 * 1        symmetric active
	 * 2        symmetric passive
	 * 3        client
	 * 4        server
	 * 5        broadcast
	 * 6        reserved for NTP control message
	 * 7        reserved for private use
	 * 
	 * In unicast and anycast modes, the client sets this field to 3 (client)
	 * in the request and the server sets it to 4 (server) in the reply. In
	 * multicast mode, the server sets this field to 5 (broadcast).
	 */ 
	public byte mode = 0;
	
	
	/**
	 * This value indicates the stratum level of the local clock, with values
	 * defined as follows:
	 * 
	 * Stratum  Meaning
	 * ----------------------------------------------
	 * 0        unspecified or unavailable
	 * 1        primary reference (e.g., radio clock)
	 * 2-15     secondary reference (via NTP or SNTP)
	 * 16-255   reserved
	 */
	public short stratum = 0;
	

	/**
	 * This value indicates the maximum interval between successive messages,
	 * in seconds to the nearest power of two. The values that can appear in
	 * this field presently range from 4 (16 s) to 14 (16284 s); however, most
	 * applications use only the sub-range 6 (64 s) to 10 (1024 s).
	 */
	public byte pollInterval = 0;
	
	
	/**
	 * This value indicates the precision of the local clock, in seconds to
	 * the nearest power of two.  The values that normally appear in this field
	 * range from -6 for mains-frequency clocks to -20 for microsecond clocks
	 * found in some workstations.
	 */
	public byte precision = 0;
	
	
	/**
	 * This value indicates the total roundtrip delay to the primary reference
	 * source, in seconds.  Note that this variable can take on both positive
	 * and negative values, depending on the relative time and frequency
	 * offsets. The values that normally appear in this field range from
	 * negative values of a few milliseconds to positive values of several
	 * hundred milliseconds.
	 */
	public double rootDelay = 0;
	
	
	/**
	 * This value indicates the nominal error relative to the primary reference
	 * source, in seconds.  The values  that normally appear in this field
	 * range from 0 to several hundred milliseconds.
	 */ 
	public double rootDispersion = 0;
	
	
	/**
	 * This is a 4-byte array identifying the particular reference source.
	 * In the case of NTP Version 3 or Version 4 stratum-0 (unspecified) or
	 * stratum-1 (primary) servers, this is a four-character ASCII string, left
	 * justified and zero padded to 32 bits. In NTP Version 3 secondary
	 * servers, this is the 32-bit IPv4 address of the reference source. In NTP
	 * Version 4 secondary servers, this is the low order 32 bits of the latest
	 * transmit timestamp of the reference source. NTP primary (stratum 1)
	 * servers should set this field to a code identifying the external
	 * reference source according to the following list. If the external
	 * reference is one of those listed, the associated code should be used.
	 * Codes for sources not listed can be contrived as appropriate.
	 * 
	 * Code     External Reference Source
	 * ----     -------------------------
	 * LOCL     uncalibrated local clock used as a primary reference for
	 *          a subnet without external means of synchronization
	 * PPS      atomic clock or other pulse-per-second source
	 *          individually calibrated to national standards
	 * ACTS     NIST dialup modem service
	 * USNO     USNO modem service
	 * PTB      PTB (Germany) modem service
	 * TDF      Allouis (France) Radio 164 kHz
	 * DCF      Mainflingen (Germany) Radio 77.5 kHz
	 * MSF      Rugby (UK) Radio 60 kHz
	 * WWV      Ft. Collins (US) Radio 2.5, 5, 10, 15, 20 MHz
	 * WWVB     Boulder (US) Radio 60 kHz
	 * WWVH     Kaui Hawaii (US) Radio 2.5, 5, 10, 15 MHz
	 * CHU      Ottawa (Canada) Radio 3330, 7335, 14670 kHz
	 * LORC     LORAN-C radionavigation system
	 * OMEG     OMEGA radionavigation system
	 * GPS      Global Positioning Service
	 * GOES     Geostationary Orbit Environment Satellite
	 */
	public byte[] referenceIdentifier = {0, 0, 0, 0};
	
	
	/**
	 * This is the time at which the local clock was last set or corrected, in
	 * seconds since 00:00 1-Jan-1900.
	 */
	public double referenceTimestamp = 0;
	
	
	/**
	 * This is the time at which the request departed the client for the
	 * server, in seconds since 00:00 1-Jan-1900.
	 */
	public double originateTimestamp = 0;
	
	
	/**
	 * This is the time at which the request arrived at the server, in seconds
	 * since 00:00 1-Jan-1900.
	 */
	public double receiveTimestamp = 0;
	
	
	/**
	 * This is the time at which the reply departed the server for the client,
	 * in seconds since 00:00 1-Jan-1900.
	 */
	public double transmitTimestamp = 0;
	
	
	
	/**
	 * Constructs a new NtpMessage from an array of bytes.
	 */
	public CldcNtpMessage(byte[] array)
	{
				
		// See the packet format diagram in RFC 2030 for details 
		leapIndicator = (byte) ((array[0] >> 6) & 0x3);
		version = (byte) ((array[0] >> 3) & 0x7);
		mode = (byte) (array[0] & 0x7);
		stratum = unsignedByteToShort(array[1]);
		pollInterval = array[2];
		precision = array[3];
		
		rootDelay = (array[4] * 256.0) + 
			unsignedByteToShort(array[5]) +
			(unsignedByteToShort(array[6]) / 256.0) +
			(unsignedByteToShort(array[7]) / 65536.0);
		
		rootDispersion = (unsignedByteToShort(array[8]) * 256.0) + 
			unsignedByteToShort(array[9]) +
			(unsignedByteToShort(array[10]) / 256.0) +
			(unsignedByteToShort(array[11]) / 65536.0);
		
		referenceIdentifier[0] = array[12];
		referenceIdentifier[1] = array[13];
		referenceIdentifier[2] = array[14];
		referenceIdentifier[3] = array[15];
		
		referenceTimestamp = decodeTimestamp(array, 16);
		originateTimestamp = decodeTimestamp(array, 24);
		receiveTimestamp = decodeTimestamp(array, 32);
		transmitTimestamp = decodeTimestamp(array, 40);
	}
	
	
	
	/**
	 * Constructs a new NtpMessage in client -> server mode, and sets the
	 * transmit timestamp to the current time.
	 */
	public CldcNtpMessage()
	{
		// Note that all the other member variables are already set with
		// appropriate default values.
		this.mode = 3;
		//this.transmitTimestamp = (System.currentTimeMillis()/1000.0) + 2208988800.0; 
	}
	
	
	
	/**
	 * This method constructs the data bytes of a raw NTP packet.
	 */
	public byte[] toByteArray()
	{
		// All bytes are automatically set to 0
		byte[] p = new byte[48];

		p[0] = (byte) (leapIndicator << 6 | version << 3 | mode);
		p[1] = (byte) stratum;
		p[2] = (byte) pollInterval;
		p[3] = (byte) precision;
		
		// root delay is a signed 16.16-bit FP, in Java an int is 32-bits
		int l = (int) (rootDelay * 65536.0);
		p[4] = (byte) ((l >> 24) & 0xFF);
		p[5] = (byte) ((l >> 16) & 0xFF);
		p[6] = (byte) ((l >> 8) & 0xFF);
		p[7] = (byte) (l & 0xFF);
		
		// root dispersion is an unsigned 16.16-bit FP, in Java there are no
		// unsigned primitive types, so we use a long which is 64-bits 
		long ul = (long) (rootDispersion * 65536.0);
		p[8] = (byte) ((ul >> 24) & 0xFF);
		p[9] = (byte) ((ul >> 16) & 0xFF);
		p[10] = (byte) ((ul >> 8) & 0xFF);
		p[11] = (byte) (ul & 0xFF);
		
		p[12] = referenceIdentifier[0];
		p[13] = referenceIdentifier[1];
		p[14] = referenceIdentifier[2];
		p[15] = referenceIdentifier[3];
		
		encodeTimestamp(p, 16, referenceTimestamp);
		encodeTimestamp(p, 24, originateTimestamp);
		encodeTimestamp(p, 32, receiveTimestamp);
		encodeTimestamp(p, 40, transmitTimestamp);
		
		return p; 
	}
	
	
	
	/**
	 * Returns a string representation of a NtpMessage
	 */
	public String toString()
	{
		String precisionStr = "n/a";
		//	new DecimalFormat("0.#E0").format(Math.pow(2, precision));
		

		
		return "Leap indicator: " + leapIndicator + "\n" +
			"Version: " + version + "\n" +
			"Mode: " + mode + "\n" +
			"Stratum: " + stratum + "\n" +
			"Poll: " + pollInterval + "\n" +
			"Precision: " + precision + " (" + precisionStr + " seconds)\n" + 
			"Root delay: " +(rootDelay*1000) + " ms\n" +
			"Root dispersion: " + (rootDispersion*1000) + " ms\n" + 
			"Reference identifier: " + referenceIdentifierToString(referenceIdentifier, stratum, version) + "\n" +
			"Reference timestamp: " + timestampToString(referenceTimestamp) + "\n" +
			"Originate timestamp: " + timestampToString(originateTimestamp) + "\n" +
			"Receive timestamp:   " + timestampToString(receiveTimestamp) + "\n" +
			"Transmit timestamp:  " + timestampToString(transmitTimestamp);
	}
	
	
	
	/**
	 * Converts an unsigned byte to a short.  By default, Java assumes that
	 * a byte is signed.
	 */
	public static short unsignedByteToShort(byte b)
	{
		if((b & 0x80)==0x80) return (short) (128 + (b & 0x7f));
		else return (short) b;
	}
	
	
	
	/**
	 * Will read 8 bytes of a message beginning at <code>pointer</code>
	 * and return it as a double, according to the NTP 64-bit timestamp
	 * format.
	 */
	public static double decodeTimestamp(byte[] array, int pointer)
	{
		double r = 0.0;
		
		for(int i=0; i<8; i++)
		{
			r += unsignedByteToShort(array[pointer+i]) * CldcMath.pow(2, (3-i)*8);
		}
		
		return r;
	}
	
	
	
	/**
	 * Encodes a timestamp in the specified position in the message
	 */
	public static void encodeTimestamp(byte[] array, int pointer, double timestamp)
	{
		// Converts a double into a 64-bit fixed point
		for(int i=0; i<8; i++)
		{
			// 2^24, 2^16, 2^8, .. 2^-32
			double base = CldcMath.pow(2, (3-i)*8);
			
			// Capture byte value
			array[pointer+i] = (byte) (timestamp / base);

			// Subtract captured value from remaining total
			timestamp = timestamp - (double) (unsignedByteToShort(array[pointer+i]) * base);
		}
		Random rg = new Random();
		// From RFC 2030: It is advisable to fill the non-significant
		// low order bits of the timestamp with a random, unbiased
		// bitstring, both to avoid systematic roundoff errors and as
		// a means of loop detection and replay detection.
		array[7] = (byte) (rg.nextDouble() *255.0);
	}
	
	
	
	/**
	 * Returns a timestamp (number of seconds since 00:00 1-Jan-1900) as a
	 * formatted date/time string. 
	 */
	public static String timestampToString(double timestamp)
	{
		if(timestamp==0) return "0";
		
		// timestamp is relative to 1900, utc is used by Java and is relative
		// to 1970 
		double utc = timestamp - (2208988800.0);
		
		// milliseconds
		long ms = (long) (utc * 1000.0);
		
		// date/time
		//String date = new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss").format(new Date(ms));
		String date = ms+"[ms]";
		
		// fraction
		double fraction = timestamp - ((long) timestamp);
		//String fractionSting = new DecimalFormat(".000000").format(fraction);
		String fractionSting = "n/a";
		
		return "["+timestamp+"].["+fraction+"]";
	}
	
	
	
	/**
	 * Returns a string representation of a reference identifier according
	 * to the rules set out in RFC 2030.
	 */
	public static String referenceIdentifierToString(byte[] ref, short stratum, byte version)
	{
		// From the RFC 2030:
		// In the case of NTP Version 3 or Version 4 stratum-0 (unspecified)
		// or stratum-1 (primary) servers, this is a four-character ASCII
		// string, left justified and zero padded to 32 bits.
		if(stratum==0 || stratum==1)
		{
			return new String(ref);
		}
		
		// In NTP Version 3 secondary servers, this is the 32-bit IPv4
		// address of the reference source.
		else if(version==3)
		{
			return unsignedByteToShort(ref[0]) + "." +
				unsignedByteToShort(ref[1]) + "." +
				unsignedByteToShort(ref[2]) + "." +
				unsignedByteToShort(ref[3]);
		}
		
		// In NTP Version 4 secondary servers, this is the low order 32 bits
		// of the latest transmit timestamp of the reference source.
		else if(version==4)
		{
			return "" + ((unsignedByteToShort(ref[0]) / 256.0) + 
				(unsignedByteToShort(ref[1]) / 65536.0) +
				(unsignedByteToShort(ref[2]) / 16777216.0) +
				(unsignedByteToShort(ref[3]) / 4294967296.0));
		}
		
		return "";
	}
}   

--- NEW FILE: CldcSntpClient.java ---
package com.shockfish.tinyos.net;
import java.io.ByteArrayInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;

import javax.microedition.io.Connector;
import javax.microedition.io.UDPDatagramConnection;
import javax.microedition.io.Datagram;
import javax.microedition.io.DatagramConnection;


/**
 * NtpClient - an NTP client for Java.  This program connects to an NTP server
 * and prints the response to the console.
 * 
 * The local clock offset calculation is implemented according to the SNTP
 * algorithm specified in RFC 2030.  
 * 
 * Note that on windows platforms, the curent time-of-day timestamp is limited
 * to an resolution of 10ms and adversely affects the accuracy of the results.
 * 
 * 
 * This code is copyright (c) Adam Buckley 2004
 *
 * This program is free software; you can redistribute it and/or modify it 
 * under the terms of the GNU General Public License as published by the Free 
 * Software Foundation; either version 2 of the License, or (at your option) 
 * any later version.  A HTML version of the GNU General Public License can be
 * seen at http://www.gnu.org/licenses/gpl.html
 *
 * This program 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 General Public License for 
 * more details.
 *  
 * @author Adam Buckley
 * @author Pierre Metrailler, Shockfish SA, for the CLDC port.
 * 
 * Note 1 : the Siemens MIDP has an awful time resolution.
 * Note 2 : the Siemens Tc65 drifts at about ~0.3s/hour
 */




public class CldcSntpClient
{
	public static long getOffset(String serverName, String gprsConf) throws IOException
	{


			// Send request
			UDPDatagramConnection sc = (UDPDatagramConnection)Connector.open("datagram://"+serverName+":"+123+gprsConf);
			byte[] buf = new CldcNtpMessage().toByteArray();
			Datagram packet = sc.newDatagram(buf, buf.length);
			
			// Set the transmit timestamp *just* before sending the packet
			// ToDo: Does this actually improve performance or not?
			CldcNtpMessage.encodeTimestamp(packet.getData(), 40, (System.currentTimeMillis()/1000.0) + 2208988800.0);
	
			sc.send(packet);
			// Get response
			System.out.println("NTP request sent, waiting for response...\n");
			packet = sc.newDatagram(buf, buf.length);
			sc.receive(packet);
			
			// Immediately record the incoming timestamp
			double destinationTimestamp =(System.currentTimeMillis()/1000.0) + 2208988800.0;
			
			
			// Process response
			CldcNtpMessage msg = new CldcNtpMessage(packet.getData());
			
			// Corrected, according to RFC2030 errata
			double roundTripDelay = (destinationTimestamp-msg.originateTimestamp) -
				(msg.transmitTimestamp-msg.receiveTimestamp);
				
			double localClockOffset =
				((msg.receiveTimestamp - msg.originateTimestamp) +
				(msg.transmitTimestamp - destinationTimestamp)) / 2;
			
			
			// Display response
			System.out.println("NTP server: " + serverName);
			System.out.println(msg.toString());
			System.out.println("Dest. timestamp: " +CldcNtpMessage.timestampToString(destinationTimestamp));
			System.out.println("Round-trip delay: " + (roundTripDelay*1000) + " ms");
			System.out.println("Local clock offset: " + (localClockOffset*1000) + " ms");
			System.out.println("Local delta: "+(destinationTimestamp-msg.originateTimestamp));
			System.out.println("Server processing time: "+(msg.transmitTimestamp-msg.receiveTimestamp));
			long cTime = ((long)((localClockOffset*1000)))+ System.currentTimeMillis();
			System.out.println("Correct time :"+cTime);
			sc.close();
			return (long)((localClockOffset*1000));
	}
	
}



More information about the Tinyos-contrib-commits mailing list