Skip to content

tftp.netascii

tftp.netascii

Streaming netascii translation (RFC 764 as used by RFC 1350).

On the wire, a line ends in CR LF and a bare CR is sent as CR NUL. Locally, this module uses LF for a line end and leaves every other byte alone, so the translation is lossless both ways: a local \r\n travels as \r\0\r\n and comes back as \r\n.

Both directions are incremental, so a CR that falls on a block boundary is handled correctly.

NetasciiReader(raw)

A binary reader whose bytes come out netascii-encoded.

Only readinto and close are provided -- what a sender needs. The source is read with readinto when it has one, so a non-blocking source's WouldBlock passes through (after any bytes already produced) instead of a blocking read stalling the caller.

Source code in src/tftp/netascii.py
67
68
69
70
71
def __init__(self, raw: BinaryIO) -> None:
    self._raw = raw
    self._pending = b""
    self._eof = False
    self._readinto = getattr(raw, "readinto", None)

NetasciiWriter(raw)

A binary writer that decodes netascii before writing it through.

A trailing CR is held back until the next write shows what follows it; :meth:close flushes it as a plain CR.

Source code in src/tftp/netascii.py
117
118
119
120
121
def __init__(self, raw: BinaryIO) -> None:
    self._raw = raw
    #: Writes all of what it is given: a raw stream may take part of it a call.
    self._write = as_write(raw)
    self._held_cr = False

decode(data)

Translate a complete netascii byte string back to local bytes.

Source code in src/tftp/netascii.py
29
30
31
32
33
def decode(data: bytes) -> bytes:
    """Translate a complete netascii byte string back to local bytes."""
    # On a well-formed stream every CR is followed by LF or NUL, so a CR LF
    # pair can only be an encoded line end.
    return data.replace(b"\r\n", b"\n").replace(b"\r\0", b"\r")

encode(data)

Translate local bytes to netascii in one go.

Source code in src/tftp/netascii.py
23
24
25
26
def encode(data: bytes) -> bytes:
    """Translate local bytes to netascii in one go."""
    # CR first: the CRs that LF translation introduces must not gain a NUL.
    return data.replace(b"\r", b"\r\0").replace(b"\n", b"\r\n")

encoded_size(fileobj)

Netascii size of a seekable binary file, leaving its position alone.

Every CR and LF grows by one byte. Returns None when the file cannot be scanned (not seekable).

Source code in src/tftp/netascii.py
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
def encoded_size(fileobj: BinaryIO) -> Optional[int]:
    """Netascii size of a seekable binary file, leaving its position alone.

    Every CR and LF grows by one byte. Returns ``None`` when the file cannot
    be scanned (not seekable).
    """
    try:
        start = fileobj.tell()
    except (AttributeError, OSError, ValueError):
        return None
    total = 0
    try:
        while True:
            chunk = fileobj.read(_CHUNK)
            if not chunk:
                break
            total += len(chunk) + chunk.count(b"\r") + chunk.count(b"\n")
    finally:
        fileobj.seek(start)
    return total