• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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.util;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.net.Network;
22 import android.os.Handler;
23 
24 import com.android.server.connectivity.mdns.MdnsConstants;
25 import com.android.server.connectivity.mdns.MdnsRecord;
26 
27 import java.nio.ByteBuffer;
28 import java.nio.CharBuffer;
29 import java.nio.charset.Charset;
30 import java.nio.charset.CharsetEncoder;
31 import java.nio.charset.StandardCharsets;
32 
33 /**
34  * Mdns utility functions.
35  */
36 public class MdnsUtils {
37 
MdnsUtils()38     private MdnsUtils() { }
39 
40     /**
41      * Convert the string to DNS case-insensitive lowercase
42      *
43      * Per rfc6762#page-46, accented characters are not defined to be automatically equivalent to
44      * their unaccented counterparts. So the "DNS lowercase" should be if character is A-Z then they
45      * transform into a-z. Otherwise, they are kept as-is.
46      */
toDnsLowerCase(@onNull String string)47     public static String toDnsLowerCase(@NonNull String string) {
48         final char[] outChars = new char[string.length()];
49         for (int i = 0; i < string.length(); i++) {
50             outChars[i] = toDnsLowerCase(string.charAt(i));
51         }
52         return new String(outChars);
53     }
54 
55     /**
56      * Compare two strings by DNS case-insensitive lowercase.
57      */
equalsIgnoreDnsCase(@onNull String a, @NonNull String b)58     public static boolean equalsIgnoreDnsCase(@NonNull String a, @NonNull String b) {
59         if (a.length() != b.length()) return false;
60         for (int i = 0; i < a.length(); i++) {
61             if (toDnsLowerCase(a.charAt(i)) != toDnsLowerCase(b.charAt(i))) {
62                 return false;
63             }
64         }
65         return true;
66     }
67 
toDnsLowerCase(char a)68     private static char toDnsLowerCase(char a) {
69         return a >= 'A' && a <= 'Z' ? (char) (a + ('a' - 'A')) : a;
70     }
71 
72     /*** Ensure that current running thread is same as given handler thread */
ensureRunningOnHandlerThread(@onNull Handler handler)73     public static void ensureRunningOnHandlerThread(@NonNull Handler handler) {
74         if (!isRunningOnHandlerThread(handler)) {
75             throw new IllegalStateException(
76                     "Not running on Handler thread: " + Thread.currentThread().getName());
77         }
78     }
79 
80     /*** Check that current running thread is same as given handler thread */
isRunningOnHandlerThread(@onNull Handler handler)81     public static boolean isRunningOnHandlerThread(@NonNull Handler handler) {
82         if (handler.getLooper().getThread() == Thread.currentThread()) {
83             return true;
84         }
85         return false;
86     }
87 
88     /*** Check whether the target network is matched current network */
isNetworkMatched(@ullable Network targetNetwork, @Nullable Network currentNetwork)89     public static boolean isNetworkMatched(@Nullable Network targetNetwork,
90             @Nullable Network currentNetwork) {
91         return targetNetwork == null || targetNetwork.equals(currentNetwork);
92     }
93 
94     /**
95      * Truncate a service name to up to maxLength UTF-8 bytes.
96      */
truncateServiceName(@onNull String originalName, int maxLength)97     public static String truncateServiceName(@NonNull String originalName, int maxLength) {
98         // UTF-8 is at most 4 bytes per character; return early in the common case where
99         // the name can't possibly be over the limit given its string length.
100         if (originalName.length() <= maxLength / 4) return originalName;
101 
102         final Charset utf8 = StandardCharsets.UTF_8;
103         final CharsetEncoder encoder = utf8.newEncoder();
104         final ByteBuffer out = ByteBuffer.allocate(maxLength);
105         // encode will write as many characters as possible to the out buffer, and just
106         // return an overflow code if there were too many characters (no need to check the
107         // return code here, this method truncates the name on purpose).
108         encoder.encode(CharBuffer.wrap(originalName), out, true /* endOfInput */);
109         return new String(out.array(), 0, out.position(), utf8);
110     }
111 
112     /**
113      * Checks if the MdnsRecord needs to be renewed or not.
114      *
115      * <p>As per RFC6762 7.1 no need to query if remaining TTL is more than half the original one,
116      * so send the queries if half the TTL has passed.
117      */
isRecordRenewalNeeded(@onNull MdnsRecord mdnsRecord, final long now)118     public static boolean isRecordRenewalNeeded(@NonNull MdnsRecord mdnsRecord, final long now) {
119         return mdnsRecord.getTtl() > 0
120                 && mdnsRecord.getRemainingTTL(now) <= mdnsRecord.getTtl() / 2;
121     }
122 }