From d491e976a3542e1de7b240df1276d8f380ffbde1 Mon Sep 17 00:00:00 2001 From: Sebastian Walz Date: Sun, 21 Oct 2018 01:11:51 +0200 Subject: [PATCH 1/9] =?UTF-8?q?Option=20=C2=BBbase=C2=AB=20added,=20=C2=BB?= =?UTF-8?q?:=C2=AB=20allowed=20in=20options?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ?base=16 for hexadecimal ?base=8 for octal (one byte) --- protocol | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/protocol b/protocol index 5bfeeda..39b798b 100755 --- a/protocol +++ b/protocol @@ -94,6 +94,7 @@ class Protocol(): self.hdr_char_fill_even="-" # Fill character for border even positions self.hdr_char_sep="|" # Field separator character self.bits_per_line=32 # Number of bits per line + self.base_of_top_tens=10 # Base of top numbers self.do_print_top_tens=True # True: print top numbers for bit tens self.do_print_top_units=True # True: print top numbers for bit units self.field_list=[] # Header fields to be printed out @@ -138,11 +139,15 @@ class Protocol(): opts=opts.split(",") for opt in opts: try: - var, value = opt.split("=") + var, value = opt.replace(':','=').split("=") if var.lower()=="bits": self.bits_per_line=int(value) if self.bits_per_line<=0: raise ProtocolException("FATAL: Invalid value for 'bits' option (%s)" % value) + elif var.lower()=="base": + self.base_of_top_tens=int(value) + if self.base_of_top_tens<=0 or self.base_of_top_tens>16: + raise ProtocolException("FATAL: Invalid value for 'base' option (%s)" % value) elif var.lower()=="numbers": if value.lower() in ["0", "n", "no", "none", "false"]: self.do_print_top_tens=False @@ -185,14 +190,14 @@ class Protocol(): lines=["", ""] if self.do_print_top_tens is True: for i in range(0, self.bits_per_line): - if str(i)[-1:]=="0": - lines[0]+=" %s" % str(i)[0] + if i%self.base_of_top_tens==0: + lines[0]+=" %s" % chars[int(i/self.base_of_top_tens)%self.base_of_top_tens] else: lines[0]+=" " lines[0]+="\n" if self.do_print_top_units is True: for i in range(0, self.bits_per_line): - lines[1]+=" %s" % str(i)[-1:] + lines[1]+=" %s" % chars[i%self.base_of_top_tens] #lines[1]+="\n" result = "".join(lines) return result if len(result)>0 else None From 01b0229a6c34fe94abe099b3df4ea1ec62731990 Mon Sep 17 00:00:00 2001 From: Sebastian Walz Date: Sun, 21 Oct 2018 01:18:15 +0200 Subject: [PATCH 2/9] missing line fixed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit copy&paste fail ¯\_(ツ)_/¯ --- protocol | 1 + 1 file changed, 1 insertion(+) diff --git a/protocol b/protocol index 39b798b..8301678 100755 --- a/protocol +++ b/protocol @@ -188,6 +188,7 @@ class Protocol(): character in the middle. """ lines=["", ""] + chars="0123456789abcdef" if self.do_print_top_tens is True: for i in range(0, self.bits_per_line): if i%self.base_of_top_tens==0: From be7078f82a425da9069269c67dda8c3450ad9ad7 Mon Sep 17 00:00:00 2001 From: Sebastian Walz Date: Sun, 21 Oct 2018 01:26:56 +0200 Subject: [PATCH 3/9] Update README.txt --- README.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.txt b/README.txt index 22f7c99..1ac0c2b 100644 --- a/README.txt +++ b/README.txt @@ -234,6 +234,8 @@ position of the horizonal lines. sepchar= : Instructs protocol to use the supplied character instead of the default "|" for the field separator character. + base= : Base of top numbers. By default it's 10, Use 8 for bytewise + numbering and 16 for wordwise numbering. The following diagram shows the character modifiers described above. From 926e95143c2e14d44c9112fefe561e564d6fbd37 Mon Sep 17 00:00:00 2001 From: Sebastian Walz Date: Sun, 21 Oct 2018 05:03:08 +0200 Subject: [PATCH 4/9] =?UTF-8?q?options=20=C2=BBrows=C2=AB=20added,=20calcu?= =?UTF-8?q?lations=20in=20value-fields?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- protocol | 143 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 76 insertions(+), 67 deletions(-) diff --git a/protocol b/protocol index 8301678..34706c0 100755 --- a/protocol +++ b/protocol @@ -56,7 +56,7 @@ ################################################################################ # STANDARD LIBRARY IMPORTS -import sys +import sys, re from datetime import date # INTERNAL IMPORTS @@ -88,17 +88,18 @@ class Protocol(): Class constructor. @param spec is the textual specification that describes the protocol. """ - self.hdr_char_start="+" # Character for start of the border line - self.hdr_char_end="+" # Character for end of the border line - self.hdr_char_fill_odd="+" # Fill character for border odd positions - self.hdr_char_fill_even="-" # Fill character for border even positions - self.hdr_char_sep="|" # Field separator character - self.bits_per_line=32 # Number of bits per line - self.base_of_top_tens=10 # Base of top numbers - self.do_print_top_tens=True # True: print top numbers for bit tens - self.do_print_top_units=True # True: print top numbers for bit units - self.field_list=[] # Header fields to be printed out - self.parse_spec(spec) # Parse the received spec and populate self.field_list + self.hdr_char_start = "+" # Character for start of the border line + self.hdr_char_end = "+" # Character for end of the border line + self.hdr_char_fill_odd = "+" # Fill character for border odd positions + self.hdr_char_fill_even = "-" # Fill character for border even positions + self.hdr_char_sep = "|" # Field separator character + self.bits_per_line = 32 # Number of bits per line + self.base_of_top_tens = 10 # Base of top numbers + self.do_print_top_tens = True # True: print top numbers for bit tens + self.do_print_top_units = True # True: print top numbers for bit units + self.do_print_line_number = 0 # >0: print line numbers on left side + self.field_list = [] # Header fields to be printed out + self.parse_spec(spec) # Parse the received spec and populate self.field_list def parse_spec(self, spec): @@ -120,19 +121,44 @@ class Protocol(): fields=spec opts=None - # Parse field spec items=fields.split(",") for item in items: try: - text, bits = item.split(":") - bits=int(bits) - if bits<=0: - raise ProtocolException("FATAL: Fields must be at least one bit long (%s)" %spec) + parts = item.split(":") + if len(parts)==1: + if item in specs.protocols: + self.parse_spec(specs.protocols[item]) + else: + start_with_the_same=[] + for spec in specs.protocols: + if spec.startswith(item): + start_with_the_same.append(spec) + if len(start_with_the_same)==1: + self.parse_spec(specs.protocols[start_with_the_same[0]]) + elif len(start_with_the_same)==0: + raise ProtocolException("FATAL: neither does supplied protocol '%s' exist nor does any known protocol start with that." % item) + else: + fail = "Ambiguous protocol specifier '%s'. Did you mean any of these?" % item + for spec in start_with_the_same: + fail += "\n %s" % spec + raise ProtocolException(fail) + else: + text = parts[0] + for item in range(1, len(parts)-1): + text += ":" + parts[item] + bits = parts[-1] + bits = bits.replace("bytes", "8").replace("qwords", "64").replace("dwords", "32").replace("words", "16").replace("double", "64").replace("floats", "32") + if re.match("^[0-9+-/\*~|&^<>()]+$", bits): + bits=eval(bits) + if bits<=0: + raise ProtocolException("FATAL: Fields must be at least one bit long (%s)" % spec) + else: + raise ProtocolException("FATAL: invalid number of bits (%s)" % bits) + self.field_list.append({"text":text, "len":bits}) except ProtocolException: raise except: - raise ProtocolException("FATAL: Invalid field_list specification (%s)" %spec) - self.field_list.append({"text":text, "len":bits}) + raise ProtocolException("FATAL: Invalid field_list specification (%s)" % spec) # Parse options if opts is not None: @@ -141,13 +167,23 @@ class Protocol(): try: var, value = opt.replace(':','=').split("=") if var.lower()=="bits": - self.bits_per_line=int(value) - if self.bits_per_line<=0: + value = value.replace("bytes", "8").replace("qwords", "64").replace("dwords", "32").replace("words", "16").replace("double", "64").replace("floats", "32") + if re.match("^[0-9+-/\*~|&^<>()]+$", value): + self.bits_per_line=eval(value) + if self.bits_per_line<=0: raise ProtocolException("FATAL: Invalid value for 'bits' option (%s)" % value) + else: + raise ProtocolException("FATAL: invalid number of bits (%s)" % value) elif var.lower()=="base": self.base_of_top_tens=int(value) if self.base_of_top_tens<=0 or self.base_of_top_tens>16: raise ProtocolException("FATAL: Invalid value for 'base' option (%s)" % value) + elif var.lower()=="rows": + self.do_print_line_number=int(value) + if self.do_print_line_number<0: + raise ProtocolException("FATAL: Invalid value for 'rows' option (%s)" % value) + if self.do_print_line_number > 0: + self.do_print_line_number += 1 elif var.lower()=="numbers": if value.lower() in ["0", "n", "no", "none", "false"]: self.do_print_top_tens=False @@ -189,17 +225,18 @@ class Protocol(): """ lines=["", ""] chars="0123456789abcdef" + lines[0]+=" "*self.do_print_line_number if self.do_print_top_tens is True: for i in range(0, self.bits_per_line): - if i%self.base_of_top_tens==0: + if i%self.base_of_top_tens == 0: lines[0]+=" %s" % chars[int(i/self.base_of_top_tens)%self.base_of_top_tens] else: lines[0]+=" " lines[0]+="\n" + lines[0]+=" "*self.do_print_line_number if self.do_print_top_units is True: for i in range(0, self.bits_per_line): lines[1]+=" %s" % chars[i%self.base_of_top_tens] - #lines[1]+="\n" result = "".join(lines) return result if len(result)>0 else None @@ -216,9 +253,9 @@ class Protocol(): if width<=0: return "" else: - a="%s" % self.hdr_char_start - b=(self.hdr_char_fill_even+self.hdr_char_fill_odd)*(width-1) - c="%s%s" % (self.hdr_char_fill_even, self.hdr_char_end) + a = "%s" % self.hdr_char_start + b = (self.hdr_char_fill_even+self.hdr_char_fill_odd)*(width-1) + c = "%s%s" % (self.hdr_char_fill_even, self.hdr_char_end) return a+b+c @@ -310,13 +347,14 @@ class Protocol(): numbers=self._get_top_numbers() if numbers is not None: lines.append(numbers) - lines.append(self._get_horizontal()) + lines.append(" "*self.do_print_line_number+self._get_horizontal()) # Print all protocol fields bits_in_line=0 current_line="" fields_done=0 p=-1 + line_number=0 while p < len(proto_fields)-1: p+=1 @@ -339,7 +377,10 @@ class Protocol(): # If this is the first thing we print on a line, add the # starting character if bits_in_line==0: - current_line+=self._get_separator() + if self.do_print_line_number: + current_line+=str(line_number).rjust(self.do_print_line_number-1)+" " + line_number+=1 + current_line+=self._get_separator() # Add the whole field current_line+=str.center(field_text, (field_len*2)-1) @@ -369,6 +410,7 @@ class Protocol(): line_left=self._get_horizontal(self.bits_per_line - field_len) if len(line_left)==0: line_left=self.hdr_char_start + line_left=" "*self.do_print_line_number+line_left # Now print some empty space to cover the part that # we can join with the field below. @@ -389,7 +431,7 @@ class Protocol(): else: lines.append(self._get_horizontal()) else: - lines.append(self._get_horizontal()) + lines.append(" "*self.do_print_line_number+self._get_horizontal()) # If this is not the last character of the line but we have no @@ -420,10 +462,11 @@ class Protocol(): # Let's figure out which character we need to use # to start and end the current line if i%2==1: - start_line=self.hdr_char_start + start_line=" "*self.do_print_line_number+self.hdr_char_start end_line=self.hdr_char_end else: - start_line=self.hdr_char_sep + start_line=str(line_number).rjust(self.do_print_line_number-1)+" "+self.hdr_char_sep + line_number+=1 end_line=self.hdr_char_sep # This is the line where we need to print the field @@ -435,7 +478,7 @@ class Protocol(): lines.append(start_line + (" " * ((self.bits_per_line*2)-1)) + end_line) # If we just added the last line, add a horizontal separator if i==lines_to_print-1: - lines.append(self._get_horizontal()) + lines.append(" "*self.do_print_line_number+self._get_horizontal()) # Case 2: We are not at the beginning of the line and we need # to print something that does not fit in the current line @@ -637,46 +680,12 @@ class Main(): # Protocol name or protocol spec else: - # If it contains ":" characters, we have a protocol spec - if argv[i].count(":")>0: - spec = argv[i] - # Otherwise, the user meant to display an existing protocol - else: - # If we got an exact match, end of story - if argv[i] in specs.protocols: - spec = specs.protocols[argv[i]] - # Otherwise, we may have received a partial match so - # we need to figure out which protocol the user meant. - # If the specification is ambiguous, we will error - else: - start_with_the_same=[] - for spec in specs.protocols: - if spec.startswith(argv[i]): - start_with_the_same.append(spec) - # If we only have one entry, it means we got some - # shortened version of the protocol name but no - # ambiguity. In that case, we will use the match. - if len(start_with_the_same)==1: - spec=specs.protocols[start_with_the_same[0]] - elif len(start_with_the_same)==0: - print("ERROR: supplied protocol '%s' does not exist." % argv[i]); - sys.exit(1) - else: - print("Ambiguous protocol specifier '%s'. Did you mean any of these?" % argv[i]) - for spec in start_with_the_same: - print(" %s" % spec) - sys.exit(1) - # Finally, based on the spec, instance an actual protocol. - # Note that if the spec is incorect, the Protocol() consutrctor - # will call sys.exit() itself, so there is no need to do - # error checking here. try: - proto = Protocol(spec) + proto = Protocol(argv[i]) self.protocols.append(proto) except ProtocolException as e: print("ERROR: %s" % str(e)) sys.exit(1) - if len(self.protocols)==0: print("ERROR: Missing protocol") sys.exit(1) From 2244527665154886b0d588bb6bf27f43bc5fea48 Mon Sep 17 00:00:00 2001 From: Sebastian Walz Date: Sun, 21 Oct 2018 05:49:53 +0200 Subject: [PATCH 5/9] unicode support --- protocol | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/protocol b/protocol index 34706c0..bb037d4 100755 --- a/protocol +++ b/protocol @@ -1,4 +1,5 @@ #!/usr/bin/python +# -*- coding: utf-8 -*- ################################################################################ # ____ _ _ # # | _ \ _ __ ___ | |_ ___ ___ ___ | | # @@ -63,6 +64,8 @@ from datetime import date from constants import * import specs +reload(sys) +sys.setdefaultencoding('utf8') # CLASS DEFINITIONS class ProtocolException(Exception): @@ -72,8 +75,8 @@ class ProtocolException(Exception): def __init__(self, errmsg): self.errmsg=errmsg - def __str__(self): - return str(self.errmsg) + def __unicode__(self): + return self.errmsg class Protocol(): @@ -194,6 +197,7 @@ class Protocol(): else: raise ProtocolException("FATAL: Invalid value for 'numbers' option (%s)" % value) elif var.lower() in ["oddchar", "evenchar", "startchar", "endchar", "sepchar"]: + value = value.decode("utf-8") if len(value)>1 or len(value)<=0: raise ProtocolException("FATAL: Invalid value for '%s' option (%s)" % (var, value)) else: @@ -272,7 +276,7 @@ class Protocol(): Processes the list of protocol fields that we got from the spec and turns it into something that we can print easily (useful for cases when we have protocol fields that span more than one line). This is just a helper - function to make __str__()'s life easier. + function to make __unicode__()'s life easier. """ new_fields=[] bits_in_line=0 @@ -330,7 +334,7 @@ class Protocol(): # Convert to string - def __str__(self): + def __unicode__(self): """ Converts the protocol specification stored in the object to a nice ASCII diagram like the ones that appear in RFCs. Conversion supports @@ -366,9 +370,9 @@ class Protocol(): # If the field text is too long, we truncate it, and add a dot # at the end. - if len(field_text) > (field_len*2)-1: + if len(field_text.decode("utf-8")) > (field_len*2)-1: field_text=field_text[0:(field_len*2)-1] - if len(field_text)>1: + if len(field_text.decode("utf-8"))>1: field_text=field_text[0:-1]+"." # If we have space for the whole field in the current line, go @@ -383,7 +387,7 @@ class Protocol(): current_line+=self._get_separator() # Add the whole field - current_line+=str.center(field_text, (field_len*2)-1) + current_line+=str.center(field_text, (field_len*2)-1-len(field_text.decode("utf8"))+len(field_text)) # Update counters bits_in_line+=field_len @@ -465,14 +469,16 @@ class Protocol(): start_line=" "*self.do_print_line_number+self.hdr_char_start end_line=self.hdr_char_end else: - start_line=str(line_number).rjust(self.do_print_line_number-1)+" "+self.hdr_char_sep - line_number+=1 + start_line=self.hdr_char_sep + if self.do_print_line_number: + start_line=str(line_number).rjust(self.do_print_line_number-1)+" "+start_line + line_number+=1 end_line=self.hdr_char_sep # This is the line where we need to print the field # text. if i == central_line: - lines.append(start_line + str.center(field_text, (self.bits_per_line*2)-1) + end_line) + lines.append(start_line + str.center(field_text, (self.bits_per_line*2)-1-len(field_text.decode("utf8"))+len(field_text)) + end_line) # This is a line we need to leave blank else: lines.append(start_line + (" " * ((self.bits_per_line*2)-1)) + end_line) @@ -490,7 +496,8 @@ class Protocol(): result= "\n".join(lines) return result - + def __str__(self): + return unicode(self).encode('utf-8') class Main(): """ From 6c983852f5fe2285f9da552cbef093b970fb41fc Mon Sep 17 00:00:00 2001 From: Sebastian Walz Date: Tue, 23 Oct 2018 02:11:12 +0200 Subject: [PATCH 6/9] typos --- README.txt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.txt b/README.txt index 1ac0c2b..cd20b88 100644 --- a/README.txt +++ b/README.txt @@ -228,14 +228,16 @@ oddchar= : Same as evenchar but for characters in odd positions of the horizontal lines. By default it takes the value '+' startchar= : Instructs protocol to use the supplied character instead - of the default "+" for the first position of an horizontal + of the default "+" for the first position of a horizontal line. endchar= : Same as startchar but for the character in the last - position of the horizonal lines. + position of the horizontal lines. sepchar= : Instructs protocol to use the supplied character instead of the default "|" for the field separator character. base= : Base of top numbers. By default it's 10, Use 8 for bytewise numbering and 16 for wordwise numbering. + rows= : width for row numbering. By default it is zero, so no + numbering of rows. The following diagram shows the character modifiers described above. From df06d30685786e3faf068784cf592ac4c1453bb4 Mon Sep 17 00:00:00 2001 From: Sebastian Walz Date: Tue, 23 Oct 2018 02:16:19 +0200 Subject: [PATCH 7/9] Update protocol --- protocol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocol b/protocol index bb037d4..2684270 100755 --- a/protocol +++ b/protocol @@ -401,7 +401,7 @@ class Protocol(): bits_in_line=0 # When we have a fragmented field, we may need to suppress # the floor of the field, so the current line connects - # with the one that follows. E.g: + # with the one that follows. E.g.: # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # | field16 | | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + From 3e7cce27ae49f3daa3c1f126d460784cbaf9de0b Mon Sep 17 00:00:00 2001 From: Sebastian Walz Date: Tue, 23 Oct 2018 02:20:29 +0200 Subject: [PATCH 8/9] DHCP --- specs.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/specs.py b/specs.py index 9f57ad4..1aa0d46 100644 --- a/specs.py +++ b/specs.py @@ -493,6 +493,8 @@ icmpv6_nadv="Type:8,Code:8,Checksum:16,R:1,S:1,O:1,Reserved:29,Target\ icmpv6_redirect="Type:8,Code:8,Checksum:16,Reserved:32,Target Address:128,\ Destination Address:128,Options:64" +dhcp = "Opcode:8,Hardware Type: 8,HW Addr Len:8,Hop Count:8,Transaction ID:32,Number of Seconds:16,Flags:16,Client IP Addr:32,Your IP Addr: 32,Server IP Addr:32,Gateway IP Addr:32,Client Hardware Addr:128,Server Host Name:512,Boot Filename:1024" + modbus_tcp="Transaction ID:16,Protocol ID:16,Length:16,Address:8,Function Code:8,Data:64" profinet_rt="Frame ID:16,User Data:80,Cycle Counter:16,Data Status:8,Transfer Status:8" @@ -749,6 +751,7 @@ protocols={"ethernet":ethernet, "icmpv6-nsol":icmpv6_nsol, "icmpv6-nadv":icmpv6_nadv, "icmpv6-redirect":icmpv6_redirect, + "dhcp":dhcp, "modbus_tcp":modbus_tcp, "profinet_rt":profinet_rt, "tsap":tsap, From 5375463167fcd0d3418a8f3471de56758083d399 Mon Sep 17 00:00:00 2001 From: Sebastian Walz Date: Tue, 23 Oct 2018 10:21:03 +0200 Subject: [PATCH 9/9] remove _get_separator --- protocol | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/protocol b/protocol index 2684270..61899cb 100755 --- a/protocol +++ b/protocol @@ -262,15 +262,6 @@ class Protocol(): c = "%s%s" % (self.hdr_char_fill_even, self.hdr_char_end) return a+b+c - - def _get_separator(self, line_end=""): - """ - @return a string containing a protocol field separator. Returned string - is a single character and matches whatever is stored in self.hdr_char_sep - """ - return self.hdr_char_sep - - def _process_field_list(self): """ Processes the list of protocol fields that we got from the spec and turns @@ -384,7 +375,7 @@ class Protocol(): if self.do_print_line_number: current_line+=str(line_number).rjust(self.do_print_line_number-1)+" " line_number+=1 - current_line+=self._get_separator() + current_line+=self.hdr_char_sep # Add the whole field current_line+=str.center(field_text, (field_len*2)-1-len(field_text.decode("utf8"))+len(field_text)) @@ -395,7 +386,7 @@ class Protocol(): # If this is the last character in the line, store the line if bits_in_line==self.bits_per_line: - current_line+=self._get_separator() + current_line+=self.hdr_char_sep lines.append(current_line) current_line="" bits_in_line=0 @@ -441,7 +432,7 @@ class Protocol(): # If this is not the last character of the line but we have no # more fields to print, wrap up elif fields_done==len(proto_fields): - current_line+=self._get_separator() + current_line+=self.hdr_char_sep lines.append(current_line) lines.append(self._get_horizontal(bits_in_line)) else: