blob: e2e8490536be372e5c050fab744366d6af2caad4 [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
299 _p1("def __init__(self):")
300 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 = ""')
311
Dan Talaycob9cb5482010-02-09 15:23:12 -0800312 print """
313
314 def pack(self):
315 \"""
316 Pack object into string
317
318 @return The packed string which can go on the wire
319
320 \"""
321 self.header.length = len(self)
322 packed = self.header.pack()
323"""
324
Dan Talayco36f2f1f2010-02-10 22:40:26 -0800325 # Have to special case the action length calculation for pkt out
326 if msg == 'packet_out':
327 _p2('self.actions_len = len(self.actions)')
Dan Talaycof75360a2010-02-05 22:22:54 -0800328 if has_core_members:
Dan Talayco6d2470b2010-02-07 22:59:49 -0800329 _p2("packed += " + parent + ".pack(self)")
Dan Talaycof75360a2010-02-05 22:22:54 -0800330 if has_list:
331 if list_type == None:
332 _p2('for obj in self.' + list_var + ':')
333 _p3('packed += obj.pack()')
334 else:
335 _p2('packed += self.' + list_var + '.pack()')
336 if has_string:
337 _p2('packed += self.data')
Dan Talayco6d2470b2010-02-07 22:59:49 -0800338 _p2("return packed")
Dan Talaycof75360a2010-02-05 22:22:54 -0800339
Dan Talaycob9cb5482010-02-09 15:23:12 -0800340 print """
341 def unpack(self, binary_string):
342 \"""
343 Unpack object from a binary string
344
345 @param binary_string The wire protocol byte string holding the object
346 represented as an array of bytes.
Dan Talayco411489d2010-02-12 23:03:46 -0800347 @return The remainder of binary_string that was not parsed.
Dan Talaycob9cb5482010-02-09 15:23:12 -0800348
349 \"""
350 binary_string = self.header.unpack(binary_string)
351"""
Dan Talaycof75360a2010-02-05 22:22:54 -0800352 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
Dan Talayco6d2470b2010-02-07 22:59:49 -0800359 _p2('binary_string = self.actions.unpack(' +
360 'binary_string, bytes=self.actions_len)')
Dan Talaycof75360a2010-02-05 22:22:54 -0800361 elif msg == "flow_mod": # Special case this
Dan Talayco6d2470b2010-02-07 22:59:49 -0800362 _p2("ai_len = self.header.length - (OFP_FLOW_MOD_BYTES + " +
363 "OFP_HEADER_BYTES)")
364 _p2("binary_string = self.actions.unpack(binary_string, " +
365 "bytes=ai_len)")
Dan Talaycof75360a2010-02-05 22:22:54 -0800366 else:
367 _p2("binary_string = self." + list_var + ".unpack(binary_string)")
368 if has_string:
369 _p2("self.data = binary_string")
370 _p2("binary_string = ''")
371 else:
372 _p2("# Fixme: If no self.data, add check for data remaining")
373 _p2("return binary_string")
374
Dan Talaycob9cb5482010-02-09 15:23:12 -0800375 print """
376 def __len__(self):
377 \"""
378 Return the length of this object once packed into a string
379
380 @return An integer representing the number bytes in the packed
381 string.
382
383 \"""
384 length = OFP_HEADER_BYTES
385"""
Dan Talayco6d2470b2010-02-07 22:59:49 -0800386 if has_core_members:
387 _p2("length += " + parent + ".__len__(self)")
388 if has_list:
389 if list_type == None:
390 _p2("for obj in self." + list_var + ":")
391 _p3("length += len(obj)")
392 else:
393 _p2("length += len(self." + list_var + ")")
394 if has_string:
395 _p2("length += len(self.data)")
396 _p2("return length")
Dan Talaycof75360a2010-02-05 22:22:54 -0800397
Dan Talaycob9cb5482010-02-09 15:23:12 -0800398 print """
Dan Talaycob9cb5482010-02-09 15:23:12 -0800399 def show(self, prefix=''):
400 \"""
Dan Talayco46755fa2010-03-09 21:44:29 -0800401 Generate a string (with multiple lines) describing the contents
402 of the object in a readable manner
Dan Talaycob9cb5482010-02-09 15:23:12 -0800403
Dan Talayco46755fa2010-03-09 21:44:29 -0800404 @param prefix Pre-pended at the beginning of each line.
Dan Talaycob9cb5482010-02-09 15:23:12 -0800405
406 \"""
407"""
Dan Talayco46755fa2010-03-09 21:44:29 -0800408 _p2("outstr = prefix + '" + msg + " (" + msg_name + ")\\n'")
Dan Talaycof75360a2010-02-05 22:22:54 -0800409 _p2("prefix += ' '")
Dan Talayco46755fa2010-03-09 21:44:29 -0800410 _p2("outstr += prefix + 'ofp header\\n'")
411 _p2("outstr += self.header.show(prefix + ' ')")
Dan Talaycof75360a2010-02-05 22:22:54 -0800412 if has_core_members:
Dan Talayco46755fa2010-03-09 21:44:29 -0800413 _p2("outstr += " + parent + ".show(self, prefix)")
Dan Talaycof75360a2010-02-05 22:22:54 -0800414 if has_list:
415 if list_type == None:
Dan Talayco46755fa2010-03-09 21:44:29 -0800416 _p2('outstr += prefix + "Array ' + list_var + '\\n"')
Dan Talaycof75360a2010-02-05 22:22:54 -0800417 _p2('for obj in self.' + list_var +':')
Dan Talayco46755fa2010-03-09 21:44:29 -0800418 _p3("outstr += obj.show(prefix + ' ')")
Dan Talaycof75360a2010-02-05 22:22:54 -0800419 else:
Dan Talayco46755fa2010-03-09 21:44:29 -0800420 _p2('outstr += prefix + "List ' + list_var + '\\n"')
421 _p2('outstr += self.' + list_var + ".show(prefix + ' ')")
Dan Talaycof75360a2010-02-05 22:22:54 -0800422 if has_string:
Dan Talayco46755fa2010-03-09 21:44:29 -0800423 _p2("outstr += prefix + 'data is of length ' + str(len(self.data)) + '\\n'")
Dan Talayco6d2470b2010-02-07 22:59:49 -0800424 _p2("##@todo Fix this circular reference")
425 _p2("# if len(self.data) > 0:")
426 _p3("# obj = of_message_parse(self.data)")
427 _p3("# if obj != None:")
Dan Talayco46755fa2010-03-09 21:44:29 -0800428 _p4("# outstr += obj.show(prefix)")
Dan Talayco6d2470b2010-02-07 22:59:49 -0800429 _p3("# else:")
Dan Talayco46755fa2010-03-09 21:44:29 -0800430 _p4('# outstr += prefix + "Unable to parse data\\n"')
431 _p2('return outstr')
Dan Talaycof75360a2010-02-05 22:22:54 -0800432
Dan Talaycob9cb5482010-02-09 15:23:12 -0800433 print """
434 def __eq__(self, other):
435 \"""
436 Return True if self and other hold the same data
437
438 @param other Other object in comparison
439
440 \"""
441 if type(self) != type(other): return False
442 if not self.header.__eq__(other.header): return False
443"""
Dan Talaycof75360a2010-02-05 22:22:54 -0800444 if has_core_members:
Dan Talayco6d2470b2010-02-07 22:59:49 -0800445 _p2("if not " + parent + ".__eq__(self, other): return False")
Dan Talaycof75360a2010-02-05 22:22:54 -0800446 if has_string:
447 _p2("if self.data != other.data: return False")
448 if has_list:
449 _p2("if self." + list_var + " != other." + list_var + ": return False")
450 _p2("return True")
451
Dan Talaycob9cb5482010-02-09 15:23:12 -0800452 print """
453 def __ne__(self, other):
454 \"""
455 Return True if self and other do not hold the same data
Dan Talaycof75360a2010-02-05 22:22:54 -0800456
Dan Talaycob9cb5482010-02-09 15:23:12 -0800457 @param other Other object in comparison
458
459 \"""
460 return not self.__eq__(other)
461 """
Dan Talaycof75360a2010-02-05 22:22:54 -0800462
463
464################################################################
465#
466# Stats request subclasses
467# description_request, flow, aggregate, table, port, vendor
468#
469################################################################
470
471# table and desc stats requests are special with empty body
472extra_ofp_stats_req_defs = """
473# Stats request bodies for desc and table stats are not defined in the
474# OpenFlow header; We define them here. They are empty classes, really
475
476class ofp_desc_stats_request:
477 \"""
478 Forced definition of ofp_desc_stats_request (empty class)
479 \"""
480 def __init__(self):
481 pass
482 def pack(self, assertstruct=True):
483 return ""
484 def unpack(self, binary_string):
485 return binary_string
486 def __len__(self):
487 return 0
488 def show(self, prefix=''):
Dan Talayco46755fa2010-03-09 21:44:29 -0800489 return prefix + "ofp_desc_stats_request (empty)\\n"
Dan Talaycof75360a2010-02-05 22:22:54 -0800490 def __eq__(self, other):
491 return type(self) == type(other)
492 def __ne__(self, other):
493 return type(self) != type(other)
494
495OFP_DESC_STATS_REQUEST_BYTES = 0
496
497class ofp_table_stats_request:
498 \"""
499 Forced definition of ofp_table_stats_request (empty class)
500 \"""
501 def __init__(self):
502 pass
503 def pack(self, assertstruct=True):
504 return ""
505 def unpack(self, binary_string):
506 return binary_string
507 def __len__(self):
508 return 0
509 def show(self, prefix=''):
Dan Talayco46755fa2010-03-09 21:44:29 -0800510 return prefix + "ofp_table_stats_request (empty)\\n"
Dan Talaycof75360a2010-02-05 22:22:54 -0800511 def __eq__(self, other):
512 return type(self) == type(other)
513 def __ne__(self, other):
514 return type(self) != type(other)
515
516OFP_TABLE_STATS_REQUEST_BYTES = 0
517
518"""
519
520stats_request_template = """
521class --TYPE--_stats_request(ofp_stats_request, ofp_--TYPE--_stats_request):
522 \"""
523 Wrapper class for --TYPE-- stats request message
524 \"""
525 def __init__(self):
526 self.header = ofp_header()
527 ofp_stats_request.__init__(self)
528 ofp_--TYPE--_stats_request.__init__(self)
529 self.header.type = OFPT_STATS_REQUEST
530 self.type = --STATS_NAME--
531
532 def pack(self, assertstruct=True):
Dan Talayco2f820be2010-03-07 11:36:29 -0800533 self.header.length = len(self)
Dan Talayco36f2f1f2010-02-10 22:40:26 -0800534 packed = self.header.pack()
535 packed += ofp_stats_request.pack(self)
Dan Talaycof75360a2010-02-05 22:22:54 -0800536 packed += ofp_--TYPE--_stats_request.pack(self)
Dan Talayco6d2470b2010-02-07 22:59:49 -0800537 return packed
Dan Talaycof75360a2010-02-05 22:22:54 -0800538
539 def unpack(self, binary_string):
Dan Talayco36f2f1f2010-02-10 22:40:26 -0800540 binary_string = self.header.unpack(binary_string)
Dan Talaycof75360a2010-02-05 22:22:54 -0800541 binary_string = ofp_stats_request.unpack(self, binary_string)
542 binary_string = ofp_--TYPE--_stats_request.unpack(self, binary_string)
543 if len(binary_string) != 0:
Dan Talayco36f2f1f2010-02-10 22:40:26 -0800544 print "ERROR unpacking --TYPE--: extra data"
Dan Talaycof75360a2010-02-05 22:22:54 -0800545 return binary_string
546
547 def __len__(self):
548 return len(self.header) + OFP_STATS_REQUEST_BYTES + \\
549 OFP_--TYPE_UPPER--_STATS_REQUEST_BYTES
550
551 def show(self, prefix=''):
Dan Talayco46755fa2010-03-09 21:44:29 -0800552 outstr = prefix + "--TYPE--_stats_request\\n"
553 outstr += prefix + "ofp header:\\n"
554 outstr += self.header.show(prefix + ' ')
555 outstr += ofp_stats_request.show(self)
556 outstr += ofp_--TYPE--_stats_request.show(self)
557 return outstr
Dan Talaycof75360a2010-02-05 22:22:54 -0800558
559 def __eq__(self, other):
Dan Talayco36f2f1f2010-02-10 22:40:26 -0800560 if type(self) != type(other): return False
561 return (self.header == other.header and
562 ofp_stats_request.__eq__(self, other) and
Dan Talaycof75360a2010-02-05 22:22:54 -0800563 ofp_--TYPE--_stats_request.__eq__(self, other))
564
565 def __ne__(self, other): return not self.__eq__(other)
566"""
567
568################################################################
569#
570# Stats replies always have an array at the end.
571# For aggregate and desc, these arrays are always of length 1
572# This array is always called stats
573#
574################################################################
575
576
577# Template for objects stats reply messages
578stats_reply_template = """
579class --TYPE--_stats_reply(ofp_stats_reply):
580 \"""
581 Wrapper class for --TYPE-- stats reply
582 \"""
583 def __init__(self):
584 self.header = ofp_header()
585 ofp_stats_reply.__init__(self)
586 self.header.type = OFPT_STATS_REPLY
587 self.type = --STATS_NAME--
588 # stats: Array of type --TYPE--_stats_entry
589 self.stats = []
590
591 def pack(self, assertstruct=True):
Dan Talayco2f820be2010-03-07 11:36:29 -0800592 self.header.length = len(self)
Dan Talayco36f2f1f2010-02-10 22:40:26 -0800593 packed = self.header.pack()
594 packed += ofp_stats_reply.pack(self)
Dan Talaycof75360a2010-02-05 22:22:54 -0800595 for obj in self.stats:
596 packed += obj.pack()
Dan Talayco6d2470b2010-02-07 22:59:49 -0800597 return packed
Dan Talaycof75360a2010-02-05 22:22:54 -0800598
599 def unpack(self, binary_string):
Dan Talayco36f2f1f2010-02-10 22:40:26 -0800600 binary_string = self.header.unpack(binary_string)
Dan Talaycof75360a2010-02-05 22:22:54 -0800601 binary_string = ofp_stats_reply.unpack(self, binary_string)
602 dummy = --TYPE--_stats_entry()
603 while len(binary_string) >= len(dummy):
604 obj = --TYPE--_stats_entry()
605 binary_string = obj.unpack(binary_string)
606 self.stats.append(obj)
607 if len(binary_string) != 0:
608 print "ERROR unpacking --TYPE-- stats string: extra bytes"
609 return binary_string
610
611 def __len__(self):
612 length = len(self.header) + OFP_STATS_REPLY_BYTES
613 for obj in self.stats:
614 length += len(obj)
615 return length
616
617 def show(self, prefix=''):
Dan Talayco46755fa2010-03-09 21:44:29 -0800618 outstr = prefix + "--TYPE--_stats_reply\\n"
619 outstr += prefix + "ofp header:\\n"
620 outstr += self.header.show(prefix + ' ')
621 outstr += ofp_stats_reply.show(self)
622 outstr += prefix + "Stats array of length " + str(len(self.stats)) + '\\n'
Dan Talaycof75360a2010-02-05 22:22:54 -0800623 for obj in self.stats:
Dan Talayco46755fa2010-03-09 21:44:29 -0800624 outstr += obj.show()
625 return outstr
Dan Talaycof75360a2010-02-05 22:22:54 -0800626
627 def __eq__(self, other):
Dan Talayco36f2f1f2010-02-10 22:40:26 -0800628 if type(self) != type(other): return False
629 return (self.header == other.header and
630 ofp_stats_reply.__eq__(self, other) and
631 self.stats == other.stats)
Dan Talaycof75360a2010-02-05 22:22:54 -0800632
633 def __ne__(self, other): return not self.__eq__(other)
634"""
635
636#
637# To address variations in stats reply bodies, the following
638# "_entry" classes are defined for each element in the reply
639#
640
641extra_stats_entry_defs = """
642# Stats entries define the content of one element in a stats
643# reply for the indicated type; define _entry for consistency
644
645aggregate_stats_entry = ofp_aggregate_stats_reply
646desc_stats_entry = ofp_desc_stats
647port_stats_entry = ofp_port_stats
648queue_stats_entry = ofp_queue_stats
649table_stats_entry = ofp_table_stats
650"""
651
652# Special case flow_stats to handle actions_list
653
654flow_stats_entry_def = """
655#
656# Flow stats entry contains an action list of variable length, so
657# it is done by hand
658#
659
660class flow_stats_entry(ofp_flow_stats):
661 \"""
662 Special case flow stats entry to handle action list object
663 \"""
664 def __init__(self):
665 ofp_flow_stats.__init__(self)
666 self.actions = action_list()
667
668 def pack(self, assertstruct=True):
Dan Talayco6d2470b2010-02-07 22:59:49 -0800669 self.length = len(self)
Dan Talaycof75360a2010-02-05 22:22:54 -0800670 packed = ofp_flow_stats.pack(self, assertstruct)
671 packed += self.actions.pack()
Dan Talayco6d2470b2010-02-07 22:59:49 -0800672 if len(packed) != self.length:
673 print("ERROR: flow_stats_entry pack length not equal",
674 self.length, len(packed))
Dan Talaycof75360a2010-02-05 22:22:54 -0800675 return packed
676
677 def unpack(self, binary_string):
678 binary_string = ofp_flow_stats.unpack(self, binary_string)
679 ai_len = self.length - OFP_FLOW_STATS_BYTES
Dan Talayco6d2470b2010-02-07 22:59:49 -0800680 if ai_len < 0:
681 print("ERROR: flow_stats_entry unpack length too small",
682 self.length)
Dan Talaycof75360a2010-02-05 22:22:54 -0800683 binary_string = self.actions.unpack(binary_string, bytes=ai_len)
684 return binary_string
685
686 def __len__(self):
687 return OFP_FLOW_STATS_BYTES + len(self.actions)
688
689 def show(self, prefix=''):
Dan Talayco46755fa2010-03-09 21:44:29 -0800690 outstr = prefix + "flow_stats_entry\\n"
691 outstr += ofp_flow_stats.show(self, prefix + ' ')
692 outstr += self.actions.show(prefix + ' ')
693 return outstr
Dan Talaycof75360a2010-02-05 22:22:54 -0800694
695 def __eq__(self, other):
Dan Talayco36f2f1f2010-02-10 22:40:26 -0800696 if type(self) != type(other): return False
Dan Talaycof75360a2010-02-05 22:22:54 -0800697 return (ofp_flow_stats.__eq__(self, other) and
698 self.actions == other.actions)
699
700 def __ne__(self, other): return not self.__eq__(other)
701"""
702
703stats_types = [
704 'aggregate',
705 'desc',
706 'flow',
707 'port',
708 'queue',
709 'table']
710
711if __name__ == '__main__':
712
713 print message_top_matter
714
715 print """
716################################################################
717#
718# OpenFlow Message Definitions
719#
720################################################################
721"""
722
723 msg_types = message_class_map.keys()
724 msg_types.sort()
725
726 for t in msg_types:
727 gen_message_wrapper(t)
728 print
729
730 print """
731################################################################
732#
733# Stats request and reply subclass definitions
734#
735################################################################
736"""
737
738 print extra_ofp_stats_req_defs
739 print extra_stats_entry_defs
740 print flow_stats_entry_def
741
742 # Generate stats request and reply subclasses
743 for t in stats_types:
744 stats_name = "OFPST_" + t.upper()
745 to_print = re.sub('--TYPE--', t, stats_request_template)
746 to_print = re.sub('--TYPE_UPPER--', t.upper(), to_print)
747 to_print = re.sub('--STATS_NAME--', stats_name, to_print)
748 print to_print
749 to_print = re.sub('--TYPE--', t, stats_reply_template)
750 to_print = re.sub('--STATS_NAME--', stats_name, to_print)
751 print to_print
752
Dan Talayco411489d2010-02-12 23:03:46 -0800753 # Lastly, generate a tuple containing all the message classes
754 print """
755message_type_list = (
756 aggregate_stats_reply,
757 aggregate_stats_request,
758 bad_action_error_msg,
759 bad_request_error_msg,
760 barrier_reply,
761 barrier_request,
762 desc_stats_reply,
763 desc_stats_request,
764 echo_reply,
765 echo_request,
766 features_reply,
767 features_request,
768 flow_mod,
769 flow_mod_failed_error_msg,
770 flow_removed,
771 flow_stats_reply,
772 flow_stats_request,
773 get_config_reply,
774 get_config_request,
775 hello,
776 hello_failed_error_msg,
777 packet_in,
778 packet_out,
779 port_mod,
780 port_mod_failed_error_msg,
781 port_stats_reply,
782 port_stats_request,
783 port_status,
784 queue_get_config_reply,
785 queue_get_config_request,
786 queue_op_failed_error_msg,
787 queue_stats_reply,
788 queue_stats_request,
789 set_config,
790 table_stats_reply,
791 table_stats_request,
792 vendor
793 )
794"""
Dan Talaycof75360a2010-02-05 22:22:54 -0800795
796#
797# OFP match variants
798# ICMP 0x801 (?) ==> icmp_type/code replace tp_src/dst
799#
800
801