1*ff35212dScey# Copyright (C) 2020 Google LLC 2*ff35212dScey# 3*ff35212dScey# Licensed under the Apache License, Version 2.0 (the "License"); 4*ff35212dScey# you may not use this file except in compliance with the License. 5*ff35212dScey# You may obtain a copy of the License at 6*ff35212dScey# 7*ff35212dScey# http://www.apache.org/licenses/LICENSE-2.0 8*ff35212dScey# 9*ff35212dScey# Unless required by applicable law or agreed to in writing, software 10*ff35212dScey# distributed under the License is distributed on an "AS IS" BASIS, 11*ff35212dScey# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12*ff35212dScey# See the License for the specific language governing permissions and 13*ff35212dScey# limitations under the License. 14*ff35212dScey 15*ff35212dSceyr"""Read APN conf xml file and output an textpb. 16*ff35212dScey 17*ff35212dSceyHow to run: 18*ff35212dScey 19*ff35212dSceyupdate_apn.par --apn_file=./apns-full-conf.xml \ 20*ff35212dScey--data_dir=./data --out_file=/tmpapns.textpb 21*ff35212dScey""" 22*ff35212dScey 23*ff35212dSceyfrom __future__ import absolute_import 24*ff35212dSceyfrom __future__ import division 25*ff35212dSceyfrom __future__ import print_function 26*ff35212dSceyimport argparse 27*ff35212dSceyimport collections 28*ff35212dSceyfrom xml.dom import minidom 29*ff35212dSceyfrom google.protobuf import text_format 30*ff35212dScey 31*ff35212dSceyfrom proto import carrier_list_pb2 32*ff35212dSceyfrom proto import carrier_settings_pb2 33*ff35212dSceyfrom src import carrierId_pb2 34*ff35212dScey 35*ff35212dSceyparser = argparse.ArgumentParser() 36*ff35212dSceyparser.add_argument( 37*ff35212dScey '--apn_file', default='./apns-full-conf.xml', help='Path to APN xml file') 38*ff35212dSceyparser.add_argument( 39*ff35212dScey '--data_dir', default='./data', help='Folder path for CarrierSettings data') 40*ff35212dSceyparser.add_argument( 41*ff35212dScey '--support_carrier_id', action='store_true', help='To support using carrier_id in apns.xml') 42*ff35212dSceyparser.add_argument( 43*ff35212dScey '--aosp_carrier_list', default='packages/providers/TelephonyProvider/assets/latest_carrier_id/carrier_list.textpb', help='Resource file path to the AOSP carrier list file') 44*ff35212dSceyparser.add_argument( 45*ff35212dScey '--out_file', default='./tmpapns.textpb', help='Temp APN file') 46*ff35212dSceyFLAGS = parser.parse_args() 47*ff35212dScey 48*ff35212dSceyCARRIER_LISTS = ['tier1_carriers.textpb', 'other_carriers.textpb'] 49*ff35212dScey 50*ff35212dScey 51*ff35212dSceydef to_string(cid): 52*ff35212dScey """Return a string for CarrierId.""" 53*ff35212dScey ret = cid.mcc_mnc 54*ff35212dScey if cid.HasField('spn'): 55*ff35212dScey ret += 'SPN=' + cid.spn.upper() 56*ff35212dScey if cid.HasField('imsi'): 57*ff35212dScey ret += 'IMSI=' + cid.imsi.upper() 58*ff35212dScey if cid.HasField('gid1'): 59*ff35212dScey ret += 'GID1=' + cid.gid1.upper() 60*ff35212dScey return ret 61*ff35212dScey 62*ff35212dScey 63*ff35212dSceydef get_cname(cid, known_carriers): 64*ff35212dScey """Return a canonical name based on cid and known_carriers. 65*ff35212dScey 66*ff35212dScey If found a match in known_carriers, return it. Otherwise generate a new one 67*ff35212dScey by concating the values. 68*ff35212dScey 69*ff35212dScey Args: 70*ff35212dScey cid: proto of CarrierId 71*ff35212dScey known_carriers: mapping from mccmnc and possible mvno data to canonical name 72*ff35212dScey 73*ff35212dScey Returns: 74*ff35212dScey string for canonical name, like verizon_us or 27402 75*ff35212dScey """ 76*ff35212dScey return get_known_cname(to_string(cid), known_carriers) 77*ff35212dScey 78*ff35212dSceydef get_known_cname(name, known_carriers): 79*ff35212dScey if name in known_carriers: 80*ff35212dScey return known_carriers[name] 81*ff35212dScey else: 82*ff35212dScey return name 83*ff35212dScey 84*ff35212dSceydef get_knowncarriers(files): 85*ff35212dScey """Create a mapping from mccmnc and possible mvno data to canonical name. 86*ff35212dScey 87*ff35212dScey Args: 88*ff35212dScey files: list of paths to carrier list textpb files 89*ff35212dScey 90*ff35212dScey Returns: 91*ff35212dScey A dict, key is to_string(carrier_id), value is cname. 92*ff35212dScey """ 93*ff35212dScey ret = dict() 94*ff35212dScey for path in files: 95*ff35212dScey with open(path, 'r', encoding='utf-8') as f: 96*ff35212dScey carriers = text_format.Parse(f.read(), carrier_list_pb2.CarrierList()) 97*ff35212dScey for carriermap in carriers.entry: 98*ff35212dScey # could print error if already exist 99*ff35212dScey for cid in carriermap.carrier_id: 100*ff35212dScey ret[to_string(cid)] = carriermap.canonical_name 101*ff35212dScey 102*ff35212dScey return ret 103*ff35212dScey 104*ff35212dScey 105*ff35212dSceydef gen_cid(apn_node): 106*ff35212dScey """Generate carrier id proto from APN node. 107*ff35212dScey 108*ff35212dScey Args: 109*ff35212dScey apn_node: DOM node from getElementsByTag 110*ff35212dScey 111*ff35212dScey Returns: 112*ff35212dScey CarrierId proto 113*ff35212dScey """ 114*ff35212dScey ret = carrier_list_pb2.CarrierId() 115*ff35212dScey ret.mcc_mnc = (apn_node.getAttribute('mcc') + apn_node.getAttribute('mnc')) 116*ff35212dScey mvno_type = apn_node.getAttribute('mvno_type') 117*ff35212dScey mvno_data = apn_node.getAttribute('mvno_match_data') 118*ff35212dScey if mvno_type.lower() == 'spn': 119*ff35212dScey ret.spn = mvno_data 120*ff35212dScey if mvno_type.lower() == 'imsi': 121*ff35212dScey ret.imsi = mvno_data 122*ff35212dScey # in apn xml, gid means gid1, and no gid2 123*ff35212dScey if mvno_type.lower() == 'gid': 124*ff35212dScey ret.gid1 = mvno_data 125*ff35212dScey return ret 126*ff35212dScey 127*ff35212dScey 128*ff35212dSceyAPN_TYPE_MAP = { 129*ff35212dScey '*': carrier_settings_pb2.ApnItem.ALL, 130*ff35212dScey 'default': carrier_settings_pb2.ApnItem.DEFAULT, 131*ff35212dScey 'internet': carrier_settings_pb2.ApnItem.DEFAULT, 132*ff35212dScey 'vzw800': carrier_settings_pb2.ApnItem.DEFAULT, 133*ff35212dScey 'mms': carrier_settings_pb2.ApnItem.MMS, 134*ff35212dScey 'sup': carrier_settings_pb2.ApnItem.SUPL, 135*ff35212dScey 'supl': carrier_settings_pb2.ApnItem.SUPL, 136*ff35212dScey 'agps': carrier_settings_pb2.ApnItem.SUPL, 137*ff35212dScey 'pam': carrier_settings_pb2.ApnItem.DUN, 138*ff35212dScey 'dun': carrier_settings_pb2.ApnItem.DUN, 139*ff35212dScey 'hipri': carrier_settings_pb2.ApnItem.HIPRI, 140*ff35212dScey 'ota': carrier_settings_pb2.ApnItem.FOTA, 141*ff35212dScey 'fota': carrier_settings_pb2.ApnItem.FOTA, 142*ff35212dScey 'admin': carrier_settings_pb2.ApnItem.FOTA, 143*ff35212dScey 'ims': carrier_settings_pb2.ApnItem.IMS, 144*ff35212dScey 'cbs': carrier_settings_pb2.ApnItem.CBS, 145*ff35212dScey 'ia': carrier_settings_pb2.ApnItem.IA, 146*ff35212dScey 'emergency': carrier_settings_pb2.ApnItem.EMERGENCY, 147*ff35212dScey 'xcap': carrier_settings_pb2.ApnItem.XCAP, 148*ff35212dScey 'ut': carrier_settings_pb2.ApnItem.UT, 149*ff35212dScey 'rcs': carrier_settings_pb2.ApnItem.RCS, 150*ff35212dScey} 151*ff35212dScey 152*ff35212dScey 153*ff35212dSceydef map_apntype(typestr): 154*ff35212dScey """Map from APN type string to list of ApnType enums. 155*ff35212dScey 156*ff35212dScey Args: 157*ff35212dScey typestr: APN type string in apn conf xml, comma separated 158*ff35212dScey Returns: 159*ff35212dScey List of ApnType values in ApnItem proto. 160*ff35212dScey """ 161*ff35212dScey typelist = [apn.strip().lower() for apn in typestr.split(',')] 162*ff35212dScey return list(set([APN_TYPE_MAP[t] for t in typelist if t])) 163*ff35212dScey 164*ff35212dScey 165*ff35212dSceyAPN_PROTOCOL_MAP = { 166*ff35212dScey 'ip': carrier_settings_pb2.ApnItem.IP, 167*ff35212dScey 'ipv4': carrier_settings_pb2.ApnItem.IP, 168*ff35212dScey 'ipv6': carrier_settings_pb2.ApnItem.IPV6, 169*ff35212dScey 'ipv4v6': carrier_settings_pb2.ApnItem.IPV4V6, 170*ff35212dScey 'ppp': carrier_settings_pb2.ApnItem.PPP 171*ff35212dScey} 172*ff35212dScey 173*ff35212dSceyBOOL_MAP = {'true': True, 'false': False, '1': True, '0': False} 174*ff35212dScey 175*ff35212dSceyAPN_SKIPXLAT_MAP = { 176*ff35212dScey -1: carrier_settings_pb2.ApnItem.SKIP_464XLAT_DEFAULT, 177*ff35212dScey 0: carrier_settings_pb2.ApnItem.SKIP_464XLAT_DISABLE, 178*ff35212dScey 1: carrier_settings_pb2.ApnItem.SKIP_464XLAT_ENABLE 179*ff35212dScey} 180*ff35212dScey 181*ff35212dScey# not include already handeld string keys like mcc, protocol 182*ff35212dSceyAPN_STRING_KEYS = [ 183*ff35212dScey 'bearer_bitmask', 'server', 'proxy', 'port', 'user', 'password', 'mmsc', 184*ff35212dScey 'mmsc_proxy', 'mmsc_proxy_port' 185*ff35212dScey] 186*ff35212dScey 187*ff35212dScey# keys that are different between apn.xml and apn.proto 188*ff35212dSceyAPN_REMAP_KEYS = { 189*ff35212dScey 'mmsproxy': 'mmsc_proxy', 190*ff35212dScey 'mmsport': 'mmsc_proxy_port' 191*ff35212dScey} 192*ff35212dScey 193*ff35212dSceyAPN_INT_KEYS = [ 194*ff35212dScey 'authtype', 'mtu', 'profile_id', 'max_conns', 'wait_time', 'max_conns_time' 195*ff35212dScey] 196*ff35212dScey 197*ff35212dSceyAPN_BOOL_KEYS = [ 198*ff35212dScey 'modem_cognitive', 'user_visible', 'user_editable' 199*ff35212dScey] 200*ff35212dScey 201*ff35212dScey 202*ff35212dSceydef gen_apnitem(node): 203*ff35212dScey """Create ApnItem proto based on APN node from xml file. 204*ff35212dScey 205*ff35212dScey Args: 206*ff35212dScey node: xml dom node from apn conf xml file. 207*ff35212dScey 208*ff35212dScey Returns: 209*ff35212dScey An ApnItem proto. 210*ff35212dScey """ 211*ff35212dScey apn = carrier_settings_pb2.ApnItem() 212*ff35212dScey apn.name = node.getAttribute('carrier') 213*ff35212dScey apn.value = node.getAttribute('apn') 214*ff35212dScey apn.type.extend(map_apntype(node.getAttribute('type'))) 215*ff35212dScey for key in ['protocol', 'roaming_protocol']: 216*ff35212dScey if node.hasAttribute(key): 217*ff35212dScey setattr(apn, key, APN_PROTOCOL_MAP[node.getAttribute(key).lower()]) 218*ff35212dScey 219*ff35212dScey for key in node.attributes.keys(): 220*ff35212dScey # Treat bearer as bearer_bitmask if no bearer_bitmask specified 221*ff35212dScey if key == 'bearer' and not node.hasAttribute('bearer_bitmask'): 222*ff35212dScey setattr(apn, 'bearer_bitmask', node.getAttribute(key)) 223*ff35212dScey continue 224*ff35212dScey if key == 'skip_464xlat': 225*ff35212dScey setattr(apn, key, APN_SKIPXLAT_MAP[int(node.getAttribute(key))]) 226*ff35212dScey continue 227*ff35212dScey if key in APN_STRING_KEYS: 228*ff35212dScey setattr(apn, key, node.getAttribute(key)) 229*ff35212dScey continue 230*ff35212dScey if key in APN_REMAP_KEYS: 231*ff35212dScey setattr(apn, APN_REMAP_KEYS[key], node.getAttribute(key)) 232*ff35212dScey continue 233*ff35212dScey if key in APN_INT_KEYS: 234*ff35212dScey setattr(apn, key, int(node.getAttribute(key))) 235*ff35212dScey continue 236*ff35212dScey if key in APN_BOOL_KEYS: 237*ff35212dScey setattr(apn, key, BOOL_MAP[node.getAttribute(key).lower()]) 238*ff35212dScey continue 239*ff35212dScey 240*ff35212dScey return apn 241*ff35212dScey 242*ff35212dSceydef is_mccmnc_only_attribute(attribute): 243*ff35212dScey """Check if the given CarrierAttribute only contains mccmnc_tuple 244*ff35212dScey 245*ff35212dScey Args: 246*ff35212dScey attribute: message CarrierAttribute defined in carrierId.proto 247*ff35212dScey 248*ff35212dScey Returns: 249*ff35212dScey True, if the given CarrierAttribute only contains mccmnc_tuple 250*ff35212dScey False, otherwise 251*ff35212dScey """ 252*ff35212dScey for descriptor in attribute.DESCRIPTOR.fields: 253*ff35212dScey if descriptor.name != "mccmnc_tuple": 254*ff35212dScey if len(getattr(attribute, descriptor.name)): 255*ff35212dScey return False 256*ff35212dScey return True 257*ff35212dScey 258*ff35212dSceydef process_apnmap_by_mccmnc(apn_node, pb2_carrier_id, known, apn_map): 259*ff35212dScey """Process apn map based on the MCCMNC combination in apns.xml. 260*ff35212dScey 261*ff35212dScey Args: 262*ff35212dScey apn_node: APN node 263*ff35212dScey pb2_carrier_id: carrier id proto from APN node 264*ff35212dScey known: mapping from mccmnc and possible mvno data to canonical name 265*ff35212dScey apn_map: apn map 266*ff35212dScey 267*ff35212dScey Returns: 268*ff35212dScey None by default 269*ff35212dScey """ 270*ff35212dScey apn_carrier_id = apn_node.getAttribute('carrier_id') 271*ff35212dScey if apn_carrier_id != '': 272*ff35212dScey print("Cannot use mccmnc and carrier_id at the same time," 273*ff35212dScey + " carrier_id<" + apn_carrier_id + "> is ignored.") 274*ff35212dScey cname = get_cname(pb2_carrier_id, known) 275*ff35212dScey apn = gen_apnitem(apn_node) 276*ff35212dScey apn_map[cname].append(apn) 277*ff35212dScey 278*ff35212dSceydef process_apnmap_by_carrier_id(apn_node, aospCarrierList, known, apn_map): 279*ff35212dScey """Process apn map based on the carrier_id in apns.xml. 280*ff35212dScey 281*ff35212dScey Args: 282*ff35212dScey apn_node: APN node 283*ff35212dScey aospCarrierList: CarrierList from AOSP 284*ff35212dScey known: mapping from mccmnc and possible mvno data to canonical name 285*ff35212dScey apn_map: apn map 286*ff35212dScey 287*ff35212dScey Returns: 288*ff35212dScey None by default 289*ff35212dScey """ 290*ff35212dScey cname_map = dict() 291*ff35212dScey apn_carrier_id = apn_node.getAttribute('carrier_id') 292*ff35212dScey if apn_carrier_id != '': 293*ff35212dScey if apn_carrier_id in cname_map: 294*ff35212dScey for cname in cname_map[apn_carrier_id]: 295*ff35212dScey apn_map[cname].append(gen_apnitem(apn_node)) 296*ff35212dScey else: 297*ff35212dScey # convert cid to mccmnc combination 298*ff35212dScey cnameList = [] 299*ff35212dScey for aosp_carrier_id in aospCarrierList.carrier_id: 300*ff35212dScey aosp_canonical_id = str(aosp_carrier_id.canonical_id) 301*ff35212dScey if aosp_canonical_id == apn_carrier_id: 302*ff35212dScey for attribute in aosp_carrier_id.carrier_attribute: 303*ff35212dScey mcc_mnc_only = is_mccmnc_only_attribute(attribute) 304*ff35212dScey for mcc_mnc in attribute.mccmnc_tuple: 305*ff35212dScey cname = mcc_mnc 306*ff35212dScey # Handle gid1, spn, imsi in the order used by 307*ff35212dScey # CarrierConfigConverterV2#generateCanonicalNameForOthers 308*ff35212dScey gid1_list = attribute.gid1 if len(attribute.gid1) else [""] 309*ff35212dScey for gid1 in gid1_list: 310*ff35212dScey cname_gid1 = cname + ("GID1=" + gid1.upper() if gid1 else "") 311*ff35212dScey 312*ff35212dScey spn_list = attribute.spn if len(attribute.spn) else [""] 313*ff35212dScey for spn in spn_list: 314*ff35212dScey cname_spn = cname_gid1 + ("SPN=" + spn.upper() if spn else "") 315*ff35212dScey 316*ff35212dScey imsi_list = attribute.imsi_prefix_xpattern if\ 317*ff35212dScey len(attribute.imsi_prefix_xpattern) else [""] 318*ff35212dScey for imsi in imsi_list: 319*ff35212dScey cname_imsi = cname_spn + ("IMSI=" + imsi.upper() if imsi else "") 320*ff35212dScey 321*ff35212dScey if cname_imsi == cname and not mcc_mnc_only: 322*ff35212dScey # Ignore fields which cannot be handled for now 323*ff35212dScey continue 324*ff35212dScey 325*ff35212dScey cnameList.append(get_known_cname(cname_imsi, known)) 326*ff35212dScey break # break from aospCarrierList.carrier_id since cid is found 327*ff35212dScey if cnameList: 328*ff35212dScey for cname in cnameList: 329*ff35212dScey apn_map[cname].append(gen_apnitem(apn_node)) 330*ff35212dScey 331*ff35212dScey # cache cnameList to avoid searching again 332*ff35212dScey cname_map[aosp_canonical_id] = cnameList 333*ff35212dScey else: 334*ff35212dScey print("Can't find cname list, carrier_id in APN files might be wrong : " + apn_carrier_id) 335*ff35212dScey 336*ff35212dSceydef main(): 337*ff35212dScey known = get_knowncarriers([FLAGS.data_dir + '/' + f for f in CARRIER_LISTS]) 338*ff35212dScey 339*ff35212dScey with open(FLAGS.apn_file, 'r', encoding='utf-8') as apnfile: 340*ff35212dScey dom = minidom.parse(apnfile) 341*ff35212dScey 342*ff35212dScey with open(FLAGS.aosp_carrier_list, 'r', encoding='utf-8', newline='\n') as f: 343*ff35212dScey aospCarrierList = text_format.Parse(f.read(), carrierId_pb2.CarrierList()) 344*ff35212dScey 345*ff35212dScey apn_map = collections.defaultdict(list) 346*ff35212dScey for apn_node in dom.getElementsByTagName('apn'): 347*ff35212dScey pb2_carrier_id = gen_cid(apn_node) 348*ff35212dScey if pb2_carrier_id.mcc_mnc or not FLAGS.support_carrier_id: 349*ff35212dScey # case 1 : mccmnc 350*ff35212dScey # case 2 : mccmnc+mvno 351*ff35212dScey process_apnmap_by_mccmnc(apn_node, pb2_carrier_id, known, apn_map) 352*ff35212dScey else: 353*ff35212dScey # case 3 : carrier_id 354*ff35212dScey process_apnmap_by_carrier_id(apn_node, aospCarrierList, known, apn_map) 355*ff35212dScey 356*ff35212dScey mcs = carrier_settings_pb2.MultiCarrierSettings() 357*ff35212dScey for c in apn_map: 358*ff35212dScey carriersettings = mcs.setting.add() 359*ff35212dScey carriersettings.canonical_name = c 360*ff35212dScey carriersettings.apns.apn.extend(apn_map[c]) 361*ff35212dScey 362*ff35212dScey with open(FLAGS.out_file, 'w', encoding='utf-8') as apnout: 363*ff35212dScey apnout.write(text_format.MessageToString(mcs, as_utf8=True)) 364*ff35212dScey 365*ff35212dScey 366*ff35212dSceyif __name__ == '__main__': 367*ff35212dScey main() 368