| # 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 |
| |
| 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( |
| '--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 |
| """ |
| name = to_string(cid) |
| 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 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) |
| |
| apn_map = collections.defaultdict(list) |
| for apn_node in dom.getElementsByTagName('apn'): |
| cname = get_cname(gen_cid(apn_node), known) |
| apn = gen_apnitem(apn_node) |
| apn_map[cname].append(apn) |
| |
| 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() |