blob: f0658743ae69e55505bd102d6543434a4562385b [file] [log] [blame]
Dan Talaycof75360a2010-02-05 22:22:54 -08001#!/usr/bin/python
2#
3# This python script generates wrapper functions for OpenFlow messages
4#
5# See the doc string below for more info
6#
7
8# To do:
9# Default type values for messages
10# Generate all message objects
11# Action list objects?
12# Autogen lengths when possible
13# Dictionaries for enum strings
14# Resolve sub struct initializers (see ofp_flow_mod)
15
16
17"""
18Generate wrapper classes for OpenFlow messages
19
20(C) Copyright Stanford University
21Date February 2010
22Created by dtalayco
23
24Attempting to follow http://www.python.org/dev/peps/pep-0008/
25The main exception is that our class names do not use CamelCase
26so as to more closely match the original C code names.
27
28This file is meant to generate a file of_wrapper.py which imports
29the base classes generated form automatic processing of openflow.h
30and produces wrapper classes for each OpenFlow message type.
31
32This file will normally be included in of_message.py which provides
33additional hand-generated work.
34
35There are two types of structures/classes here: base components and
36message classes.
37
38Base components are the base data classes which are fixed
39length structures including:
40 ofp_header
41 Each ofp_action structure
42 ofp_phy_port
43 The array elements of all the stats reply messages
44The base components are to be imported from a file of_header.py.
45
46Message classes define a complete message on the wire. These are
47comprised of possibly variable length lists of possibly variably
48typed objects from the base component list above.
49
50Each OpenFlow message has a header and zero or more fixed length
51members (the "core members" of the class) followed by zero or more
52variable length lists.
53
54The wrapper classes should live in their own name space, probably
55of_message. Automatically generated base component and skeletons for
56the message classes are assumed generated and the wrapper classes
57will inherit from those.
58
59Every message class must implement pack and unpack functions to
60convert between the class and a string representing what goes on the
61wire.
62
63For unpacking, the low level (base-component) classes must implement
64their own unpack functions. A single top level unpack function
65will do the parsing and call the lower layer unpack functions as
66appropriate.
67
68Every base and message class should implement a show function to
69(recursively) display the contents of the object.
70
71Certain OpenFlow message types are further subclassed. These include
72stats_request, stats_reply and error.
73
74"""
75
76# Don't generate header object in messages
77# Map each message to a body that doesn't include the header
78# The body has does not include variable length info at the end
79
80import re
81import string
82import sys
83
84message_top_matter = """
85# Python OpenFlow message wrapper classes
86
87from ofp import *
88from action_list import action_list
89
90# This will never happen; done to avoid lint warning
91if __name__ == '__main__':
92 def of_message_parse(msg): return None
93
94# Define templates for documentation
95class ofp_template_msg:
96 \"""
97 Sample base class for template_msg; normally auto generated
98 This class should live in the of_header name space and provides the
99 base class for this type of message. It will be wrapped for the
100 high level API.
101
102 \"""
103 def __init__(self):
104 \"""
105 Constructor for base class
106
107 \"""
108 self.header = ofp_header()
109 # Additional base data members declared here
110 # Normally will define pack, unpack, __len__ functions
111
112class template_msg(ofp_template_msg):
113 \"""
114 Sample class wrapper for template_msg
115 This class should live in the of_message name space and provides the
116 high level API for an OpenFlow message object. These objects must
117 implement the functions indicated in this template.
118
119 \"""
120 def __init__(self):
121 \"""
122 Constructor
123 Must set the header type value appropriately for the message
124
125 \"""
126 ofp_template_msg.__init__(self)
127 # For a real message, will be set to an integer
128 self.header.type = "TEMPLATE_MSG_VALUE"
129 def pack(self):
130 \"""
131 Pack object into string
132
133 @return The packed string which can go on the wire
134
135 \"""
136 pass
137 def unpack(self, binary_string):
138 \"""
139 Unpack object from a binary string
140
141 @param binary_string The wire protocol byte string holding the object
142 represented as an array of bytes.
143
144 @return Typically returns the remainder of binary_string that
145 was not parsed. May give a warning if that string is non-empty
146
147 \"""
148 pass
149 def __len__(self):
150 \"""
151 Return the length of this object once packed into a string
152
153 @return An integer representing the number bytes in the packed
154 string.
155
156 \"""
157 pass
158 def show(self, prefix=''):
159 \"""
160 Display the contents of the object in a readable manner
161
162 @param prefix Printed at the beginning of each line.
163
164 \"""
165 pass
166 def __eq__(self, other):
167 \"""
168 Return True if self and other hold the same data
169
170 @param other Other object in comparison
171
172 \"""
173 pass
174 def __ne__(self, other):
175 \"""
176 Return True if self and other do not hold the same data
177
178 @param other Other object in comparison
179
180 \"""
181 pass
182"""
183
184# Dictionary mapping wrapped classes to the auto-generated structure
185# underlieing the class (body only, not header or var-length data)
186message_class_map = {
187 "hello" : "ofp_header",
188 "error" : "ofp_error_msg",
189 "echo_request" : "ofp_header",
190 "echo_reply" : "ofp_header",
191 "vendor" : "ofp_vendor_header",
192 "features_request" : "ofp_header",
193 "features_reply" : "ofp_switch_features",
194 "get_config_request" : "ofp_header",
195 "get_config_reply" : "ofp_switch_config",
196 "set_config" : "ofp_switch_config",
197 "packet_in" : "ofp_packet_in",
198 "flow_removed" : "ofp_flow_removed",
199 "port_status" : "ofp_port_status",
200 "packet_out" : "ofp_packet_out",
201 "flow_mod" : "ofp_flow_mod",
202 "port_mod" : "ofp_port_mod",
203 "stats_request" : "ofp_stats_request",
204 "stats_reply" : "ofp_stats_reply",
205 "barrier_request" : "ofp_header",
206 "barrier_reply" : "ofp_header",
207 "queue_get_config_request" : "ofp_queue_get_config_request",
208 "queue_get_config_reply" : "ofp_queue_get_config_reply"
209}
210
211# These messages have a string member at the end of the data
212string_members = [
213 "hello",
214 "error",
215 "echo_request",
216 "echo_reply",
217 "vendor",
218 "packet_in",
219 "packet_out"
220]
221
222# These messages have a list (with the given name) in the data,
223# after the core members; the type is given for validation
224list_members = {
225 "features_reply" : ('ports', None),
226 "packet_out" : ('actions', 'action_list'),
227 "flow_mod" : ('actions', 'action_list'),
228 "queue_get_config_reply" : ('queues', None)
229}
230
231_ind = " "
232
233def _p1(s): print _ind + s
234def _p2(s): print _ind * 2 + s
235def _p3(s): print _ind * 3 + s
236def _p4(s): print _ind * 4 + s
237
238# Okay, this gets kind of ugly:
239# There are three variables:
240# has_core_members: If parent class is not ofp_header, has inheritance
241# has_list: Whether class has trailing array or class
242# has_string: Whether class has trailing string
243
244def gen_message_wrapper(msg):
245 """
246 Generate a wrapper for the given message based on above info
247 @param msg String identifying the message name for the class
248 """
249
250 msg_name = "OFPT_" + msg.upper()
251 parent = message_class_map[msg]
252
253 has_list = False # Has trailing list
254 has_core_members = False
255 has_string = False # Has trailing string
256 if parent != 'ofp_header':
257 has_core_members = True
258 if msg in list_members.keys():
259 (list_var, list_type) = list_members[msg]
260 has_list = True
261 if msg in string_members:
262 has_string = True
263
264 if has_core_members:
265 print "class " + msg + "(" + parent + "):"
266 else:
267 print "class " + msg + ":"
268 _p1('"""')
269 _p1("Wrapper class for " + msg)
270 print
271 if has_list:
272 if list_type == None:
273 _p1("Has trailing variable array " + list_var);
274 else:
275 _p1("Has trailing object " + list_var + " of type " + list_type);
276 print
277 if has_string:
278 _p1("Has trailing string data")
279 print
280 _p1('"""')
281
282 print
283 _p1("def __init__(self):")
284 if has_core_members:
285 _p2(parent + ".__init__(self)")
286 _p2("self.header = ofp_header()")
287 _p2("self.header.type = " + msg_name)
288 if has_list:
289 if list_type == None:
290 _p2('self.' + list_var + ' = []')
291 else:
292 _p2('self.' + list_var + ' = ' + list_type + '()')
293 if has_string:
294 _p2('self.data = ""')
295
296 print
297 _p1("def pack(self):")
298 _p2("# Fixme: Calculate length for header, etc, once __len__ fixed")
299 _p2("packed = self.header.pack()")
300 if has_core_members:
301 _p2("packed += " + parent + ".pack()")
302 if has_list:
303 if list_type == None:
304 _p2('for obj in self.' + list_var + ':')
305 _p3('packed += obj.pack()')
306 else:
307 _p2('packed += self.' + list_var + '.pack()')
308 if has_string:
309 _p2('packed += self.data')
310
311 print
312 _p1("def unpack(self, binary_string):")
313 _p2("binary_string = self.header.unpack(binary_string)")
314 if has_core_members:
315 _p2("binary_string = " + parent + ".unpack(self, binary_string)")
316 if has_list:
317 if list_type == None:
318 _p2("for obj in self." + list_var + ":")
319 _p3("binary_string = obj.unpack(binary_string)")
320 elif msg == "packet_out": # Special case this
321 _p2('binary_string = self.actions.unpack(bytes=self.actions_len)')
322 elif msg == "flow_mod": # Special case this
323 _p2("ai_len = self.header.length - OFP_FLOW_MOD_BYTES")
324 _p2("binary_string = self.actions.unpack(bytes=ai_len)")
325 else:
326 _p2("binary_string = self." + list_var + ".unpack(binary_string)")
327 if has_string:
328 _p2("self.data = binary_string")
329 _p2("binary_string = ''")
330 else:
331 _p2("# Fixme: If no self.data, add check for data remaining")
332 _p2("return binary_string")
333
334 print
335 _p1("def __len__(self):")
336 _p2("# Fixme: Do the right thing")
337 _p2("return len(self.pack())")
338
339 print
340 _p1("def show(self, prefix=''):")
341 _p2("print prefix + '" + msg + " (" + msg_name + ")'")
342 _p2("prefix += ' '")
343 _p2("self.header.show(prefix)")
344 if has_core_members:
345 _p2(parent + ".show(self, prefix)")
346 if has_list:
347 if list_type == None:
348 _p2('print prefix + "Array ' + list_var + '"')
349 _p2('for obj in self.' + list_var +':')
350 _p3("obj.show(prefix + ' ')")
351 else:
352 _p2('print prefix + "List ' + list_var + '"')
353 _p2('self.' + list_var + ".show(prefix + ' ')")
354 if has_string:
355 _p2("print prefix + 'data is of length ' + str(len(self.data))")
356 _p2("if len(self.data) > 0:")
357 _p3("obj = of_message_parse(self.data)")
358 _p3("if obj != None:")
359 _p4("obj.show(prefix)")
360 _p3("else:")
361 _p4('print prefix + "Unable to parse data"')
362
363 print
364 _p1("def __eq__(self, other):")
365 _p2("if type(self) != type (other): return False")
366 _p2("if self.header.__ne__(other.header): return False")
367 if has_core_members:
368 _p2("if " + parent + ".__ne__(other." + parent + "): return False")
369 if has_string:
370 _p2("if self.data != other.data: return False")
371 if has_list:
372 _p2("if self." + list_var + " != other." + list_var + ": return False")
373 _p2("return True")
374
375 print
376 _p1("def __ne__(self, other): return not self.__eq__(other)")
377
378
379
380################################################################
381#
382# Stats request subclasses
383# description_request, flow, aggregate, table, port, vendor
384#
385################################################################
386
387# table and desc stats requests are special with empty body
388extra_ofp_stats_req_defs = """
389# Stats request bodies for desc and table stats are not defined in the
390# OpenFlow header; We define them here. They are empty classes, really
391
392class ofp_desc_stats_request:
393 \"""
394 Forced definition of ofp_desc_stats_request (empty class)
395 \"""
396 def __init__(self):
397 pass
398 def pack(self, assertstruct=True):
399 return ""
400 def unpack(self, binary_string):
401 return binary_string
402 def __len__(self):
403 return 0
404 def show(self, prefix=''):
405 pass
406 def __eq__(self, other):
407 return type(self) == type(other)
408 def __ne__(self, other):
409 return type(self) != type(other)
410
411OFP_DESC_STATS_REQUEST_BYTES = 0
412
413class ofp_table_stats_request:
414 \"""
415 Forced definition of ofp_table_stats_request (empty class)
416 \"""
417 def __init__(self):
418 pass
419 def pack(self, assertstruct=True):
420 return ""
421 def unpack(self, binary_string):
422 return binary_string
423 def __len__(self):
424 return 0
425 def show(self, prefix=''):
426 pass
427 def __eq__(self, other):
428 return type(self) == type(other)
429 def __ne__(self, other):
430 return type(self) != type(other)
431
432OFP_TABLE_STATS_REQUEST_BYTES = 0
433
434"""
435
436stats_request_template = """
437class --TYPE--_stats_request(ofp_stats_request, ofp_--TYPE--_stats_request):
438 \"""
439 Wrapper class for --TYPE-- stats request message
440 \"""
441 def __init__(self):
442 self.header = ofp_header()
443 ofp_stats_request.__init__(self)
444 ofp_--TYPE--_stats_request.__init__(self)
445 self.header.type = OFPT_STATS_REQUEST
446 self.type = --STATS_NAME--
447
448 def pack(self, assertstruct=True):
449 packed = ofp_stats_request.pack(self)
450 packed += ofp_--TYPE--_stats_request.pack(self)
451
452 def unpack(self, binary_string):
453 binary_string = ofp_stats_request.unpack(self, binary_string)
454 binary_string = ofp_--TYPE--_stats_request.unpack(self, binary_string)
455 if len(binary_string) != 0:
456 print "Error unpacking --TYPE--: extra data"
457 return binary_string
458
459 def __len__(self):
460 return len(self.header) + OFP_STATS_REQUEST_BYTES + \\
461 OFP_--TYPE_UPPER--_STATS_REQUEST_BYTES
462
463 def show(self, prefix=''):
464 print prefix + "--TYPE--_stats_request"
465 ofp_stats_request.show(self)
466 ofp_--TYPE--_stats_request.show(self)
467
468 def __eq__(self, other):
469 return (ofp_stats_request.__eq__(self, other) and
470 ofp_--TYPE--_stats_request.__eq__(self, other))
471
472 def __ne__(self, other): return not self.__eq__(other)
473"""
474
475################################################################
476#
477# Stats replies always have an array at the end.
478# For aggregate and desc, these arrays are always of length 1
479# This array is always called stats
480#
481################################################################
482
483
484# Template for objects stats reply messages
485stats_reply_template = """
486class --TYPE--_stats_reply(ofp_stats_reply):
487 \"""
488 Wrapper class for --TYPE-- stats reply
489 \"""
490 def __init__(self):
491 self.header = ofp_header()
492 ofp_stats_reply.__init__(self)
493 self.header.type = OFPT_STATS_REPLY
494 self.type = --STATS_NAME--
495 # stats: Array of type --TYPE--_stats_entry
496 self.stats = []
497
498 def pack(self, assertstruct=True):
499 packed = ofp_stats_reply.pack(self)
500 for obj in self.stats:
501 packed += obj.pack()
502
503 def unpack(self, binary_string):
504 binary_string = ofp_stats_reply.unpack(self, binary_string)
505 dummy = --TYPE--_stats_entry()
506 while len(binary_string) >= len(dummy):
507 obj = --TYPE--_stats_entry()
508 binary_string = obj.unpack(binary_string)
509 self.stats.append(obj)
510 if len(binary_string) != 0:
511 print "ERROR unpacking --TYPE-- stats string: extra bytes"
512 return binary_string
513
514 def __len__(self):
515 length = len(self.header) + OFP_STATS_REPLY_BYTES
516 for obj in self.stats:
517 length += len(obj)
518 return length
519
520 def show(self, prefix=''):
521 print prefix + "--TYPE--_stats_reply"
522 ofp_stats_reply.show(self)
523 for obj in self.stats:
524 obj.show()
525
526 def __eq__(self, other):
527 if ofp_stats_reply.__ne__(self, other): return False
528 return self.stats == other.stats
529
530 def __ne__(self, other): return not self.__eq__(other)
531"""
532
533#
534# To address variations in stats reply bodies, the following
535# "_entry" classes are defined for each element in the reply
536#
537
538extra_stats_entry_defs = """
539# Stats entries define the content of one element in a stats
540# reply for the indicated type; define _entry for consistency
541
542aggregate_stats_entry = ofp_aggregate_stats_reply
543desc_stats_entry = ofp_desc_stats
544port_stats_entry = ofp_port_stats
545queue_stats_entry = ofp_queue_stats
546table_stats_entry = ofp_table_stats
547"""
548
549# Special case flow_stats to handle actions_list
550
551flow_stats_entry_def = """
552#
553# Flow stats entry contains an action list of variable length, so
554# it is done by hand
555#
556
557class flow_stats_entry(ofp_flow_stats):
558 \"""
559 Special case flow stats entry to handle action list object
560 \"""
561 def __init__(self):
562 ofp_flow_stats.__init__(self)
563 self.actions = action_list()
564
565 def pack(self, assertstruct=True):
566 if self.length < OFP_FLOW_STATS_BYTES:
567 print "ERROR in flow_stats_entry pack: length member is too small"
568 return None
569 packed = ofp_flow_stats.pack(self, assertstruct)
570 packed += self.actions.pack()
571 return packed
572
573 def unpack(self, binary_string):
574 binary_string = ofp_flow_stats.unpack(self, binary_string)
575 ai_len = self.length - OFP_FLOW_STATS_BYTES
576 binary_string = self.actions.unpack(binary_string, bytes=ai_len)
577 return binary_string
578
579 def __len__(self):
580 return OFP_FLOW_STATS_BYTES + len(self.actions)
581
582 def show(self, prefix=''):
583 print prefix + "flow_stats_entry"
584 ofp_flow_stats.show(self, prefix + ' ')
585 self.actions.show(prefix + ' ')
586
587 def __eq__(self, other):
588 return (ofp_flow_stats.__eq__(self, other) and
589 self.actions == other.actions)
590
591 def __ne__(self, other): return not self.__eq__(other)
592"""
593
594stats_types = [
595 'aggregate',
596 'desc',
597 'flow',
598 'port',
599 'queue',
600 'table']
601
602if __name__ == '__main__':
603
604 print message_top_matter
605
606 print """
607################################################################
608#
609# OpenFlow Message Definitions
610#
611################################################################
612"""
613
614 msg_types = message_class_map.keys()
615 msg_types.sort()
616
617 for t in msg_types:
618 gen_message_wrapper(t)
619 print
620
621 print """
622################################################################
623#
624# Stats request and reply subclass definitions
625#
626################################################################
627"""
628
629 print extra_ofp_stats_req_defs
630 print extra_stats_entry_defs
631 print flow_stats_entry_def
632
633 # Generate stats request and reply subclasses
634 for t in stats_types:
635 stats_name = "OFPST_" + t.upper()
636 to_print = re.sub('--TYPE--', t, stats_request_template)
637 to_print = re.sub('--TYPE_UPPER--', t.upper(), to_print)
638 to_print = re.sub('--STATS_NAME--', stats_name, to_print)
639 print to_print
640 to_print = re.sub('--TYPE--', t, stats_reply_template)
641 to_print = re.sub('--STATS_NAME--', stats_name, to_print)
642 print to_print
643
644
645#
646# OFP match variants
647# ICMP 0x801 (?) ==> icmp_type/code replace tp_src/dst
648#
649
650