From 6f961f2e4c805dcf98809e7711efad07133ff68b Mon Sep 17 00:00:00 2001 From: Fu Hanxi Date: Tue, 17 Sep 2024 10:29:53 +0200 Subject: [PATCH 1/2] refactor: pull telnetlib.py from python v3.12.6 --- pytest-embedded-jtag/__init__.py | 0 .../pytest_embedded_jtag/__init__.py | 2 + .../pytest_embedded_jtag/_telnetlib/LICENSE | 284 ++++++++ .../_telnetlib/__init__.py | 0 .../_telnetlib/telnetlib.py | 680 ++++++++++++++++++ .../pytest_embedded_jtag/openocd.py | 5 +- .../pytest_embedded_serial/dut.py | 4 +- ruff.toml | 5 +- 8 files changed, 974 insertions(+), 6 deletions(-) create mode 100644 pytest-embedded-jtag/__init__.py create mode 100644 pytest-embedded-jtag/pytest_embedded_jtag/_telnetlib/LICENSE create mode 100644 pytest-embedded-jtag/pytest_embedded_jtag/_telnetlib/__init__.py create mode 100644 pytest-embedded-jtag/pytest_embedded_jtag/_telnetlib/telnetlib.py diff --git a/pytest-embedded-jtag/__init__.py b/pytest-embedded-jtag/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pytest-embedded-jtag/pytest_embedded_jtag/__init__.py b/pytest-embedded-jtag/pytest_embedded_jtag/__init__.py index 343a1a39..e7b63a00 100644 --- a/pytest-embedded-jtag/pytest_embedded_jtag/__init__.py +++ b/pytest-embedded-jtag/pytest_embedded_jtag/__init__.py @@ -1,11 +1,13 @@ """Make pytest-embedded plugin work with JTAG.""" +from ._telnetlib.telnetlib import Telnet from .gdb import Gdb from .openocd import OpenOcd __all__ = [ 'Gdb', 'OpenOcd', + 'Telnet', ] __version__ = '1.11.5' diff --git a/pytest-embedded-jtag/pytest_embedded_jtag/_telnetlib/LICENSE b/pytest-embedded-jtag/pytest_embedded_jtag/_telnetlib/LICENSE new file mode 100644 index 00000000..35da3c21 --- /dev/null +++ b/pytest-embedded-jtag/pytest_embedded_jtag/_telnetlib/LICENSE @@ -0,0 +1,284 @@ +# This license only applies to the telnetlib.py file under the current folder +# This file is copied from the Python 3.12.6 source code +# https://github.com/python/cpython/blob/3.12/Lib/telnetlib.py +# For contributing history please refer to the original github page + +A. HISTORY OF THE SOFTWARE +========================== + +Python was created in the early 1990s by Guido van Rossum at Stichting +Mathematisch Centrum (CWI, see https://www.cwi.nl) in the Netherlands +as a successor of a language called ABC. Guido remains Python's +principal author, although it includes many contributions from others. + +In 1995, Guido continued his work on Python at the Corporation for +National Research Initiatives (CNRI, see https://www.cnri.reston.va.us) +in Reston, Virginia where he released several versions of the +software. + +In May 2000, Guido and the Python core development team moved to +BeOpen.com to form the BeOpen PythonLabs team. In October of the same +year, the PythonLabs team moved to Digital Creations, which became +Zope Corporation. In 2001, the Python Software Foundation (PSF, see +https://www.python.org/psf/) was formed, a non-profit organization +created specifically to own Python-related Intellectual Property. +Zope Corporation was a sponsoring member of the PSF. + +All Python releases are Open Source (see https://opensource.org for +the Open Source Definition). Historically, most, but not all, Python +releases have also been GPL-compatible; the table below summarizes +the various releases. + + Release Derived Year Owner GPL- + from compatible? (1) + + 0.9.0 thru 1.2 1991-1995 CWI yes + 1.3 thru 1.5.2 1.2 1995-1999 CNRI yes + 1.6 1.5.2 2000 CNRI no + 2.0 1.6 2000 BeOpen.com no + 1.6.1 1.6 2001 CNRI yes (2) + 2.1 2.0+1.6.1 2001 PSF no + 2.0.1 2.0+1.6.1 2001 PSF yes + 2.1.1 2.1+2.0.1 2001 PSF yes + 2.1.2 2.1.1 2002 PSF yes + 2.1.3 2.1.2 2002 PSF yes + 2.2 and above 2.1.1 2001-now PSF yes + +Footnotes: + +(1) GPL-compatible doesn't mean that we're distributing Python under + the GPL. All Python licenses, unlike the GPL, let you distribute + a modified version without making your changes open source. The + GPL-compatible licenses make it possible to combine Python with + other software that is released under the GPL; the others don't. + +(2) According to Richard Stallman, 1.6.1 is not GPL-compatible, + because its license has a choice of law clause. According to + CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1 + is "not incompatible" with the GPL. + +Thanks to the many outside volunteers who have worked under Guido's +direction to make these releases possible. + + +B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON +=============================================================== + +Python software and documentation are licensed under the +Python Software Foundation License Version 2. + +Starting with Python 3.8.6, examples, recipes, and other code in +the documentation are dual licensed under the PSF License Version 2 +and the Zero-Clause BSD license. + +Some software incorporated into Python is under different licenses. +The licenses are listed with code falling under that license. + + +PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 +-------------------------------------------- + +1. This LICENSE AGREEMENT is between the Python Software Foundation +("PSF"), and the Individual or Organization ("Licensee") accessing and +otherwise using this software ("Python") in source or binary form and +its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, PSF hereby +grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, +analyze, test, perform and/or display publicly, prepare derivative works, +distribute, and otherwise use Python alone or in any derivative version, +provided, however, that PSF's License Agreement and PSF's notice of copyright, +i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, +2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023 Python Software Foundation; +All Rights Reserved" are retained in Python alone or in any derivative version +prepared by Licensee. + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python. + +4. PSF is making Python available to Licensee on an "AS IS" +basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. Nothing in this License Agreement shall be deemed to create any +relationship of agency, partnership, or joint venture between PSF and +Licensee. This License Agreement does not grant permission to use PSF +trademarks or trade name in a trademark sense to endorse or promote +products or services of Licensee, or any third party. + +8. By copying, installing or otherwise using Python, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0 +------------------------------------------- + +BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1 + +1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an +office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the +Individual or Organization ("Licensee") accessing and otherwise using +this software in source or binary form and its associated +documentation ("the Software"). + +2. Subject to the terms and conditions of this BeOpen Python License +Agreement, BeOpen hereby grants Licensee a non-exclusive, +royalty-free, world-wide license to reproduce, analyze, test, perform +and/or display publicly, prepare derivative works, distribute, and +otherwise use the Software alone or in any derivative version, +provided, however, that the BeOpen Python License is retained in the +Software, alone or in any derivative version prepared by Licensee. + +3. BeOpen is making the Software available to Licensee on an "AS IS" +basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE +SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS +AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY +DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +5. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +6. This License Agreement shall be governed by and interpreted in all +respects by the law of the State of California, excluding conflict of +law provisions. Nothing in this License Agreement shall be deemed to +create any relationship of agency, partnership, or joint venture +between BeOpen and Licensee. This License Agreement does not grant +permission to use BeOpen trademarks or trade names in a trademark +sense to endorse or promote products or services of Licensee, or any +third party. As an exception, the "BeOpen Python" logos available at +http://www.pythonlabs.com/logos.html may be used according to the +permissions granted on that web page. + +7. By copying, installing or otherwise using the software, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1 +--------------------------------------- + +1. This LICENSE AGREEMENT is between the Corporation for National +Research Initiatives, having an office at 1895 Preston White Drive, +Reston, VA 20191 ("CNRI"), and the Individual or Organization +("Licensee") accessing and otherwise using Python 1.6.1 software in +source or binary form and its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, CNRI +hereby grants Licensee a nonexclusive, royalty-free, world-wide +license to reproduce, analyze, test, perform and/or display publicly, +prepare derivative works, distribute, and otherwise use Python 1.6.1 +alone or in any derivative version, provided, however, that CNRI's +License Agreement and CNRI's notice of copyright, i.e., "Copyright (c) +1995-2001 Corporation for National Research Initiatives; All Rights +Reserved" are retained in Python 1.6.1 alone or in any derivative +version prepared by Licensee. Alternately, in lieu of CNRI's License +Agreement, Licensee may substitute the following text (omitting the +quotes): "Python 1.6.1 is made available subject to the terms and +conditions in CNRI's License Agreement. This Agreement together with +Python 1.6.1 may be located on the internet using the following +unique, persistent identifier (known as a handle): 1895.22/1013. This +Agreement may also be obtained from a proxy server on the internet +using the following URL: http://hdl.handle.net/1895.22/1013". + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python 1.6.1 or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python 1.6.1. + +4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS" +basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. This License Agreement shall be governed by the federal +intellectual property law of the United States, including without +limitation the federal copyright law, and, to the extent such +U.S. federal law does not apply, by the law of the Commonwealth of +Virginia, excluding Virginia's conflict of law provisions. +Notwithstanding the foregoing, with regard to derivative works based +on Python 1.6.1 that incorporate non-separable material that was +previously distributed under the GNU General Public License (GPL), the +law of the Commonwealth of Virginia shall govern this License +Agreement only as to issues arising under or with respect to +Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this +License Agreement shall be deemed to create any relationship of +agency, partnership, or joint venture between CNRI and Licensee. This +License Agreement does not grant permission to use CNRI trademarks or +trade name in a trademark sense to endorse or promote products or +services of Licensee, or any third party. + +8. By clicking on the "ACCEPT" button where indicated, or by copying, +installing or otherwise using Python 1.6.1, Licensee agrees to be +bound by the terms and conditions of this License Agreement. + + ACCEPT + + +CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2 +-------------------------------------------------- + +Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam, +The Netherlands. All rights reserved. + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation, and that the name of Stichting Mathematisch +Centrum or CWI not be used in advertising or publicity pertaining to +distribution of the software without specific, written prior +permission. + +STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO +THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE +FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +ZERO-CLAUSE BSD LICENSE FOR CODE IN THE PYTHON DOCUMENTATION +---------------------------------------------------------------------- + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. diff --git a/pytest-embedded-jtag/pytest_embedded_jtag/_telnetlib/__init__.py b/pytest-embedded-jtag/pytest_embedded_jtag/_telnetlib/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pytest-embedded-jtag/pytest_embedded_jtag/_telnetlib/telnetlib.py b/pytest-embedded-jtag/pytest_embedded_jtag/_telnetlib/telnetlib.py new file mode 100644 index 00000000..1a4584e0 --- /dev/null +++ b/pytest-embedded-jtag/pytest_embedded_jtag/_telnetlib/telnetlib.py @@ -0,0 +1,680 @@ +r"""TELNET client class. + +Based on RFC 854: TELNET Protocol Specification, by J. Postel and +J. Reynolds + +Example: + +>>> from telnetlib import Telnet +>>> tn = Telnet('www.python.org', 79) # connect to finger port +>>> tn.write(b'guido\r\n') +>>> print(tn.read_all()) +Login Name TTY Idle When Where +guido Guido van Rossum pts/2 snag.cnri.reston.. + +>>> + +Note that read_all() won't read until eof -- it just reads some data +-- but it guarantees to read at least one byte unless EOF is hit. + +It is possible to pass a Telnet object to a selector in order to wait until +more data is available. Note that in this case, read_eager() may return b'' +even if there was data on the socket, because the protocol negotiation may have +eaten the data. This is why EOFError is needed in some cases to distinguish +between "no data" and "connection closed" (since the socket also appears ready +for reading when it is closed). + +To do: +- option negotiation +- timeout should be intrinsic to the connection object instead of an + option on one of the read calls only + +""" + + +# Imported modules +import selectors +import socket +import sys +import warnings +from time import monotonic as _time + +warnings._deprecated(__name__, remove=(3, 13)) + +__all__ = ["Telnet"] + +# Tunable parameters +DEBUGLEVEL = 0 + +# Telnet protocol defaults +TELNET_PORT = 23 + +# Telnet protocol characters (don't change) +IAC = bytes([255]) # "Interpret As Command" +DONT = bytes([254]) +DO = bytes([253]) +WONT = bytes([252]) +WILL = bytes([251]) +theNULL = bytes([0]) + +SE = bytes([240]) # Subnegotiation End +NOP = bytes([241]) # No Operation +DM = bytes([242]) # Data Mark +BRK = bytes([243]) # Break +IP = bytes([244]) # Interrupt process +AO = bytes([245]) # Abort output +AYT = bytes([246]) # Are You There +EC = bytes([247]) # Erase Character +EL = bytes([248]) # Erase Line +GA = bytes([249]) # Go Ahead +SB = bytes([250]) # Subnegotiation Begin + + +# Telnet protocol options code (don't change) +# These ones all come from arpa/telnet.h +BINARY = bytes([0]) # 8-bit data path +ECHO = bytes([1]) # echo +RCP = bytes([2]) # prepare to reconnect +SGA = bytes([3]) # suppress go ahead +NAMS = bytes([4]) # approximate message size +STATUS = bytes([5]) # give status +TM = bytes([6]) # timing mark +RCTE = bytes([7]) # remote controlled transmission and echo +NAOL = bytes([8]) # negotiate about output line width +NAOP = bytes([9]) # negotiate about output page size +NAOCRD = bytes([10]) # negotiate about CR disposition +NAOHTS = bytes([11]) # negotiate about horizontal tabstops +NAOHTD = bytes([12]) # negotiate about horizontal tab disposition +NAOFFD = bytes([13]) # negotiate about formfeed disposition +NAOVTS = bytes([14]) # negotiate about vertical tab stops +NAOVTD = bytes([15]) # negotiate about vertical tab disposition +NAOLFD = bytes([16]) # negotiate about output LF disposition +XASCII = bytes([17]) # extended ascii character set +LOGOUT = bytes([18]) # force logout +BM = bytes([19]) # byte macro +DET = bytes([20]) # data entry terminal +SUPDUP = bytes([21]) # supdup protocol +SUPDUPOUTPUT = bytes([22]) # supdup output +SNDLOC = bytes([23]) # send location +TTYPE = bytes([24]) # terminal type +EOR = bytes([25]) # end or record +TUID = bytes([26]) # TACACS user identification +OUTMRK = bytes([27]) # output marking +TTYLOC = bytes([28]) # terminal location number +VT3270REGIME = bytes([29]) # 3270 regime +X3PAD = bytes([30]) # X.3 PAD +NAWS = bytes([31]) # window size +TSPEED = bytes([32]) # terminal speed +LFLOW = bytes([33]) # remote flow control +LINEMODE = bytes([34]) # Linemode option +XDISPLOC = bytes([35]) # X Display Location +OLD_ENVIRON = bytes([36]) # Old - Environment variables +AUTHENTICATION = bytes([37]) # Authenticate +ENCRYPT = bytes([38]) # Encryption option +NEW_ENVIRON = bytes([39]) # New - Environment variables +# the following ones come from +# http://www.iana.org/assignments/telnet-options +# Unfortunately, that document does not assign identifiers +# to all of them, so we are making them up +TN3270E = bytes([40]) # TN3270E +XAUTH = bytes([41]) # XAUTH +CHARSET = bytes([42]) # CHARSET +RSP = bytes([43]) # Telnet Remote Serial Port +COM_PORT_OPTION = bytes([44]) # Com Port Control Option +SUPPRESS_LOCAL_ECHO = bytes([45]) # Telnet Suppress Local Echo +TLS = bytes([46]) # Telnet Start TLS +KERMIT = bytes([47]) # KERMIT +SEND_URL = bytes([48]) # SEND-URL +FORWARD_X = bytes([49]) # FORWARD_X +PRAGMA_LOGON = bytes([138]) # TELOPT PRAGMA LOGON +SSPI_LOGON = bytes([139]) # TELOPT SSPI LOGON +PRAGMA_HEARTBEAT = bytes([140]) # TELOPT PRAGMA HEARTBEAT +EXOPL = bytes([255]) # Extended-Options-List +NOOPT = bytes([0]) + + +# poll/select have the advantage of not requiring any extra file descriptor, +# contrarily to epoll/kqueue (also, they require a single syscall). +if hasattr(selectors, 'PollSelector'): + _TelnetSelector = selectors.PollSelector +else: + _TelnetSelector = selectors.SelectSelector + + +class Telnet: + + """Telnet interface class. + + An instance of this class represents a connection to a telnet + server. The instance is initially not connected; the open() + method must be used to establish a connection. Alternatively, the + host name and optional port number can be passed to the + constructor, too. + + Don't try to reopen an already connected instance. + + This class has many read_*() methods. Note that some of them + raise EOFError when the end of the connection is read, because + they can return an empty string for other reasons. See the + individual doc strings. + + read_until(expected, [timeout]) + Read until the expected string has been seen, or a timeout is + hit (default is no timeout); may block. + + read_all() + Read all data until EOF; may block. + + read_some() + Read at least one byte or EOF; may block. + + read_very_eager() + Read all data available already queued or on the socket, + without blocking. + + read_eager() + Read either data already queued or some data available on the + socket, without blocking. + + read_lazy() + Read all data in the raw queue (processing it first), without + doing any socket I/O. + + read_very_lazy() + Reads all data in the cooked queue, without doing any socket + I/O. + + read_sb_data() + Reads available data between SB ... SE sequence. Don't block. + + set_option_negotiation_callback(callback) + Each time a telnet option is read on the input flow, this callback + (if set) is called with the following parameters : + callback(telnet socket, command, option) + option will be chr(0) when there is no option. + No other action is done afterwards by telnetlib. + + """ + sock = None # for __del__() + + def __init__(self, host=None, port=0, + timeout=socket._GLOBAL_DEFAULT_TIMEOUT): + """Constructor. + + When called without arguments, create an unconnected instance. + With a hostname argument, it connects the instance; port number + and timeout are optional. + """ + self.debuglevel = DEBUGLEVEL + self.host = host + self.port = port + self.timeout = timeout + self.sock = None + self.rawq = b'' + self.irawq = 0 + self.cookedq = b'' + self.eof = 0 + self.iacseq = b'' # Buffer for IAC sequence. + self.sb = 0 # flag for SB and SE sequence. + self.sbdataq = b'' + self.option_callback = None + if host is not None: + self.open(host, port, timeout) + + def open(self, host, port=0, timeout=socket._GLOBAL_DEFAULT_TIMEOUT): + """Connect to a host. + + The optional second argument is the port number, which + defaults to the standard telnet port (23). + + Don't try to reopen an already connected instance. + """ + self.eof = 0 + if not port: + port = TELNET_PORT + self.host = host + self.port = port + self.timeout = timeout + sys.audit("telnetlib.Telnet.open", self, host, port) + self.sock = socket.create_connection((host, port), timeout) + + def __del__(self): + """Destructor -- close the connection.""" + self.close() + + def msg(self, msg, *args): + """Print a debug message, when the debug level is > 0. + + If extra arguments are present, they are substituted in the + message using the standard string formatting operator. + + """ + if self.debuglevel > 0: + print('Telnet(%s,%s):' % (self.host, self.port), end=' ') + if args: + print(msg % args) + else: + print(msg) + + def set_debuglevel(self, debuglevel): + """Set the debug level. + + The higher it is, the more debug output you get (on sys.stdout). + + """ + self.debuglevel = debuglevel + + def close(self): + """Close the connection.""" + sock = self.sock + self.sock = None + self.eof = True + self.iacseq = b'' + self.sb = 0 + if sock: + sock.close() + + def get_socket(self): + """Return the socket object used internally.""" + return self.sock + + def fileno(self): + """Return the fileno() of the socket object used internally.""" + return self.sock.fileno() + + def write(self, buffer): + """Write a string to the socket, doubling any IAC characters. + + Can block if the connection is blocked. May raise + OSError if the connection is closed. + + """ + if IAC in buffer: + buffer = buffer.replace(IAC, IAC+IAC) + sys.audit("telnetlib.Telnet.write", self, buffer) + self.msg("send %r", buffer) + self.sock.sendall(buffer) + + def read_until(self, match, timeout=None): + """Read until a given string is encountered or until timeout. + + When no match is found, return whatever is available instead, + possibly the empty string. Raise EOFError if the connection + is closed and no cooked data is available. + + """ + n = len(match) + self.process_rawq() + i = self.cookedq.find(match) + if i >= 0: + i = i+n + buf = self.cookedq[:i] + self.cookedq = self.cookedq[i:] + return buf + if timeout is not None: + deadline = _time() + timeout + with _TelnetSelector() as selector: + selector.register(self, selectors.EVENT_READ) + while not self.eof: + if selector.select(timeout): + i = max(0, len(self.cookedq)-n) + self.fill_rawq() + self.process_rawq() + i = self.cookedq.find(match, i) + if i >= 0: + i = i+n + buf = self.cookedq[:i] + self.cookedq = self.cookedq[i:] + return buf + if timeout is not None: + timeout = deadline - _time() + if timeout < 0: + break + return self.read_very_lazy() + + def read_all(self): + """Read all data until EOF; block until connection closed.""" + self.process_rawq() + while not self.eof: + self.fill_rawq() + self.process_rawq() + buf = self.cookedq + self.cookedq = b'' + return buf + + def read_some(self): + """Read at least one byte of cooked data unless EOF is hit. + + Return b'' if EOF is hit. Block if no data is immediately + available. + + """ + self.process_rawq() + while not self.cookedq and not self.eof: + self.fill_rawq() + self.process_rawq() + buf = self.cookedq + self.cookedq = b'' + return buf + + def read_very_eager(self): + """Read everything that's possible without blocking in I/O (eager). + + Raise EOFError if connection closed and no cooked data + available. Return b'' if no cooked data available otherwise. + Don't block unless in the midst of an IAC sequence. + + """ + self.process_rawq() + while not self.eof and self.sock_avail(): + self.fill_rawq() + self.process_rawq() + return self.read_very_lazy() + + def read_eager(self): + """Read readily available data. + + Raise EOFError if connection closed and no cooked data + available. Return b'' if no cooked data available otherwise. + Don't block unless in the midst of an IAC sequence. + + """ + self.process_rawq() + while not self.cookedq and not self.eof and self.sock_avail(): + self.fill_rawq() + self.process_rawq() + return self.read_very_lazy() + + def read_lazy(self): + """Process and return data that's already in the queues (lazy). + + Raise EOFError if connection closed and no data available. + Return b'' if no cooked data available otherwise. Don't block + unless in the midst of an IAC sequence. + + """ + self.process_rawq() + return self.read_very_lazy() + + def read_very_lazy(self): + """Return any data available in the cooked queue (very lazy). + + Raise EOFError if connection closed and no data available. + Return b'' if no cooked data available otherwise. Don't block. + + """ + buf = self.cookedq + self.cookedq = b'' + if not buf and self.eof and not self.rawq: + raise EOFError('telnet connection closed') + return buf + + def read_sb_data(self): + """Return any data available in the SB ... SE queue. + + Return b'' if no SB ... SE available. Should only be called + after seeing a SB or SE command. When a new SB command is + found, old unread SB data will be discarded. Don't block. + + """ + buf = self.sbdataq + self.sbdataq = b'' + return buf + + def set_option_negotiation_callback(self, callback): + """Provide a callback function called after each receipt of a telnet option.""" + self.option_callback = callback + + def process_rawq(self): + """Transfer from raw queue to cooked queue. + + Set self.eof when connection is closed. Don't block unless in + the midst of an IAC sequence. + + """ + buf = [b'', b''] + try: + while self.rawq: + c = self.rawq_getchar() + if not self.iacseq: + if c == theNULL: + continue + if c == b"\021": + continue + if c != IAC: + buf[self.sb] = buf[self.sb] + c + continue + else: + self.iacseq += c + elif len(self.iacseq) == 1: + # 'IAC: IAC CMD [OPTION only for WILL/WONT/DO/DONT]' + if c in (DO, DONT, WILL, WONT): + self.iacseq += c + continue + + self.iacseq = b'' + if c == IAC: + buf[self.sb] = buf[self.sb] + c + else: + if c == SB: # SB ... SE start. + self.sb = 1 + self.sbdataq = b'' + elif c == SE: + self.sb = 0 + self.sbdataq = self.sbdataq + buf[1] + buf[1] = b'' + if self.option_callback: + # Callback is supposed to look into + # the sbdataq + self.option_callback(self.sock, c, NOOPT) + else: + # We can't offer automatic processing of + # suboptions. Alas, we should not get any + # unless we did a WILL/DO before. + self.msg('IAC %d not recognized' % ord(c)) + elif len(self.iacseq) == 2: + cmd = self.iacseq[1:2] + self.iacseq = b'' + opt = c + if cmd in (DO, DONT): + self.msg('IAC %s %d', + cmd == DO and 'DO' or 'DONT', ord(opt)) + if self.option_callback: + self.option_callback(self.sock, cmd, opt) + else: + self.sock.sendall(IAC + WONT + opt) + elif cmd in (WILL, WONT): + self.msg('IAC %s %d', + cmd == WILL and 'WILL' or 'WONT', ord(opt)) + if self.option_callback: + self.option_callback(self.sock, cmd, opt) + else: + self.sock.sendall(IAC + DONT + opt) + except EOFError: # raised by self.rawq_getchar() + self.iacseq = b'' # Reset on EOF + self.sb = 0 + self.cookedq = self.cookedq + buf[0] + self.sbdataq = self.sbdataq + buf[1] + + def rawq_getchar(self): + """Get next char from raw queue. + + Block if no data is immediately available. Raise EOFError + when connection is closed. + + """ + if not self.rawq: + self.fill_rawq() + if self.eof: + raise EOFError + c = self.rawq[self.irawq:self.irawq+1] + self.irawq = self.irawq + 1 + if self.irawq >= len(self.rawq): + self.rawq = b'' + self.irawq = 0 + return c + + def fill_rawq(self): + """Fill raw queue from exactly one recv() system call. + + Block if no data is immediately available. Set self.eof when + connection is closed. + + """ + if self.irawq >= len(self.rawq): + self.rawq = b'' + self.irawq = 0 + # The buffer size should be fairly small so as to avoid quadratic + # behavior in process_rawq() above + buf = self.sock.recv(50) + self.msg("recv %r", buf) + self.eof = (not buf) + self.rawq = self.rawq + buf + + def sock_avail(self): + """Test whether data is available on the socket.""" + with _TelnetSelector() as selector: + selector.register(self, selectors.EVENT_READ) + return bool(selector.select(0)) + + def interact(self): + """Interaction function, emulates a very dumb telnet client.""" + if sys.platform == "win32": + self.mt_interact() + return + with _TelnetSelector() as selector: + selector.register(self, selectors.EVENT_READ) + selector.register(sys.stdin, selectors.EVENT_READ) + + while True: + for key, events in selector.select(): + if key.fileobj is self: + try: + text = self.read_eager() + except EOFError: + print('*** Connection closed by remote host ***') + return + if text: + sys.stdout.write(text.decode('ascii')) + sys.stdout.flush() + elif key.fileobj is sys.stdin: + line = sys.stdin.readline().encode('ascii') + if not line: + return + self.write(line) + + def mt_interact(self): + """Multithreaded version of interact().""" + import _thread + _thread.start_new_thread(self.listener, ()) + while 1: + line = sys.stdin.readline() + if not line: + break + self.write(line.encode('ascii')) + + def listener(self): + """Helper for mt_interact() -- this executes in the other thread.""" + while 1: + try: + data = self.read_eager() + except EOFError: + print('*** Connection closed by remote host ***') + return + if data: + sys.stdout.write(data.decode('ascii')) + else: + sys.stdout.flush() + + def expect(self, list, timeout=None): + """Read until one from a list of a regular expressions matches. + + The first argument is a list of regular expressions, either + compiled (re.Pattern instances) or uncompiled (strings). + The optional second argument is a timeout, in seconds; default + is no timeout. + + Return a tuple of three items: the index in the list of the + first regular expression that matches; the re.Match object + returned; and the text read up till and including the match. + + If EOF is read and no text was read, raise EOFError. + Otherwise, when nothing matches, return (-1, None, text) where + text is the text received so far (may be the empty string if a + timeout happened). + + If a regular expression ends with a greedy match (e.g. '.*') + or if more than one expression can match the same input, the + results are undeterministic, and may depend on the I/O timing. + + """ + re = None + list = list[:] + indices = range(len(list)) + for i in indices: + if not hasattr(list[i], "search"): + if not re: import re + list[i] = re.compile(list[i]) + if timeout is not None: + deadline = _time() + timeout + with _TelnetSelector() as selector: + selector.register(self, selectors.EVENT_READ) + while not self.eof: + self.process_rawq() + for i in indices: + m = list[i].search(self.cookedq) + if m: + e = m.end() + text = self.cookedq[:e] + self.cookedq = self.cookedq[e:] + return (i, m, text) + if timeout is not None: + ready = selector.select(timeout) + timeout = deadline - _time() + if not ready: + if timeout < 0: + break + else: + continue + self.fill_rawq() + text = self.read_very_lazy() + if not text and self.eof: + raise EOFError + return (-1, None, text) + + def __enter__(self): + return self + + def __exit__(self, type, value, traceback): + self.close() + + +def test(): + """Test program for telnetlib. + + Usage: python telnetlib.py [-d] ... [host [port]] + + Default host is localhost; default port is 23. + + """ + debuglevel = 0 + while sys.argv[1:] and sys.argv[1] == '-d': + debuglevel = debuglevel+1 + del sys.argv[1] + host = 'localhost' + if sys.argv[1:]: + host = sys.argv[1] + port = 0 + if sys.argv[2:]: + portstr = sys.argv[2] + try: + port = int(portstr) + except ValueError: + port = socket.getservbyname(portstr, 'tcp') + with Telnet() as tn: + tn.set_debuglevel(debuglevel) + tn.open(host, port, timeout=0.5) + tn.interact() + +if __name__ == '__main__': + test() diff --git a/pytest-embedded-jtag/pytest_embedded_jtag/openocd.py b/pytest-embedded-jtag/pytest_embedded_jtag/openocd.py index a06a1891..5c34be17 100644 --- a/pytest-embedded-jtag/pytest_embedded_jtag/openocd.py +++ b/pytest-embedded-jtag/pytest_embedded_jtag/openocd.py @@ -1,13 +1,14 @@ import logging import os import shlex -import telnetlib import time from typing import AnyStr, Optional from pytest_embedded.log import DuplicateStdoutPopen from pytest_embedded.utils import to_bytes, to_str +from ._telnetlib.telnetlib import Telnet + class OpenOcd(DuplicateStdoutPopen): """ @@ -56,7 +57,7 @@ def __init__( # open telnet port to interact with openocd for i in range(30): try: - self.telnet = telnetlib.Telnet('127.0.0.1', self.telnet_port, 5) + self.telnet = Telnet('127.0.0.1', self.telnet_port, 5) break except ConnectionRefusedError: time.sleep(1) diff --git a/pytest-embedded-serial/pytest_embedded_serial/dut.py b/pytest-embedded-serial/pytest_embedded_serial/dut.py index 26aa8a1d..0c33062b 100644 --- a/pytest-embedded-serial/pytest_embedded_serial/dut.py +++ b/pytest-embedded-serial/pytest_embedded_serial/dut.py @@ -6,9 +6,7 @@ from .serial import Serial if TYPE_CHECKING: - from telnetlib import Telnet - - from pytest_embedded_jtag import Gdb, OpenOcd + from pytest_embedded_jtag import Gdb, OpenOcd, Telnet class SerialDut(Dut): diff --git a/ruff.toml b/ruff.toml index 4ee1b3b5..71af517c 100644 --- a/ruff.toml +++ b/ruff.toml @@ -66,5 +66,8 @@ select = [ [format] quote-style = "single" -exclude = ["**/tests/*"] +exclude = [ + "**/tests/*", + "pytest-embedded-jtag/pytest_embedded_jtag/_telnetlib/telnetlib.py" +] docstring-code-format = true From 2954d13020635aafe08956a3a071411a9e883d5b Mon Sep 17 00:00:00 2001 From: Fu Hanxi Date: Tue, 17 Sep 2024 10:20:04 +0200 Subject: [PATCH 2/2] refactor: remove deprecate warning from telnetlib.py and ignore some ruff warnings --- .../pytest_embedded_jtag/_telnetlib/telnetlib.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/pytest-embedded-jtag/pytest_embedded_jtag/_telnetlib/telnetlib.py b/pytest-embedded-jtag/pytest_embedded_jtag/_telnetlib/telnetlib.py index 1a4584e0..3b36fcdc 100644 --- a/pytest-embedded-jtag/pytest_embedded_jtag/_telnetlib/telnetlib.py +++ b/pytest-embedded-jtag/pytest_embedded_jtag/_telnetlib/telnetlib.py @@ -36,11 +36,8 @@ import selectors import socket import sys -import warnings from time import monotonic as _time -warnings._deprecated(__name__, remove=(3, 13)) - __all__ = ["Telnet"] # Tunable parameters @@ -250,7 +247,7 @@ def msg(self, msg, *args): """ if self.debuglevel > 0: - print('Telnet(%s,%s):' % (self.host, self.port), end=' ') + print(f'Telnet({self.host},{self.port}):', end=' ') if args: print(msg % args) else: @@ -447,7 +444,7 @@ def process_rawq(self): else: self.iacseq += c elif len(self.iacseq) == 1: - # 'IAC: IAC CMD [OPTION only for WILL/WONT/DO/DONT]' + # 'IAC: IAC CMD [OPTION only for WILL/WONT/DO/DONT]' # noqa: ERA001 if c in (DO, DONT, WILL, WONT): self.iacseq += c continue @@ -586,7 +583,7 @@ def listener(self): else: sys.stdout.flush() - def expect(self, list, timeout=None): + def expect(self, list, timeout=None): # noqa: A002 """Read until one from a list of a regular expressions matches. The first argument is a list of regular expressions, either @@ -609,11 +606,12 @@ def expect(self, list, timeout=None): """ re = None - list = list[:] + list = list[:] # noqa: A001 indices = range(len(list)) for i in indices: if not hasattr(list[i], "search"): - if not re: import re + if not re: + import re list[i] = re.compile(list[i]) if timeout is not None: deadline = _time() + timeout @@ -645,7 +643,7 @@ def expect(self, list, timeout=None): def __enter__(self): return self - def __exit__(self, type, value, traceback): + def __exit__(self, type, value, traceback): # noqa: A002 self.close()