Changed the interface to the debug client to make it a bit more robust and harmonize it with other such interfaces. eric7

Wed, 07 Feb 2024 15:28:08 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Wed, 07 Feb 2024 15:28:08 +0100
branch
eric7
changeset 10552
c5b0c8a5fa7d
parent 10544
70762114c59a
child 10553
c5ef3ec68b75

Changed the interface to the debug client to make it a bit more robust and harmonize it with other such interfaces.
(grafted from d80184d38152028028839a3e0ce75bacc698e3da)

src/eric7/DebugClients/Python/AsyncFile.py file | annotate | diff | comparison | revisions
src/eric7/DebugClients/Python/DebugClientBase.py file | annotate | diff | comparison | revisions
src/eric7/DebugClients/Python/DebugUtilities.py file | annotate | diff | comparison | revisions
src/eric7/Debugger/DebuggerInterfaceNone.py file | annotate | diff | comparison | revisions
src/eric7/Debugger/DebuggerInterfacePython.py file | annotate | diff | comparison | revisions
diff -r 70762114c59a -r c5b0c8a5fa7d src/eric7/DebugClients/Python/AsyncFile.py
--- a/src/eric7/DebugClients/Python/AsyncFile.py	Fri Feb 02 19:29:29 2024 +0100
+++ b/src/eric7/DebugClients/Python/AsyncFile.py	Wed Feb 07 15:28:08 2024 +0100
@@ -10,7 +10,9 @@
 
 import contextlib
 import socket
+import struct
 import threading
+import zlib
 
 from DebugUtilities import prepareJsonCommand
 
@@ -109,14 +111,16 @@
         self.writeLock.acquire()
         while self.wpending:
             try:
-                buf = self.wpending.pop(0)
+                data = self.wpending.pop(0)
             except IndexError:
                 break
 
             try:
                 with contextlib.suppress(UnicodeEncodeError, UnicodeDecodeError):
-                    buf = buf.encode("utf-8", "backslashreplace")
-                self.sock.sendall(buf)
+                    data = data.encode("utf-8", "backslashreplace")
+                header = struct.pack(b"!II", len(data), zlib.adler32(data) & 0xFFFFFFFF)
+                self.sock.sendall(header)
+                self.sock.sendall(data)
                 self.nWriteErrors = 0
             except OSError:
                 self.nWriteErrors += 1
@@ -188,24 +192,27 @@
 
     def readCommand(self):
         """
-        Public method to read a length prefixed command string.
+        Public method to read a command string prefixed by a command header.
 
         @return command string
         @rtype str
         """
-        # The command string is prefixed by a 9 character long length field.
-        length = self.sock.recv(9)
-        length = int(length)
-        data = b""
-        while len(data) < length:
-            remaining = length - len(data)
-            newBytes = self.sock.recv(min(remaining, AsyncFile.CMD_BUFSIZE))
-            data += newBytes
-            if newBytes[-1] == b"\n":
-                break
+        header = self.sock.recv(struct.calcsize(b"!II"))
+        if header:
+            length, datahash = struct.unpack(b"!II", header)
 
-        # step 2: convert the data
-        return data.decode("utf8", "backslashreplace")
+            length = int(length)
+            data = bytearray()
+            while len(data) < length:
+                newData = self.sock.recv(length - len(data))
+                data += newData
+                if not newData:
+                    break
+
+            if data and zlib.adler32(data) & 0xFFFFFFFF == datahash:
+                return data.decode("utf8", "backslashreplace")
+
+        return ""
 
     def readline_p(self, size=-1):
         """
@@ -348,14 +355,14 @@
             # convert to string to send it
             s = repr(s)
 
-        cmd = prepareJsonCommand(
+        jsonCommand = prepareJsonCommand(
             "ClientOutput",
             {
                 "text": s,
                 "debuggerId": "",
             },
         )
-        self.wpending.append(cmd)
+        self.wpending.append(jsonCommand)
         self.flush()
         self.writeLock.release()
 
diff -r 70762114c59a -r c5b0c8a5fa7d src/eric7/DebugClients/Python/DebugClientBase.py
--- a/src/eric7/DebugClients/Python/DebugClientBase.py	Fri Feb 02 19:29:29 2024 +0100
+++ b/src/eric7/DebugClients/Python/DebugClientBase.py	Wed Feb 07 15:28:08 2024 +0100
@@ -890,9 +890,9 @@
         if "debuggerId" not in params:
             params["debuggerId"] = self.__debuggerId
 
-        cmd = prepareJsonCommand(method, params)
+        jsonCommand = prepareJsonCommand(method, params)
 
-        self.writestream.write_p(cmd)
+        self.writestream.write_p(jsonCommand)
         self.writestream.flush()
 
     def sendClearTemporaryBreakpoint(self, filename, lineno):
diff -r 70762114c59a -r c5b0c8a5fa7d src/eric7/DebugClients/Python/DebugUtilities.py
--- a/src/eric7/DebugClients/Python/DebugUtilities.py	Fri Feb 02 19:29:29 2024 +0100
+++ b/src/eric7/DebugClients/Python/DebugUtilities.py	Wed Feb 07 15:28:08 2024 +0100
@@ -154,7 +154,7 @@
         "method": method,
         "params": params,
     }
-    return json.dumps(commandDict) + "\n"
+    return json.dumps(commandDict)
 
 
 ###########################################################################
diff -r 70762114c59a -r c5b0c8a5fa7d src/eric7/Debugger/DebuggerInterfaceNone.py
--- a/src/eric7/Debugger/DebuggerInterfaceNone.py	Fri Feb 02 19:29:29 2024 +0100
+++ b/src/eric7/Debugger/DebuggerInterfaceNone.py	Wed Feb 07 15:28:08 2024 +0100
@@ -34,7 +34,7 @@
         self.passive = passive
 
         self.qsock = None
-        self.queue = []
+        self.__commandQueue = []
         # set default values for capabilities of clients
         self.clientCapabilities = ClientDefaultCapabilities
 
@@ -139,7 +139,7 @@
         (Needed on Win OS)
         """
         self.qsock = None
-        self.queue = []
+        self.__commandQueue.clear()
 
     def isConnected(self):
         """
diff -r 70762114c59a -r c5b0c8a5fa7d src/eric7/Debugger/DebuggerInterfacePython.py
--- a/src/eric7/Debugger/DebuggerInterfacePython.py	Fri Feb 02 19:29:29 2024 +0100
+++ b/src/eric7/Debugger/DebuggerInterfacePython.py	Wed Feb 07 15:28:08 2024 +0100
@@ -12,6 +12,8 @@
 import logging
 import os
 import shlex
+import struct
+import zlib
 
 from PyQt6.QtCore import QObject, QProcess, QProcessEnvironment, QTimer
 
@@ -53,7 +55,7 @@
         self.process = None
         self.__startedVenv = ""
 
-        self.queue = []
+        self.__commandQueue = []
         self.__mainDebugger = None
         self.__connections = {}
         self.__pendingConnections = []
@@ -208,7 +210,7 @@
             else str(Preferences.getDebugger("Python3Redirect"))
         )
         noencoding = (
-            Preferences.getDebugger("Python3NoEncoding") and "--no-encoding" or ""
+            "--no-encoding" if Preferences.getDebugger("Python3NoEncoding") else ""
         )
         multiprocessEnabled = (
             "--multiprocess" if Preferences.getDebugger("MultiProcessEnabled") else ""
@@ -706,10 +708,10 @@
         if self.__mainDebugger:
             # Send commands that were waiting for the connection.
             conn = self.__connections[self.__mainDebugger]
-            for cmd in self.queue:
-                self.__writeJsonCommandToSocket(cmd, conn)
+            for jsonStr in self.__commandQueue:
+                self.__writeJsonCommandToSocket(jsonStr, conn)
 
-        self.queue = []
+        self.__commandQueue.clear()
 
     def shutdown(self):
         """
@@ -732,7 +734,7 @@
             self.__shutdownSocket(sock)
 
         # reinitialize
-        self.queue = []
+        self.__commandQueue.clear()
 
         self.__mainDebugger = None
 
@@ -1401,14 +1403,27 @@
         @param sock reference to the socket to read data from
         @type QTcpSocket
         """
-        while sock and sock.canReadLine():
-            qs = sock.readLine()
-            line = bytes(qs).decode(encoding=Preferences.getSystem("StringEncoding"))
+        while sock and sock.bytesAvailable():
+            header = sock.read(struct.calcsize(b"!II"))
+            length, datahash = struct.unpack(b"!II", header)
+
+            data = bytearray()
+            while len(data) < length:
+                maxSize = length - len(data)
+                if sock.bytesAvailable() < maxSize:
+                    sock.waitForReadyRead(50)
+                data += sock.read(maxSize)
 
-            logging.debug("<Debug-Server> %s", line)
-            ##print("Server: ", line)    ## debug       # __IGNORE_WARNING_M891__
+            if zlib.adler32(data) & 0xFFFFFFFF != datahash:
+                # corrupted data -> discard and continue
+                continue
 
-            self.__handleJsonCommand(line, sock)
+            jsonStr = data.decode("utf-8", "backslashreplace")
+
+            logging.debug("<Debug-Server> %s", jsonStr)
+            ##print("Server: ", jsonStr)    ## debug       # __IGNORE_WARNING_M891__
+
+            self.__handleJsonCommand(jsonStr, sock)
 
     def __handleJsonCommand(self, jsonStr, sock):
         """
@@ -1646,29 +1661,30 @@
             "method": command,
             "params": params,
         }
-        cmd = json.dumps(commandDict) + "\n"
+        jsonStr = json.dumps(commandDict)
 
         if debuggerId and debuggerId in self.__connections:
             sock = self.__connections[debuggerId]
         elif sock is None and self.__mainDebugger is not None:
             sock = self.__connections[self.__mainDebugger]
         if sock is not None:
-            self.__writeJsonCommandToSocket(cmd, sock)
+            self.__writeJsonCommandToSocket(jsonStr, sock)
         else:
-            self.queue.append(cmd)
+            self.__commandQueue.append(jsonStr)
 
-    def __writeJsonCommandToSocket(self, cmd, sock):
+    def __writeJsonCommandToSocket(self, jsonCommand, sock):
         """
         Private method to write a JSON command to the socket.
 
-        @param cmd JSON command to be sent
+        @param jsonCommand JSON encoded command to be sent
         @type str
         @param sock reference to the socket to write to
         @type QTcpSocket
         """
-        data = cmd.encode("utf8", "backslashreplace")
-        length = "{0:09d}".format(len(data))
-        sock.write(length.encode() + data)
+        data = jsonCommand.encode("utf8", "backslashreplace")
+        header = struct.pack(b"!II", len(data), zlib.adler32(data) & 0xFFFFFFFF)
+        sock.write(header)
+        sock.write(data)
         sock.flush()
 
 

eric ide

mercurial