blob: 61aada82fb7c52fccb09dbc5e449b2212718844a [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
Dan Talaycoac1cb812010-02-06 20:34:18 -080083sys.path.append("../../src/python/oftest/ofmsg")
84from ofp import *
85from ofp_aux import class_to_members_map
Dan Talaycof75360a2010-02-05 22:22:54 -080086
87message_top_matter = """
88# Python OpenFlow message wrapper classes
89
90from ofp import *
91from action_list import action_list
92
93# This will never happen; done to avoid lint warning
94if __name__ == '__main__':
95 def of_message_parse(msg): return None
96
97# Define templates for documentation
98class ofp_template_msg:
99 \"""
100 Sample base class for template_msg; normally auto generated
101 This class should live in the of_header name space and provides the
102 base class for this type of message. It will be wrapped for the
103 high level API.
104
105 \"""
106 def __init__(self):
107 \"""
108 Constructor for base class
109
110 \"""
111 self.header = ofp_header()
112 # Additional base data members declared here
Dan Talaycoac1cb812010-02-06 20:34:18 -0800113
Dan Talaycof75360a2010-02-05 22:22:54 -0800114 # Normally will define pack, unpack, __len__ functions
115
116class template_msg(ofp_template_msg):
117 \"""
118 Sample class wrapper for template_msg
119 This class should live in the of_message name space and provides the
120 high level API for an OpenFlow message object. These objects must
121 implement the functions indicated in this template.
122
123 \"""
124 def __init__(self):
125 \"""
126 Constructor
127 Must set the header type value appropriately for the message
128
129 \"""
Dan Talaycoac1cb812010-02-06 20:34:18 -0800130
131 ##@var header
132 # OpenFlow message header: length, version, xid, type
Dan Talaycof75360a2010-02-05 22:22:54 -0800133 ofp_template_msg.__init__(self)
Dan Talaycoac1cb812010-02-06 20:34:18 -0800134 self.header = ofp_header()
Dan Talaycof75360a2010-02-05 22:22:54 -0800135 # For a real message, will be set to an integer
136 self.header.type = "TEMPLATE_MSG_VALUE"
137 def pack(self):
138 \"""
139 Pack object into string
140
141 @return The packed string which can go on the wire
142
143 \"""
144 pass
145 def unpack(self, binary_string):
146 \"""
147 Unpack object from a binary string
148
149 @param binary_string The wire protocol byte string holding the object
150 represented as an array of bytes.
151
152 @return Typically returns the remainder of binary_string that
153 was not parsed. May give a warning if that string is non-empty
154
155 \"""
156 pass
157 def __len__(self):
158 \"""
159 Return the length of this object once packed into a string
160
161 @return An integer representing the number bytes in the packed
162 string.
163
164 \"""
165 pass
166 def show(self, prefix=''):
167 \"""
168 Display the contents of the object in a readable manner
169
170 @param prefix Printed at the beginning of each line.
171
172 \"""
173 pass
174 def __eq__(self, other):
175 \"""
176 Return True if self and other hold the same data
177
178 @param other Other object in comparison
179
180 \"""
181 pass
182 def __ne__(self, other):
183 \"""
184 Return True if self and other do not hold the same data
185
186 @param other Other object in comparison
187
188 \"""
189 pass
190"""
191
192# Dictionary mapping wrapped classes to the auto-generated structure
193# underlieing the class (body only, not header or var-length data)
194message_class_map = {
195 "hello" : "ofp_header",
196 "error" : "ofp_error_msg",
197 "echo_request" : "ofp_header",
198 "echo_reply" : "ofp_header",
199 "vendor" : "ofp_vendor_header",
200 "features_request" : "ofp_header",
201 "features_reply" : "ofp_switch_features",
202 "get_config_request" : "ofp_header",
203 "get_config_reply" : "ofp_switch_config",
204 "set_config" : "ofp_switch_config",
205 "packet_in" : "ofp_packet_in",
206 "flow_removed" : "ofp_flow_removed",
207 "port_status" : "ofp_port_status",
208 "packet_out" : "ofp_packet_out",
209 "flow_mod" : "ofp_flow_mod",
210 "port_mod" : "ofp_port_mod",
211 "stats_request" : "ofp_stats_request",
212 "stats_reply" : "ofp_stats_reply",
213 "barrier_request" : "ofp_header",
214 "barrier_reply" : "ofp_header",
215 "queue_get_config_request" : "ofp_queue_get_config_request",
216 "queue_get_config_reply" : "ofp_queue_get_config_reply"
217}
218
219# These messages have a string member at the end of the data
220string_members = [
221 "hello",
222 "error",
223 "echo_request",
224 "echo_reply",
225 "vendor",
226 "packet_in",
227 "packet_out"
228]
229
230# These messages have a list (with the given name) in the data,
231# after the core members; the type is given for validation
232list_members = {
233 "features_reply" : ('ports', None),
234 "packet_out" : ('actions', 'action_list'),
235 "flow_mod" : ('actions', 'action_list'),
236 "queue_get_config_reply" : ('queues', None)
237}
238
239_ind = " "
240
241def _p1(s): print _ind + s
242def _p2(s): print _ind * 2 + s
243def _p3(s): print _ind * 3 + s
244def _p4(s): print _ind * 4 + s
245
246# Okay, this gets kind of ugly:
247# There are three variables:
248# has_core_members: If parent class is not ofp_header, has inheritance
249# has_list: Whether class has trailing array or class
250# has_string: Whether class has trailing string
251
252def gen_message_wrapper(msg):
253 """
254 Generate a wrapper for the given message based on above info
255 @param msg String identifying the message name for the class
256 """
257
258 msg_name = "OFPT_" + msg.upper()
259 parent = message_class_map[msg]
260
261 has_list = False # Has trailing list
262 has_core_members = False
263 has_string = False # Has trailing string
264 if parent != 'ofp_header':
265 has_core_members = True
266 if msg in list_members.keys():
267 (list_var, list_type) = list_members[msg]
268 has_list = True
269 if msg in string_members:
270 has_string = True
271
272 if has_core_members:
273 print "class " + msg + "(" + parent + "):"
274 else:
275 print "class " + msg + ":"
276 _p1('"""')
277 _p1("Wrapper class for " + msg)
278 print
Dan Talaycoac1cb812010-02-06 20:34:18 -0800279 _p1("OpenFlow message header: length, version, xid, type")
280 _p1("@arg length: The total length of the message")
281 _p1("@arg version: The OpenFlow version (" + str(OFP_VERSION) + ")")
282 _p1("@arg xid: The transaction ID")
283 _p1("@arg type: The message type (" + msg_name + "=" +
284 str(eval(msg_name)) + ")")
285 print
286 if has_core_members and parent in class_to_members_map.keys():
287 _p1("Data members inherited from " + parent + ":")
288 for var in class_to_members_map[parent]:
289 _p1("@arg " + var)
Dan Talaycof75360a2010-02-05 22:22:54 -0800290 if has_list:
291 if list_type == None:
Dan Talaycoac1cb812010-02-06 20:34:18 -0800292 _p1("@arg " + list_var + ": Variable length array of TBD")
Dan Talaycof75360a2010-02-05 22:22:54 -0800293 else:
Dan Talaycoac1cb812010-02-06 20:34:18 -0800294 _p1("@arg " + list_var + ": Object of type " + list_type);
Dan Talaycof75360a2010-02-05 22:22:54 -0800295 if has_string:
Dan Talaycoac1cb812010-02-06 20:34:18 -0800296 _p1("@arg data: Binary string following message members")
297 print
Dan Talaycof75360a2010-02-05 22:22:54 -0800298 _p1('"""')
299
300 print
301 _p1("def __init__(self):")
302 if has_core_members:
303 _p2(parent + ".__init__(self)")
Dan Talaycoac1cb812010-02-06 20:34:18 -0800304 _p2("##@var header")
305 _p2("# OpenFlow message header: length, version, xid, type")
306 _p2("# @arg length: The total length of the message")
307 _p2("# @arg version: The OpenFlow version (" + str(OFP_VERSION) + ")")
308 _p2("# @arg xid: The transaction ID")
309 _p2("# @arg type: The message type (" + msg_name + "=" +
310 str(eval(msg_name)) + ")")
311 print
312 if has_list:
313 _p2("##@var " + list_var)
314 if list_type == None:
315 _p2("# Array of objects of type TBD")
316 else:
317 _p2("# Object of type " + list_type)
318 print
319 if has_string:
320 _p2("##@var data")
321 _p2("# Binary string following message members")
322 print
323
Dan Talaycof75360a2010-02-05 22:22:54 -0800324 _p2("self.header = ofp_header()")
325 _p2("self.header.type = " + msg_name)
326 if has_list:
327 if list_type == None:
328 _p2('self.' + list_var + ' = []')
329 else:
330 _p2('self.' + list_var + ' = ' + list_type + '()')
331 if has_string:
332 _p2('self.data = ""')
333
334 print
335 _p1("def pack(self):")
336 _p2("# Fixme: Calculate length for header, etc, once __len__ fixed")
337 _p2("packed = self.header.pack()")
338 if has_core_members:
339 _p2("packed += " + parent + ".pack()")
340 if has_list:
341 if list_type == None:
342 _p2('for obj in self.' + list_var + ':')
343 _p3('packed += obj.pack()')
344 else:
345 _p2('packed += self.' + list_var + '.pack()')
346 if has_string:
347 _p2('packed += self.data')
348
349 print
350 _p1("def unpack(self, binary_string):")
351 _p2("binary_string = self.header.unpack(binary_string)")
352 if has_core_members:
353 _p2("binary_string = " + parent + ".unpack(self, binary_string)")
354 if has_list:
355 if list_type == None:
356 _p2("for obj in self." + list_var + ":")
357 _p3("binary_string = obj.unpack(binary_string)")
358 elif msg == "packet_out": # Special case this
359 _p2('binary_string = self.actions.unpack(bytes=self.actions_len)')
360 elif msg == "flow_mod": # Special case this
361 _p2("ai_len = self.header.length - OFP_FLOW_MOD_BYTES")
362 _p2("binary_string = self.actions.unpack(bytes=ai_len)")
363 else:
364 _p2("binary_string = self." + list_var + ".unpack(binary_string)")
365 if has_string:
366 _p2("self.data = binary_string")
367 _p2("binary_string = ''")
368 else:
369 _p2("# Fixme: If no self.data, add check for data remaining")
370 _p2("return binary_string")
371
372 print
373 _p1("def __len__(self):")
374 _p2("# Fixme: Do the right thing")
375 _p2("return len(self.pack())")
376
377 print
378 _p1("def show(self, prefix=''):")
379 _p2("print prefix + '" + msg + " (" + msg_name + ")'")
380 _p2("prefix += ' '")
381 _p2("self.header.show(prefix)")
382 if has_core_members:
383 _p2(parent + ".show(self, prefix)")
384 if has_list:
385 if list_type == None:
386 _p2('print prefix + "Array ' + list_var + '"')
387 _p2('for obj in self.' + list_var +':')
388 _p3("obj.show(prefix + ' ')")
389 else:
390 _p2('print prefix + "List ' + list_var + '"')
391 _p2('self.' + list_var + ".show(prefix + ' ')")
392 if has_string:
393 _p2("print prefix + 'data is of length ' + str(len(self.data))")
394 _p2("if len(self.data) > 0:")
395 _p3("obj = of_message_parse(self.data)")
396 _p3("if obj != None:")
397 _p4("obj.show(prefix)")
398 _p3("else:")
399 _p4('print prefix + "Unable to parse data"')
400
401 print
402 _p1("def __eq__(self, other):")
403 _p2("if type(self) != type (other): return False")
404 _p2("if self.header.__ne__(other.header): return False")
405 if has_core_members:
406 _p2("if " + parent + ".__ne__(other." + parent + "): return False")
407 if has_string:
408 _p2("if self.data != other.data: return False")
409 if has_list:
410 _p2("if self." + list_var + " != other." + list_var + ": return False")
411 _p2("return True")
412
413 print
414 _p1("def __ne__(self, other): return not self.__eq__(other)")
415
416
417
418################################################################
419#
420# Stats request subclasses
421# description_request, flow, aggregate, table, port, vendor
422#
423################################################################
424
425# table and desc stats requests are special with empty body
426extra_ofp_stats_req_defs = """
427# Stats request bodies for desc and table stats are not defined in the
428# OpenFlow header; We define them here. They are empty classes, really
429
430class ofp_desc_stats_request:
431 \"""
432 Forced definition of ofp_desc_stats_request (empty class)
433 \"""
434 def __init__(self):
435 pass
436 def pack(self, assertstruct=True):
437 return ""
438 def unpack(self, binary_string):
439 return binary_string
440 def __len__(self):
441 return 0
442 def show(self, prefix=''):
443 pass
444 def __eq__(self, other):
445 return type(self) == type(other)
446 def __ne__(self, other):
447 return type(self) != type(other)
448
449OFP_DESC_STATS_REQUEST_BYTES = 0
450
451class ofp_table_stats_request:
452 \"""
453 Forced definition of ofp_table_stats_request (empty class)
454 \"""
455 def __init__(self):
456 pass
457 def pack(self, assertstruct=True):
458 return ""
459 def unpack(self, binary_string):
460 return binary_string
461 def __len__(self):
462 return 0
463 def show(self, prefix=''):
464 pass
465 def __eq__(self, other):
466 return type(self) == type(other)
467 def __ne__(self, other):
468 return type(self) != type(other)
469
470OFP_TABLE_STATS_REQUEST_BYTES = 0
471
472"""
473
474stats_request_template = """
475class --TYPE--_stats_request(ofp_stats_request, ofp_--TYPE--_stats_request):
476 \"""
477 Wrapper class for --TYPE-- stats request message
478 \"""
479 def __init__(self):
480 self.header = ofp_header()
481 ofp_stats_request.__init__(self)
482 ofp_--TYPE--_stats_request.__init__(self)
483 self.header.type = OFPT_STATS_REQUEST
484 self.type = --STATS_NAME--
485
486 def pack(self, assertstruct=True):
487 packed = ofp_stats_request.pack(self)
488 packed += ofp_--TYPE--_stats_request.pack(self)
489
490 def unpack(self, binary_string):
491 binary_string = ofp_stats_request.unpack(self, binary_string)
492 binary_string = ofp_--TYPE--_stats_request.unpack(self, binary_string)
493 if len(binary_string) != 0:
494 print "Error unpacking --TYPE--: extra data"
495 return binary_string
496
497 def __len__(self):
498 return len(self.header) + OFP_STATS_REQUEST_BYTES + \\
499 OFP_--TYPE_UPPER--_STATS_REQUEST_BYTES
500
501 def show(self, prefix=''):
502 print prefix + "--TYPE--_stats_request"
503 ofp_stats_request.show(self)
504 ofp_--TYPE--_stats_request.show(self)
505
506 def __eq__(self, other):
507 return (ofp_stats_request.__eq__(self, other) and
508 ofp_--TYPE--_stats_request.__eq__(self, other))
509
510 def __ne__(self, other): return not self.__eq__(other)
511"""
512
513################################################################
514#
515# Stats replies always have an array at the end.
516# For aggregate and desc, these arrays are always of length 1
517# This array is always called stats
518#
519################################################################
520
521
522# Template for objects stats reply messages
523stats_reply_template = """
524class --TYPE--_stats_reply(ofp_stats_reply):
525 \"""
526 Wrapper class for --TYPE-- stats reply
527 \"""
528 def __init__(self):
529 self.header = ofp_header()
530 ofp_stats_reply.__init__(self)
531 self.header.type = OFPT_STATS_REPLY
532 self.type = --STATS_NAME--
533 # stats: Array of type --TYPE--_stats_entry
534 self.stats = []
535
536 def pack(self, assertstruct=True):
537 packed = ofp_stats_reply.pack(self)
538 for obj in self.stats:
539 packed += obj.pack()
540
541 def unpack(self, binary_string):
542 binary_string = ofp_stats_reply.unpack(self, binary_string)
543 dummy = --TYPE--_stats_entry()
544 while len(binary_string) >= len(dummy):
545 obj = --TYPE--_stats_entry()
546 binary_string = obj.unpack(binary_string)
547 self.stats.append(obj)
548 if len(binary_string) != 0:
549 print "ERROR unpacking --TYPE-- stats string: extra bytes"
550 return binary_string
551
552 def __len__(self):
553 length = len(self.header) + OFP_STATS_REPLY_BYTES
554 for obj in self.stats:
555 length += len(obj)
556 return length
557
558 def show(self, prefix=''):
559 print prefix + "--TYPE--_stats_reply"
560 ofp_stats_reply.show(self)
561 for obj in self.stats:
562 obj.show()
563
564 def __eq__(self, other):
565 if ofp_stats_reply.__ne__(self, other): return False
566 return self.stats == other.stats
567
568 def __ne__(self, other): return not self.__eq__(other)
569"""
570
571#
572# To address variations in stats reply bodies, the following
573# "_entry" classes are defined for each element in the reply
574#
575
576extra_stats_entry_defs = """
577# Stats entries define the content of one element in a stats
578# reply for the indicated type; define _entry for consistency
579
580aggregate_stats_entry = ofp_aggregate_stats_reply
581desc_stats_entry = ofp_desc_stats
582port_stats_entry = ofp_port_stats
583queue_stats_entry = ofp_queue_stats
584table_stats_entry = ofp_table_stats
585"""
586
587# Special case flow_stats to handle actions_list
588
589flow_stats_entry_def = """
590#
591# Flow stats entry contains an action list of variable length, so
592# it is done by hand
593#
594
595class flow_stats_entry(ofp_flow_stats):
596 \"""
597 Special case flow stats entry to handle action list object
598 \"""
599 def __init__(self):
600 ofp_flow_stats.__init__(self)
601 self.actions = action_list()
602
603 def pack(self, assertstruct=True):
604 if self.length < OFP_FLOW_STATS_BYTES:
605 print "ERROR in flow_stats_entry pack: length member is too small"
606 return None
607 packed = ofp_flow_stats.pack(self, assertstruct)
608 packed += self.actions.pack()
609 return packed
610
611 def unpack(self, binary_string):
612 binary_string = ofp_flow_stats.unpack(self, binary_string)
613 ai_len = self.length - OFP_FLOW_STATS_BYTES
614 binary_string = self.actions.unpack(binary_string, bytes=ai_len)
615 return binary_string
616
617 def __len__(self):
618 return OFP_FLOW_STATS_BYTES + len(self.actions)
619
620 def show(self, prefix=''):
621 print prefix + "flow_stats_entry"
622 ofp_flow_stats.show(self, prefix + ' ')
623 self.actions.show(prefix + ' ')
624
625 def __eq__(self, other):
626 return (ofp_flow_stats.__eq__(self, other) and
627 self.actions == other.actions)
628
629 def __ne__(self, other): return not self.__eq__(other)
630"""
631
632stats_types = [
633 'aggregate',
634 'desc',
635 'flow',
636 'port',
637 'queue',
638 'table']
639
640if __name__ == '__main__':
641
642 print message_top_matter
643
644 print """
645################################################################
646#
647# OpenFlow Message Definitions
648#
649################################################################
650"""
651
652 msg_types = message_class_map.keys()
653 msg_types.sort()
654
655 for t in msg_types:
656 gen_message_wrapper(t)
657 print
658
659 print """
660################################################################
661#
662# Stats request and reply subclass definitions
663#
664################################################################
665"""
666
667 print extra_ofp_stats_req_defs
668 print extra_stats_entry_defs
669 print flow_stats_entry_def
670
671 # Generate stats request and reply subclasses
672 for t in stats_types:
673 stats_name = "OFPST_" + t.upper()
674 to_print = re.sub('--TYPE--', t, stats_request_template)
675 to_print = re.sub('--TYPE_UPPER--', t.upper(), to_print)
676 to_print = re.sub('--STATS_NAME--', stats_name, to_print)
677 print to_print
678 to_print = re.sub('--TYPE--', t, stats_reply_template)
679 to_print = re.sub('--STATS_NAME--', stats_name, to_print)
680 print to_print
681
682
683#
684# OFP match variants
685# ICMP 0x801 (?) ==> icmp_type/code replace tp_src/dst
686#
687
688