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