blob: 34036d3070f784dbdf7bf22529c6dde9120b1a9d [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 Talaycod7e2dbe2010-02-13 21:51:15 -080083sys.path.append("../../src/python/oftest")
Dan Talaycob9cb5482010-02-09 15:23:12 -080084from cstruct import *
85from class_maps import class_to_members_map
Dan Talaycof75360a2010-02-05 22:22:54 -080086
87message_top_matter = """
88# Python OpenFlow message wrapper classes
89
Dan Talaycob9cb5482010-02-09 15:23:12 -080090from cstruct import *
Dan Talaycof75360a2010-02-05 22:22:54 -080091from action_list import action_list
Dan Talayco411489d2010-02-12 23:03:46 -080092from error import *
Dan Talaycof75360a2010-02-05 22:22:54 -080093
Dan Talaycof75360a2010-02-05 22:22:54 -080094# 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
Dan Talaycoac1cb812010-02-06 20:34:18 -0800110
Dan Talaycof75360a2010-02-05 22:22:54 -0800111 # Normally will define pack, unpack, __len__ functions
112
113class template_msg(ofp_template_msg):
114 \"""
115 Sample class wrapper for template_msg
116 This class should live in the of_message name space and provides the
117 high level API for an OpenFlow message object. These objects must
118 implement the functions indicated in this template.
119
120 \"""
121 def __init__(self):
122 \"""
123 Constructor
124 Must set the header type value appropriately for the message
125
126 \"""
Dan Talaycoac1cb812010-02-06 20:34:18 -0800127
128 ##@var header
129 # OpenFlow message header: length, version, xid, type
Dan Talaycof75360a2010-02-05 22:22:54 -0800130 ofp_template_msg.__init__(self)
Dan Talaycoac1cb812010-02-06 20:34:18 -0800131 self.header = ofp_header()
Dan Talaycof75360a2010-02-05 22:22:54 -0800132 # For a real message, will be set to an integer
133 self.header.type = "TEMPLATE_MSG_VALUE"
134 def pack(self):
135 \"""
136 Pack object into string
137
138 @return The packed string which can go on the wire
139
140 \"""
141 pass
142 def unpack(self, binary_string):
143 \"""
144 Unpack object from a binary string
145
146 @param binary_string The wire protocol byte string holding the object
147 represented as an array of bytes.
148
149 @return Typically returns the remainder of binary_string that
150 was not parsed. May give a warning if that string is non-empty
151
152 \"""
153 pass
154 def __len__(self):
155 \"""
156 Return the length of this object once packed into a string
157
158 @return An integer representing the number bytes in the packed
159 string.
160
161 \"""
162 pass
163 def show(self, prefix=''):
164 \"""
Dan Talayco46755fa2010-03-09 21:44:29 -0800165 Generate a string (with multiple lines) describing the contents
166 of the object in a readable manner
Dan Talaycof75360a2010-02-05 22:22:54 -0800167
Dan Talayco46755fa2010-03-09 21:44:29 -0800168 @param prefix Pre-pended at the beginning of each line.
Dan Talaycof75360a2010-02-05 22:22:54 -0800169
170 \"""
171 pass
172 def __eq__(self, other):
173 \"""
174 Return True if self and other hold the same data
175
176 @param other Other object in comparison
177
178 \"""
179 pass
180 def __ne__(self, other):
181 \"""
182 Return True if self and other do not hold the same data
183
184 @param other Other object in comparison
185
186 \"""
187 pass
188"""
189
190# Dictionary mapping wrapped classes to the auto-generated structure
191# underlieing the class (body only, not header or var-length data)
192message_class_map = {
193 "hello" : "ofp_header",
194 "error" : "ofp_error_msg",
195 "echo_request" : "ofp_header",
196 "echo_reply" : "ofp_header",
197 "vendor" : "ofp_vendor_header",
198 "features_request" : "ofp_header",
199 "features_reply" : "ofp_switch_features",
200 "get_config_request" : "ofp_header",
201 "get_config_reply" : "ofp_switch_config",
202 "set_config" : "ofp_switch_config",
203 "packet_in" : "ofp_packet_in",
204 "flow_removed" : "ofp_flow_removed",
205 "port_status" : "ofp_port_status",
206 "packet_out" : "ofp_packet_out",
207 "flow_mod" : "ofp_flow_mod",
208 "port_mod" : "ofp_port_mod",
209 "stats_request" : "ofp_stats_request",
210 "stats_reply" : "ofp_stats_reply",
211 "barrier_request" : "ofp_header",
212 "barrier_reply" : "ofp_header",
213 "queue_get_config_request" : "ofp_queue_get_config_request",
214 "queue_get_config_reply" : "ofp_queue_get_config_reply"
215}
216
217# These messages have a string member at the end of the data
218string_members = [
219 "hello",
220 "error",
221 "echo_request",
222 "echo_reply",
223 "vendor",
224 "packet_in",
225 "packet_out"
226]
227
228# These messages have a list (with the given name) in the data,
229# after the core members; the type is given for validation
230list_members = {
231 "features_reply" : ('ports', None),
232 "packet_out" : ('actions', 'action_list'),
233 "flow_mod" : ('actions', 'action_list'),
234 "queue_get_config_reply" : ('queues', None)
235}
236
237_ind = " "
238
239def _p1(s): print _ind + s
240def _p2(s): print _ind * 2 + s
241def _p3(s): print _ind * 3 + s
242def _p4(s): print _ind * 4 + s
243
244# Okay, this gets kind of ugly:
245# There are three variables:
246# has_core_members: If parent class is not ofp_header, has inheritance
247# has_list: Whether class has trailing array or class
248# has_string: Whether class has trailing string
249
250def gen_message_wrapper(msg):
251 """
252 Generate a wrapper for the given message based on above info
253 @param msg String identifying the message name for the class
254 """
255
256 msg_name = "OFPT_" + msg.upper()
257 parent = message_class_map[msg]
258
259 has_list = False # Has trailing list
260 has_core_members = False
261 has_string = False # Has trailing string
262 if parent != 'ofp_header':
263 has_core_members = True
264 if msg in list_members.keys():
265 (list_var, list_type) = list_members[msg]
266 has_list = True
267 if msg in string_members:
268 has_string = True
269
270 if has_core_members:
271 print "class " + msg + "(" + parent + "):"
272 else:
273 print "class " + msg + ":"
274 _p1('"""')
275 _p1("Wrapper class for " + msg)
276 print
Dan Talaycoac1cb812010-02-06 20:34:18 -0800277 _p1("OpenFlow message header: length, version, xid, type")
278 _p1("@arg length: The total length of the message")
279 _p1("@arg version: The OpenFlow version (" + str(OFP_VERSION) + ")")
280 _p1("@arg xid: The transaction ID")
281 _p1("@arg type: The message type (" + msg_name + "=" +
282 str(eval(msg_name)) + ")")
283 print
284 if has_core_members and parent in class_to_members_map.keys():
285 _p1("Data members inherited from " + parent + ":")
286 for var in class_to_members_map[parent]:
287 _p1("@arg " + var)
Dan Talaycof75360a2010-02-05 22:22:54 -0800288 if has_list:
289 if list_type == None:
Dan Talaycoac1cb812010-02-06 20:34:18 -0800290 _p1("@arg " + list_var + ": Variable length array of TBD")
Dan Talaycof75360a2010-02-05 22:22:54 -0800291 else:
Dan Talaycoac1cb812010-02-06 20:34:18 -0800292 _p1("@arg " + list_var + ": Object of type " + list_type);
Dan Talaycof75360a2010-02-05 22:22:54 -0800293 if has_string:
Dan Talaycoac1cb812010-02-06 20:34:18 -0800294 _p1("@arg data: Binary string following message members")
295 print
Dan Talaycof75360a2010-02-05 22:22:54 -0800296 _p1('"""')
297
298 print
Rich Lanec3c2ae12013-01-04 10:13:17 -0800299 _p1("def __init__(self, **kwargs):")
Dan Talaycof75360a2010-02-05 22:22:54 -0800300 if has_core_members:
301 _p2(parent + ".__init__(self)")
Dan Talaycof75360a2010-02-05 22:22:54 -0800302 _p2("self.header = ofp_header()")
303 _p2("self.header.type = " + msg_name)
304 if has_list:
305 if list_type == None:
306 _p2('self.' + list_var + ' = []')
307 else:
308 _p2('self.' + list_var + ' = ' + list_type + '()')
309 if has_string:
310 _p2('self.data = ""')
Rich Lanec3c2ae12013-01-04 10:13:17 -0800311 _p2('for (k, v) in kwargs.items():')
312 _p3('if hasattr(self, k):')
313 _p4('setattr(self, k, v)')
314 _p3('else:')
315 _p4('raise NameError("field %s does not exist in %s" % (k, self.__class__))')
Dan Talaycof75360a2010-02-05 22:22:54 -0800316
Dan Talaycob9cb5482010-02-09 15:23:12 -0800317 print """
318
319 def pack(self):
320 \"""
321 Pack object into string
322
323 @return The packed string which can go on the wire
324
325 \"""
326 self.header.length = len(self)
327 packed = self.header.pack()
328"""
329
Dan Talayco36f2f1f2010-02-10 22:40:26 -0800330 # Have to special case the action length calculation for pkt out
331 if msg == 'packet_out':
332 _p2('self.actions_len = len(self.actions)')
Dan Talaycof75360a2010-02-05 22:22:54 -0800333 if has_core_members:
Dan Talayco6d2470b2010-02-07 22:59:49 -0800334 _p2("packed += " + parent + ".pack(self)")
Dan Talaycof75360a2010-02-05 22:22:54 -0800335 if has_list:
336 if list_type == None:
337 _p2('for obj in self.' + list_var + ':')
338 _p3('packed += obj.pack()')
339 else:
340 _p2('packed += self.' + list_var + '.pack()')
341 if has_string:
342 _p2('packed += self.data')
Dan Talayco6d2470b2010-02-07 22:59:49 -0800343 _p2("return packed")
Dan Talaycof75360a2010-02-05 22:22:54 -0800344
Dan Talaycob9cb5482010-02-09 15:23:12 -0800345 print """
346 def unpack(self, binary_string):
347 \"""
348 Unpack object from a binary string
349
350 @param binary_string The wire protocol byte string holding the object
351 represented as an array of bytes.
Dan Talayco411489d2010-02-12 23:03:46 -0800352 @return The remainder of binary_string that was not parsed.
Dan Talaycob9cb5482010-02-09 15:23:12 -0800353
354 \"""
355 binary_string = self.header.unpack(binary_string)
356"""
Dan Talaycof75360a2010-02-05 22:22:54 -0800357 if has_core_members:
358 _p2("binary_string = " + parent + ".unpack(self, binary_string)")
359 if has_list:
Dan Talaycoff606492010-05-13 14:22:37 -0700360 if msg == "features_reply": # Special case port parsing
361 # For now, cheat and assume the rest of the message is port list
362 _p2("while len(binary_string) >= OFP_PHY_PORT_BYTES:")
363 _p3("new_port = ofp_phy_port()")
364 _p3("binary_string = new_port.unpack(binary_string)")
365 _p3("self.ports.append(new_port)")
366 elif list_type == None:
Dan Talaycof75360a2010-02-05 22:22:54 -0800367 _p2("for obj in self." + list_var + ":")
368 _p3("binary_string = obj.unpack(binary_string)")
369 elif msg == "packet_out": # Special case this
Dan Talayco6d2470b2010-02-07 22:59:49 -0800370 _p2('binary_string = self.actions.unpack(' +
371 'binary_string, bytes=self.actions_len)')
Dan Talaycof75360a2010-02-05 22:22:54 -0800372 elif msg == "flow_mod": # Special case this
Dan Talayco6d2470b2010-02-07 22:59:49 -0800373 _p2("ai_len = self.header.length - (OFP_FLOW_MOD_BYTES + " +
374 "OFP_HEADER_BYTES)")
375 _p2("binary_string = self.actions.unpack(binary_string, " +
376 "bytes=ai_len)")
Dan Talaycof75360a2010-02-05 22:22:54 -0800377 else:
378 _p2("binary_string = self." + list_var + ".unpack(binary_string)")
379 if has_string:
380 _p2("self.data = binary_string")
381 _p2("binary_string = ''")
382 else:
383 _p2("# Fixme: If no self.data, add check for data remaining")
384 _p2("return binary_string")
385
Dan Talaycob9cb5482010-02-09 15:23:12 -0800386 print """
387 def __len__(self):
388 \"""
389 Return the length of this object once packed into a string
390
391 @return An integer representing the number bytes in the packed
392 string.
393
394 \"""
395 length = OFP_HEADER_BYTES
396"""
Dan Talayco6d2470b2010-02-07 22:59:49 -0800397 if has_core_members:
398 _p2("length += " + parent + ".__len__(self)")
399 if has_list:
400 if list_type == None:
401 _p2("for obj in self." + list_var + ":")
402 _p3("length += len(obj)")
403 else:
404 _p2("length += len(self." + list_var + ")")
405 if has_string:
406 _p2("length += len(self.data)")
407 _p2("return length")
Dan Talaycof75360a2010-02-05 22:22:54 -0800408
Dan Talaycob9cb5482010-02-09 15:23:12 -0800409 print """
Dan Talaycob9cb5482010-02-09 15:23:12 -0800410 def show(self, prefix=''):
411 \"""
Dan Talayco46755fa2010-03-09 21:44:29 -0800412 Generate a string (with multiple lines) describing the contents
413 of the object in a readable manner
Dan Talaycob9cb5482010-02-09 15:23:12 -0800414
Dan Talayco46755fa2010-03-09 21:44:29 -0800415 @param prefix Pre-pended at the beginning of each line.
Dan Talaycob9cb5482010-02-09 15:23:12 -0800416
417 \"""
418"""
Dan Talayco46755fa2010-03-09 21:44:29 -0800419 _p2("outstr = prefix + '" + msg + " (" + msg_name + ")\\n'")
Dan Talaycof75360a2010-02-05 22:22:54 -0800420 _p2("prefix += ' '")
Dan Talayco46755fa2010-03-09 21:44:29 -0800421 _p2("outstr += prefix + 'ofp header\\n'")
422 _p2("outstr += self.header.show(prefix + ' ')")
Dan Talaycof75360a2010-02-05 22:22:54 -0800423 if has_core_members:
Dan Talayco46755fa2010-03-09 21:44:29 -0800424 _p2("outstr += " + parent + ".show(self, prefix)")
Dan Talaycof75360a2010-02-05 22:22:54 -0800425 if has_list:
426 if list_type == None:
Dan Talayco46755fa2010-03-09 21:44:29 -0800427 _p2('outstr += prefix + "Array ' + list_var + '\\n"')
Dan Talaycof75360a2010-02-05 22:22:54 -0800428 _p2('for obj in self.' + list_var +':')
Dan Talayco46755fa2010-03-09 21:44:29 -0800429 _p3("outstr += obj.show(prefix + ' ')")
Dan Talaycof75360a2010-02-05 22:22:54 -0800430 else:
Dan Talayco46755fa2010-03-09 21:44:29 -0800431 _p2('outstr += prefix + "List ' + list_var + '\\n"')
432 _p2('outstr += self.' + list_var + ".show(prefix + ' ')")
Dan Talaycof75360a2010-02-05 22:22:54 -0800433 if has_string:
Dan Talayco46755fa2010-03-09 21:44:29 -0800434 _p2("outstr += prefix + 'data is of length ' + str(len(self.data)) + '\\n'")
Dan Talayco6d2470b2010-02-07 22:59:49 -0800435 _p2("##@todo Fix this circular reference")
436 _p2("# if len(self.data) > 0:")
437 _p3("# obj = of_message_parse(self.data)")
438 _p3("# if obj != None:")
Dan Talayco46755fa2010-03-09 21:44:29 -0800439 _p4("# outstr += obj.show(prefix)")
Dan Talayco6d2470b2010-02-07 22:59:49 -0800440 _p3("# else:")
Dan Talayco46755fa2010-03-09 21:44:29 -0800441 _p4('# outstr += prefix + "Unable to parse data\\n"')
442 _p2('return outstr')
Dan Talaycof75360a2010-02-05 22:22:54 -0800443
Dan Talaycob9cb5482010-02-09 15:23:12 -0800444 print """
445 def __eq__(self, other):
446 \"""
447 Return True if self and other hold the same data
448
449 @param other Other object in comparison
450
451 \"""
452 if type(self) != type(other): return False
453 if not self.header.__eq__(other.header): return False
454"""
Dan Talaycof75360a2010-02-05 22:22:54 -0800455 if has_core_members:
Dan Talayco6d2470b2010-02-07 22:59:49 -0800456 _p2("if not " + parent + ".__eq__(self, other): return False")
Dan Talaycof75360a2010-02-05 22:22:54 -0800457 if has_string:
458 _p2("if self.data != other.data: return False")
459 if has_list:
460 _p2("if self." + list_var + " != other." + list_var + ": return False")
461 _p2("return True")
462
Dan Talaycob9cb5482010-02-09 15:23:12 -0800463 print """
464 def __ne__(self, other):
465 \"""
466 Return True if self and other do not hold the same data
Dan Talaycof75360a2010-02-05 22:22:54 -0800467
Dan Talaycob9cb5482010-02-09 15:23:12 -0800468 @param other Other object in comparison
469
470 \"""
471 return not self.__eq__(other)
472 """
Dan Talaycof75360a2010-02-05 22:22:54 -0800473
474
475################################################################
476#
477# Stats request subclasses
478# description_request, flow, aggregate, table, port, vendor
479#
480################################################################
481
482# table and desc stats requests are special with empty body
483extra_ofp_stats_req_defs = """
484# Stats request bodies for desc and table stats are not defined in the
485# OpenFlow header; We define them here. They are empty classes, really
486
487class ofp_desc_stats_request:
488 \"""
489 Forced definition of ofp_desc_stats_request (empty class)
490 \"""
491 def __init__(self):
492 pass
493 def pack(self, assertstruct=True):
494 return ""
495 def unpack(self, binary_string):
496 return binary_string
497 def __len__(self):
498 return 0
499 def show(self, prefix=''):
Dan Talayco46755fa2010-03-09 21:44:29 -0800500 return prefix + "ofp_desc_stats_request (empty)\\n"
Dan Talaycof75360a2010-02-05 22:22:54 -0800501 def __eq__(self, other):
502 return type(self) == type(other)
503 def __ne__(self, other):
504 return type(self) != type(other)
505
506OFP_DESC_STATS_REQUEST_BYTES = 0
507
508class ofp_table_stats_request:
509 \"""
510 Forced definition of ofp_table_stats_request (empty class)
511 \"""
512 def __init__(self):
513 pass
514 def pack(self, assertstruct=True):
515 return ""
516 def unpack(self, binary_string):
517 return binary_string
518 def __len__(self):
519 return 0
520 def show(self, prefix=''):
Dan Talayco46755fa2010-03-09 21:44:29 -0800521 return prefix + "ofp_table_stats_request (empty)\\n"
Dan Talaycof75360a2010-02-05 22:22:54 -0800522 def __eq__(self, other):
523 return type(self) == type(other)
524 def __ne__(self, other):
525 return type(self) != type(other)
526
527OFP_TABLE_STATS_REQUEST_BYTES = 0
528
529"""
530
531stats_request_template = """
532class --TYPE--_stats_request(ofp_stats_request, ofp_--TYPE--_stats_request):
533 \"""
534 Wrapper class for --TYPE-- stats request message
535 \"""
Rich Lanec3c2ae12013-01-04 10:13:17 -0800536 def __init__(self, **kwargs):
Dan Talaycof75360a2010-02-05 22:22:54 -0800537 self.header = ofp_header()
538 ofp_stats_request.__init__(self)
539 ofp_--TYPE--_stats_request.__init__(self)
540 self.header.type = OFPT_STATS_REQUEST
541 self.type = --STATS_NAME--
Rich Lanec3c2ae12013-01-04 10:13:17 -0800542 for (k, v) in kwargs.items():
543 if hasattr(self, k):
544 setattr(self, k, v)
545 else:
546 raise NameError("field %s does not exist in %s" % (k, self.__class__))
Dan Talaycof75360a2010-02-05 22:22:54 -0800547
548 def pack(self, assertstruct=True):
Dan Talayco2f820be2010-03-07 11:36:29 -0800549 self.header.length = len(self)
Dan Talayco36f2f1f2010-02-10 22:40:26 -0800550 packed = self.header.pack()
551 packed += ofp_stats_request.pack(self)
Dan Talaycof75360a2010-02-05 22:22:54 -0800552 packed += ofp_--TYPE--_stats_request.pack(self)
Dan Talayco6d2470b2010-02-07 22:59:49 -0800553 return packed
Dan Talaycof75360a2010-02-05 22:22:54 -0800554
555 def unpack(self, binary_string):
Dan Talayco36f2f1f2010-02-10 22:40:26 -0800556 binary_string = self.header.unpack(binary_string)
Dan Talaycof75360a2010-02-05 22:22:54 -0800557 binary_string = ofp_stats_request.unpack(self, binary_string)
558 binary_string = ofp_--TYPE--_stats_request.unpack(self, binary_string)
559 if len(binary_string) != 0:
Dan Talayco36f2f1f2010-02-10 22:40:26 -0800560 print "ERROR unpacking --TYPE--: extra data"
Dan Talaycof75360a2010-02-05 22:22:54 -0800561 return binary_string
562
563 def __len__(self):
564 return len(self.header) + OFP_STATS_REQUEST_BYTES + \\
565 OFP_--TYPE_UPPER--_STATS_REQUEST_BYTES
566
567 def show(self, prefix=''):
Dan Talayco46755fa2010-03-09 21:44:29 -0800568 outstr = prefix + "--TYPE--_stats_request\\n"
569 outstr += prefix + "ofp header:\\n"
570 outstr += self.header.show(prefix + ' ')
571 outstr += ofp_stats_request.show(self)
572 outstr += ofp_--TYPE--_stats_request.show(self)
573 return outstr
Dan Talaycof75360a2010-02-05 22:22:54 -0800574
575 def __eq__(self, other):
Dan Talayco36f2f1f2010-02-10 22:40:26 -0800576 if type(self) != type(other): return False
577 return (self.header == other.header and
578 ofp_stats_request.__eq__(self, other) and
Dan Talaycof75360a2010-02-05 22:22:54 -0800579 ofp_--TYPE--_stats_request.__eq__(self, other))
580
581 def __ne__(self, other): return not self.__eq__(other)
582"""
583
584################################################################
585#
586# Stats replies always have an array at the end.
587# For aggregate and desc, these arrays are always of length 1
588# This array is always called stats
589#
590################################################################
591
592
593# Template for objects stats reply messages
594stats_reply_template = """
595class --TYPE--_stats_reply(ofp_stats_reply):
596 \"""
597 Wrapper class for --TYPE-- stats reply
598 \"""
599 def __init__(self):
600 self.header = ofp_header()
601 ofp_stats_reply.__init__(self)
602 self.header.type = OFPT_STATS_REPLY
603 self.type = --STATS_NAME--
604 # stats: Array of type --TYPE--_stats_entry
605 self.stats = []
606
607 def pack(self, assertstruct=True):
Dan Talayco2f820be2010-03-07 11:36:29 -0800608 self.header.length = len(self)
Dan Talayco36f2f1f2010-02-10 22:40:26 -0800609 packed = self.header.pack()
610 packed += ofp_stats_reply.pack(self)
Dan Talaycof75360a2010-02-05 22:22:54 -0800611 for obj in self.stats:
612 packed += obj.pack()
Dan Talayco6d2470b2010-02-07 22:59:49 -0800613 return packed
Dan Talaycof75360a2010-02-05 22:22:54 -0800614
615 def unpack(self, binary_string):
Dan Talayco36f2f1f2010-02-10 22:40:26 -0800616 binary_string = self.header.unpack(binary_string)
Dan Talaycof75360a2010-02-05 22:22:54 -0800617 binary_string = ofp_stats_reply.unpack(self, binary_string)
618 dummy = --TYPE--_stats_entry()
619 while len(binary_string) >= len(dummy):
620 obj = --TYPE--_stats_entry()
621 binary_string = obj.unpack(binary_string)
622 self.stats.append(obj)
623 if len(binary_string) != 0:
624 print "ERROR unpacking --TYPE-- stats string: extra bytes"
625 return binary_string
626
627 def __len__(self):
628 length = len(self.header) + OFP_STATS_REPLY_BYTES
629 for obj in self.stats:
630 length += len(obj)
631 return length
632
633 def show(self, prefix=''):
Dan Talayco46755fa2010-03-09 21:44:29 -0800634 outstr = prefix + "--TYPE--_stats_reply\\n"
635 outstr += prefix + "ofp header:\\n"
636 outstr += self.header.show(prefix + ' ')
637 outstr += ofp_stats_reply.show(self)
638 outstr += prefix + "Stats array of length " + str(len(self.stats)) + '\\n'
Dan Talaycof75360a2010-02-05 22:22:54 -0800639 for obj in self.stats:
Dan Talayco46755fa2010-03-09 21:44:29 -0800640 outstr += obj.show()
641 return outstr
Dan Talaycof75360a2010-02-05 22:22:54 -0800642
643 def __eq__(self, other):
Dan Talayco36f2f1f2010-02-10 22:40:26 -0800644 if type(self) != type(other): return False
645 return (self.header == other.header and
646 ofp_stats_reply.__eq__(self, other) and
647 self.stats == other.stats)
Dan Talaycof75360a2010-02-05 22:22:54 -0800648
649 def __ne__(self, other): return not self.__eq__(other)
650"""
651
652#
653# To address variations in stats reply bodies, the following
654# "_entry" classes are defined for each element in the reply
655#
656
657extra_stats_entry_defs = """
658# Stats entries define the content of one element in a stats
659# reply for the indicated type; define _entry for consistency
660
661aggregate_stats_entry = ofp_aggregate_stats_reply
662desc_stats_entry = ofp_desc_stats
663port_stats_entry = ofp_port_stats
664queue_stats_entry = ofp_queue_stats
665table_stats_entry = ofp_table_stats
666"""
667
668# Special case flow_stats to handle actions_list
669
670flow_stats_entry_def = """
671#
672# Flow stats entry contains an action list of variable length, so
673# it is done by hand
674#
675
676class flow_stats_entry(ofp_flow_stats):
677 \"""
678 Special case flow stats entry to handle action list object
679 \"""
680 def __init__(self):
681 ofp_flow_stats.__init__(self)
682 self.actions = action_list()
683
684 def pack(self, assertstruct=True):
Dan Talayco6d2470b2010-02-07 22:59:49 -0800685 self.length = len(self)
Dan Talaycof75360a2010-02-05 22:22:54 -0800686 packed = ofp_flow_stats.pack(self, assertstruct)
687 packed += self.actions.pack()
Dan Talayco6d2470b2010-02-07 22:59:49 -0800688 if len(packed) != self.length:
689 print("ERROR: flow_stats_entry pack length not equal",
690 self.length, len(packed))
Dan Talaycof75360a2010-02-05 22:22:54 -0800691 return packed
692
693 def unpack(self, binary_string):
694 binary_string = ofp_flow_stats.unpack(self, binary_string)
695 ai_len = self.length - OFP_FLOW_STATS_BYTES
Dan Talayco6d2470b2010-02-07 22:59:49 -0800696 if ai_len < 0:
697 print("ERROR: flow_stats_entry unpack length too small",
698 self.length)
Dan Talaycof75360a2010-02-05 22:22:54 -0800699 binary_string = self.actions.unpack(binary_string, bytes=ai_len)
700 return binary_string
701
702 def __len__(self):
703 return OFP_FLOW_STATS_BYTES + len(self.actions)
704
705 def show(self, prefix=''):
Dan Talayco46755fa2010-03-09 21:44:29 -0800706 outstr = prefix + "flow_stats_entry\\n"
707 outstr += ofp_flow_stats.show(self, prefix + ' ')
708 outstr += self.actions.show(prefix + ' ')
709 return outstr
Dan Talaycof75360a2010-02-05 22:22:54 -0800710
711 def __eq__(self, other):
Dan Talayco36f2f1f2010-02-10 22:40:26 -0800712 if type(self) != type(other): return False
Dan Talaycof75360a2010-02-05 22:22:54 -0800713 return (ofp_flow_stats.__eq__(self, other) and
714 self.actions == other.actions)
715
716 def __ne__(self, other): return not self.__eq__(other)
717"""
718
719stats_types = [
720 'aggregate',
721 'desc',
722 'flow',
723 'port',
724 'queue',
725 'table']
726
727if __name__ == '__main__':
728
729 print message_top_matter
730
731 print """
732################################################################
733#
734# OpenFlow Message Definitions
735#
736################################################################
737"""
738
739 msg_types = message_class_map.keys()
740 msg_types.sort()
741
742 for t in msg_types:
743 gen_message_wrapper(t)
744 print
745
746 print """
747################################################################
748#
749# Stats request and reply subclass definitions
750#
751################################################################
752"""
753
754 print extra_ofp_stats_req_defs
755 print extra_stats_entry_defs
756 print flow_stats_entry_def
757
758 # Generate stats request and reply subclasses
759 for t in stats_types:
760 stats_name = "OFPST_" + t.upper()
761 to_print = re.sub('--TYPE--', t, stats_request_template)
762 to_print = re.sub('--TYPE_UPPER--', t.upper(), to_print)
763 to_print = re.sub('--STATS_NAME--', stats_name, to_print)
764 print to_print
765 to_print = re.sub('--TYPE--', t, stats_reply_template)
766 to_print = re.sub('--STATS_NAME--', stats_name, to_print)
767 print to_print
768
Dan Talayco411489d2010-02-12 23:03:46 -0800769 # Lastly, generate a tuple containing all the message classes
770 print """
771message_type_list = (
772 aggregate_stats_reply,
773 aggregate_stats_request,
774 bad_action_error_msg,
775 bad_request_error_msg,
776 barrier_reply,
777 barrier_request,
778 desc_stats_reply,
779 desc_stats_request,
780 echo_reply,
781 echo_request,
782 features_reply,
783 features_request,
784 flow_mod,
785 flow_mod_failed_error_msg,
786 flow_removed,
787 flow_stats_reply,
788 flow_stats_request,
789 get_config_reply,
790 get_config_request,
791 hello,
792 hello_failed_error_msg,
793 packet_in,
794 packet_out,
795 port_mod,
796 port_mod_failed_error_msg,
797 port_stats_reply,
798 port_stats_request,
799 port_status,
800 queue_get_config_reply,
801 queue_get_config_request,
802 queue_op_failed_error_msg,
803 queue_stats_reply,
804 queue_stats_request,
805 set_config,
806 table_stats_reply,
807 table_stats_request,
808 vendor
809 )
810"""
Dan Talaycof75360a2010-02-05 22:22:54 -0800811
812#
813# OFP match variants
814# ICMP 0x801 (?) ==> icmp_type/code replace tp_src/dst
815#
816
817