# Copyright (C) 2020 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. r"""Read APN conf xml file and output an textpb. How to run: update_apn.par --apn_file=./apns-full-conf.xml \ --data_dir=./data --out_file=/tmpapns.textpb """ from __future__ import absolute_import from __future__ import division from __future__ import print_function import argparse import collections from xml.dom import minidom from google.protobuf import text_format import carrier_list_pb2 import carrier_settings_pb2 import carrierId_pb2 parser = argparse.ArgumentParser() parser.add_argument( '--apn_file', default='./apns-full-conf.xml', help='Path to APN xml file') parser.add_argument( '--data_dir', default='./data', help='Folder path for CarrierSettings data') parser.add_argument( '--support_carrier_id', action='store_true', help='To support using carrier_id in apns.xml') parser.add_argument( '--aosp_carrier_list', default='packages/providers/TelephonyProvider/assets/latest_carrier_id/carrier_list.textpb', help='Resource file path to the AOSP carrier list file') parser.add_argument( '--out_file', default='./tmpapns.textpb', help='Temp APN file') FLAGS = parser.parse_args() CARRIER_LISTS = ['tier1_carriers.textpb', 'other_carriers.textpb'] def to_string(cid): """Return a string for CarrierId.""" ret = cid.mcc_mnc if cid.HasField('spn'): ret += 'SPN=' + cid.spn.upper() if cid.HasField('imsi'): ret += 'IMSI=' + cid.imsi.upper() if cid.HasField('gid1'): ret += 'GID1=' + cid.gid1.upper() return ret def get_cname(cid, known_carriers): """Return a canonical name based on cid and known_carriers. If found a match in known_carriers, return it. Otherwise generate a new one by concating the values. Args: cid: proto of CarrierId known_carriers: mapping from mccmnc and possible mvno data to canonical name Returns: string for canonical name, like verizon_us or 27402 """ return get_known_cname(to_string(cid), known_carriers) def get_known_cname(name, known_carriers): if name in known_carriers: return known_carriers[name] else: return name def get_knowncarriers(files): """Create a mapping from mccmnc and possible mvno data to canonical name. Args: files: list of paths to carrier list textpb files Returns: A dict, key is to_string(carrier_id), value is cname. """ ret = dict() for path in files: with open(path, 'r', encoding='utf-8') as f: carriers = text_format.Parse(f.read(), carrier_list_pb2.CarrierList()) for carriermap in carriers.entry: # could print error if already exist for cid in carriermap.carrier_id: ret[to_string(cid)] = carriermap.canonical_name return ret def gen_cid(apn_node): """Generate carrier id proto from APN node. Args: apn_node: DOM node from getElementsByTag Returns: CarrierId proto """ ret = carrier_list_pb2.CarrierId() ret.mcc_mnc = (apn_node.getAttribute('mcc') + apn_node.getAttribute('mnc')) mvno_type = apn_node.getAttribute('mvno_type') mvno_data = apn_node.getAttribute('mvno_match_data') if mvno_type.lower() == 'spn': ret.spn = mvno_data if mvno_type.lower() == 'imsi': ret.imsi = mvno_data # in apn xml, gid means gid1, and no gid2 if mvno_type.lower() == 'gid': ret.gid1 = mvno_data return ret APN_TYPE_MAP = { '*': carrier_settings_pb2.ApnItem.ALL, 'default': carrier_settings_pb2.ApnItem.DEFAULT, 'internet': carrier_settings_pb2.ApnItem.DEFAULT, 'vzw800': carrier_settings_pb2.ApnItem.DEFAULT, 'mms': carrier_settings_pb2.ApnItem.MMS, 'sup': carrier_settings_pb2.ApnItem.SUPL, 'supl': carrier_settings_pb2.ApnItem.SUPL, 'agps': carrier_settings_pb2.ApnItem.SUPL, 'pam': carrier_settings_pb2.ApnItem.DUN, 'dun': carrier_settings_pb2.ApnItem.DUN, 'hipri': carrier_settings_pb2.ApnItem.HIPRI, 'ota': carrier_settings_pb2.ApnItem.FOTA, 'fota': carrier_settings_pb2.ApnItem.FOTA, 'admin': carrier_settings_pb2.ApnItem.FOTA, 'ims': carrier_settings_pb2.ApnItem.IMS, 'cbs': carrier_settings_pb2.ApnItem.CBS, 'ia': carrier_settings_pb2.ApnItem.IA, 'emergency': carrier_settings_pb2.ApnItem.EMERGENCY, 'xcap': carrier_settings_pb2.ApnItem.XCAP, 'ut': carrier_settings_pb2.ApnItem.UT, 'rcs': carrier_settings_pb2.ApnItem.RCS, } def map_apntype(typestr): """Map from APN type string to list of ApnType enums. Args: typestr: APN type string in apn conf xml, comma separated Returns: List of ApnType values in ApnItem proto. """ typelist = [apn.strip().lower() for apn in typestr.split(',')] return list(set([APN_TYPE_MAP[t] for t in typelist if t])) APN_PROTOCOL_MAP = { 'ip': carrier_settings_pb2.ApnItem.IP, 'ipv4': carrier_settings_pb2.ApnItem.IP, 'ipv6': carrier_settings_pb2.ApnItem.IPV6, 'ipv4v6': carrier_settings_pb2.ApnItem.IPV4V6, 'ppp': carrier_settings_pb2.ApnItem.PPP } BOOL_MAP = {'true': True, 'false': False, '1': True, '0': False} APN_SKIPXLAT_MAP = { -1: carrier_settings_pb2.ApnItem.SKIP_464XLAT_DEFAULT, 0: carrier_settings_pb2.ApnItem.SKIP_464XLAT_DISABLE, 1: carrier_settings_pb2.ApnItem.SKIP_464XLAT_ENABLE } # not include already handeld string keys like mcc, protocol APN_STRING_KEYS = [ 'bearer_bitmask', 'server', 'proxy', 'port', 'user', 'password', 'mmsc', 'mmsc_proxy', 'mmsc_proxy_port' ] # keys that are different between apn.xml and apn.proto APN_REMAP_KEYS = { 'mmsproxy': 'mmsc_proxy', 'mmsport': 'mmsc_proxy_port' } APN_INT_KEYS = [ 'authtype', 'mtu', 'profile_id', 'max_conns', 'wait_time', 'max_conns_time' ] APN_BOOL_KEYS = [ 'modem_cognitive', 'user_visible', 'user_editable' ] def gen_apnitem(node): """Create ApnItem proto based on APN node from xml file. Args: node: xml dom node from apn conf xml file. Returns: An ApnItem proto. """ apn = carrier_settings_pb2.ApnItem() apn.name = node.getAttribute('carrier') apn.value = node.getAttribute('apn') apn.type.extend(map_apntype(node.getAttribute('type'))) for key in ['protocol', 'roaming_protocol']: if node.hasAttribute(key): setattr(apn, key, APN_PROTOCOL_MAP[node.getAttribute(key).lower()]) for key in node.attributes.keys(): # Treat bearer as bearer_bitmask if no bearer_bitmask specified if key == 'bearer' and not node.hasAttribute('bearer_bitmask'): setattr(apn, 'bearer_bitmask', node.getAttribute(key)) continue if key == 'skip_464xlat': setattr(apn, key, APN_SKIPXLAT_MAP[int(node.getAttribute(key))]) continue if key in APN_STRING_KEYS: setattr(apn, key, node.getAttribute(key)) continue if key in APN_REMAP_KEYS: setattr(apn, APN_REMAP_KEYS[key], node.getAttribute(key)) continue if key in APN_INT_KEYS: setattr(apn, key, int(node.getAttribute(key))) continue if key in APN_BOOL_KEYS: setattr(apn, key, BOOL_MAP[node.getAttribute(key).lower()]) continue return apn def is_mccmnc_only_attribute(attribute): """Check if the given CarrierAttribute only contains mccmnc_tuple Args: attribute: message CarrierAttribute defined in carrierId.proto Returns: True, if the given CarrierAttribute only contains mccmnc_tuple False, otherwise """ for descriptor in attribute.DESCRIPTOR.fields: if descriptor.name != "mccmnc_tuple": if len(getattr(attribute, descriptor.name)): return False return True def process_apnmap_by_mccmnc(apn_node, pb2_carrier_id, known, apn_map): """Process apn map based on the MCCMNC combination in apns.xml. Args: apn_node: APN node pb2_carrier_id: carrier id proto from APN node known: mapping from mccmnc and possible mvno data to canonical name apn_map: apn map Returns: None by default """ apn_carrier_id = apn_node.getAttribute('carrier_id') if apn_carrier_id != '': print("Cannot use mccmnc and carrier_id at the same time," + " carrier_id<" + apn_carrier_id + "> is ignored.") cname = get_cname(pb2_carrier_id, known) apn = gen_apnitem(apn_node) apn_map[cname].append(apn) def process_apnmap_by_carrier_id(apn_node, aospCarrierList, known, apn_map): """Process apn map based on the carrier_id in apns.xml. Args: apn_node: APN node aospCarrierList: CarrierList from AOSP known: mapping from mccmnc and possible mvno data to canonical name apn_map: apn map Returns: None by default """ cname_map = dict() apn_carrier_id = apn_node.getAttribute('carrier_id') if apn_carrier_id != '': if apn_carrier_id in cname_map: for cname in cname_map[apn_carrier_id]: apn_map[cname].append(gen_apnitem(apn_node)) else: # convert cid to mccmnc combination cnameList = [] for aosp_carrier_id in aospCarrierList.carrier_id: aosp_canonical_id = str(aosp_carrier_id.canonical_id) if aosp_canonical_id == apn_carrier_id: for attribute in aosp_carrier_id.carrier_attribute: mcc_mnc_only = is_mccmnc_only_attribute(attribute) for mcc_mnc in attribute.mccmnc_tuple: cname = mcc_mnc # Handle gid1, spn, imsi in the order used by # CarrierConfigConverterV2#generateCanonicalNameForOthers gid1_list = attribute.gid1 if len(attribute.gid1) else [""] for gid1 in gid1_list: cname_gid1 = cname + ("GID1=" + gid1.upper() if gid1 else "") spn_list = attribute.spn if len(attribute.spn) else [""] for spn in spn_list: cname_spn = cname_gid1 + ("SPN=" + spn.upper() if spn else "") imsi_list = attribute.imsi_prefix_xpattern if\ len(attribute.imsi_prefix_xpattern) else [""] for imsi in imsi_list: cname_imsi = cname_spn + ("IMSI=" + imsi.upper() if imsi else "") if cname_imsi == cname and not mcc_mnc_only: # Ignore fields which cannot be handled for now continue cnameList.append(get_known_cname(cname_imsi, known)) break # break from aospCarrierList.carrier_id since cid is found if cnameList: for cname in cnameList: apn_map[cname].append(gen_apnitem(apn_node)) # cache cnameList to avoid searching again cname_map[aosp_canonical_id] = cnameList else: print("Can't find cname list, carrier_id in APN files might be wrong : " + apn_carrier_id) def main(): known = get_knowncarriers([FLAGS.data_dir + '/' + f for f in CARRIER_LISTS]) with open(FLAGS.apn_file, 'r', encoding='utf-8') as apnfile: dom = minidom.parse(apnfile) with open(FLAGS.aosp_carrier_list, 'r', encoding='utf-8', newline='\n') as f: aospCarrierList = text_format.Parse(f.read(), carrierId_pb2.CarrierList()) apn_map = collections.defaultdict(list) for apn_node in dom.getElementsByTagName('apn'): pb2_carrier_id = gen_cid(apn_node) if pb2_carrier_id.mcc_mnc or not FLAGS.support_carrier_id: # case 1 : mccmnc # case 2 : mccmnc+mvno process_apnmap_by_mccmnc(apn_node, pb2_carrier_id, known, apn_map) else: # case 3 : carrier_id process_apnmap_by_carrier_id(apn_node, aospCarrierList, known, apn_map) mcs = carrier_settings_pb2.MultiCarrierSettings() for c in apn_map: carriersettings = mcs.setting.add() carriersettings.canonical_name = c carriersettings.apns.apn.extend(apn_map[c]) with open(FLAGS.out_file, 'w', encoding='utf-8') as apnout: apnout.write(text_format.MessageToString(mcs, as_utf8=True)) if __name__ == '__main__': main()