1 /* 2 * Copyright (C) 2021 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 com.android.server.connectivity.mdns; 18 19 import android.annotation.Nullable; 20 21 import androidx.annotation.VisibleForTesting; 22 23 import com.android.server.connectivity.mdns.util.MdnsUtils; 24 25 import java.io.IOException; 26 import java.util.Arrays; 27 import java.util.Locale; 28 import java.util.Objects; 29 30 /** An mDNS "SRV" record, which contains service information. */ 31 @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) 32 public class MdnsServiceRecord extends MdnsRecord { 33 public static final int PROTO_NONE = 0; 34 public static final int PROTO_TCP = 1; 35 public static final int PROTO_UDP = 2; 36 private static final String PROTO_TOKEN_TCP = "_tcp"; 37 private static final String PROTO_TOKEN_UDP = "_udp"; 38 private int servicePriority; 39 private int serviceWeight; 40 private int servicePort; 41 private String[] serviceHost; 42 MdnsServiceRecord(String[] name, MdnsPacketReader reader)43 public MdnsServiceRecord(String[] name, MdnsPacketReader reader) throws IOException { 44 this(name, reader, false); 45 } 46 MdnsServiceRecord(String[] name, MdnsPacketReader reader, boolean isQuestion)47 public MdnsServiceRecord(String[] name, MdnsPacketReader reader, boolean isQuestion) 48 throws IOException { 49 super(name, TYPE_SRV, reader, isQuestion); 50 } 51 MdnsServiceRecord(String[] name, boolean isUnicast)52 public MdnsServiceRecord(String[] name, boolean isUnicast) { 53 super(name, TYPE_SRV, 54 MdnsConstants.QCLASS_INTERNET | (isUnicast ? MdnsConstants.QCLASS_UNICAST : 0), 55 0L /* receiptTimeMillis */, false /* cacheFlush */, 0L /* ttlMillis */); 56 } 57 MdnsServiceRecord(String[] name, long receiptTimeMillis, boolean cacheFlush, long ttlMillis, int servicePriority, int serviceWeight, int servicePort, String[] serviceHost)58 public MdnsServiceRecord(String[] name, long receiptTimeMillis, boolean cacheFlush, 59 long ttlMillis, int servicePriority, int serviceWeight, int servicePort, 60 String[] serviceHost) { 61 super(name, TYPE_SRV, MdnsConstants.QCLASS_INTERNET, receiptTimeMillis, cacheFlush, 62 ttlMillis); 63 this.servicePriority = servicePriority; 64 this.serviceWeight = serviceWeight; 65 this.servicePort = servicePort; 66 this.serviceHost = serviceHost; 67 } 68 69 /** Returns the service's port number. */ getServicePort()70 public int getServicePort() { 71 return servicePort; 72 } 73 74 /** Returns the service's host name. */ getServiceHost()75 public String[] getServiceHost() { 76 return serviceHost; 77 } 78 79 /** Returns the service's priority. */ getServicePriority()80 public int getServicePriority() { 81 return servicePriority; 82 } 83 84 /** Returns the service's weight. */ getServiceWeight()85 public int getServiceWeight() { 86 return serviceWeight; 87 } 88 89 // Format of name is <instance-name>.<service-name>.<protocol>.<domain> 90 91 /** Returns the service's instance name, which uniquely identifies the service instance. */ getServiceInstanceName()92 public String getServiceInstanceName() { 93 if (name.length < 1) { 94 return null; 95 } 96 return name[0]; 97 } 98 99 /** Returns the service's name. */ getServiceName()100 public String getServiceName() { 101 if (name.length < 2) { 102 return null; 103 } 104 return name[1]; 105 } 106 107 /** Returns the service's protocol. */ getServiceProtocol()108 public int getServiceProtocol() { 109 if (name.length < 3) { 110 return PROTO_NONE; 111 } 112 113 String protocol = name[2]; 114 if (protocol.equals(PROTO_TOKEN_TCP)) { 115 return PROTO_TCP; 116 } 117 if (protocol.equals(PROTO_TOKEN_UDP)) { 118 return PROTO_UDP; 119 } 120 return PROTO_NONE; 121 } 122 123 @Override readData(MdnsPacketReader reader)124 protected void readData(MdnsPacketReader reader) throws IOException { 125 servicePriority = reader.readUInt16(); 126 serviceWeight = reader.readUInt16(); 127 servicePort = reader.readUInt16(); 128 serviceHost = reader.readLabels(); 129 } 130 131 @Override writeData(MdnsPacketWriter writer)132 protected void writeData(MdnsPacketWriter writer) throws IOException { 133 writer.writeUInt16(servicePriority); 134 writer.writeUInt16(serviceWeight); 135 writer.writeUInt16(servicePort); 136 writer.writeLabels(serviceHost); 137 } 138 139 @Override toString()140 public String toString() { 141 return String.format( 142 Locale.ROOT, 143 "SRV: %s:%d (prio=%d, weight=%d)", 144 labelsToString(serviceHost), 145 servicePort, 146 servicePriority, 147 serviceWeight); 148 } 149 150 @Override hashCode()151 public int hashCode() { 152 return (super.hashCode() * 31) 153 + Objects.hash(servicePriority, serviceWeight, 154 Arrays.hashCode(MdnsUtils.toDnsLabelsLowerCase(serviceHost)), 155 servicePort); 156 } 157 158 @Override equals(@ullable Object other)159 public boolean equals(@Nullable Object other) { 160 if (this == other) { 161 return true; 162 } 163 if (!(other instanceof MdnsServiceRecord)) { 164 return false; 165 } 166 MdnsServiceRecord otherRecord = (MdnsServiceRecord) other; 167 168 return super.equals(other) 169 && (servicePriority == otherRecord.servicePriority) 170 && (serviceWeight == otherRecord.serviceWeight) 171 && MdnsUtils.equalsDnsLabelIgnoreDnsCase(serviceHost, otherRecord.serviceHost) 172 && (servicePort == otherRecord.servicePort); 173 } 174 } 175