1*ff35212dScey /* 2*ff35212dScey * Copyright (C) 2020 Google LLC 3*ff35212dScey * 4*ff35212dScey * Licensed under the Apache License, Version 2.0 (the "License"); 5*ff35212dScey * you may not use this file except in compliance with the License. 6*ff35212dScey * You may obtain a copy of the License at 7*ff35212dScey * 8*ff35212dScey * http://www.apache.org/licenses/LICENSE-2.0 9*ff35212dScey * 10*ff35212dScey * Unless required by applicable law or agreed to in writing, software 11*ff35212dScey * distributed under the License is distributed on an "AS IS" BASIS, 12*ff35212dScey * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13*ff35212dScey * See the License for the specific language governing permissions and 14*ff35212dScey * limitations under the License. 15*ff35212dScey */ 16*ff35212dScey package com.google.carrier; 17*ff35212dScey 18*ff35212dScey import com.google.common.base.Preconditions; 19*ff35212dScey import com.google.carrier.CarrierConfig; 20*ff35212dScey import com.google.carrier.CarrierId; 21*ff35212dScey import com.google.carrier.CarrierMap; 22*ff35212dScey import com.google.carrier.CarrierSettings; 23*ff35212dScey import com.google.carrier.MultiCarrierSettings; 24*ff35212dScey import com.google.carrier.VendorConfigClient; 25*ff35212dScey import com.google.carrier.VendorConfigs; 26*ff35212dScey import java.util.ArrayList; 27*ff35212dScey import java.util.Collections; 28*ff35212dScey import java.util.Comparator; 29*ff35212dScey import java.util.List; 30*ff35212dScey 31*ff35212dScey /** Utility methods */ 32*ff35212dScey public class CarrierProtoUtils { 33*ff35212dScey 34*ff35212dScey /** 35*ff35212dScey * The base of release version. 36*ff35212dScey * 37*ff35212dScey * A valid release version must be a multiple of the base. 38*ff35212dScey * A file version must be smaller than the base - otherwise the version schema is broken. 39*ff35212dScey */ 40*ff35212dScey public static final long RELEASE_VERSION_BASE = 1000000000L; 41*ff35212dScey 42*ff35212dScey /** 43*ff35212dScey * Bump the version by an offset, to differentiate release/branch. 44*ff35212dScey * 45*ff35212dScey * The input version must be smaller than RELEASE_VERSION_BASE, and the offset must be a 46*ff35212dScey * multiple of RELEASE_VERSION_BASE. 47*ff35212dScey */ addVersionOffset(long version, long offset)48*ff35212dScey public static long addVersionOffset(long version, long offset) { 49*ff35212dScey 50*ff35212dScey Preconditions.checkArgument(version < RELEASE_VERSION_BASE, 51*ff35212dScey "OMG, by design the file version should be smaller than %s, but is %s.", 52*ff35212dScey RELEASE_VERSION_BASE, version); 53*ff35212dScey Preconditions.checkArgument((offset % RELEASE_VERSION_BASE) == 0, 54*ff35212dScey "The offset %s is not a multiple of %s", 55*ff35212dScey offset, RELEASE_VERSION_BASE); 56*ff35212dScey 57*ff35212dScey return version + offset; 58*ff35212dScey } 59*ff35212dScey 60*ff35212dScey /** 61*ff35212dScey * Merge configs fields in {@code patch} into {@code base}; configs sorted by key. 62*ff35212dScey * See {@link mergeCarrierConfig(CarrierConfig, CarrierConfig.Builder)}. 63*ff35212dScey */ 64*ff35212dScey public static CarrierConfig mergeCarrierConfig(CarrierConfig base, CarrierConfig patch) { 65*ff35212dScey Preconditions.checkNotNull(patch); 66*ff35212dScey 67*ff35212dScey return mergeCarrierConfig(base, patch.toBuilder()); 68*ff35212dScey } 69*ff35212dScey 70*ff35212dScey /** 71*ff35212dScey * Merge configs fields in {@code patch} into {@code base}; configs sorted by key. 72*ff35212dScey * 73*ff35212dScey * <p>Sorting is desired because: 74*ff35212dScey * 75*ff35212dScey * <ul> 76*ff35212dScey * <li>1. The order doesn't matter for the consumer so sorting will not cause behavior change; 77*ff35212dScey * <li>2. The output proto is serilized to text for human review; undeterministic order can 78*ff35212dScey * confuse reviewers as they cannot easily tell if it's re-ordering or actual data change. 79*ff35212dScey * </ul> 80*ff35212dScey */ 81*ff35212dScey public static CarrierConfig mergeCarrierConfig(CarrierConfig base, CarrierConfig.Builder patch) { 82*ff35212dScey Preconditions.checkNotNull(base); 83*ff35212dScey Preconditions.checkNotNull(patch); 84*ff35212dScey 85*ff35212dScey CarrierConfig.Builder baseBuilder = base.toBuilder(); 86*ff35212dScey 87*ff35212dScey // Traverse each config in patch 88*ff35212dScey for (int i = 0; i < patch.getConfigCount(); i++) { 89*ff35212dScey CarrierConfig.Config patchConfig = patch.getConfig(i); 90*ff35212dScey 91*ff35212dScey // Try to find an config in base with the same key as the config from patch 92*ff35212dScey int j = 0; 93*ff35212dScey for (j = 0; j < baseBuilder.getConfigCount(); j++) { 94*ff35212dScey if (baseBuilder.getConfig(j).getKey().equals(patchConfig.getKey())) { 95*ff35212dScey break; 96*ff35212dScey } 97*ff35212dScey } 98*ff35212dScey 99*ff35212dScey // If match found, replace base with patch; otherwise append patch into base. 100*ff35212dScey if (j < baseBuilder.getConfigCount()) { 101*ff35212dScey baseBuilder.setConfig(j, patchConfig); 102*ff35212dScey } else { 103*ff35212dScey baseBuilder.addConfig(patchConfig); 104*ff35212dScey } 105*ff35212dScey } 106*ff35212dScey 107*ff35212dScey // Sort configs in baseBuilder by key 108*ff35212dScey List<CarrierConfig.Config> configs = new ArrayList<>(baseBuilder.getConfigList()); 109*ff35212dScey Collections.sort(configs, Comparator.comparing(CarrierConfig.Config::getKey)); 110*ff35212dScey baseBuilder.clearConfig(); 111*ff35212dScey baseBuilder.addAllConfig(configs); 112*ff35212dScey 113*ff35212dScey return baseBuilder.build(); 114*ff35212dScey } 115*ff35212dScey 116*ff35212dScey /** 117*ff35212dScey * Find a carrier's CarrierSettings by canonical_name from a MultiCarrierSettings. 118*ff35212dScey * 119*ff35212dScey * <p>Return null if not found. 120*ff35212dScey */ 121*ff35212dScey public static CarrierSettings findCarrierSettingsByCanonicalName( 122*ff35212dScey MultiCarrierSettings mcs, String name) { 123*ff35212dScey 124*ff35212dScey Preconditions.checkNotNull(mcs); 125*ff35212dScey Preconditions.checkNotNull(name); 126*ff35212dScey 127*ff35212dScey for (int i = 0; i < mcs.getSettingCount(); i++) { 128*ff35212dScey CarrierSettings cs = mcs.getSetting(i); 129*ff35212dScey if (cs.getCanonicalName().equals(name)) { 130*ff35212dScey return cs; 131*ff35212dScey } 132*ff35212dScey } 133*ff35212dScey 134*ff35212dScey return null; 135*ff35212dScey } 136*ff35212dScey 137*ff35212dScey /** Apply device overly to a carrier setting */ 138*ff35212dScey public static CarrierSettings.Builder applyDeviceOverlayToCarrierSettings( 139*ff35212dScey MultiCarrierSettings allOverlay, CarrierSettings.Builder base) { 140*ff35212dScey return applyDeviceOverlayToCarrierSettings(allOverlay, base.build()); 141*ff35212dScey } 142*ff35212dScey 143*ff35212dScey /** Apply device overly to a carrier setting */ 144*ff35212dScey public static CarrierSettings.Builder applyDeviceOverlayToCarrierSettings( 145*ff35212dScey MultiCarrierSettings allOverlay, CarrierSettings base) { 146*ff35212dScey 147*ff35212dScey Preconditions.checkNotNull(allOverlay); 148*ff35212dScey Preconditions.checkNotNull(base); 149*ff35212dScey 150*ff35212dScey // Find overlay of the base carrier. If not found, just return base. 151*ff35212dScey CarrierSettings overlay = 152*ff35212dScey findCarrierSettingsByCanonicalName(allOverlay, base.getCanonicalName()); 153*ff35212dScey if (overlay == null) { 154*ff35212dScey return base.toBuilder(); 155*ff35212dScey } 156*ff35212dScey 157*ff35212dScey CarrierSettings.Builder resultBuilder = base.toBuilder(); 158*ff35212dScey // Add version number of base settings and overlay, so the result version number 159*ff35212dScey // monotonically increases. 160*ff35212dScey resultBuilder.setVersion(base.getVersion() + overlay.getVersion()); 161*ff35212dScey // Merge overlay settings into base settings 162*ff35212dScey resultBuilder.setConfigs(mergeCarrierConfig(resultBuilder.getConfigs(), overlay.getConfigs())); 163*ff35212dScey // Replace base apns with overlay apns (if not empty) 164*ff35212dScey if (overlay.getApns().getApnCount() > 0) { 165*ff35212dScey resultBuilder.setApns(overlay.getApns()); 166*ff35212dScey } 167*ff35212dScey // Merge the overlay vendor configuration into base vendor configuration 168*ff35212dScey // Can be cutomized 169*ff35212dScey return resultBuilder; 170*ff35212dScey } 171*ff35212dScey 172*ff35212dScey /** Apply device overly to multiple carriers setting */ 173*ff35212dScey public static MultiCarrierSettings applyDeviceOverlayToMultiCarrierSettings( 174*ff35212dScey MultiCarrierSettings overlay, MultiCarrierSettings base) { 175*ff35212dScey 176*ff35212dScey Preconditions.checkNotNull(overlay); 177*ff35212dScey Preconditions.checkNotNull(base); 178*ff35212dScey 179*ff35212dScey MultiCarrierSettings.Builder resultBuilder = base.toBuilder().clearSetting(); 180*ff35212dScey long version = 0L; 181*ff35212dScey 182*ff35212dScey for (CarrierSettings cs : base.getSettingList()) { 183*ff35212dScey // Apply overlay and put overlayed carrier setting back 184*ff35212dScey CarrierSettings.Builder merged = applyDeviceOverlayToCarrierSettings(overlay, cs); 185*ff35212dScey resultBuilder.addSetting(merged); 186*ff35212dScey // The top-level version number is the sum of all version numbers of settings 187*ff35212dScey version += merged.getVersion(); 188*ff35212dScey } 189*ff35212dScey resultBuilder.setVersion(version); 190*ff35212dScey 191*ff35212dScey return resultBuilder.build(); 192*ff35212dScey } 193*ff35212dScey 194*ff35212dScey /** 195*ff35212dScey * Sort a list of CarrierMap with single CarrierId. 196*ff35212dScey * 197*ff35212dScey * <p>Precondition: no duplication in input list 198*ff35212dScey * 199*ff35212dScey * <p>Order by: 200*ff35212dScey * 201*ff35212dScey * <ul> 202*ff35212dScey * <li>mcc_mnc 203*ff35212dScey * <li>(for the same mcc_mnc) any mvno_data comes before MVNODATA_NOT_SET (Preconditon: there is 204*ff35212dScey * only one entry with MVNODATA_NOT_SET per mcc_mnc otherwise they're duplicated) 205*ff35212dScey * <li>(for MVNOs of the same mcc_mnc) mvno_data case + value as string 206*ff35212dScey * </ul> 207*ff35212dScey */ 208*ff35212dScey public static void sortCarrierMapEntries(List<CarrierMap> list) { 209*ff35212dScey final Comparator<CarrierMap> byMccMnc = 210*ff35212dScey Comparator.comparing( 211*ff35212dScey (cm) -> { 212*ff35212dScey return cm.getCarrierId(0).getMccMnc(); 213*ff35212dScey }); 214*ff35212dScey final Comparator<CarrierMap> mvnoFirst = 215*ff35212dScey Comparator.comparingInt( 216*ff35212dScey (cm) -> { 217*ff35212dScey switch (cm.getCarrierId(0).getMvnoDataCase()) { 218*ff35212dScey case MVNODATA_NOT_SET: 219*ff35212dScey return 1; 220*ff35212dScey default: 221*ff35212dScey return 0; 222*ff35212dScey } 223*ff35212dScey }); 224*ff35212dScey final Comparator<CarrierMap> byMvnoDataCaseValue = 225*ff35212dScey Comparator.comparing( 226*ff35212dScey (cm) -> { 227*ff35212dScey final CarrierId cid = cm.getCarrierId(0); 228*ff35212dScey switch (cid.getMvnoDataCase()) { 229*ff35212dScey case GID1: 230*ff35212dScey return "GID1=" + cid.getGid1(); 231*ff35212dScey case IMSI: 232*ff35212dScey return "IMSI=" + cid.getImsi(); 233*ff35212dScey case SPN: 234*ff35212dScey return "SPN=" + cid.getSpn(); 235*ff35212dScey case MVNODATA_NOT_SET: 236*ff35212dScey throw new AssertionError("MNO should not be compared here but in `mvnoFirst`"); 237*ff35212dScey } 238*ff35212dScey throw new AssertionError("uncaught case " + cid.getMvnoDataCase()); 239*ff35212dScey }); 240*ff35212dScey Collections.sort(list, byMccMnc.thenComparing(mvnoFirst).thenComparing(byMvnoDataCaseValue)); 241*ff35212dScey } 242*ff35212dScey } 243