• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2022 Google LLC
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 package com.google.android.libraries.mobiledatadownload.internal;
17 
18 import android.content.Context;
19 import com.google.android.libraries.mobiledatadownload.Flags;
20 import com.google.android.libraries.mobiledatadownload.file.transforms.TransformProtos;
21 import com.google.android.libraries.mobiledatadownload.internal.logging.LogUtil;
22 import com.google.android.libraries.mobiledatadownload.internal.util.FileGroupUtil;
23 import com.google.mobiledatadownload.TransformProto.Transforms;
24 import com.google.mobiledatadownload.internal.MetadataProto.DataFile;
25 import com.google.mobiledatadownload.internal.MetadataProto.DataFile.ChecksumType;
26 import com.google.mobiledatadownload.internal.MetadataProto.DataFileGroupInternal;
27 import com.google.mobiledatadownload.internal.MetadataProto.DataFileGroupInternal.AllowedReaders;
28 import com.google.mobiledatadownload.internal.MetadataProto.DeltaFile;
29 import com.google.mobiledatadownload.internal.MetadataProto.DeltaFile.DiffDecoder;
30 import com.google.mobiledatadownload.internal.MetadataProto.DownloadConditions.DeviceNetworkPolicy;
31 
32 /** DataFileGroupValidator - validates the passed in DataFileGroup */
33 public class DataFileGroupValidator {
34 
35   private static final String TAG = "DataFileGroupValidator";
36 
37   /**
38    * Checks if the data file group that we received is a valid group. A group is valid if all the
39    * data required to download it is present. If any field that is not required is set, it will be
40    * ignored.
41    *
42    * <p>This is just a sanity check. For example, it doesn't check if the file is present at the
43    * given location.
44    *
45    * @param dataFileGroup The data file group on which sanity check needs to be done.
46    * @return true if the group is valid.
47    */
48   // TODO(b/124072754): Change to package private once all code is refactored.
isValidGroup( DataFileGroupInternal dataFileGroup, Context context, Flags flags)49   public static boolean isValidGroup(
50       DataFileGroupInternal dataFileGroup, Context context, Flags flags) {
51     // Check if the group name is empty.
52     if (dataFileGroup.getGroupName().isEmpty()) {
53       LogUtil.e("%s Group name missing in added group", TAG);
54       return false;
55     }
56 
57     if (dataFileGroup.getGroupName().contains(MddConstants.SPLIT_CHAR)) {
58       LogUtil.e("%s Group name = %s contains '|'", TAG, dataFileGroup.getGroupName());
59       return false;
60     }
61 
62     if (dataFileGroup.getOwnerPackage().contains(MddConstants.SPLIT_CHAR)) {
63       LogUtil.e("%s Owner package = %s contains '|'", TAG, dataFileGroup.getOwnerPackage());
64       return false;
65     }
66 
67     // Check if any file is missing any of the required fields.
68     for (DataFile dataFile : dataFileGroup.getFileList()) {
69       if (dataFile.getFileId().isEmpty()
70           || dataFile.getFileId().contains(MddConstants.SPLIT_CHAR)
71           || !isValidDataFile(dataFile)) {
72         LogUtil.e(
73             "%s File details missing in added group = %s, file id = %s",
74             TAG, dataFileGroup.getGroupName(), dataFile.getFileId());
75         return false;
76       }
77       if (!hasValidTransforms(dataFileGroup, dataFile, flags)) {
78         return false;
79       }
80       if (!hasValidDeltaFiles(dataFileGroup.getGroupName(), dataFile)) {
81         return false;
82       }
83       // Check if sideloaded files are present and if sideloading is enabled
84       if (FileGroupUtil.isSideloadedFile(dataFile) && !flags.enableSideloading()) {
85         LogUtil.e(
86             "%s File detected as sideloaded, but sideloading is not enabled. group = %s, file id ="
87                 + " %s, file url = %s",
88             TAG, dataFileGroup.getGroupName(), dataFile.getFileId(), dataFile.getUrlToDownload());
89         return false;
90       }
91     }
92 
93     // Check if a file id is repeated.
94     for (int i = 0; i < dataFileGroup.getFileCount(); i++) {
95       for (int j = i + 1; j < dataFileGroup.getFileCount(); j++) {
96         if (dataFileGroup.getFile(i).getFileId().equals(dataFileGroup.getFile(j).getFileId())) {
97           LogUtil.e(
98               "%s Repeated file id in added group = %s, file id = %s",
99               TAG, dataFileGroup.getGroupName(), dataFileGroup.getFile(i).getFileId());
100           return false;
101         }
102       }
103     }
104 
105     if (dataFileGroup
106             .getDownloadConditions()
107             .getDeviceNetworkPolicy()
108             .equals(DeviceNetworkPolicy.DOWNLOAD_FIRST_ON_WIFI_THEN_ON_ANY_NETWORK)
109         && dataFileGroup.getDownloadConditions().getDownloadFirstOnWifiPeriodSecs() <= 0) {
110       LogUtil.e(
111           "%s For DOWNLOAD_FIRST_ON_WIFI_THEN_ON_ANY_NETWORK policy, "
112               + "the download_first_on_wifi_period_secs must be > 0",
113           TAG);
114       return false;
115     }
116 
117     if (!Migrations.isMigratedToNewFileKey(context)
118         && dataFileGroup.getAllowedReadersEnum().equals(AllowedReaders.ALL_APPS)) {
119       LogUtil.e(
120           "%s For AllowedReaders ALL_APPS policy, the device should be migrated to new key", TAG);
121       return false;
122     }
123 
124     return true;
125   }
126 
hasValidTransforms( DataFileGroupInternal dataFileGroup, DataFile dataFile, Flags flags)127   private static boolean hasValidTransforms(
128       DataFileGroupInternal dataFileGroup, DataFile dataFile, Flags flags) {
129     // Verify for Download transforms
130     if (dataFile.hasDownloadTransforms()) {
131       if (!isValidTransforms(dataFile.getDownloadTransforms())) {
132         return false;
133       }
134       if (!hasValidZipDownloadTransform(dataFileGroup.getGroupName(), dataFile, flags)) {
135         return false;
136       }
137       if (dataFile.getChecksumType() != ChecksumType.NONE
138           && !dataFile.hasDownloadedFileChecksum()) {
139         LogUtil.e(
140             "Download checksum must be provided. Group = %s, file id = %s",
141             dataFileGroup.getGroupName(), dataFile.getFileId());
142         return false;
143       }
144     }
145     // Verify for Read transforms
146     if (dataFile.hasReadTransforms() && !isValidTransforms(dataFile.getReadTransforms())) {
147       return false;
148     }
149     return true;
150   }
151 
hasValidZipDownloadTransform( String groupName, DataFile dataFile, Flags flags)152   private static boolean hasValidZipDownloadTransform(
153       String groupName, DataFile dataFile, Flags flags) {
154     if (FileGroupUtil.hasZipDownloadTransform(dataFile)) {
155       if (!flags.enableZipFolder()) {
156         LogUtil.e(
157             "Feature enableZipFolder is not enabled. Group = %s, file id = %s",
158             groupName, dataFile.getFileId());
159         return false;
160       }
161       if (dataFile.getDownloadTransforms().getTransformCount() > 1) {
162         LogUtil.e(
163             "Download zip folder transform cannot not be applied with other transforms. Group ="
164                 + " %s, file id = %s",
165             groupName, dataFile.getFileId());
166         return false;
167       }
168       if (!"*".equals(dataFile.getDownloadTransforms().getTransform(0).getZip().getTarget())) {
169         LogUtil.e(
170             "Download zip folder transform can only have * as target. Group = %s, file id = %s",
171             groupName, dataFile.getFileId());
172         return false;
173       }
174     }
175     return true;
176   }
177 
isValidTransforms(Transforms transforms)178   private static boolean isValidTransforms(Transforms transforms) {
179     try {
180       TransformProtos.toEncodedFragment(transforms);
181       return true;
182     } catch (IllegalArgumentException illegalArgumentException) {
183       LogUtil.e(illegalArgumentException, "Invalid transform specification");
184       return false;
185     }
186   }
187 
hasValidDeltaFiles(String groupName, DataFile dataFile)188   private static boolean hasValidDeltaFiles(String groupName, DataFile dataFile) {
189     for (DeltaFile deltaFile : dataFile.getDeltaFileList()) {
190       if (!isValidDeltaFile(deltaFile)) {
191         LogUtil.e(
192             "%s Delta File of Datafile details missing in added group = %s, file id = %s"
193                 + ", delta file UrlToDownload = %s.",
194             TAG, groupName, dataFile.getFileId(), deltaFile.getUrlToDownload());
195         return false;
196       }
197     }
198     return true;
199   }
200 
isValidDataFile(DataFile dataFile)201   private static boolean isValidDataFile(DataFile dataFile) {
202     // When a data file has zip transform, downloaded file checksum is used for identifying the data
203     // file; otherwise, checksum is used.
204     boolean hasNonEmptyChecksum;
205     if (FileGroupUtil.hasZipDownloadTransform(dataFile)) {
206       hasNonEmptyChecksum =
207           dataFile.hasDownloadedFileChecksum() && !dataFile.getDownloadedFileChecksum().isEmpty();
208     } else {
209       hasNonEmptyChecksum = dataFile.hasChecksum() && !dataFile.getChecksum().isEmpty();
210     }
211 
212     boolean validChecksum;
213     switch (dataFile.getChecksumType()) {
214         // Default stands for SHA1.
215       case DEFAULT:
216         validChecksum = hasNonEmptyChecksum;
217         break;
218       case NONE:
219         validChecksum = !hasNonEmptyChecksum;
220         break;
221       default:
222         validChecksum = false;
223     }
224 
225     // File checksum is not needed for zip folder download transforms.
226     validChecksum |= FileGroupUtil.hasZipDownloadTransform(dataFile) && !hasNonEmptyChecksum;
227 
228     boolean validAndroidSharingConfig =
229         dataFile.getAndroidSharingChecksumType() == DataFile.AndroidSharingChecksumType.SHA2_256
230             ? !dataFile.getAndroidSharingChecksum().isEmpty()
231             : true;
232 
233     return !dataFile.getUrlToDownload().isEmpty()
234         && !dataFile.getUrlToDownload().contains(MddConstants.SPLIT_CHAR)
235         && dataFile.getByteSize() >= 0
236         && validChecksum
237         && validAndroidSharingConfig
238         && !FileGroupUtil.getFileChecksum(dataFile).contains(MddConstants.SPLIT_CHAR);
239   }
240 
isValidDeltaFile(DeltaFile deltaFile)241   private static boolean isValidDeltaFile(DeltaFile deltaFile) {
242     return !deltaFile.getUrlToDownload().isEmpty()
243         && !deltaFile.getUrlToDownload().contains(MddConstants.SPLIT_CHAR)
244         && deltaFile.hasByteSize()
245         && deltaFile.getByteSize() >= 0
246         && !deltaFile.getChecksum().isEmpty()
247         && !deltaFile.getChecksum().contains(MddConstants.SPLIT_CHAR)
248         && deltaFile.hasDiffDecoder()
249         && !deltaFile.getDiffDecoder().equals(DiffDecoder.UNSPECIFIED)
250         && deltaFile.hasBaseFile()
251         && !deltaFile.getBaseFile().getChecksum().isEmpty()
252         && !deltaFile.getBaseFile().getChecksum().contains(MddConstants.SPLIT_CHAR);
253   }
254 }
255