• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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