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 android.content.pm.verify.domain; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.os.IBinder; 22 import android.os.Parcel; 23 import android.util.ArraySet; 24 25 import java.util.Collections; 26 import java.util.Map; 27 import java.util.Set; 28 29 /** 30 * @hide 31 */ 32 public class DomainVerificationUtils { 33 34 private static final int STRINGS_TARGET_BYTE_SIZE = IBinder.getSuggestedMaxIpcSizeBytes() / 2; 35 36 /** 37 * Write a map containing web hosts to the given parcel, using {@link Parcel#writeBlob(byte[])} 38 * if the limit exceeds {@link IBinder#getSuggestedMaxIpcSizeBytes()} / 2. This assumes that the 39 * written map is the only data structure in the caller that varies based on the host data set. 40 * Other data that will be written to the parcel after this method will not be considered in the 41 * calculation. 42 */ writeHostMap(@onNull Parcel dest, @NonNull Map<String, ?> map)43 public static void writeHostMap(@NonNull Parcel dest, @NonNull Map<String, ?> map) { 44 boolean targetSizeExceeded = false; 45 int totalSize = dest.dataSize(); 46 for (String host : map.keySet()) { 47 totalSize += estimatedByteSizeOf(host); 48 if (totalSize > STRINGS_TARGET_BYTE_SIZE) { 49 targetSizeExceeded = true; 50 break; 51 } 52 } 53 54 dest.writeBoolean(targetSizeExceeded); 55 56 if (!targetSizeExceeded) { 57 dest.writeMap(map); 58 return; 59 } 60 61 Parcel data = Parcel.obtain(); 62 try { 63 data.writeMap(map); 64 dest.writeBlob(data.marshall()); 65 } finally { 66 data.recycle(); 67 } 68 } 69 70 /** 71 * Retrieve a map previously written by {@link #writeHostMap(Parcel, Map)}. 72 */ 73 @NonNull 74 @SuppressWarnings("rawtypes") readHostMap(@onNull Parcel in, @NonNull T map, @NonNull ClassLoader classLoader)75 public static <T extends Map> T readHostMap(@NonNull Parcel in, @NonNull T map, 76 @NonNull ClassLoader classLoader) { 77 boolean targetSizeExceeded = in.readBoolean(); 78 79 if (!targetSizeExceeded) { 80 in.readMap(map, classLoader); 81 return map; 82 } 83 84 Parcel data = Parcel.obtain(); 85 try { 86 byte[] blob = in.readBlob(); 87 data.unmarshall(blob, 0, blob.length); 88 data.setDataPosition(0); 89 data.readMap(map, classLoader); 90 } finally { 91 data.recycle(); 92 } 93 94 return map; 95 } 96 97 /** 98 * {@link ArraySet} variant of {@link #writeHostMap(Parcel, Map)}. 99 */ writeHostSet(@onNull Parcel dest, @NonNull Set<String> set)100 public static void writeHostSet(@NonNull Parcel dest, @NonNull Set<String> set) { 101 boolean targetSizeExceeded = false; 102 int totalSize = dest.dataSize(); 103 for (String host : set) { 104 totalSize += estimatedByteSizeOf(host); 105 if (totalSize > STRINGS_TARGET_BYTE_SIZE) { 106 targetSizeExceeded = true; 107 break; 108 } 109 } 110 111 dest.writeBoolean(targetSizeExceeded); 112 113 if (!targetSizeExceeded) { 114 writeSet(dest, set); 115 return; 116 } 117 118 Parcel data = Parcel.obtain(); 119 try { 120 writeSet(data, set); 121 dest.writeBlob(data.marshall()); 122 } finally { 123 data.recycle(); 124 } 125 } 126 127 /** 128 * {@link ArraySet} variant of {@link #readHostMap(Parcel, Map, ClassLoader)}. 129 */ 130 @NonNull readHostSet(@onNull Parcel in)131 public static Set<String> readHostSet(@NonNull Parcel in) { 132 boolean targetSizeExceeded = in.readBoolean(); 133 134 if (!targetSizeExceeded) { 135 return readSet(in); 136 } 137 138 Parcel data = Parcel.obtain(); 139 try { 140 byte[] blob = in.readBlob(); 141 data.unmarshall(blob, 0, blob.length); 142 data.setDataPosition(0); 143 return readSet(data); 144 } finally { 145 data.recycle(); 146 } 147 } 148 writeSet(@onNull Parcel dest, @Nullable Set<String> set)149 private static void writeSet(@NonNull Parcel dest, @Nullable Set<String> set) { 150 if (set == null) { 151 dest.writeInt(-1); 152 return; 153 } 154 dest.writeInt(set.size()); 155 for (String string : set) { 156 dest.writeString(string); 157 } 158 } 159 160 @NonNull readSet(@onNull Parcel in)161 private static Set<String> readSet(@NonNull Parcel in) { 162 int size = in.readInt(); 163 if (size == -1) { 164 return Collections.emptySet(); 165 } 166 167 ArraySet<String> set = new ArraySet<>(size); 168 for (int count = 0; count < size; count++) { 169 set.add(in.readString()); 170 } 171 return set; 172 } 173 174 /** 175 * Ballpark the size of domains to avoid unnecessary allocation of ashmem when sending domains 176 * across the client-server API. 177 */ estimatedByteSizeOf(String string)178 public static int estimatedByteSizeOf(String string) { 179 return string.length() * 2 + 12; 180 } 181 } 182