content: /*
   * Copyright (C) 2012 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package android.net.wifi.p2p.nsd;
  
  import android.net.wifi.p2p.WifiP2pDevice;
  
  import java.io.ByteArrayInputStream;
  import java.io.DataInputStream;
  import java.io.IOException;
  import java.util.HashMap;
  import java.util.Map;
  
  /**
   * A class for a response of bonjour service discovery.
   *
   * @hide
   */
  public class WifiP2pDnsSdServiceResponse extends WifiP2pServiceResponse {
  
      /**
       * DNS query name.
       * e.g)
       * for PTR
       * "_ipp._tcp.local."
       * for TXT
       * "MyPrinter._ipp._tcp.local."
       */
      private String mDnsQueryName;
  
      /**
       * Service instance name.
       * e.g) "MyPrinter"
       * This field is only used when the dns type equals to
       * {@link WifiP2pDnsSdServiceInfo#DNS_TYPE_PTR}.
       */
      private String mInstanceName;
  
      /**
       * DNS Type.
       * Should be {@link WifiP2pDnsSdServiceInfo#DNS_TYPE_PTR} or
       * {@link WifiP2pDnsSdServiceInfo#DNS_TYPE_TXT}.
       */
      private int mDnsType;
  
      /**
       * DnsSd version number.
       * Should be {@link WifiP2pDnsSdServiceInfo#VERSION_1}.
       */
      private int mVersion;
  
      /**
       * Txt record.
       * This field is only used when the dns type equals to
       * {@link WifiP2pDnsSdServiceInfo#DNS_TYPE_TXT}.
       */
      private final HashMap<String, String> mTxtRecord = new HashMap<String, String>();
  
      /**
       * Virtual memory packet.
       * see E.3 of the Wi-Fi Direct technical specification for the detail.<br>
       * The spec can be obtained from wi-fi.org
       * Key: pointer Value: domain name.<br>
       */
      private final static Map<Integer, String> sVmpack;
  
      static {
          sVmpack = new HashMap<Integer, String>();
          sVmpack.put(0x0c, "_tcp.local.");
          sVmpack.put(0x11, "local.");
          sVmpack.put(0x1c, "_udp.local.");
      }
  
      /**
       * Returns query DNS name.
       * @return DNS name.
       */
      public String getDnsQueryName() {
          return mDnsQueryName;
      }
  
      /**
       * Return query DNS type.
       * @return DNS type.
       */
      public int getDnsType() {
          return mDnsType;
      }
  
      /**
       * Return bonjour version number.
       * @return version number.
       */
      public int getVersion() {
          return mVersion;
      }
  
      /**
       * Return instance name.
       * @return
       */
      public String getInstanceName() {
          return mInstanceName;
      }
  
      /**
       * Return TXT record data.
       * @return TXT record data.
       */
      public Map<String, String> getTxtRecord() {
          return mTxtRecord;
      }
  
      @Override
      public String toString() {
          StringBuffer sbuf = new StringBuffer();
          sbuf.append("serviceType:DnsSd(").append(mServiceType).append(")");
          sbuf.append(" status:").append(Status.toString(mStatus));
          sbuf.append(" srcAddr:").append(mDevice.deviceAddress);
          sbuf.append(" version:").append(String.format("%02x", mVersion));
          sbuf.append(" dnsName:").append(mDnsQueryName);
          sbuf.append(" TxtRecord:");
          for (String key : mTxtRecord.keySet()) {
              sbuf.append(" key:").append(key).append(" value:").append(mTxtRecord.get(key));
          }
          if (mInstanceName != null) {
              sbuf.append(" InsName:").append(mInstanceName);
          }
          return sbuf.toString();
      }
  
      /**
       * This is only used in framework.
       * @param status status code.
       * @param dev source device.
       * @param data RDATA.
       * @hide
       */
      protected WifiP2pDnsSdServiceResponse(int status,
              int tranId, WifiP2pDevice dev, byte[] data) {
          super(WifiP2pServiceInfo.SERVICE_TYPE_BONJOUR,
                  status, tranId, dev, data);
          if (!parse()) {
              throw new IllegalArgumentException("Malformed bonjour service response");
          }
      }
  
      /**
       * Parse DnsSd service discovery response.
       *
       * @return {@code true} if the operation succeeded
       */
      private boolean parse() {
          /*
           * The data format from Wi-Fi Direct spec is as follows.
           * ________________________________________________
           * |  encoded and compressed dns name (variable)  |
           * ________________________________________________
           * |       dnstype(2byte)      |  version(1byte)  |
           * ________________________________________________
           * |              RDATA (variable)                |
           */
          if (mData == null) {
              // the empty is OK.
              return true;
          }
  
          DataInputStream dis = new DataInputStream(new ByteArrayInputStream(mData));
  
          mDnsQueryName = readDnsName(dis);
          if (mDnsQueryName == null) {
              return false;
          }
  
          try {
              mDnsType = dis.readUnsignedShort();
              mVersion = dis.readUnsignedByte();
          } catch (IOException e) {
              e.printStackTrace();
              return false;
          }
  
          if (mDnsType == WifiP2pDnsSdServiceInfo.DNS_TYPE_PTR) {
              String rData = readDnsName(dis);
              if (rData == null) {
                  return false;
              }
              if (rData.length() <= mDnsQueryName.length()) {
                  return false;
              }
  
              mInstanceName = rData.substring(0,
                      rData.length() - mDnsQueryName.length() -1);
          } else if (mDnsType == WifiP2pDnsSdServiceInfo.DNS_TYPE_TXT) {
              return readTxtData(dis);
          } else {
              return false;
          }
  
          return true;
      }
  
      /**
       * Read dns name.
       *
       * @param dis data input stream.
       * @return dns name
       */
      private String readDnsName(DataInputStream dis) {
          StringBuffer sb = new StringBuffer();
  
          // copy virtual memory packet.
          HashMap<Integer, String> vmpack = new HashMap<Integer, String>(sVmpack);
          if (mDnsQueryName != null) {
              vmpack.put(0x27, mDnsQueryName);
          }
          try {
              while (true) {
                  int i = dis.readUnsignedByte();
                  if (i == 0x00) {
                      return sb.toString();
                  } else if (i == 0xc0) {
                      // refer to pointer.
                      String ref = vmpack.get(dis.readUnsignedByte());
                      if (ref == null) {
                          //invalid.
                          return null;
                      }
                      sb.append(ref);
                      return sb.toString();
                  } else {
                      byte[] data = new byte[i];
                      dis.readFully(data);
                      sb.append(new String(data));
                      sb.append(".");
                  }
              }
          } catch (IOException e) {
              e.printStackTrace();
          }
          return null;
      }
  
      /**
       * Read TXT record data.
       *
       * @param dis
       * @return true if TXT data is valid
       */
      private boolean readTxtData(DataInputStream dis) {
          try {
              while (dis.available() > 0) {
                  int len = dis.readUnsignedByte();
                  if (len == 0) {
                      break;
                  }
                  byte[] data = new byte[len];
                  dis.readFully(data);
                  String[] keyVal = new String(data).split("=");
                  if (keyVal.length != 2) {
                      return false;
                  }
                  mTxtRecord.put(keyVal[0], keyVal[1]);
              }
              return true;
          } catch (IOException e) {
              e.printStackTrace();
          }
          return false;
      }
  
      /**
       * Creates DnsSd service response.
       *  This is only called from WifiP2pServiceResponse
       *
       * @param status status code.
       * @param dev source device.
       * @param data DnsSd response data.
       * @return DnsSd service response data.
       * @hide
       */
      static WifiP2pDnsSdServiceResponse newInstance(int status,
              int transId, WifiP2pDevice dev, byte[] data) {
          if (status != WifiP2pServiceResponse.Status.SUCCESS) {
              return new WifiP2pDnsSdServiceResponse(status,
                      transId, dev, null);
          }
          try {
              return new WifiP2pDnsSdServiceResponse(status,
                      transId, dev, data);
          } catch (IllegalArgumentException e) {
              e.printStackTrace();
          }
          return null;
      }
  }
  