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 android.adservices.adselection; 18 19 import android.adservices.common.AdTechIdentifier; 20 import android.annotation.FlaggedApi; 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.net.Uri; 24 import android.os.Parcel; 25 import android.os.Parcelable; 26 27 import com.android.adservices.flags.Flags; 28 29 import java.util.Arrays; 30 import java.util.List; 31 import java.util.Objects; 32 33 /** 34 * Contains a list of buyer supplied {@link AdWithBid} bundle and its signature. 35 * 36 * <p>Instances of this class are created by SDKs to be injected as part of {@link 37 * AdSelectionConfig} and passed to {@link AdSelectionManager#selectAds} 38 * 39 * <p>SignedContextualAds are signed using ECDSA algorithm with SHA256 hashing algorithm (aka 40 * SHA256withECDSA). Keys used should belong to P-256 curve (aka “secp256r1” or “prime256v1”). 41 * 42 * <p>Signature should include the buyer, decisionLogicUri and adsWithBid fields. 43 * 44 * <p>While creating the signature a specific serialization rules must be followed as it's outlined 45 * here: 46 * 47 * <ul> 48 * <li>{@code Objects} concatenate the serialized values of their fields with the {@code |} (pipe) 49 * in between each field 50 * <li>{@code All fields} are sorted by alphabetical order within the object 51 * <li>{@code Nullable fields} are skipped if they are null/unset 52 * <li>{@code Doubles} are converted to String preserving precision 53 * <li>{@code Integers} are converted to string values 54 * <li>{@code Sets} are sorted alphabetically 55 * <li>{@code Lists} keep the same order 56 * <li>{@code Strings} get encoded into byte[] using UTF-8 encoding 57 * </ul> 58 */ 59 @FlaggedApi(Flags.FLAG_FLEDGE_AD_SELECTION_FILTERING_ENABLED) 60 public final class SignedContextualAds implements Parcelable { 61 private static final String BUYER_CANNOT_BE_NULL = "Buyer cannot be null."; 62 private static final String DECISION_LOGIC_URI_CANNOT_BE_NULL = 63 "DecisionLogicUri cannot be null."; 64 private static final String ADS_WITH_BID_CANNOT_BE_NULL = "AdsWithBid cannot be null."; 65 private static final String SIGNATURE_CANNOT_BE_NULL = "Signature cannot be null."; 66 @NonNull private final AdTechIdentifier mBuyer; 67 @NonNull private final Uri mDecisionLogicUri; 68 @NonNull private final List<AdWithBid> mAdsWithBid; 69 @NonNull private final byte[] mSignature; 70 71 @NonNull 72 public static final Creator<SignedContextualAds> CREATOR = 73 new Creator<>() { 74 @Override 75 public SignedContextualAds createFromParcel(@NonNull Parcel in) { 76 Objects.requireNonNull(in); 77 return new SignedContextualAds(in); 78 } 79 80 @Override 81 public SignedContextualAds[] newArray(int size) { 82 return new SignedContextualAds[0]; 83 } 84 }; 85 SignedContextualAds( @onNull AdTechIdentifier buyer, @NonNull Uri decisionLogicUri, @NonNull List<AdWithBid> adsWithBid, @NonNull byte[] signature)86 private SignedContextualAds( 87 @NonNull AdTechIdentifier buyer, 88 @NonNull Uri decisionLogicUri, 89 @NonNull List<AdWithBid> adsWithBid, 90 @NonNull byte[] signature) { 91 this.mBuyer = buyer; 92 this.mDecisionLogicUri = decisionLogicUri; 93 this.mAdsWithBid = adsWithBid; 94 this.mSignature = signature; 95 } 96 SignedContextualAds(@onNull Parcel in)97 private SignedContextualAds(@NonNull Parcel in) { 98 Objects.requireNonNull(in); 99 mBuyer = AdTechIdentifier.CREATOR.createFromParcel(in); 100 mDecisionLogicUri = Uri.CREATOR.createFromParcel(in); 101 mAdsWithBid = in.createTypedArrayList(AdWithBid.CREATOR); 102 mSignature = in.createByteArray(); 103 } 104 105 @Override describeContents()106 public int describeContents() { 107 return 0; 108 } 109 110 @Override writeToParcel(@onNull Parcel dest, int flags)111 public void writeToParcel(@NonNull Parcel dest, int flags) { 112 Objects.requireNonNull(dest); 113 114 mBuyer.writeToParcel(dest, flags); 115 mDecisionLogicUri.writeToParcel(dest, flags); 116 dest.writeTypedList(mAdsWithBid); 117 dest.writeByteArray(mSignature); 118 } 119 120 @Override equals(Object o)121 public boolean equals(Object o) { 122 if (this == o) return true; 123 if (!(o instanceof SignedContextualAds)) return false; 124 SignedContextualAds that = (SignedContextualAds) o; 125 return Objects.equals(mBuyer, that.mBuyer) 126 && Objects.equals(mDecisionLogicUri, that.mDecisionLogicUri) 127 && Objects.equals(mAdsWithBid, that.mAdsWithBid) 128 && Arrays.equals(mSignature, that.mSignature); 129 } 130 131 @Override hashCode()132 public int hashCode() { 133 return Objects.hash(mBuyer, mDecisionLogicUri, mAdsWithBid, Arrays.hashCode(mSignature)); 134 } 135 136 /** 137 * @return the Ad tech identifier from which this contextual Ad would have been downloaded 138 */ 139 @NonNull getBuyer()140 public AdTechIdentifier getBuyer() { 141 return mBuyer; 142 } 143 144 /** 145 * @return the URI used to retrieve the updateBid() and reportWin() function used during the ad 146 * selection and reporting process 147 */ 148 @NonNull getDecisionLogicUri()149 public Uri getDecisionLogicUri() { 150 return mDecisionLogicUri; 151 } 152 153 /** 154 * @return the Ad data with bid value associated with this ad 155 */ 156 @NonNull getAdsWithBid()157 public List<AdWithBid> getAdsWithBid() { 158 return mAdsWithBid; 159 } 160 161 /** 162 * Returns a copy of the signature for the contextual ads object. 163 * 164 * <p>See {@link SignedContextualAds} for more details. 165 * 166 * @return the signature 167 */ 168 @NonNull getSignature()169 public byte[] getSignature() { 170 return Arrays.copyOf(mSignature, mSignature.length); 171 } 172 173 @Override toString()174 public String toString() { 175 return "SignedContextualAds{" 176 + "mBuyer=" 177 + mBuyer 178 + ", mDecisionLogicUri=" 179 + mDecisionLogicUri 180 + ", mAdsWithBid=" 181 + mAdsWithBid 182 + ", mSignature=" 183 + Arrays.toString(mSignature) 184 + '}'; 185 } 186 187 /** Builder for {@link SignedContextualAds} object */ 188 public static final class Builder { 189 @Nullable private AdTechIdentifier mBuyer; 190 @Nullable private Uri mDecisionLogicUri; 191 @Nullable private List<AdWithBid> mAdsWithBid; 192 @Nullable private byte[] mSignature; 193 Builder()194 public Builder() {} 195 196 /** Returns a {@link SignedContextualAds.Builder} from a {@link SignedContextualAds}. */ Builder(@onNull SignedContextualAds signedContextualAds)197 public Builder(@NonNull SignedContextualAds signedContextualAds) { 198 Objects.requireNonNull(signedContextualAds); 199 200 this.mBuyer = signedContextualAds.getBuyer(); 201 this.mDecisionLogicUri = signedContextualAds.getDecisionLogicUri(); 202 this.mAdsWithBid = signedContextualAds.getAdsWithBid(); 203 this.mSignature = signedContextualAds.getSignature(); 204 } 205 206 /** 207 * Sets the buyer Ad tech Identifier 208 * 209 * <p>See {@link #getBuyer()} for more details 210 */ 211 @NonNull setBuyer(@onNull AdTechIdentifier buyer)212 public SignedContextualAds.Builder setBuyer(@NonNull AdTechIdentifier buyer) { 213 Objects.requireNonNull(buyer, BUYER_CANNOT_BE_NULL); 214 215 this.mBuyer = buyer; 216 return this; 217 } 218 219 /** 220 * Sets the URI to fetch the decision logic used in ad selection and reporting 221 * 222 * <p>See {@link #getDecisionLogicUri()} for more details 223 */ 224 @NonNull setDecisionLogicUri(@onNull Uri decisionLogicUri)225 public SignedContextualAds.Builder setDecisionLogicUri(@NonNull Uri decisionLogicUri) { 226 Objects.requireNonNull(decisionLogicUri, DECISION_LOGIC_URI_CANNOT_BE_NULL); 227 228 this.mDecisionLogicUri = decisionLogicUri; 229 return this; 230 } 231 232 /** 233 * Sets the Ads with pre-defined bid values 234 * 235 * <p>See {@link #getAdsWithBid()} for more details 236 */ 237 @NonNull setAdsWithBid(@onNull List<AdWithBid> adsWithBid)238 public SignedContextualAds.Builder setAdsWithBid(@NonNull List<AdWithBid> adsWithBid) { 239 Objects.requireNonNull(adsWithBid, ADS_WITH_BID_CANNOT_BE_NULL); 240 241 this.mAdsWithBid = adsWithBid; 242 return this; 243 } 244 245 /** Sets the copied signature */ 246 @NonNull setSignature(@onNull byte[] signature)247 public SignedContextualAds.Builder setSignature(@NonNull byte[] signature) { 248 Objects.requireNonNull(signature, SIGNATURE_CANNOT_BE_NULL); 249 250 this.mSignature = Arrays.copyOf(signature, signature.length); 251 return this; 252 } 253 254 /** 255 * Builds a {@link SignedContextualAds} instance. 256 * 257 * @throws NullPointerException if any required params are null 258 */ 259 @NonNull build()260 public SignedContextualAds build() { 261 Objects.requireNonNull(mBuyer, BUYER_CANNOT_BE_NULL); 262 Objects.requireNonNull(mDecisionLogicUri, DECISION_LOGIC_URI_CANNOT_BE_NULL); 263 Objects.requireNonNull(mAdsWithBid, ADS_WITH_BID_CANNOT_BE_NULL); 264 Objects.requireNonNull(mSignature, SIGNATURE_CANNOT_BE_NULL); 265 266 return new SignedContextualAds(mBuyer, mDecisionLogicUri, mAdsWithBid, mSignature); 267 } 268 } 269 } 270