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.adservices.service.signals.updateprocessors; 18 19 import org.json.JSONArray; 20 import org.json.JSONObject; 21 22 import java.nio.ByteBuffer; 23 import java.nio.charset.StandardCharsets; 24 import java.util.Base64; 25 import java.util.Set; 26 27 /** Collection of common utilities for implementers of the UpdateProcessor interface. */ 28 public class UpdateProcessorUtils { 29 30 private static final int KEY_SIZE_BYTES = 4; 31 private static final int VALUE_MAX_SIZE_BYTES = 100; 32 33 /** 34 * Casts the given update object to a JSONArray throwing an appropriate error if the input is 35 * not a JSONArray. 36 * 37 * @param commandName The name of the command running this method (needed for constructing the 38 * error message in the event of a failure). 39 * @param updates The JSONArray to be cast. 40 * @return The post-cast JSONArray. 41 */ castToJSONArray(String commandName, Object updates)42 public static JSONArray castToJSONArray(String commandName, Object updates) { 43 if (!(updates instanceof JSONArray)) { 44 throw new IllegalArgumentException( 45 String.format("Value for \"%s\" must be a JSON array", commandName)); 46 } 47 return (JSONArray) updates; 48 } 49 50 /** 51 * Casts the given update object to a JSONObject throwing an appropriate error if the input is 52 * not a JSONObject. 53 * 54 * @param commandName The name of the command running this method (needed for constructing the 55 * error message in the event of a failure). 56 * @param updates The JSONObject to be cast. 57 * @return The post-cast JSONObject. 58 */ castToJSONObject(String commandName, Object updates)59 public static JSONObject castToJSONObject(String commandName, Object updates) { 60 if (!(updates instanceof JSONObject)) { 61 throw new IllegalArgumentException( 62 String.format("Value for \"%s\" must be a JSON object", commandName)); 63 } 64 return (JSONObject) updates; 65 } 66 67 /** 68 * Adds a key to the set of touched keys, throwing an exception if the key is already in the 69 * set. 70 * 71 * @param key The key to add. 72 * @param keysTouched The set of keys already touched. 73 */ touchKey(ByteBuffer key, Set<ByteBuffer> keysTouched)74 public static void touchKey(ByteBuffer key, Set<ByteBuffer> keysTouched) { 75 if (!keysTouched.add(key)) { 76 throw new IllegalArgumentException("Keys must only appear once per update JSON"); 77 } 78 } 79 80 /** 81 * Converts a key from base 64 to a ByteBuffer wrapping a size 4 byte array. Throws an error if 82 * the input is not valid base 64 or does not fit in 4 bytes. 83 * 84 * @param commandName The name of the command calling this method (needed for constructing the 85 * error message in the event of a failure). 86 * @param key The base 64 key to convert. 87 * @return A byte buffer of the decoded bytes. 88 */ decodeKey(String commandName, String key)89 public static ByteBuffer decodeKey(String commandName, String key) { 90 byte[] toReturn = new byte[4]; 91 try { 92 Base64.getDecoder().decode(key.getBytes(StandardCharsets.ISO_8859_1), toReturn); 93 } catch (IllegalArgumentException e) { 94 throw new IllegalArgumentException( 95 String.format( 96 "Keys in \"%s\" must be valid base 64 and under %d bytes.", 97 commandName, KEY_SIZE_BYTES)); 98 } 99 return ByteBuffer.wrap(toReturn); 100 } 101 102 /** 103 * Converts a value from a base 64 string to a byte array. Throws an error if the input is not 104 * valid base 64 or does not fit in VALUE_MAX_SIZE_BYTES bytes. 105 * 106 * @param commandName The name of the command calling this method (needed for constructing the 107 * error message in the event of a failure). 108 * @param value The base 64 value to convert. 109 * @return A byte array of the decoded bytes. 110 */ decodeValue(String commandName, String value)111 public static byte[] decodeValue(String commandName, String value) { 112 byte[] toReturn; 113 try { 114 toReturn = Base64.getDecoder().decode(value); 115 } catch (IllegalArgumentException e) { 116 throw new IllegalArgumentException( 117 String.format("Values in \"%s\" must be valid base 64", commandName)); 118 } 119 if (toReturn.length > VALUE_MAX_SIZE_BYTES) { 120 throw new IllegalArgumentException( 121 String.format( 122 "Values in \"%s\" must be under %d bytes", 123 commandName, VALUE_MAX_SIZE_BYTES)); 124 } 125 return toReturn; 126 } 127 128 /** 129 * Takes a byte buffer and returns the underlying array, while making sure the ByteBuffer is 130 * properly wrapping an array with no offset 131 * 132 * @param buffer The buffer the convert. 133 * @return The underlying byte[]. 134 */ getByteArrayFromBuffer(ByteBuffer buffer)135 public static byte[] getByteArrayFromBuffer(ByteBuffer buffer) { 136 if (buffer.arrayOffset() != 0) { 137 throw new IllegalStateException("Improperly created ByteBuffer"); 138 } 139 return buffer.array(); 140 } 141 } 142