PyOpenocdClient 0.1.1__tar.gz → 0.1.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyOpenocdClient
3
- Version: 0.1.1
3
+ Version: 0.1.2
4
4
  Summary: Library for controlling OpenOCD from Python programs
5
5
  Author-email: Jan Matyas <info@janmatyas.net>
6
6
  Project-URL: Homepage, https://github.com/HonzaMat/PyOpenocdClient
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "PyOpenocdClient"
3
- version = "0.1.1"
3
+ version = "0.1.2"
4
4
  authors = [
5
5
  { name="Jan Matyas", email="info@janmatyas.net" },
6
6
  ]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyOpenocdClient
3
- Version: 0.1.1
3
+ Version: 0.1.2
4
4
  Summary: Library for controlling OpenOCD from Python programs
5
5
  Author-email: Jan Matyas <info@janmatyas.net>
6
6
  Project-URL: Homepage, https://github.com/HonzaMat/PyOpenocdClient
@@ -94,10 +94,35 @@ class _PyOpenocdBaseClient:
94
94
  raise ValueError("Timeout must be greater than zero")
95
95
  self._default_recv_timeout = timeout
96
96
 
97
- def _check_no_premature_recvd_bytes(self) -> None:
97
+ def _check_connection_before_command(self) -> None:
98
98
  assert self._socket is not None
99
99
  rd, _, _ = select.select([self._socket], [], [], 0) # Don't block, just poll
100
- if len(rd) > 0:
100
+
101
+ if len(rd) == 0:
102
+ # The socket is not ready for recv() now, which is the expected state.
103
+ # Success.
104
+ return
105
+
106
+ # The socket is ready for recv(). This is unexpected at this point
107
+ # and always means an error. Try receive from the socket to find out
108
+ # what happened:
109
+ try:
110
+ recvd_data = self._socket.recv(128)
111
+ except OSError as e:
112
+ # This is unlikely to happen: It would mean that the socket is ready
113
+ # for recv() but then recv() failed.
114
+ raise OcdConnectionError(
115
+ "Connection to OpenOCD broken for an unknown reason"
116
+ ) from e
117
+
118
+ if len(recvd_data) == 0:
119
+ # Empty received data means that the connection got closed by OpenOCD
120
+ # in the meanwhile.
121
+ raise OcdConnectionError("Connection closed by OpenOCD")
122
+ else:
123
+ # It looks like OpenOCD sent us some extra, unsolicited bytes (without us
124
+ # sending any command to OpenOCD). This is a violation of the communication
125
+ # protocol.
101
126
  raise OcdConnectionError(
102
127
  "Received unexpected bytes from OpenOCD before "
103
128
  "the command was even sent."
@@ -107,12 +132,27 @@ class _PyOpenocdBaseClient:
107
132
  assert self.is_connected()
108
133
  assert self._socket is not None
109
134
 
110
- # Safety:
111
- self._check_no_premature_recvd_bytes()
135
+ # Perform basic connection check before sending a command.
136
+ # Note that this is merely a safety/correctness check which itself
137
+ # does not guarantee that the subsequent send() and recv() calls
138
+ # will succeed.
139
+ self._check_connection_before_command()
112
140
 
113
141
  data = raw_cmd.encode(self.CHARSET) + self.COMMAND_DELIMITER
114
- self._socket.settimeout(self.SEND_TIMEOUT)
115
- self._socket.send(data)
142
+
143
+ try:
144
+ self._socket.settimeout(self.SEND_TIMEOUT)
145
+ except OSError as e:
146
+ raise OcdConnectionError(
147
+ "Could not send a command to OpenOCD, failed to set socket timeout"
148
+ ) from e
149
+
150
+ try:
151
+ self._socket.send(data)
152
+ except OSError as e:
153
+ raise OcdConnectionError(
154
+ "Could not send a command to OpenOCD, socket error occurred"
155
+ ) from e
116
156
 
117
157
  def _do_recv_response(self, raw_cmd: str, timeout: Optional[float] = None) -> str:
118
158
  assert self.is_connected()
@@ -123,7 +163,13 @@ class _PyOpenocdBaseClient:
123
163
  timeout if timeout is not None else self._default_recv_timeout
124
164
  )
125
165
 
126
- self._socket.settimeout(self.RECV_POLL_TIMEOUT)
166
+ try:
167
+ self._socket.settimeout(self.RECV_POLL_TIMEOUT)
168
+ except OSError as e:
169
+ raise OcdConnectionError(
170
+ "Could not receive a response from OpenOCD, "
171
+ "failed to set socket timeout"
172
+ ) from e
127
173
 
128
174
  time_start = time.time()
129
175
  while time.time() < (time_start + effective_timeout):
@@ -131,6 +177,10 @@ class _PyOpenocdBaseClient:
131
177
  d = self._socket.recv(self.RECV_BLOCK_SIZE)
132
178
  except socket.timeout:
133
179
  continue
180
+ except OSError as e:
181
+ raise OcdConnectionError(
182
+ "Could not receive a response from OpenOCD, socket error occurred"
183
+ ) from e
134
184
 
135
185
  if d == b"":
136
186
  raise OcdConnectionError("Connection closed by OpenOCD")
@@ -211,25 +211,34 @@ class PyOpenocdClient:
211
211
  raw_cmd = cmd
212
212
 
213
213
  raw_cmd = "set CMD_RETCODE [ catch { " + raw_cmd + " } CMD_OUTPUT ] ; "
214
- raw_cmd += 'return "$CMD_RETCODE $CMD_OUTPUT" ; '
214
+
215
+ # Older OpenOCD versions - prior to 93f16eed4(*) - incorrectly trimmed trailing
216
+ # whitespace from the string passed to the return command. Work around this bug
217
+ # by wrapping the string by non-whitespace characters.
218
+ # (*): https://review.openocd.org/c/openocd/+/9084
219
+ raw_cmd += 'return "<$CMD_RETCODE,$CMD_OUTPUT>" ; '
215
220
 
216
221
  raw_result = self.raw_cmd(raw_cmd, timeout=timeout)
217
222
 
218
- # Verify the raw output from OpenOCD the has the expected format. It can be:
219
- #
220
- # - Command return code (positive or negative decimal number) and that's it.
221
- #
222
- # - Or, command return code (positive or negative decimal number) followed by
223
- # a space character and optionally followed by the command's textual output.
224
- if re.match(r"^-?\d+($| )", raw_result) is None:
223
+ def is_expected_raw_result(s: str) -> bool:
224
+ return (
225
+ s.startswith("<")
226
+ and s.endswith(">")
227
+ and re.match(r"^<-?\d+,", s) is not None
228
+ )
229
+
230
+ if not is_expected_raw_result(raw_result):
225
231
  msg = (
226
232
  "Received unexpected response from OpenOCD. "
227
233
  "It looks like OpenOCD misbehaves. "
228
234
  )
229
235
  raise OcdInvalidResponseError(msg, raw_cmd, raw_result)
230
236
 
231
- raw_result_parts = raw_result.split(" ", maxsplit=1)
232
- assert len(raw_result_parts) in [1, 2]
237
+ # Remove leading "<" and trailing ">"
238
+ raw_result = raw_result[1:-1]
239
+ raw_result_parts = raw_result.split(",", maxsplit=1)
240
+ assert len(raw_result_parts) == 2
241
+
233
242
  retcode = int(raw_result_parts[0], 10)
234
243
  out = raw_result_parts[1] if len(raw_result_parts) == 2 else ""
235
244
 
File without changes