netlink-socket: Adapt to Windows and MSVC.
[cascardo/ovs.git] / build-aux / extract-ofp-errors
1 #! /usr/bin/python
2
3 import sys
4 import os.path
5 import re
6
7 macros = {}
8
9 # Map from OpenFlow version number to version ID used in ofp_header.
10 version_map = {"1.0": 0x01,
11                "1.1": 0x02,
12                "1.2": 0x03,
13                "1.3": 0x04,
14                "1.4": 0x05,
15                "1.5": 0x06}
16 version_reverse_map = dict((v, k) for (k, v) in version_map.iteritems())
17
18 token = None
19 line = ""
20 idRe = "[a-zA-Z_][a-zA-Z_0-9]*"
21 tokenRe = "#?" + idRe + "|[0-9]+|."
22 inComment = False
23 inDirective = False
24
25 def open_file(fn):
26     global fileName
27     global inputFile
28     global lineNumber
29     fileName = fn
30     inputFile = open(fileName)
31     lineNumber = 0
32
33 def tryGetLine():
34     global inputFile
35     global line
36     global lineNumber
37     line = inputFile.readline()
38     lineNumber += 1
39     return line != ""
40
41 def getLine():
42     if not tryGetLine():
43         fatal("unexpected end of input")
44
45 def getToken():
46     global token
47     global line
48     global inComment
49     global inDirective
50     while True:
51         line = line.lstrip()
52         if line != "":
53             if line.startswith("/*"):
54                 inComment = True
55                 line = line[2:]
56             elif inComment:
57                 commentEnd = line.find("*/")
58                 if commentEnd < 0:
59                     line = ""
60                 else:
61                     inComment = False
62                     line = line[commentEnd + 2:]
63             else:
64                 match = re.match(tokenRe, line)
65                 token = match.group(0)
66                 line = line[len(token):]
67                 if token.startswith('#'):
68                     inDirective = True
69                 elif token in macros and not inDirective:
70                     line = macros[token] + line
71                     continue
72                 return True
73         elif inDirective:
74             token = "$"
75             inDirective = False
76             return True
77         else:
78             global lineNumber
79             line = inputFile.readline()
80             lineNumber += 1
81             while line.endswith("\\\n"):
82                 line = line[:-2] + inputFile.readline()
83                 lineNumber += 1
84             if line == "":
85                 if token == None:
86                     fatal("unexpected end of input")
87                 token = None
88                 return False
89
90 n_errors = 0
91 def error(msg):
92     global n_errors
93     sys.stderr.write("%s:%d: %s\n" % (fileName, lineNumber, msg))
94     n_errors += 1
95
96 def fatal(msg):
97     error(msg)
98     sys.exit(1)
99
100 def skipDirective():
101     getToken()
102     while token != '$':
103         getToken()
104
105 def isId(s):
106     return re.match(idRe + "$", s) != None
107
108 def forceId():
109     if not isId(token):
110         fatal("identifier expected")
111
112 def forceInteger():
113     if not re.match('[0-9]+$', token):
114         fatal("integer expected")
115
116 def match(t):
117     if token == t:
118         getToken()
119         return True
120     else:
121         return False
122
123 def forceMatch(t):
124     if not match(t):
125         fatal("%s expected" % t)
126
127 def parseTaggedName():
128     assert token in ('struct', 'union')
129     name = token
130     getToken()
131     forceId()
132     name = "%s %s" % (name, token)
133     getToken()
134     return name
135
136 def print_enum(tag, constants, storage_class):
137     print ("""
138 %(storage_class)sconst char *
139 %(tag)s_to_string(uint16_t value)
140 {
141     switch (value) {\
142 """ % {"tag": tag,
143        "bufferlen": len(tag) + 32,
144        "storage_class": storage_class})
145     for constant in constants:
146         print ("    case %s: return \"%s\";" % (constant, constant))
147     print ("""\
148     }
149     return NULL;
150 }\
151 """ % {"tag": tag})
152
153 def usage():
154     argv0 = os.path.basename(sys.argv[0])
155     print ('''\
156 %(argv0)s, for extracting OpenFlow error codes from header files
157 usage: %(argv0)s ERROR_HEADER VENDOR_HEADER
158
159 This program reads VENDOR_HEADER to obtain OpenFlow vendor (aka
160 experimenter IDs), then ERROR_HEADER to obtain OpenFlow error number.
161 It outputs a C source file for translating OpenFlow error codes into
162 strings.
163
164 ERROR_HEADER should point to lib/ofp-errors.h.
165 VENDOR_HEADER should point to include/openflow/openflow-common.h.
166 The output is suitable for use as lib/ofp-errors.inc.\
167 ''' % {"argv0": argv0})
168     sys.exit(0)
169
170 def extract_vendor_ids(fn):
171     global vendor_map
172     vendor_map = {}
173     vendor_loc = {}
174
175     open_file(fn)
176     while tryGetLine():
177         m = re.match(r'#define\s+([A-Z0-9_]+)_VENDOR_ID\s+(0x[0-9a-fA-F]+|[0-9]+)', line)
178         if not m:
179             continue
180
181         name = m.group(1)
182         id_ = int(m.group(2), 0)
183
184         if name in vendor_map:
185             error("%s: duplicate definition of vendor" % name)
186             sys.stderr.write("%s: Here is the location of the previous "
187                              "definition.\n" % vendor_loc[name])
188             sys.exit(1)
189
190         vendor_map[name] = id_
191         vendor_loc[name] = "%s:%d" % (fileName, lineNumber)
192
193     if not vendor_map:
194         fatal("%s: no vendor definitions found" % fn)
195
196     inputFile.close()
197
198     vendor_reverse_map = {}
199     for name, id_ in vendor_map.items():
200         if id_ in vendor_reverse_map:
201             fatal("%s: duplicate vendor id for vendors %s and %s"
202                   % (id_, vendor_reverse_map[id_], name))
203         vendor_reverse_map[id_] = name
204
205 def extract_ofp_errors(fn):
206     error_types = {}
207
208     comments = []
209     names = []
210     domain = {}
211     reverse = {}
212     for domain_name in version_map.values():
213         domain[domain_name] = {}
214         reverse[domain_name] = {}
215
216     n_errors = 0
217     expected_errors = {}
218
219     open_file(fn)
220
221     while True:
222         getLine()
223         if re.match('enum ofperr', line):
224             break
225
226     while True:
227         getLine()
228         if line.startswith('/*') or not line or line.isspace():
229             continue
230         elif re.match('}', line):
231             break
232
233         if not line.lstrip().startswith('/*'):
234             fatal("unexpected syntax between errors")
235
236         comment = line.lstrip()[2:].strip()
237         while not comment.endswith('*/'):
238             getLine()
239             if line.startswith('/*') or not line or line.isspace():
240                 fatal("unexpected syntax within error")
241             comment += ' %s' % line.lstrip('* \t').rstrip(' \t\r\n')
242         comment = comment[:-2].rstrip()
243
244         m = re.match('Expected: (.*)\.$', comment)
245         if m:
246             expected_errors[m.group(1)] = (fileName, lineNumber)
247             continue
248
249         m = re.match('((?:.(?!\.  ))+.)\.  (.*)$', comment)
250         if not m:
251             fatal("unexpected syntax between errors")
252
253         dsts, comment = m.groups()
254
255         getLine()
256         m = re.match('\s+(?:OFPERR_([A-Z0-9_]+))(\s*=\s*OFPERR_OFS)?,',
257                      line)
258         if not m:
259             fatal("syntax error expecting enum value")
260
261         enum = m.group(1)
262         if enum in names:
263             fatal("%s specified twice" % enum)
264
265         comments.append(re.sub('\[[^]]*\]', '', comment))
266         names.append(enum)
267
268         for dst in dsts.split(', '):
269             m = re.match(r'([A-Z]+)([0-9.]+)(\+|-[0-9.]+)?\((\d+)(?:,(\d+))?\)$', dst)
270             if not m:
271                 fatal("%r: syntax error in destination" % dst)
272             vendor_name = m.group(1)
273             version1_name = m.group(2)
274             version2_name = m.group(3)
275             type_ = int(m.group(4))
276             if m.group(5):
277                 code = int(m.group(5))
278             else:
279                 code = None
280
281             if vendor_name not in vendor_map:
282                 fatal("%s: unknown vendor" % vendor_name)
283             vendor = vendor_map[vendor_name]
284
285             if version1_name not in version_map:
286                 fatal("%s: unknown OpenFlow version" % version1_name)
287             v1 = version_map[version1_name]
288
289             if version2_name is None:
290                 v2 = v1
291             elif version2_name == "+":
292                 v2 = max(version_map.values())
293             elif version2_name[1:] not in version_map:
294                 fatal("%s: unknown OpenFlow version" % version2_name[1:])
295             else:
296                 v2 = version_map[version2_name[1:]]
297
298             if v2 < v1:
299                 fatal("%s%s: %s precedes %s"
300                       % (version1_name, version2_name,
301                          version2_name, version1_name))
302
303             if vendor == vendor_map['NX']:
304                 if v1 >= version_map['1.2'] or v2 >= version_map['1.2']:
305                     if code is not None:
306                         fatal("%s: NX1.2+ domains do not have codes" % dst)
307                     code = 0
308             elif vendor != vendor_map['OF']:
309                 if code is not None:
310                     fatal("%s: %s domains do not have codes" % vendor_name)
311
312             for version in range(v1, v2 + 1):
313                 domain[version].setdefault(vendor, {})
314                 domain[version][vendor].setdefault(type_, {})
315                 if code in domain[version][vendor][type_]:
316                     msg = "%#x,%d,%d in OF%s means both %s and %s" % (
317                         vendor, type_, code, version_reverse_map[version],
318                         domain[version][vendor][type_][code][0], enum)
319                     if msg in expected_errors:
320                         del expected_errors[msg]
321                     else:
322                         error("%s: %s." % (dst, msg))
323                         sys.stderr.write("%s:%d: %s: Here is the location "
324                                          "of the previous definition.\n"
325                                          % (domain[version][vendor][type_][code][1],
326                                             domain[version][vendor][type_][code][2],
327                                             dst))
328                 else:
329                     domain[version][vendor][type_][code] = (enum, fileName,
330                                                    lineNumber)
331
332                 assert enum not in reverse[version]
333                 reverse[version][enum] = (vendor, type_, code)
334
335     inputFile.close()
336
337     for fn, ln in expected_errors.values():
338         sys.stderr.write("%s:%d: expected duplicate not used.\n" % (fn, ln))
339         n_errors += 1
340
341     if n_errors:
342         sys.exit(1)
343
344     print ("""\
345 /* Generated automatically; do not modify!     -*- buffer-read-only: t -*- */
346
347 #define OFPERR_N_ERRORS %d
348
349 struct ofperr_domain {
350     const char *name;
351     uint8_t version;
352     enum ofperr (*decode)(uint32_t vendor, uint16_t type, uint16_t code);
353     struct triplet errors[OFPERR_N_ERRORS];
354 };
355
356 static const char *error_names[OFPERR_N_ERRORS] = {
357 %s
358 };
359
360 static const char *error_comments[OFPERR_N_ERRORS] = {
361 %s
362 };\
363 """ % (len(names),
364        '\n'.join('    "%s",' % name for name in names),
365        '\n'.join('    "%s",' % re.sub(r'(["\\])', r'\\\1', comment)
366                  for comment in comments)))
367
368     def output_domain(map, name, description, version):
369         print ("""
370 static enum ofperr
371 %s_decode(uint32_t vendor, uint16_t type, uint16_t code)
372 {
373     switch (((uint64_t) vendor << 32) | (type << 16) | code) {""" % name)
374         found = set()
375         for enum in names:
376             if enum not in map:
377                 continue
378             vendor, type_, code = map[enum]
379             if code is None:
380                 continue
381             value = (vendor << 32) | (type_ << 16) | code
382             if value in found:
383                 continue
384             found.add(value)
385             if vendor:
386                 vendor_s = "(%#xULL << 32) | " % vendor
387             else:
388                 vendor_s = ""
389             print ("    case %s(%d << 16) | %d:" % (vendor_s, type_, code))
390             print ("        return OFPERR_%s;" % enum)
391         print ("""\
392     }
393
394     return 0;
395 }""")
396
397         print ("""
398 static const struct ofperr_domain %s = {
399     "%s",
400     %d,
401     %s_decode,
402     {""" % (name, description, version, name))
403         for enum in names:
404             if enum in map:
405                 vendor, type_, code = map[enum]
406                 if code == None:
407                     code = -1
408                 print "        { %#8x, %2d, %3d }, /* %s */" % (vendor, type_, code, enum)
409             else:
410                 print ("        {       -1, -1,  -1 }, /* %s */" % enum)
411         print ("""\
412     },
413 };""")
414
415     for version_name, id_ in version_map.items():
416         var = 'ofperr_of' + re.sub('[^A-Za-z0-9_]', '', version_name)
417         description = "OpenFlow %s" % version_name
418         output_domain(reverse[id_], var, description, id_)
419
420 if __name__ == '__main__':
421     if '--help' in sys.argv:
422         usage()
423     elif len(sys.argv) != 3:
424         sys.stderr.write("exactly two non-options arguments required; "
425                          "use --help for help\n")
426         sys.exit(1)
427     else:
428         extract_vendor_ids(sys.argv[2])
429         extract_ofp_errors(sys.argv[1])