xref: /aosp_15_r20/tools/carrier_settings/python/update_apn.py (revision ff35212d322a3e892605b94fa777c67085d45efd)
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