content: /*
   * Copyright (C) 2009 The Sipdroid Open Source Project
   * 
   * This file is part of Sipdroid (http://www.sipdroid.org)
   * 
   * Sipdroid 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 3 of the License, or
   * (at your option) any later version.
   * 
   * This source code 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.
   * 
   * You should have received a copy of the GNU General Public License
   * along with this source code; if not, write to the Free Software
   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   */
  
  package org.sipdroid.net.impl;
  
  import android.util.Log;
  
  import java.io.FileDescriptor;
  import java.io.IOException;
  import java.io.InterruptedIOException;
  import java.net.DatagramPacket;
  import java.net.DatagramSocketImpl;
  import java.net.InetAddress;
  import java.net.InetSocketAddress;
  import java.net.NetworkInterface;
  import java.net.SocketAddress;
  import java.net.SocketException;
  import java.net.SocketOptions;
  import java.net.SocketTimeoutException;
  import java.net.UnknownHostException;
  
  //import java.security.AccessController;
  
  /**
   * The default, concrete instance of datagram sockets. This class does not
   * support security checks. Alternative types of DatagramSocketImpl's may be
   * used by setting the <code>impl.prefix</code> system property.
   */
  public class PlainDatagramSocketImpl extends DatagramSocketImpl {
  
      static final int MULTICAST_IF = 1;
  
      static final int MULTICAST_TTL = 2;
  
      static final int TCP_NODELAY = 4;
  
      static final int FLAG_SHUTDOWN = 8;
  
      private final static int SO_BROADCAST = 32;
  
      final static int IP_MULTICAST_ADD = 19;
  
      final static int IP_MULTICAST_DROP = 20;
  
      final static int IP_MULTICAST_TTL = 17;
  
      /**
       * for datagram and multicast sockets we have to set REUSEADDR and REUSEPORT
       * when REUSEADDR is set for other types of sockets we need to just set
       * REUSEADDR therefore we have this other option which sets both if
       * supported by the platform. this cannot be in SOCKET_OPTIONS because since
       * it is a public interface it ends up being public even if it is not
       * declared public
       */
      static final int REUSEADDR_AND_REUSEPORT = 10001;
  
      private boolean bindToDevice;
  
      private byte[] ipaddress = { 0, 0, 0, 0 };
  
      private int ttl = 1;
  
      private OSNetworkSystem netImpl = OSNetworkSystem.getOSNetworkSystem();
  
      private volatile boolean isNativeConnected;
  
      public int receiveTimeout;
  
      public boolean streaming = true;
  
      public boolean shutdownInput;
  
      /**
       * used to keep address to which the socket was connected to at the native
       * level
       */
      private InetAddress connectedAddress;
  
      private int connectedPort = -1;
  
      /**
       * used to store the trafficClass value which is simply returned as the
       * value that was set. We also need it to pass it to methods that specify an
       * address packets are going to be sent to
       */
      private int trafficClass;
  
      public PlainDatagramSocketImpl(FileDescriptor fd, int localPort) {
          super();
          this.fd = fd;
          this.localPort = localPort;
      }
  
      public PlainDatagramSocketImpl() {
          super();
          fd = new FileDescriptor();
      }
  
      @Override
      public void bind(int port, InetAddress addr) throws SocketException {
          String prop = null; //AccessController.doPrivileged(new PriviAction<String>("bindToDevice")); //$NON-NLS-1$
          Log.i("bind", "start bind");
          boolean useBindToDevice = prop != null && prop.toLowerCase().equals("true"); //$NON-NLS-1$
          try {
          	Log.i("bind", "start bind2");
              // Ignore failures
          	setOption(SO_BROADCAST, Boolean.TRUE);
              setOption(SocketOptions.SO_REUSEADDR, Boolean.TRUE);
          bindToDevice = netImpl.bind2(fd, port, useBindToDevice, addr);
          if (0 != port) {
              localPort = port;
          } else {
  //            localPort = netImpl.getSocketLocalPort(fd, NetUtil.preferIPv6Addresses());
          }
  
          } catch (Exception e) {
          	Log.e("bind", e.toString());
          }
         
  
      }
  
      @Override
      public void close() {
          synchronized (fd) {
              if (fd.valid()) {
                  try {
                      netImpl.socketClose(fd);
                  } catch (IOException e) {
                  }
                  fd = new FileDescriptor();
              }
          }
      }
  
      @Override
      public void create() throws SocketException {
          netImpl.createDatagramSocket(fd, false); //NetUtil.preferIPv4Stack());
      }
  
      @Override
      protected void finalize() {
          close();
      }
  
      @Override
      public Object getOption(int optID) throws SocketException {
          if (optID == SocketOptions.SO_TIMEOUT) {
              return Integer.valueOf(receiveTimeout);
          } else if (optID == SocketOptions.IP_TOS) {
              return Integer.valueOf(trafficClass);
          } else {
              // Call the native first so there will be
              // an exception if the socket if closed.
              Object result = netImpl.getSocketOption(fd, optID);
              if (optID == SocketOptions.IP_MULTICAST_IF
                      && (netImpl.getSocketFlags() & MULTICAST_IF) != 0) {
                  try {
                      return InetAddress.getByAddress(ipaddress);
                  } catch (UnknownHostException e) {
                      return null;
                  }
              }
              return result;
          }
      }
  
      @Override
      public int getTimeToLive() throws IOException {
          // Call the native first so there will be an exception if the socket if
          // closed.
          int result = (((Byte) getOption(IP_MULTICAST_TTL)).byteValue()) & 0xFF;
          if ((netImpl.getSocketFlags() & MULTICAST_TTL) != 0) {
              return ttl;
          }
          return result;
      }
  
      @Override
      public byte getTTL() throws IOException {
          // Call the native first so there will be an exception if the socket if
          // closed.
          byte result = ((Byte) getOption(IP_MULTICAST_TTL)).byteValue();
          if ((netImpl.getSocketFlags() & MULTICAST_TTL) != 0) {
              return (byte) ttl;
          }
          return result;
      }
  
      @Override
      public void join(InetAddress addr) throws IOException {
  //        setOption(IP_MULTICAST_ADD, new GenericIPMreq(addr));
      }
  
      @Override
      public void joinGroup(SocketAddress addr, NetworkInterface netInterface) throws IOException {
          if (addr instanceof InetSocketAddress) {
              //InetAddress groupAddr = ((InetSocketAddress) addr).getAddress();
  //            setOption(IP_MULTICAST_ADD, new GenericIPMreq(groupAddr, netInterface));
          }
      }
  
      @Override
      public void leave(InetAddress addr) throws IOException {
  //        setOption(IP_MULTICAST_DROP, new GenericIPMreq(addr));
      }
  
      @Override
      public void leaveGroup(SocketAddress addr, NetworkInterface netInterface)
              throws IOException {
          if (addr instanceof InetSocketAddress) {
              //InetAddress groupAddr = ((InetSocketAddress) addr).getAddress();
  //            setOption(IP_MULTICAST_DROP, new GenericIPMreq(groupAddr, netInterface));
          }
      }
  
      @Override
      protected int peek(InetAddress sender) throws IOException {
          if (isNativeConnected) {
              /*
               * in this case we know the port and address from which the data
               * must have be been received as the socket is connected. However,
               * we still need to do the receive in order to know that there was
               * data received. We use a short buffer as we don't actually need
               * the packet, only the knowledge that it is there
               */
              byte[] storageArray = new byte[10];
              DatagramPacket pack = new DatagramPacket(storageArray, storageArray.length);
              netImpl.recvConnectedDatagram(fd, pack, pack.getData(), pack.getOffset(), pack
                      .getLength(), receiveTimeout, true); // peek
              // to set the sender ,we now use a native function
              // sender.ipaddress = connectedAddress.getAddress();
              netImpl.setInetAddress(sender, connectedAddress.getAddress());
              return connectedPort;
          }
          return netImpl.peekDatagram(fd, sender, receiveTimeout);
      }
  
      @Override
      public void receive(DatagramPacket pack) throws IOException {
          try {
              if (isNativeConnected) {
                  // do not peek
                  netImpl.recvConnectedDatagram(fd, pack, pack.getData(), pack.getOffset(), pack
                          .getLength(), receiveTimeout, false);
                  updatePacketRecvAddress(pack);
              } else {
                  // receiveDatagramImpl2
                  netImpl.receiveDatagram(fd, pack, pack.getData(), pack.getOffset(), pack
                          .getLength(), receiveTimeout, false);
              }
          } catch (InterruptedIOException e) {
              throw new SocketTimeoutException(e.getMessage());
          }
      }
  
      @Override
      public void send(DatagramPacket packet) throws IOException {
  
          if (isNativeConnected) {
              netImpl.sendConnectedDatagram(fd, packet.getData(), packet.getOffset(), packet
                      .getLength(), bindToDevice);
          } else {
              // sendDatagramImpl2
              netImpl.sendDatagram(fd, packet.getData(), packet.getOffset(), packet.getLength(),
                      packet.getPort(), bindToDevice, trafficClass, packet.getAddress());
          }
      }
  
      /**
       * Set the nominated socket option. As the timeouts are not set as options
       * in the IP stack, the value is stored in an instance field.
       * 
       * @throws SocketException thrown if the option value is unsupported or
       *         invalid
       */
      @Override
      public void setOption(int optID, Object val) throws SocketException {
          /*
           * for datagram sockets on some platforms we have to set both the
           * REUSEADDR AND REUSEPORT so for REUSEADDR set this option option which
           * tells the VM to set the two values as appropriate for the platform
           */
          if (optID == SocketOptions.SO_REUSEADDR) {
              optID = REUSEADDR_AND_REUSEPORT;
          }
  
          if (optID == SocketOptions.SO_TIMEOUT) {
              receiveTimeout = ((Integer) val).intValue();
          } else {
              int flags = netImpl.getSocketFlags();
              try {
                  netImpl.setSocketOption(fd, optID | (flags << 16), val);
              } catch (SocketException e) {
                  // we don't throw an exception for IP_TOS even if the platform
                  // won't let us set the requested value
                  if (optID != SocketOptions.IP_TOS) {
                      throw e;
                  }
              }
              if (optID == SocketOptions.IP_MULTICAST_IF && (flags & MULTICAST_IF) != 0) {
                  InetAddress inet = (InetAddress) val;
  //                if (NetUtil.bytesToInt(inet.getAddress(), 0) == 0 || inet.isLoopbackAddress()) {
  //                    ipaddress = ((InetAddress) val).getAddress();
  //                } else 
                  {
                      InetAddress local = null;
                      try {
                          local = InetAddress.getLocalHost();
                      } catch (UnknownHostException e) {
                          throw new SocketException("getLocalHost(): " + e.toString());
                      }
                      if (inet.equals(local)) {
                          ipaddress = ((InetAddress) val).getAddress();
                      } else {
                          throw new SocketException(val + " != getLocalHost(): " + local);
                      }
                  }
              }
              /*
               * save this value as it is actually used differently for IPv4 and
               * IPv6 so we cannot get the value using the getOption. The option
               * is actually only set for IPv4 and a masked version of the value
               * will be set as only a subset of the values are allowed on the
               * socket. Therefore we need to retain it to return the value that
               * was set. We also need the value to be passed into a number of
               * natives so that it can be used properly with IPv6
               */
              if (optID == SocketOptions.IP_TOS) {
                  trafficClass = ((Integer) val).intValue();
              }
          }
      }
  
      @Override
      public void setTimeToLive(int ttl) throws IOException {
          setOption(IP_MULTICAST_TTL, Byte.valueOf((byte) (ttl & 0xFF)));
          if ((netImpl.getSocketFlags() & MULTICAST_TTL) != 0) {
              this.ttl = ttl;
          }
      }
  
      @Override
      public void setTTL(byte ttl) throws IOException {
          setOption(IP_MULTICAST_TTL, Byte.valueOf(ttl));
          if ((netImpl.getSocketFlags() & MULTICAST_TTL) != 0) {
              this.ttl = ttl;
          }
      }
  
      @Override
      public void connect(InetAddress inetAddr, int port) throws SocketException {
  
          // connectDatagram impl2
          netImpl.connectDatagram(fd, port, trafficClass, inetAddr);
  
          // if we get here then we are connected at the native level
          try {
              connectedAddress = InetAddress.getByAddress(inetAddr.getAddress());
          } catch (UnknownHostException e) {
              // this is never expected to happen as we should not have gotten
              // here if the address is not resolvable
              throw new SocketException("K0317 "+inetAddr.getHostName()); //$NON-NLS-1$
          }
          connectedPort = port;
          isNativeConnected = true;
      }
  
      @Override
      public void disconnect() {
          try {
              netImpl.disconnectDatagram(fd);
          } catch (Exception e) {
              // there is currently no way to return an error so just eat any
              // exception
          }
          connectedPort = -1;
          connectedAddress = null;
          isNativeConnected = false;
      }
  
      @Override
      public int peekData(DatagramPacket pack) throws IOException {
          try {
              if (isNativeConnected) {
                  netImpl.recvConnectedDatagram(fd, pack, pack.getData(), pack.getOffset(), pack
                          .getLength(), receiveTimeout, true); // peek
                  updatePacketRecvAddress(pack);
              } else {
                  // receiveDatagram 2
                  netImpl.receiveDatagram(fd, pack, pack.getData(), pack.getOffset(), pack
                          .getLength(), receiveTimeout, true); // peek
              }
          } catch (InterruptedIOException e) {
              throw new SocketTimeoutException(e.toString());
          }
          return pack.getPort();
      }
  
      /**
       * Set the received address and port in the packet. We do this when the
       * Datagram socket is connected at the native level and the
       * recvConnnectedDatagramImpl does not update the packet with address from
       * which the packet was received
       * 
       * @param packet
       *            the packet to be updated
       */
      private void updatePacketRecvAddress(DatagramPacket packet) {
          packet.setAddress(connectedAddress);
          packet.setPort(connectedPort);
      }
      
  }