summaryrefslogtreecommitdiff
path: root/java/com/google/android/libraries/mobiledatadownload/internal/DataFileGroupValidator.java
blob: 3d49157d0ec79b9ea91440f76230a1eabf688672 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
/*
 * Copyright 2022 Google LLC
 *
 * 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 com.google.android.libraries.mobiledatadownload.internal;

import android.content.Context;
import com.google.android.libraries.mobiledatadownload.Flags;
import com.google.android.libraries.mobiledatadownload.file.transforms.TransformProtos;
import com.google.android.libraries.mobiledatadownload.internal.logging.LogUtil;
import com.google.android.libraries.mobiledatadownload.internal.util.FileGroupUtil;
import com.google.mobiledatadownload.TransformProto.Transforms;
import com.google.mobiledatadownload.internal.MetadataProto.DataFile;
import com.google.mobiledatadownload.internal.MetadataProto.DataFile.ChecksumType;
import com.google.mobiledatadownload.internal.MetadataProto.DataFileGroupInternal;
import com.google.mobiledatadownload.internal.MetadataProto.DataFileGroupInternal.AllowedReaders;
import com.google.mobiledatadownload.internal.MetadataProto.DeltaFile;
import com.google.mobiledatadownload.internal.MetadataProto.DeltaFile.DiffDecoder;
import com.google.mobiledatadownload.internal.MetadataProto.DownloadConditions.DeviceNetworkPolicy;

/** DataFileGroupValidator - validates the passed in DataFileGroup */
public class DataFileGroupValidator {

  private static final String TAG = "DataFileGroupValidator";

  /**
   * Checks if the data file group that we received is a valid group. A group is valid if all the
   * data required to download it is present. If any field that is not required is set, it will be
   * ignored.
   *
   * <p>This is just a sanity check. For example, it doesn't check if the file is present at the
   * given location.
   *
   * @param dataFileGroup The data file group on which sanity check needs to be done.
   * @return true if the group is valid.
   */
  // TODO(b/124072754): Change to package private once all code is refactored.
  public static boolean isValidGroup(
      DataFileGroupInternal dataFileGroup, Context context, Flags flags) {
    // Check if the group name is empty.
    if (dataFileGroup.getGroupName().isEmpty()) {
      LogUtil.e("%s Group name missing in added group", TAG);
      return false;
    }

    if (dataFileGroup.getGroupName().contains(MddConstants.SPLIT_CHAR)) {
      LogUtil.e("%s Group name = %s contains '|'", TAG, dataFileGroup.getGroupName());
      return false;
    }

    if (dataFileGroup.getOwnerPackage().contains(MddConstants.SPLIT_CHAR)) {
      LogUtil.e("%s Owner package = %s contains '|'", TAG, dataFileGroup.getOwnerPackage());
      return false;
    }

    // Check if any file is missing any of the required fields.
    for (DataFile dataFile : dataFileGroup.getFileList()) {
      if (dataFile.getFileId().isEmpty()
          || dataFile.getFileId().contains(MddConstants.SPLIT_CHAR)
          || !isValidDataFile(dataFile)) {
        LogUtil.e(
            "%s File details missing in added group = %s, file id = %s",
            TAG, dataFileGroup.getGroupName(), dataFile.getFileId());
        return false;
      }
      if (!hasValidTransforms(dataFileGroup, dataFile, flags)) {
        return false;
      }
      if (!hasValidDeltaFiles(dataFileGroup.getGroupName(), dataFile)) {
        return false;
      }
      // Check if sideloaded files are present and if sideloading is enabled
      if (FileGroupUtil.isSideloadedFile(dataFile) && !flags.enableSideloading()) {
        LogUtil.e(
            "%s File detected as sideloaded, but sideloading is not enabled. group = %s, file id ="
                + " %s, file url = %s",
            TAG, dataFileGroup.getGroupName(), dataFile.getFileId(), dataFile.getUrlToDownload());
        return false;
      }
    }

    // Check if a file id is repeated.
    for (int i = 0; i < dataFileGroup.getFileCount(); i++) {
      for (int j = i + 1; j < dataFileGroup.getFileCount(); j++) {
        if (dataFileGroup.getFile(i).getFileId().equals(dataFileGroup.getFile(j).getFileId())) {
          LogUtil.e(
              "%s Repeated file id in added group = %s, file id = %s",
              TAG, dataFileGroup.getGroupName(), dataFileGroup.getFile(i).getFileId());
          return false;
        }
      }
    }

    if (dataFileGroup
            .getDownloadConditions()
            .getDeviceNetworkPolicy()
            .equals(DeviceNetworkPolicy.DOWNLOAD_FIRST_ON_WIFI_THEN_ON_ANY_NETWORK)
        && dataFileGroup.getDownloadConditions().getDownloadFirstOnWifiPeriodSecs() <= 0) {
      LogUtil.e(
          "%s For DOWNLOAD_FIRST_ON_WIFI_THEN_ON_ANY_NETWORK policy, "
              + "the download_first_on_wifi_period_secs must be > 0",
          TAG);
      return false;
    }

    if (!Migrations.isMigratedToNewFileKey(context)
        && dataFileGroup.getAllowedReadersEnum().equals(AllowedReaders.ALL_APPS)) {
      LogUtil.e(
          "%s For AllowedReaders ALL_APPS policy, the device should be migrated to new key", TAG);
      return false;
    }

    return true;
  }

  private static boolean hasValidTransforms(
      DataFileGroupInternal dataFileGroup, DataFile dataFile, Flags flags) {
    // Verify for Download transforms
    if (dataFile.hasDownloadTransforms()) {
      if (!isValidTransforms(dataFile.getDownloadTransforms())) {
        return false;
      }
      if (!hasValidZipDownloadTransform(dataFileGroup.getGroupName(), dataFile, flags)) {
        return false;
      }
      if (dataFile.getChecksumType() != ChecksumType.NONE
          && !dataFile.hasDownloadedFileChecksum()) {
        LogUtil.e(
            "Download checksum must be provided. Group = %s, file id = %s",
            dataFileGroup.getGroupName(), dataFile.getFileId());
        return false;
      }
    }
    // Verify for Read transforms
    if (dataFile.hasReadTransforms() && !isValidTransforms(dataFile.getReadTransforms())) {
      return false;
    }
    return true;
  }

  private static boolean hasValidZipDownloadTransform(
      String groupName, DataFile dataFile, Flags flags) {
    if (FileGroupUtil.hasZipDownloadTransform(dataFile)) {
      if (!flags.enableZipFolder()) {
        LogUtil.e(
            "Feature enableZipFolder is not enabled. Group = %s, file id = %s",
            groupName, dataFile.getFileId());
        return false;
      }
      if (dataFile.getDownloadTransforms().getTransformCount() > 1) {
        LogUtil.e(
            "Download zip folder transform cannot not be applied with other transforms. Group ="
                + " %s, file id = %s",
            groupName, dataFile.getFileId());
        return false;
      }
      if (!"*".equals(dataFile.getDownloadTransforms().getTransform(0).getZip().getTarget())) {
        LogUtil.e(
            "Download zip folder transform can only have * as target. Group = %s, file id = %s",
            groupName, dataFile.getFileId());
        return false;
      }
    }
    return true;
  }

  private static boolean isValidTransforms(Transforms transforms) {
    try {
      TransformProtos.toEncodedFragment(transforms);
      return true;
    } catch (IllegalArgumentException illegalArgumentException) {
      LogUtil.e(illegalArgumentException, "Invalid transform specification");
      return false;
    }
  }

  private static boolean hasValidDeltaFiles(String groupName, DataFile dataFile) {
    for (DeltaFile deltaFile : dataFile.getDeltaFileList()) {
      if (!isValidDeltaFile(deltaFile)) {
        LogUtil.e(
            "%s Delta File of Datafile details missing in added group = %s, file id = %s"
                + ", delta file UrlToDownload = %s.",
            TAG, groupName, dataFile.getFileId(), deltaFile.getUrlToDownload());
        return false;
      }
    }
    return true;
  }

  private static boolean isValidDataFile(DataFile dataFile) {
    // When a data file has zip transform, downloaded file checksum is used for identifying the data
    // file; otherwise, checksum is used.
    boolean hasNonEmptyChecksum;
    if (FileGroupUtil.hasZipDownloadTransform(dataFile)) {
      hasNonEmptyChecksum =
          dataFile.hasDownloadedFileChecksum() && !dataFile.getDownloadedFileChecksum().isEmpty();
    } else {
      hasNonEmptyChecksum = dataFile.hasChecksum() && !dataFile.getChecksum().isEmpty();
    }

    boolean validChecksum;
    switch (dataFile.getChecksumType()) {
        // Default stands for SHA1.
      case DEFAULT:
        validChecksum = hasNonEmptyChecksum;
        break;
      case NONE:
        validChecksum = !hasNonEmptyChecksum;
        break;
      default:
        validChecksum = false;
    }

    // File checksum is not needed for zip folder download transforms.
    validChecksum |= FileGroupUtil.hasZipDownloadTransform(dataFile) && !hasNonEmptyChecksum;

    boolean validAndroidSharingConfig =
        dataFile.getAndroidSharingChecksumType() == DataFile.AndroidSharingChecksumType.SHA2_256
            ? !dataFile.getAndroidSharingChecksum().isEmpty()
            : true;

    return !dataFile.getUrlToDownload().isEmpty()
        && !dataFile.getUrlToDownload().contains(MddConstants.SPLIT_CHAR)
        && dataFile.getByteSize() >= 0
        && validChecksum
        && validAndroidSharingConfig
        && !FileGroupUtil.getFileChecksum(dataFile).contains(MddConstants.SPLIT_CHAR);
  }

  private static boolean isValidDeltaFile(DeltaFile deltaFile) {
    return !deltaFile.getUrlToDownload().isEmpty()
        && !deltaFile.getUrlToDownload().contains(MddConstants.SPLIT_CHAR)
        && deltaFile.hasByteSize()
        && deltaFile.getByteSize() >= 0
        && !deltaFile.getChecksum().isEmpty()
        && !deltaFile.getChecksum().contains(MddConstants.SPLIT_CHAR)
        && deltaFile.hasDiffDecoder()
        && !deltaFile.getDiffDecoder().equals(DiffDecoder.UNSPECIFIED)
        && deltaFile.hasBaseFile()
        && !deltaFile.getBaseFile().getChecksum().isEmpty()
        && !deltaFile.getBaseFile().getChecksum().contains(MddConstants.SPLIT_CHAR);
  }
}