/* Copyright (C) 2004-2006 Nokia Corporation Copyright (C) 2008-2011, Dirk Trossen, airs@dirk-trossen.de This program 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 as version 2.1 of the License. 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 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 com.airs.platform; import java.io.OutputStream; import java.io.InputStream; import java.net.Socket; import android.content.Context; import android.telephony.TelephonyManager; import com.airs.AIRS_remote; import com.airs.helper.SerialPortLogger; /** * Class implementing the TCP client for the remote sensing connection to your own application server * */ public class TCPClient { private Socket socket = null; private OutputStream out = null; private InputStream in = null; /** * true, if currently connected, false otherwise */ public boolean connected=false; /** * String holding the phone's IMEI number which is used for authorisation purposes at the application server */ public String IMEI=null; private AIRS_remote airs; private void debug(String msg) { SerialPortLogger.debug(msg); } /** * Starts the TCP client * @param airs Reference to the {@link AIRS_remote} service calling this TCP client * @param IPAddress String holding the IP address to connect to * @param IPPort Port of the application server to connect to * @return true, if successful, or false otherwise */ public boolean startTCP(AIRS_remote airs, String IPAddress, String IPPort) { TelephonyManager tm; this.airs = airs; try { tm = (TelephonyManager) airs.getSystemService(Context.TELEPHONY_SERVICE); IMEI = tm.getDeviceId(); } catch(Exception e) { return false; } try { // now connect to given IP address connect(IPAddress, IPPort); // write IMEI after connecting writeBytes(IMEI.getBytes()); // now flush the stream to send it definitely out.flush(); } catch (Exception ignored) { debug("TCPClient: something went wrong with connect()"); return false; } return true; } /** * Connecting to application server with IP:port information * @param IPAddress String of the IP address of the application server to connect to * @param Port port number of the application server */ public void connect(String IPAddress, String Port) { int connect_tries; int max_tries = 5; connect_tries = 0; while((connected==false) && (connect_tries<max_tries)) { try { // try to connect to given IP address debug("TCPClient::connect:Connecting to " + IPAddress + ":" + Port); socket = new Socket(IPAddress, Integer.parseInt(Port)); // set TCP keep alives socket.setKeepAlive(true); debug("TCPClient::connect:Socket Connected"); // Open stream for sending & receiving out = socket.getOutputStream(); in = socket.getInputStream(); connected=true; } catch (Exception e) { SerialPortLogger.debug("connect: Exception: " + e.toString()); connect_tries++; try { // sleep a bit before trying again with linear increase in waiting time Thread.sleep(1000+connect_tries * 500); } catch(InterruptedException interrupted) { } } } } /** * Disconnecting from application server */ public synchronized void disconnect() { try { if (out!=null) out.close(); if (in!=null) in.close(); if (socket!=null) { socket.shutdownInput(); socket.shutdownOutput(); } connected=false; in = null; out = null; socket = null; debug("TCPClient::disconnect:closed all resources -> you need to restart now"); } catch (Exception e) { debug("Exception: " + e.toString()); } } /** * Writes a {@link Method} to current TCP connection * @param method Reference to the {@link Method} to be written */ public synchronized boolean write(Method method) { int length=0; // is there is no output stream, let's leave right away if (out==null) return false; // first count the bytes! try { length += 2; // method type length += 2; // FROM.length length += method.FROM.length; // FROM.string length += 2; // TO.length length += method.TO.length; // TO.string length += 2; // event_name.length length += method.event_name.length; // event_name.string switch(method.method_type) { case method_type.method_SUBSCRIBE: length += 2 + 2 + 4; break; case method_type.method_NOTIFY: length += 2 + 2; break; case method_type.method_PUBLISH: length += 4 + 4; break; case method_type.method_CONFIRM: length += 2; switch(method.conf.confirm_type) { case confirm_type.confirm_OTHERS: length += 2 + 2; break; case confirm_type.confirm_PUBLISH: length += 4; } length += 4; // lifetime length += 2; // ret_code.length length += method.conf.ret_code.length; // ret_code.string break; case method_type.method_BYE: length += 2 + 2 + 2; length += method.BYE.reason.length; break; } length += 4; // event_body.length length += method.event_body.length; // event_body.string } catch (Exception e) { debug("write: Exception: " + e.toString()); } // create output stream try { // write length of method first writeInt(length); // count bytes transferred airs.bytes_sent += length; // write method type first writeShort(method.method_type); // then the FROM octetstring writeShort(method.FROM.length); if (method.FROM.length>0) writeBytes(method.FROM.string); //then the TO octetstring writeShort(method.TO.length); if (method.TO.length>0) writeBytes(method.TO.string); //then the event_name octetstring writeShort(method.event_name.length); if (method.event_name.length>0) writeBytes(method.event_name.string); // now the method-specific fields switch(method.method_type) { case method_type.method_SUBSCRIBE: // dialog_id and CSeq and lifetime writeShort(method.sub.dialog_id); writeShort(method.sub.CSeq); writeInt(method.sub.Expires); break; case method_type.method_NOTIFY: // only dialog_id and CSeq writeShort(method.not.dialog_id); writeShort(method.not.CSeq); break; case method_type.method_PUBLISH: // e_tag and lifetime writeInt(method.pub.e_tag); writeInt(method.pub.Expires); break; case method_type.method_CONFIRM: // depending on confirmation type writeShort(method.conf.confirm_type); // differentiate confirmation type switch(method.conf.confirm_type) { // first for the other confirmation than PUBLISH case confirm_type.confirm_OTHERS: writeShort(method.conf.dialog.dialog_id); writeShort(method.conf.dialog.CSeq); break; // then for PUBLISH case confirm_type.confirm_PUBLISH: writeInt(method.conf.e_tag); break; } // lifetime writeInt(method.conf.Expires); // return code writeShort(method.conf.ret_code.length); if (method.conf.ret_code.length !=0) writeBytes(method.conf.ret_code.string); break; case method_type.method_BYE: // dialog_id and CSeq and reason writeShort(method.BYE.dialog_id); writeShort(method.BYE.CSeq); writeShort(method.BYE.reason.length); if (method.BYE.reason.length !=0) writeBytes(method.BYE.reason.string); break; } // add the length of the body writeInt(method.event_body.length); // write body only if length greater zero if (method.event_body.length !=0) writeBytes(method.event_body.string); // now flush the stream to send it definitely out.flush(); } catch (Exception e) { debug("TCPClient::write: Exception: " + e.toString()); } return true; } /** * Reads Method from TCP connection * @return Reference to {@link Method} that was read */ public Method read() { Method method=null; int length; try { // is there is no output stream, let's leave right away if (in==null) throw new Exception("no Input stream available"); // get memory to read into method = new Method(); // read length of data length = readInt(); // count bytes transferred airs.bytes_sent += length; // read method type method.method_type = readShort(); // read FROM octetstring method.FROM.length = readShort(); method.FROM.string = new byte[method.FROM.length]; if (readBytes(method.FROM.string) != method.FROM.length) throw new Exception("wrong length from FROM"); // read TO octetstring method.TO.length = readShort(); method.TO.string = new byte[method.TO.length]; if (readBytes(method.TO.string) != method.TO.length) throw new Exception("wrong length from TO"); // read event_name octetstring method.event_name.length = readShort(); // is there an event name? if (method.event_name.length!=0) { method.event_name.string = new byte[method.event_name.length]; if (readBytes(method.event_name.string) != method.event_name.length) throw new Exception("wrong length from event_name"); } else method.event_name.string = null; switch(method.method_type) { case method_type.method_SUBSCRIBE: // dialog_id and CSeq and lifetime method.sub.dialog_id = readShort(); method.sub.CSeq = readShort(); method.sub.Expires = readInt(); break; case method_type.method_NOTIFY: // only dialog_id and CSeq method.not.dialog_id = readShort(); method.not.CSeq = readShort(); break; case method_type.method_PUBLISH: // e_tag and lifetime method.pub.e_tag = readInt(); method.pub.Expires = readInt(); break; case method_type.method_CONFIRM: // depending on confirmation type method.conf.confirm_type = readShort(); // differentiate confirmation type switch(method.conf.confirm_type) { // first for the other confirmation than PUBLISH case confirm_type.confirm_OTHERS: method.conf.dialog.dialog_id = readShort(); method.conf.dialog.CSeq = readShort(); break; // then for PUBLISH case confirm_type.confirm_PUBLISH: method.conf.e_tag = readInt(); break; } // lifetime method.conf.Expires = readInt(); // return code method.conf.ret_code.length = readShort(); method.conf.ret_code.string = new byte[method.conf.ret_code.length]; if (readBytes(method.conf.ret_code.string) != method.conf.ret_code.length) throw new Exception("wrong length from conf.ret_code"); break; case method_type.method_BYE: // dialog_id and CSeq and reason method.BYE.dialog_id = readShort(); method.BYE.CSeq = readShort(); // reason code method.BYE.reason.length = readShort(); method.BYE.reason.string = new byte[method.BYE.reason.length]; if (readBytes(method.BYE.reason.string) != method.BYE.reason.length) throw new Exception("wrong length from BYE.reason"); break; } // read event_body octetstring method.event_body.length = readInt(); // anything to read? if (method.event_body.length != 0) { method.event_body.string = new byte[method.event_body.length]; if (readBytes(method.event_body.string) != method.event_body.length) throw new Exception("wrong length from event_body"); } else method.event_body.string = null; debug("TCPClient::read:read new method"); return method; } catch (Exception exception) { debug("TCPClient::read: Exception: " + exception.toString()); } return null; } // implement the typed write() and read() functions myself since they didn't work properly private void writeBytes(byte value[]) { try { out.write(value); } catch (Exception e) { debug("TCPClient::writeShort: Exception: " + e.toString()); } } private void writeShort(short value) { byte sent[] = new byte[2]; sent[0] = (byte)((value>>8) & 0xff); sent[1] = (byte)(value & 0xff); try { out.write(sent); } catch (Exception e) { debug("TCPClient::writeShort: Exception: " + e.toString()); } } private void writeInt(int value) { byte sent[] = new byte[4]; sent[0] = (byte)((value>>24) & 0xff); sent[1] = (byte)((value>>16) & 0xff); sent[2] = (byte)((value>>8) & 0xff); sent[3] = (byte)(value & 0xff); try { out.write(sent); } catch (Exception e) { debug("TCPClient::writeInt: Exception: " + e.toString()); } } private int readBytes(byte[] value) { try { return in.read(value); } catch (Exception e) { debug("TCPClient::read: Exception: " + e.toString()); return 0; } } private short readShort() { byte received[] = new byte[2]; try { in.read(received); } catch (Exception e) { debug("TCPClient::read: Exception: " + e.toString()); } return (short)((received[0]<<8)|(received[1] & 0xff )); } private int readInt() { byte received[] = new byte[4]; try { in.read(received); } catch (Exception e) { debug("TCPClient::read: Exception: " + e.toString()); } return (int)((received[0]<<24)|(received[1]<<16)|(received[2]<<8)|(received[3] & 0xff )); } }