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