comparison contrib/tools/csv_to_fd @ 1463:8f6c77f24b1a

csv_to_fd: add QoSFilterRule. style fixes Support derived type QoSFilterRule from RFC 7155 section 4.1.1 Minor code refactor. Expand comments, removing UTF-8 chars and encoding requirement.
author Luke Mewburn <luke@mewburn.net>
date Mon, 09 Mar 2020 22:28:04 +1100
parents a86eb3375b95
children 1404de313b85
comparison
equal deleted inserted replaced
1462:b573eda8f4a2 1463:8f6c77f24b1a
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # vim: set fileencoding=utf-8 :
3 2
4 """ 3 """
5 Convert CSV files containing RADIUS or Diameter AVP tables 4 Convert CSV files containing RADIUS or Diameter AVP tables
6 into various formats. 5 into various formats.
7 6
8 Format of the CSV files is one of: 7 Format of the CSV files is one of:
9 - Row per 3GPP AVP tables: 8 - Row per 3GPP AVP tables:
10 Name, Code, Section, DataType, Must, May, ShouldNot, MustNot [, ...] 9 Name, Code, Section, DataType, Must, May, ShouldNot, MustNot [, ...]
11 - Name: 10 - Name:
12 AVP Name. String, validated as ALPHA *(ALPHA / DIGIT / "-") 11 AVP Name. String, validated as ALPHA *(ALPHA / DIGIT / "-")
12 per RFC 6733 section 3.2.
13 - Code: 13 - Code:
14 AVP Code. Integer, 0..4294967295. 14 AVP Code. Integer, 0..4294967295.
15 - Section: 15 - Section:
16 Section in relevant standard. String. 16 Section in relevant standard. String.
17 - DataType: 17 - DataType:
18 AVP Data Type. String, validated per Per RFC 6733 § 4.2 and § 4.3. 18 AVP Data Type. String, validated per basic and derived types in:
19 - RFC 6733 section 4.2
20 - RFC 6733 section 4.3
21 - RFC 7155 section 4.1
19 - Must, May, ShouldNot, MustNot: 22 - Must, May, ShouldNot, MustNot:
20 Flags, possibly comma or space separated: M, V 23 Flags, possibly comma or space separated: M, V
21 24
22 - Comment row. First cell: 25 - Comment row. First cell:
23 # Comment text Comment text 26 # Comment text Comment text
53 'shouldnot', 56 'shouldnot',
54 'mustnot', 57 'mustnot',
55 'encrypt', 58 'encrypt',
56 ] 59 ]
57 60
61 DERIVED_TO_BASE = {
62 'Address': 'OctetString', # RFC 6733 section 4.3.1
63 'Time': 'OctetString', # RFC 6733 section 4.3.1
64 'UTF8String': 'OctetString', # RFC 6733 section 4.3.1
65 'DiameterIdentity': 'OctetString', # RFC 6733 section 4.3.1
66 'DiameterURI': 'OctetString', # RFC 6733 section 4.3.1
67 'Enumerated': 'Integer32', # RFC 6733 section 4.3.1
68 'IPFilterRule': 'OctetString', # RFC 6733 section 4.3.1
69 'QoSFilterRule': 'OctetString', # RFC 7155 section 4.1.1
70 }
71
72 # See https://www.iana.org/assignments/enterprise-numbers/enterprise-numbers
58 VENDOR_TO_NAME = { 73 VENDOR_TO_NAME = {
59 0: '', 74 0: '',
60 193: 'Ericsson', 75 193: 'Ericsson',
61 8164: 'Starent', 76 8164: 'Starent',
62 10415: '3GPP', 77 10415: '3GPP',
64 79
65 80
66 class Avp(object): 81 class Avp(object):
67 """Store an AVP row.""" 82 """Store an AVP row."""
68 83
69 # Regex to validate avp-name per RFC 6733 § 3.2, 84 # Regex to validate avp-name per RFC 6733 section 3.2,
70 # with changes: 85 # with changes:
71 # - Allow avp-name to start with numbers (for 3GPP) 86 # - Allow avp-name to start with numbers (for 3GPP)
72 # - Allow '.' in avp-name, for existing dict_dcca_3gpp usage. 87 # - Allow '.' in avp-name, for existing dict_dcca_3gpp usage.
73 # TODO: if starts with digit, ensure contains a letter somewhere? 88 # TODO: if starts with digit, ensure contains a letter somewhere?
74 _name_re = re.compile(r'^[a-zA-Z0-9][a-zA-Z0-9-\.]*$') 89 _name_re = re.compile(r'^[a-zA-Z0-9][a-zA-Z0-9-\.]*$')
97 # Validate CSV fields 112 # Validate CSV fields
98 if not self._name_re.match(self.name): 113 if not self._name_re.match(self.name):
99 raise ValueError('Invalid AVP name "{}"'.format(self.name)) 114 raise ValueError('Invalid AVP name "{}"'.format(self.name))
100 if (self.code < 0 or self.code > 4294967295): 115 if (self.code < 0 or self.code > 4294967295):
101 raise ValueError('Invalid AVP code {}'.format(self.code)) 116 raise ValueError('Invalid AVP code {}'.format(self.code))
102 if self.datatype not in ( 117 if (self.datatype not in (
103 'OctetString', 'Integer32', 'Integer64', 'Unsigned32', 118 'OctetString', 'Integer32', 'Integer64', 'Unsigned32',
104 'Unsigned64', 'Float32', 'Float64', 'Grouped', 119 'Unsigned64', 'Float32', 'Float64', 'Grouped')
105 'Address', 'Time', 'UTF8String', 'DiameterIdentity', 120 and self.datatype not in DERIVED_TO_BASE):
106 'DiameterURI', 'Enumerated', 'IPFilterRule',
107 ):
108 raise ValueError('Invalid AVP data type "{}"'.format( 121 raise ValueError('Invalid AVP data type "{}"'.format(
109 self.datatype)) 122 self.datatype))
110 # TODO: validate must, may, shouldnot, mustnot 123 # TODO: validate must, may, shouldnot, mustnot
111 124
112 @property 125 @property
199 #= /*==============*/ 212 #= /*==============*/
200 # [blank line] 213 # [blank line]
201 """ 214 """
202 215
203 COMMENT_WIDTH = 64 216 COMMENT_WIDTH = 64
204
205 DERIVED_TO_BASE = {
206 'Address': 'OctetString',
207 'Time': 'OctetString',
208 'UTF8String': 'OctetString',
209 'DiameterIdentity': 'OctetString',
210 'DiameterURI': 'OctetString',
211 'Enumerated': 'Integer32',
212 'IPFilterRule': 'OctetString',
213 }
214 217
215 def __init__(self): 218 def __init__(self):
216 self.lines = [] 219 self.lines = []
217 220
218 def next_file(self, filename): 221 def next_file(self, filename):
233 self.build_flags(', '.join([avp.must, avp.mustnot])))) 236 self.build_flags(', '.join([avp.must, avp.mustnot]))))
234 self.add('\t\t\t{},\t/* Fixed flag values */'.format( 237 self.add('\t\t\t{},\t/* Fixed flag values */'.format(
235 self.build_flags(avp.must))) 238 self.build_flags(avp.must)))
236 # TODO: add trailing comma? 239 # TODO: add trailing comma?
237 self.add('\t\t\tAVP_TYPE_{}\t/* base type of data */'.format( 240 self.add('\t\t\tAVP_TYPE_{}\t/* base type of data */'.format(
238 self.DERIVED_TO_BASE.get( 241 DERIVED_TO_BASE.get(avp.datatype, avp.datatype).upper()))
239 avp.datatype, avp.datatype).upper()))
240 self.add('\t\t};') 242 self.add('\t\t};')
241 avp_type = 'NULL' 243 avp_type = 'NULL'
242 if 'Enumerated' == avp.datatype: 244 if 'Enumerated' == avp.datatype:
243 self.add('\t\tstruct dict_object\t*type;') 245 self.add('\t\tstruct dict_object\t*type;')
244 vendor_prefix = '' 246 vendor_prefix = ''
249 '"Enumerated({prefix}{name})", NULL, NULL, NULL }};'.format( 251 '"Enumerated({prefix}{name})", NULL, NULL, NULL }};'.format(
250 prefix=vendor_prefix, name=avp.name)) 252 prefix=vendor_prefix, name=avp.name))
251 # XXX: add enumerated values 253 # XXX: add enumerated values
252 self.add('\t\tCHECK_dict_new(DICT_TYPE, &tdata, NULL, &type);') 254 self.add('\t\tCHECK_dict_new(DICT_TYPE, &tdata, NULL, &type);')
253 avp_type = "type" 255 avp_type = "type"
254 elif avp.datatype in self.DERIVED_TO_BASE: 256 elif avp.datatype in DERIVED_TO_BASE:
255 avp_type = '{}_type'.format(avp.datatype) 257 avp_type = '{}_type'.format(avp.datatype)
256 self.add('\t\tCHECK_dict_new(DICT_AVP, &data, {}, NULL);'.format( 258 self.add('\t\tCHECK_dict_new(DICT_AVP, &data, {}, NULL);'.format(
257 avp_type)) 259 avp_type))
258 # TODO: remove ; on scope brace 260 # TODO: remove ; on scope brace
259 self.add('\t};') 261 self.add('\t};')
"Welcome to our mercurial repository"