diff options
Diffstat (limited to 'tester/rt/tftpy/TftpPacketTypes.py')
-rw-r--r-- | tester/rt/tftpy/TftpPacketTypes.py | 494 |
1 files changed, 0 insertions, 494 deletions
diff --git a/tester/rt/tftpy/TftpPacketTypes.py b/tester/rt/tftpy/TftpPacketTypes.py deleted file mode 100644 index 3d3bdf8..0000000 --- a/tester/rt/tftpy/TftpPacketTypes.py +++ /dev/null @@ -1,494 +0,0 @@ -# vim: ts=4 sw=4 et ai: -# -*- coding: utf8 -*- -"""This module implements the packet types of TFTP itself, and the -corresponding encode and decode methods for them.""" - - -import struct -import sys -import logging -from .TftpShared import * - -log = logging.getLogger('tftpy.TftpPacketTypes') - -class TftpSession(object): - """This class is the base class for the tftp client and server. Any shared - code should be in this class.""" - # FIXME: do we need this anymore? - pass - -class TftpPacketWithOptions(object): - """This class exists to permit some TftpPacket subclasses to share code - regarding options handling. It does not inherit from TftpPacket, as the - goal is just to share code here, and not cause diamond inheritance.""" - - def __init__(self): - self.options = {} - - # Always use unicode strings, except at the encode/decode barrier. - # Simpler to keep things clear. - def setoptions(self, options): - log.debug("in TftpPacketWithOptions.setoptions") - log.debug("options: %s", options) - myoptions = {} - for key in options: - newkey = key - if isinstance(key, bytes): - newkey = newkey.decode('ascii') - newval = options[key] - if isinstance(newval, bytes): - newval = newval.decode('ascii') - myoptions[newkey] = newval - log.debug("populated myoptions with %s = %s", newkey, myoptions[newkey]) - - log.debug("setting options hash to: %s", myoptions) - self._options = myoptions - - def getoptions(self): - log.debug("in TftpPacketWithOptions.getoptions") - return self._options - - # Set up getter and setter on options to ensure that they are the proper - # type. They should always be strings, but we don't need to force the - # client to necessarily enter strings if we can avoid it. - options = property(getoptions, setoptions) - - def decode_options(self, buffer): - """This method decodes the section of the buffer that contains an - unknown number of options. It returns a dictionary of option names and - values.""" - fmt = b"!" - options = {} - - log.debug("decode_options: buffer is: %s", repr(buffer)) - log.debug("size of buffer is %d bytes", len(buffer)) - if len(buffer) == 0: - log.debug("size of buffer is zero, returning empty hash") - return {} - - # Count the nulls in the buffer. Each one terminates a string. - log.debug("about to iterate options buffer counting nulls") - length = 0 - for i in range(len(buffer)): - if ord(buffer[i:i+1]) == 0: - log.debug("found a null at length %d", length) - if length > 0: - fmt += b"%dsx" % length - length = -1 - else: - raise TftpException("Invalid options in buffer") - length += 1 - - log.debug("about to unpack, fmt is: %s", fmt) - mystruct = struct.unpack(fmt, buffer) - - tftpassert(len(mystruct) % 2 == 0, - "packet with odd number of option/value pairs") - - for i in range(0, len(mystruct), 2): - key = mystruct[i].decode('ascii') - val = mystruct[i+1].decode('ascii') - log.debug("setting option %s to %s", key, val) - log.debug("types are %s and %s", type(key), type(val)) - options[key] = val - - return options - -class TftpPacket(object): - """This class is the parent class of all tftp packet classes. It is an - abstract class, providing an interface, and should not be instantiated - directly.""" - def __init__(self): - self.opcode = 0 - self.buffer = None - - def encode(self): - """The encode method of a TftpPacket takes keyword arguments specific - to the type of packet, and packs an appropriate buffer in network-byte - order suitable for sending over the wire. - - This is an abstract method.""" - raise NotImplementedError("Abstract method") - - def decode(self): - """The decode method of a TftpPacket takes a buffer off of the wire in - network-byte order, and decodes it, populating internal properties as - appropriate. This can only be done once the first 2-byte opcode has - already been decoded, but the data section does include the entire - datagram. - - This is an abstract method.""" - raise NotImplementedError("Abstract method") - -class TftpPacketInitial(TftpPacket, TftpPacketWithOptions): - """This class is a common parent class for the RRQ and WRQ packets, as - they share quite a bit of code.""" - def __init__(self): - TftpPacket.__init__(self) - TftpPacketWithOptions.__init__(self) - self.filename = None - self.mode = None - - def encode(self): - """Encode the packet's buffer from the instance variables.""" - tftpassert(self.filename, "filename required in initial packet") - tftpassert(self.mode, "mode required in initial packet") - # Make sure filename and mode are bytestrings. - filename = self.filename - mode = self.mode - if not isinstance(filename, bytes): - filename = filename.encode('ascii') - if not isinstance(self.mode, bytes): - mode = mode.encode('ascii') - - ptype = None - if self.opcode == 1: ptype = "RRQ" - else: ptype = "WRQ" - log.debug("Encoding %s packet, filename = %s, mode = %s", - ptype, filename, mode) - for key in self.options: - log.debug(" Option %s = %s", key, self.options[key]) - - fmt = b"!H" - fmt += b"%dsx" % len(filename) - if mode == b"octet": - fmt += b"5sx" - else: - raise AssertionError("Unsupported mode: %s" % mode) - # Add options. Note that the options list must be bytes. - options_list = [] - if len(list(self.options.keys())) > 0: - log.debug("there are options to encode") - for key in self.options: - # Populate the option name - name = key - if not isinstance(name, bytes): - name = name.encode('ascii') - options_list.append(name) - fmt += b"%dsx" % len(name) - # Populate the option value - value = self.options[key] - # Work with all strings. - if isinstance(value, int): - value = str(value) - if not isinstance(value, bytes): - value = value.encode('ascii') - options_list.append(value) - fmt += b"%dsx" % len(value) - - log.debug("fmt is %s", fmt) - log.debug("options_list is %s", options_list) - log.debug("size of struct is %d", struct.calcsize(fmt)) - - self.buffer = struct.pack(fmt, - self.opcode, - filename, - mode, - *options_list) - - log.debug("buffer is %s", repr(self.buffer)) - return self - - def decode(self): - tftpassert(self.buffer, "Can't decode, buffer is empty") - - # FIXME - this shares a lot of code with decode_options - nulls = 0 - fmt = b"" - nulls = length = tlength = 0 - log.debug("in decode: about to iterate buffer counting nulls") - subbuf = self.buffer[2:] - for i in range(len(subbuf)): - if ord(subbuf[i:i+1]) == 0: - nulls += 1 - log.debug("found a null at length %d, now have %d", length, nulls) - fmt += b"%dsx" % length - length = -1 - # At 2 nulls, we want to mark that position for decoding. - if nulls == 2: - break - length += 1 - tlength += 1 - - log.debug("hopefully found end of mode at length %d", tlength) - # length should now be the end of the mode. - tftpassert(nulls == 2, "malformed packet") - shortbuf = subbuf[:tlength+1] - log.debug("about to unpack buffer with fmt: %s", fmt) - log.debug("unpacking buffer: %s", repr(shortbuf)) - mystruct = struct.unpack(fmt, shortbuf) - - tftpassert(len(mystruct) == 2, "malformed packet") - self.filename = mystruct[0].decode('ascii') - self.mode = mystruct[1].decode('ascii').lower() # force lc - bug 17 - log.debug("set filename to %s", self.filename) - log.debug("set mode to %s", self.mode) - - self.options = self.decode_options(subbuf[tlength+1:]) - log.debug("options dict is now %s", self.options) - return self - -class TftpPacketRRQ(TftpPacketInitial): - """ -:: - - 2 bytes string 1 byte string 1 byte - ----------------------------------------------- - RRQ/ | 01/02 | Filename | 0 | Mode | 0 | - WRQ ----------------------------------------------- - """ - def __init__(self): - TftpPacketInitial.__init__(self) - self.opcode = 1 - - def __str__(self): - s = 'RRQ packet: filename = %s' % self.filename - s += ' mode = %s' % self.mode - if self.options: - s += '\n options = %s' % self.options - return s - -class TftpPacketWRQ(TftpPacketInitial): - """ -:: - - 2 bytes string 1 byte string 1 byte - ----------------------------------------------- - RRQ/ | 01/02 | Filename | 0 | Mode | 0 | - WRQ ----------------------------------------------- - """ - def __init__(self): - TftpPacketInitial.__init__(self) - self.opcode = 2 - - def __str__(self): - s = 'WRQ packet: filename = %s' % self.filename - s += ' mode = %s' % self.mode - if self.options: - s += '\n options = %s' % self.options - return s - -class TftpPacketDAT(TftpPacket): - """ -:: - - 2 bytes 2 bytes n bytes - --------------------------------- - DATA | 03 | Block # | Data | - --------------------------------- - """ - def __init__(self): - TftpPacket.__init__(self) - self.opcode = 3 - self.blocknumber = 0 - self.data = None - - def __str__(self): - s = 'DAT packet: block %s' % self.blocknumber - if self.data: - s += '\n data: %d bytes' % len(self.data) - return s - - def encode(self): - """Encode the DAT packet. This method populates self.buffer, and - returns self for easy method chaining.""" - if len(self.data) == 0: - log.debug("Encoding an empty DAT packet") - data = self.data - if not isinstance(self.data, bytes): - data = self.data.encode('ascii') - fmt = b"!HH%ds" % len(data) - self.buffer = struct.pack(fmt, - self.opcode, - self.blocknumber, - data) - return self - - def decode(self): - """Decode self.buffer into instance variables. It returns self for - easy method chaining.""" - # We know the first 2 bytes are the opcode. The second two are the - # block number. - (self.blocknumber,) = struct.unpack(str("!H"), self.buffer[2:4]) - log.debug("decoding DAT packet, block number %d", self.blocknumber) - log.debug("should be %d bytes in the packet total", len(self.buffer)) - # Everything else is data. - self.data = self.buffer[4:] - log.debug("found %d bytes of data", len(self.data)) - return self - -class TftpPacketACK(TftpPacket): - """ -:: - - 2 bytes 2 bytes - ------------------- - ACK | 04 | Block # | - -------------------- - """ - def __init__(self): - TftpPacket.__init__(self) - self.opcode = 4 - self.blocknumber = 0 - - def __str__(self): - return 'ACK packet: block %d' % self.blocknumber - - def encode(self): - log.debug("encoding ACK: opcode = %d, block = %d", - self.opcode, self.blocknumber) - self.buffer = struct.pack(str("!HH"), self.opcode, self.blocknumber) - return self - - def decode(self): - if len(self.buffer) > 4: - log.debug("detected TFTP ACK but request is too large, will truncate") - log.debug("buffer was: %s", repr(self.buffer)) - self.buffer = self.buffer[0:4] - self.opcode, self.blocknumber = struct.unpack(str("!HH"), self.buffer) - log.debug("decoded ACK packet: opcode = %d, block = %d", - self.opcode, self.blocknumber) - return self - -class TftpPacketERR(TftpPacket): - """ -:: - - 2 bytes 2 bytes string 1 byte - ---------------------------------------- - ERROR | 05 | ErrorCode | ErrMsg | 0 | - ---------------------------------------- - - Error Codes - - Value Meaning - - 0 Not defined, see error message (if any). - 1 File not found. - 2 Access violation. - 3 Disk full or allocation exceeded. - 4 Illegal TFTP operation. - 5 Unknown transfer ID. - 6 File already exists. - 7 No such user. - 8 Failed to negotiate options - """ - def __init__(self): - TftpPacket.__init__(self) - self.opcode = 5 - self.errorcode = 0 - # FIXME: We don't encode the errmsg... - self.errmsg = None - # FIXME - integrate in TftpErrors references? - self.errmsgs = { - 1: b"File not found", - 2: b"Access violation", - 3: b"Disk full or allocation exceeded", - 4: b"Illegal TFTP operation", - 5: b"Unknown transfer ID", - 6: b"File already exists", - 7: b"No such user", - 8: b"Failed to negotiate options" - } - - def __str__(self): - s = 'ERR packet: errorcode = %d' % self.errorcode - s += '\n msg = %s' % self.errmsgs.get(self.errorcode, '') - return s - - def encode(self): - """Encode the DAT packet based on instance variables, populating - self.buffer, returning self.""" - fmt = b"!HH%dsx" % len(self.errmsgs[self.errorcode]) - log.debug("encoding ERR packet with fmt %s", fmt) - self.buffer = struct.pack(fmt, - self.opcode, - self.errorcode, - self.errmsgs[self.errorcode]) - return self - - def decode(self): - "Decode self.buffer, populating instance variables and return self." - buflen = len(self.buffer) - tftpassert(buflen >= 4, "malformed ERR packet, too short") - log.debug("Decoding ERR packet, length %s bytes", buflen) - if buflen == 4: - log.debug("Allowing this affront to the RFC of a 4-byte packet") - fmt = b"!HH" - log.debug("Decoding ERR packet with fmt: %s", fmt) - self.opcode, self.errorcode = struct.unpack(fmt, - self.buffer) - else: - log.debug("Good ERR packet > 4 bytes") - fmt = b"!HH%dsx" % (len(self.buffer) - 5) - log.debug("Decoding ERR packet with fmt: %s", fmt) - self.opcode, self.errorcode, self.errmsg = struct.unpack(fmt, - self.buffer) - log.error("ERR packet - errorcode: %d, message: %s" - % (self.errorcode, self.errmsg)) - return self - -class TftpPacketOACK(TftpPacket, TftpPacketWithOptions): - """ -:: - - +-------+---~~---+---+---~~---+---+---~~---+---+---~~---+---+ - | opc | opt1 | 0 | value1 | 0 | optN | 0 | valueN | 0 | - +-------+---~~---+---+---~~---+---+---~~---+---+---~~---+---+ - """ - def __init__(self): - TftpPacket.__init__(self) - TftpPacketWithOptions.__init__(self) - self.opcode = 6 - - def __str__(self): - return 'OACK packet:\n options = %s' % self.options - - def encode(self): - fmt = b"!H" # opcode - options_list = [] - log.debug("in TftpPacketOACK.encode") - for key in self.options: - value = self.options[key] - if isinstance(value, int): - value = str(value) - if not isinstance(key, bytes): - key = key.encode('ascii') - if not isinstance(value, bytes): - value = value.encode('ascii') - log.debug("looping on option key %s", key) - log.debug("value is %s", value) - fmt += b"%dsx" % len(key) - fmt += b"%dsx" % len(value) - options_list.append(key) - options_list.append(value) - self.buffer = struct.pack(fmt, self.opcode, *options_list) - return self - - def decode(self): - self.options = self.decode_options(self.buffer[2:]) - return self - - def match_options(self, options): - """This method takes a set of options, and tries to match them with - its own. It can accept some changes in those options from the server as - part of a negotiation. Changed or unchanged, it will return a dict of - the options so that the session can update itself to the negotiated - options.""" - for name in self.options: - if name in options: - if name == 'blksize': - # We can accept anything between the min and max values. - size = int(self.options[name]) - if size >= MIN_BLKSIZE and size <= MAX_BLKSIZE: - log.debug("negotiated blksize of %d bytes", size) - options['blksize'] = size - else: - raise TftpException("blksize %s option outside allowed range" % size) - elif name == 'tsize': - size = int(self.options[name]) - if size < 0: - raise TftpException("Negative file sizes not supported") - else: - raise TftpException("Unsupported option: %s" % name) - return True |