Home | History | Annotate | Download | only in nsd
      1 /*
      2  * Copyright (C) 2012 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.net.wifi.p2p.nsd;
     18 
     19 import android.net.nsd.DnsSdTxtRecord;
     20 import android.text.TextUtils;
     21 
     22 import java.util.ArrayList;
     23 import java.util.HashMap;
     24 import java.util.List;
     25 import java.util.Locale;
     26 import java.util.Map;
     27 
     28 /**
     29  * A class for storing Bonjour service information that is advertised
     30  * over a Wi-Fi peer-to-peer setup.
     31  *
     32  * {@see android.net.wifi.p2p.WifiP2pManager#addLocalService}
     33  * {@see android.net.wifi.p2p.WifiP2pManager#removeLocalService}
     34  * {@see WifiP2pServiceInfo}
     35  * {@see WifiP2pUpnpServiceInfo}
     36  */
     37 public class WifiP2pDnsSdServiceInfo extends WifiP2pServiceInfo {
     38 
     39     /**
     40      * Bonjour version 1.
     41      * @hide
     42      */
     43     public static final int VERSION_1 = 0x01;
     44 
     45     /**
     46      * Pointer record.
     47      * @hide
     48      */
     49     public static final int DNS_TYPE_PTR = 12;
     50 
     51     /**
     52      * Text record.
     53      * @hide
     54      */
     55     public static final int DNS_TYPE_TXT = 16;
     56 
     57     /**
     58      * virtual memory packet.
     59      * see E.3 of the Wi-Fi Direct technical specification for the detail.<br>
     60      * Key: domain name Value: pointer address.<br>
     61      */
     62     private final static Map<String, String> sVmPacket;
     63 
     64     static {
     65         sVmPacket = new HashMap<String, String>();
     66         sVmPacket.put("_tcp.local.", "c00c");
     67         sVmPacket.put("local.", "c011");
     68         sVmPacket.put("_udp.local.", "c01c");
     69     }
     70 
     71     /**
     72      * This constructor is only used in newInstance().
     73      *
     74      * @param queryList
     75      */
     76     private WifiP2pDnsSdServiceInfo(List<String> queryList) {
     77         super(queryList);
     78     }
     79 
     80     /**
     81      * Create a Bonjour service information object.
     82      *
     83      * @param instanceName instance name.<br>
     84      *  e.g) "MyPrinter"
     85      * @param serviceType service type.<br>
     86      *  e.g) "_ipp._tcp"
     87      * @param txtMap TXT record with key/value pair in a map confirming to format defined at
     88      * http://files.dns-sd.org/draft-cheshire-dnsext-dns-sd.txt
     89      * @return Bonjour service information object
     90      */
     91     public static WifiP2pDnsSdServiceInfo newInstance(String instanceName,
     92             String serviceType, Map<String, String> txtMap) {
     93         if (TextUtils.isEmpty(instanceName) || TextUtils.isEmpty(serviceType)) {
     94             throw new IllegalArgumentException(
     95                     "instance name or service type cannot be empty");
     96         }
     97 
     98         DnsSdTxtRecord txtRecord = new DnsSdTxtRecord();
     99         if (txtMap != null) {
    100             for (String key : txtMap.keySet()) {
    101                 txtRecord.set(key, txtMap.get(key));
    102             }
    103         }
    104 
    105         ArrayList<String> queries = new ArrayList<String>();
    106         queries.add(createPtrServiceQuery(instanceName, serviceType));
    107         queries.add(createTxtServiceQuery(instanceName, serviceType, txtRecord));
    108 
    109         return new WifiP2pDnsSdServiceInfo(queries);
    110     }
    111 
    112     /**
    113      * Create wpa_supplicant service query for PTR record.
    114      *
    115      * @param instanceName instance name.<br>
    116      *  e.g) "MyPrinter"
    117      * @param serviceType service type.<br>
    118      *  e.g) "_ipp._tcp"
    119      * @return wpa_supplicant service query.
    120      */
    121     private static String createPtrServiceQuery(String instanceName,
    122             String serviceType) {
    123 
    124         StringBuffer sb = new StringBuffer();
    125         sb.append("bonjour ");
    126         sb.append(createRequest(serviceType + ".local.", DNS_TYPE_PTR, VERSION_1));
    127         sb.append(" ");
    128 
    129         byte[] data = instanceName.getBytes();
    130         sb.append(String.format(Locale.US, "%02x", data.length));
    131         sb.append(WifiP2pServiceInfo.bin2HexStr(data));
    132         // This is the start point of this response.
    133         // Therefore, it indicates the request domain name.
    134         sb.append("c027");
    135         return sb.toString();
    136     }
    137 
    138     /**
    139      * Create wpa_supplicant service query for TXT record.
    140      *
    141      * @param instanceName instance name.<br>
    142      *  e.g) "MyPrinter"
    143      * @param serviceType service type.<br>
    144      *  e.g) "_ipp._tcp"
    145      * @param txtRecord TXT record.<br>
    146      * @return wpa_supplicant service query.
    147      */
    148     private static String createTxtServiceQuery(String instanceName,
    149             String serviceType,
    150             DnsSdTxtRecord txtRecord) {
    151 
    152 
    153         StringBuffer sb = new StringBuffer();
    154         sb.append("bonjour ");
    155 
    156         sb.append(createRequest((instanceName + "." + serviceType + ".local."),
    157                 DNS_TYPE_TXT, VERSION_1));
    158         sb.append(" ");
    159         byte[] rawData = txtRecord.getRawData();
    160         if (rawData.length == 0) {
    161             sb.append("00");
    162         } else {
    163             sb.append(bin2HexStr(rawData));
    164         }
    165         return sb.toString();
    166     }
    167 
    168     /**
    169      * Create bonjour service discovery request.
    170      *
    171      * @param dnsName dns name
    172      * @param dnsType dns type
    173      * @param version version number
    174      * @hide
    175      */
    176     static String createRequest(String dnsName, int dnsType, int version) {
    177         StringBuffer sb = new StringBuffer();
    178 
    179         /*
    180          * The request format is as follows.
    181          * ________________________________________________
    182          * |  Encoded and Compressed dns name (variable)  |
    183          * ________________________________________________
    184          * |   Type (2)           | Version (1) |
    185          */
    186         if (dnsType == WifiP2pDnsSdServiceInfo.DNS_TYPE_TXT) {
    187             dnsName = dnsName.toLowerCase(Locale.ROOT); // TODO: is this right?
    188         }
    189         sb.append(compressDnsName(dnsName));
    190         sb.append(String.format(Locale.US, "%04x", dnsType));
    191         sb.append(String.format(Locale.US, "%02x", version));
    192 
    193         return sb.toString();
    194     }
    195 
    196     /**
    197      * Compress DNS data.
    198      *
    199      * see E.3 of the Wi-Fi Direct technical specification for the detail.
    200      *
    201      * @param dnsName dns name
    202      * @return compressed dns name
    203      */
    204     private static String compressDnsName(String dnsName) {
    205         StringBuffer sb = new StringBuffer();
    206 
    207         // The domain name is replaced with a pointer to a prior
    208         // occurrence of the same name in virtual memory packet.
    209         while (true) {
    210             String data = sVmPacket.get(dnsName);
    211             if (data != null) {
    212                 sb.append(data);
    213                 break;
    214             }
    215             int i = dnsName.indexOf('.');
    216             if (i == -1) {
    217                 if (dnsName.length() > 0) {
    218                     sb.append(String.format(Locale.US, "%02x", dnsName.length()));
    219                     sb.append(WifiP2pServiceInfo.bin2HexStr(dnsName.getBytes()));
    220                 }
    221                 // for a sequence of labels ending in a zero octet
    222                 sb.append("00");
    223                 break;
    224             }
    225 
    226             String name = dnsName.substring(0, i);
    227             dnsName = dnsName.substring(i + 1);
    228             sb.append(String.format(Locale.US, "%02x", name.length()));
    229             sb.append(WifiP2pServiceInfo.bin2HexStr(name.getBytes()));
    230         }
    231         return sb.toString();
    232     }
    233 }
    234