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