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