summaryrefslogtreecommitdiff
path: root/tftpy/TftpClient.py
blob: 852181b887b8d64c7839e0cb4060cc607bbc1880 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# vim: ts=4 sw=4 et ai:
"""This module implements the TFTP Client functionality. Instantiate an
instance of the client, and then use its upload or download method. Logging is
performed via a standard logging object set in TftpShared."""


import logging
import types

from .TftpContexts import TftpContextClientDownload, TftpContextClientUpload
from .TftpPacketTypes import *
from .TftpShared import *

log = logging.getLogger("tftpy.TftpClient")


class TftpClient(TftpSession):
    """This class is an implementation of a tftp client. Once instantiated, a
    download can be initiated via the download() method, or an upload via the
    upload() method."""

    def __init__(self, host, port=69, options={}, localip=""):
        TftpSession.__init__(self)
        self.context = None
        self.host = host
        self.iport = port
        self.filename = None
        self.options = options
        self.localip = localip
        if "blksize" in self.options:
            size = self.options["blksize"]
            tftpassert(int == type(size), "blksize must be an int")
            if size < MIN_BLKSIZE or size > MAX_BLKSIZE:
                raise TftpException("Invalid blksize: %d" % size)

    def download(
        self,
        filename,
        output,
        packethook=None,
        timeout=SOCK_TIMEOUT,
        retries=DEF_TIMEOUT_RETRIES,
    ):
        """This method initiates a tftp download from the configured remote
        host, requesting the filename passed. It writes the file to output,
        which can be a file-like object or a path to a local file. If a
        packethook is provided, it must be a function that takes a single
        parameter, which will be a copy of each DAT packet received in the
        form of a TftpPacketDAT object. The timeout parameter may be used to
        override the default SOCK_TIMEOUT setting, which is the amount of time
        that the client will wait for a receive packet to arrive.
        The retires paramater may be used to override the default DEF_TIMEOUT_RETRIES
        settings, which is the amount of retransmission attemtpts the client will initiate
        after encountering a timeout.

        Note: If output is a hyphen, stdout is used."""
        # We're downloading.
        log.debug("Creating download context with the following params:")
        log.debug(f"host = {self.host}, port = {self.iport}, filename = {filename}")
        log.debug(
            "options = %s, packethook = %s, timeout = %s"
            % (self.options, packethook, timeout)
        )
        self.context = TftpContextClientDownload(
            self.host,
            self.iport,
            filename,
            output,
            self.options,
            packethook,
            timeout,
            retries=retries,
            localip=self.localip,
        )
        self.context.start()
        # Download happens here
        self.context.end()

        metrics = self.context.metrics

        log.info("")
        log.info("Download complete.")
        if metrics.duration == 0:
            log.info("Duration too short, rate undetermined")
        else:
            log.info(
                "Downloaded %.2f bytes in %.2f seconds"
                % (metrics.bytes, metrics.duration)
            )
            log.info("Average rate: %.2f kbps" % metrics.kbps)
        log.info("%.2f bytes in resent data" % metrics.resent_bytes)
        log.info("Received %d duplicate packets" % metrics.dupcount)

    def upload(
        self,
        filename,
        input,
        packethook=None,
        timeout=SOCK_TIMEOUT,
        retries=DEF_TIMEOUT_RETRIES,
    ):
        """This method initiates a tftp upload to the configured remote host,
        uploading the filename passed. It reads the file from input, which
        can be a file-like object or a path to a local file. If a packethook
        is provided, it must be a function that takes a single parameter,
        which will be a copy of each DAT packet sent in the form of a
        TftpPacketDAT object. The timeout parameter may be used to override
        the default SOCK_TIMEOUT setting, which is the amount of time that
        the client will wait for a DAT packet to be ACKd by the server.
        The retires paramater may be used to override the default DEF_TIMEOUT_RETRIES
        settings, which is the amount of retransmission attemtpts the client will initiate
        after encountering a timeout.

        Note: If input is a hyphen, stdin is used."""
        self.context = TftpContextClientUpload(
            self.host,
            self.iport,
            filename,
            input,
            self.options,
            packethook,
            timeout,
            retries=retries,
            localip=self.localip,
        )
        self.context.start()
        # Upload happens here
        self.context.end()

        metrics = self.context.metrics

        log.info("")
        log.info("Upload complete.")
        if metrics.duration == 0:
            log.info("Duration too short, rate undetermined")
        else:
            log.info(
                "Uploaded %d bytes in %.2f seconds" % (metrics.bytes, metrics.duration)
            )
            log.info("Average rate: %.2f kbps" % metrics.kbps)
        log.info("%.2f bytes in resent data" % metrics.resent_bytes)
        log.info("Resent %d packets" % metrics.dupcount)