You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
368 lines
12 KiB
368 lines
12 KiB
# 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()
|