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