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 33 34parser = argparse.ArgumentParser() 35parser.add_argument( 36 '--apn_file', default='./apns-full-conf.xml', help='Path to APN xml file') 37parser.add_argument( 38 '--data_dir', default='./data', help='Folder path for CarrierSettings data') 39parser.add_argument( 40 '--out_file', default='./tmpapns.textpb', help='Temp APN file') 41FLAGS = parser.parse_args() 42 43CARRIER_LISTS = ['tier1_carriers.textpb', 'other_carriers.textpb'] 44 45 46def to_string(cid): 47 """Return a string for CarrierId.""" 48 ret = cid.mcc_mnc 49 if cid.HasField('spn'): 50 ret += 'SPN=' + cid.spn.upper() 51 if cid.HasField('imsi'): 52 ret += 'IMSI=' + cid.imsi.upper() 53 if cid.HasField('gid1'): 54 ret += 'GID1=' + cid.gid1.upper() 55 return ret 56 57 58def get_cname(cid, known_carriers): 59 """Return a canonical name based on cid and known_carriers. 60 61 If found a match in known_carriers, return it. Otherwise generate a new one 62 by concating the values. 63 64 Args: 65 cid: proto of CarrierId 66 known_carriers: mapping from mccmnc and possible mvno data to canonical name 67 68 Returns: 69 string for canonical name, like verizon_us or 27402 70 """ 71 name = to_string(cid) 72 if name in known_carriers: 73 return known_carriers[name] 74 else: 75 return name 76 77 78def get_knowncarriers(files): 79 """Create a mapping from mccmnc and possible mvno data to canonical name. 80 81 Args: 82 files: list of paths to carrier list textpb files 83 84 Returns: 85 A dict, key is to_string(carrier_id), value is cname. 86 """ 87 ret = dict() 88 for path in files: 89 with open(path, 'r', encoding='utf-8') as f: 90 carriers = text_format.Parse(f.read(), carrier_list_pb2.CarrierList()) 91 for carriermap in carriers.entry: 92 # could print error if already exist 93 for cid in carriermap.carrier_id: 94 ret[to_string(cid)] = carriermap.canonical_name 95 96 return ret 97 98 99def gen_cid(apn_node): 100 """Generate carrier id proto from APN node. 101 102 Args: 103 apn_node: DOM node from getElementsByTag 104 105 Returns: 106 CarrierId proto 107 """ 108 ret = carrier_list_pb2.CarrierId() 109 ret.mcc_mnc = (apn_node.getAttribute('mcc') + apn_node.getAttribute('mnc')) 110 mvno_type = apn_node.getAttribute('mvno_type') 111 mvno_data = apn_node.getAttribute('mvno_match_data') 112 if mvno_type.lower() == 'spn': 113 ret.spn = mvno_data 114 if mvno_type.lower() == 'imsi': 115 ret.imsi = mvno_data 116 # in apn xml, gid means gid1, and no gid2 117 if mvno_type.lower() == 'gid': 118 ret.gid1 = mvno_data 119 return ret 120 121 122APN_TYPE_MAP = { 123 '*': carrier_settings_pb2.ApnItem.ALL, 124 'default': carrier_settings_pb2.ApnItem.DEFAULT, 125 'internet': carrier_settings_pb2.ApnItem.DEFAULT, 126 'vzw800': carrier_settings_pb2.ApnItem.DEFAULT, 127 'mms': carrier_settings_pb2.ApnItem.MMS, 128 'sup': carrier_settings_pb2.ApnItem.SUPL, 129 'supl': carrier_settings_pb2.ApnItem.SUPL, 130 'agps': carrier_settings_pb2.ApnItem.SUPL, 131 'pam': carrier_settings_pb2.ApnItem.DUN, 132 'dun': carrier_settings_pb2.ApnItem.DUN, 133 'hipri': carrier_settings_pb2.ApnItem.HIPRI, 134 'ota': carrier_settings_pb2.ApnItem.FOTA, 135 'fota': carrier_settings_pb2.ApnItem.FOTA, 136 'admin': carrier_settings_pb2.ApnItem.FOTA, 137 'ims': carrier_settings_pb2.ApnItem.IMS, 138 'cbs': carrier_settings_pb2.ApnItem.CBS, 139 'ia': carrier_settings_pb2.ApnItem.IA, 140 'emergency': carrier_settings_pb2.ApnItem.EMERGENCY, 141 'xcap': carrier_settings_pb2.ApnItem.XCAP, 142 'ut': carrier_settings_pb2.ApnItem.UT, 143 'rcs': carrier_settings_pb2.ApnItem.RCS, 144} 145 146 147def map_apntype(typestr): 148 """Map from APN type string to list of ApnType enums. 149 150 Args: 151 typestr: APN type string in apn conf xml, comma separated 152 Returns: 153 List of ApnType values in ApnItem proto. 154 """ 155 typelist = [apn.strip().lower() for apn in typestr.split(',')] 156 return list(set([APN_TYPE_MAP[t] for t in typelist if t])) 157 158 159APN_PROTOCOL_MAP = { 160 'ip': carrier_settings_pb2.ApnItem.IP, 161 'ipv4': carrier_settings_pb2.ApnItem.IP, 162 'ipv6': carrier_settings_pb2.ApnItem.IPV6, 163 'ipv4v6': carrier_settings_pb2.ApnItem.IPV4V6, 164 'ppp': carrier_settings_pb2.ApnItem.PPP 165} 166 167BOOL_MAP = {'true': True, 'false': False, '1': True, '0': False} 168 169APN_SKIPXLAT_MAP = { 170 -1: carrier_settings_pb2.ApnItem.SKIP_464XLAT_DEFAULT, 171 0: carrier_settings_pb2.ApnItem.SKIP_464XLAT_DISABLE, 172 1: carrier_settings_pb2.ApnItem.SKIP_464XLAT_ENABLE 173} 174 175# not include already handeld string keys like mcc, protocol 176APN_STRING_KEYS = [ 177 'bearer_bitmask', 'server', 'proxy', 'port', 'user', 'password', 'mmsc', 178 'mmsc_proxy', 'mmsc_proxy_port' 179] 180 181# keys that are different between apn.xml and apn.proto 182APN_REMAP_KEYS = { 183 'mmsproxy': 'mmsc_proxy', 184 'mmsport': 'mmsc_proxy_port' 185} 186 187APN_INT_KEYS = [ 188 'authtype', 'mtu', 'profile_id', 'max_conns', 'wait_time', 'max_conns_time' 189] 190 191APN_BOOL_KEYS = [ 192 'modem_cognitive', 'user_visible', 'user_editable' 193] 194 195 196def gen_apnitem(node): 197 """Create ApnItem proto based on APN node from xml file. 198 199 Args: 200 node: xml dom node from apn conf xml file. 201 202 Returns: 203 An ApnItem proto. 204 """ 205 apn = carrier_settings_pb2.ApnItem() 206 apn.name = node.getAttribute('carrier') 207 apn.value = node.getAttribute('apn') 208 apn.type.extend(map_apntype(node.getAttribute('type'))) 209 for key in ['protocol', 'roaming_protocol']: 210 if node.hasAttribute(key): 211 setattr(apn, key, APN_PROTOCOL_MAP[node.getAttribute(key).lower()]) 212 213 for key in node.attributes.keys(): 214 # Treat bearer as bearer_bitmask if no bearer_bitmask specified 215 if key == 'bearer' and not node.hasAttribute('bearer_bitmask'): 216 setattr(apn, 'bearer_bitmask', node.getAttribute(key)) 217 continue 218 if key == 'skip_464xlat': 219 setattr(apn, key, APN_SKIPXLAT_MAP[int(node.getAttribute(key))]) 220 continue 221 if key in APN_STRING_KEYS: 222 setattr(apn, key, node.getAttribute(key)) 223 continue 224 if key in APN_REMAP_KEYS: 225 setattr(apn, APN_REMAP_KEYS[key], node.getAttribute(key)) 226 continue 227 if key in APN_INT_KEYS: 228 setattr(apn, key, int(node.getAttribute(key))) 229 continue 230 if key in APN_BOOL_KEYS: 231 setattr(apn, key, BOOL_MAP[node.getAttribute(key).lower()]) 232 continue 233 234 return apn 235 236 237def main(): 238 known = get_knowncarriers([FLAGS.data_dir + '/' + f for f in CARRIER_LISTS]) 239 240 with open(FLAGS.apn_file, 'r', encoding='utf-8') as apnfile: 241 dom = minidom.parse(apnfile) 242 243 apn_map = collections.defaultdict(list) 244 for apn_node in dom.getElementsByTagName('apn'): 245 cname = get_cname(gen_cid(apn_node), known) 246 apn = gen_apnitem(apn_node) 247 apn_map[cname].append(apn) 248 249 mcs = carrier_settings_pb2.MultiCarrierSettings() 250 for c in apn_map: 251 carriersettings = mcs.setting.add() 252 carriersettings.canonical_name = c 253 carriersettings.apns.apn.extend(apn_map[c]) 254 255 with open(FLAGS.out_file, 'w', encoding='utf-8') as apnout: 256 apnout.write(text_format.MessageToString(mcs, as_utf8=True)) 257 258 259if __name__ == '__main__': 260 main() 261