blob: 93005fae17728c75f5ab21df8331df97576997a9 [file] [log] [blame]
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.content.pm.verify.domain;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.IBinder;
import android.os.Parcel;
import android.util.ArraySet;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
/**
* @hide
*/
public class DomainVerificationUtils {
private static final int STRINGS_TARGET_BYTE_SIZE = IBinder.getSuggestedMaxIpcSizeBytes() / 2;
/**
* Write a map containing web hosts to the given parcel, using {@link Parcel#writeBlob(byte[])}
* if the limit exceeds {@link IBinder#getSuggestedMaxIpcSizeBytes()} / 2. This assumes that the
* written map is the only data structure in the caller that varies based on the host data set.
* Other data that will be written to the parcel after this method will not be considered in the
* calculation.
*/
public static void writeHostMap(@NonNull Parcel dest, @NonNull Map<String, ?> map) {
boolean targetSizeExceeded = false;
int totalSize = dest.dataSize();
for (String host : map.keySet()) {
totalSize += estimatedByteSizeOf(host);
if (totalSize > STRINGS_TARGET_BYTE_SIZE) {
targetSizeExceeded = true;
break;
}
}
dest.writeBoolean(targetSizeExceeded);
if (!targetSizeExceeded) {
dest.writeMap(map);
return;
}
Parcel data = Parcel.obtain();
try {
data.writeMap(map);
dest.writeBlob(data.marshall());
} finally {
data.recycle();
}
}
/**
* Retrieve a map previously written by {@link #writeHostMap(Parcel, Map)}.
*/
@NonNull
@SuppressWarnings("rawtypes")
public static <T extends Map> T readHostMap(@NonNull Parcel in, @NonNull T map,
@NonNull ClassLoader classLoader) {
boolean targetSizeExceeded = in.readBoolean();
if (!targetSizeExceeded) {
in.readMap(map, classLoader);
return map;
}
Parcel data = Parcel.obtain();
try {
byte[] blob = in.readBlob();
data.unmarshall(blob, 0, blob.length);
data.setDataPosition(0);
data.readMap(map, classLoader);
} finally {
data.recycle();
}
return map;
}
/**
* {@link ArraySet} variant of {@link #writeHostMap(Parcel, Map)}.
*/
public static void writeHostSet(@NonNull Parcel dest, @NonNull Set<String> set) {
boolean targetSizeExceeded = false;
int totalSize = dest.dataSize();
for (String host : set) {
totalSize += estimatedByteSizeOf(host);
if (totalSize > STRINGS_TARGET_BYTE_SIZE) {
targetSizeExceeded = true;
break;
}
}
dest.writeBoolean(targetSizeExceeded);
if (!targetSizeExceeded) {
writeSet(dest, set);
return;
}
Parcel data = Parcel.obtain();
try {
writeSet(data, set);
dest.writeBlob(data.marshall());
} finally {
data.recycle();
}
}
/**
* {@link ArraySet} variant of {@link #readHostMap(Parcel, Map, ClassLoader)}.
*/
@NonNull
public static Set<String> readHostSet(@NonNull Parcel in) {
boolean targetSizeExceeded = in.readBoolean();
if (!targetSizeExceeded) {
return readSet(in);
}
Parcel data = Parcel.obtain();
try {
byte[] blob = in.readBlob();
data.unmarshall(blob, 0, blob.length);
data.setDataPosition(0);
return readSet(data);
} finally {
data.recycle();
}
}
private static void writeSet(@NonNull Parcel dest, @Nullable Set<String> set) {
if (set == null) {
dest.writeInt(-1);
return;
}
dest.writeInt(set.size());
for (String string : set) {
dest.writeString(string);
}
}
@NonNull
private static Set<String> readSet(@NonNull Parcel in) {
int size = in.readInt();
if (size == -1) {
return Collections.emptySet();
}
ArraySet<String> set = new ArraySet<>(size);
for (int count = 0; count < size; count++) {
set.add(in.readString());
}
return set;
}
/**
* Ballpark the size of domains to avoid unnecessary allocation of ashmem when sending domains
* across the client-server API.
*/
public static int estimatedByteSizeOf(String string) {
return string.length() * 2 + 12;
}
}