pycfdp 0.2.2__py3-none-any.whl
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.
- cfdp/__init__.py +106 -0
- cfdp/checksum.py +94 -0
- cfdp/constants.py +153 -0
- cfdp/core.py +895 -0
- cfdp/event.py +55 -0
- cfdp/filestore/__init__.py +2 -0
- cfdp/filestore/base.py +99 -0
- cfdp/filestore/native.py +162 -0
- cfdp/meta/__init__.py +3 -0
- cfdp/meta/datafield.py +11 -0
- cfdp/meta/filestore.py +127 -0
- cfdp/meta/message.py +659 -0
- cfdp/pdu/__init__.py +8 -0
- cfdp/pdu/ack.py +90 -0
- cfdp/pdu/eof.py +123 -0
- cfdp/pdu/filedata.py +90 -0
- cfdp/pdu/finished.py +99 -0
- cfdp/pdu/header.py +159 -0
- cfdp/pdu/keep_alive.py +108 -0
- cfdp/pdu/metadata.py +211 -0
- cfdp/pdu/nak.py +220 -0
- cfdp/pdu/prompt.py +75 -0
- cfdp/pure/__init__.py +68 -0
- cfdp/pure/config.py +141 -0
- cfdp/pure/indications.py +130 -0
- cfdp/pure/machines/__init__.py +25 -0
- cfdp/pure/machines/receiver1.py +433 -0
- cfdp/pure/machines/receiver2.py +893 -0
- cfdp/pure/machines/sender1.py +400 -0
- cfdp/pure/machines/sender2.py +774 -0
- cfdp/pure/outputs.py +203 -0
- cfdp/pure/query_port.py +48 -0
- cfdp/pure/transaction.py +43 -0
- cfdp/py.typed +0 -0
- cfdp/remote_entity_config.py +12 -0
- cfdp/shell/__init__.py +44 -0
- cfdp/shell/checksum_service.py +136 -0
- cfdp/shell/dispatcher.py +185 -0
- cfdp/shell/effect_executor.py +285 -0
- cfdp/shell/machine_registry.py +165 -0
- cfdp/shell/query_port.py +70 -0
- cfdp/shell/timer_service.py +252 -0
- cfdp/transaction_handle.py +50 -0
- cfdp/transport/__init__.py +0 -0
- cfdp/transport/base.py +12 -0
- cfdp/transport/spacepacket.py +47 -0
- cfdp/transport/udp.py +90 -0
- cfdp/transport/zmq.py +54 -0
- pycfdp-0.2.2.dist-info/METADATA +76 -0
- pycfdp-0.2.2.dist-info/RECORD +53 -0
- pycfdp-0.2.2.dist-info/WHEEL +5 -0
- pycfdp-0.2.2.dist-info/licenses/LICENSE.txt +19 -0
- pycfdp-0.2.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
"""Pure Class-1 (unacknowledged) sender machine (issue #14).
|
|
2
|
+
|
|
3
|
+
A pure function of ``(state, event)`` returning an ordered ``list[Output]``. It
|
|
4
|
+
obeys the machine contract:
|
|
5
|
+
|
|
6
|
+
Sender1(transaction, config, query_port)
|
|
7
|
+
update_state(event) -> list[Output]
|
|
8
|
+
|
|
9
|
+
No ``kernel`` reference, no threads, no open file descriptors, no wall clock.
|
|
10
|
+
The machine builds PDUs itself (pure) and returns them in :class:`SendPdu`; it
|
|
11
|
+
reads file bytes, size, and transport readiness through the injected
|
|
12
|
+
:class:`~cfdp.pure.query_port.QueryPort`, synchronously (design D3/D5). The EOF
|
|
13
|
+
*checksum* is computed asynchronously (issue #21): the machine emits
|
|
14
|
+
:class:`RequestChecksum` and sends the EOF in the ``E42_CHECKSUM_READY``
|
|
15
|
+
continuation so a large-file checksum never blocks the dispatcher (design R1).
|
|
16
|
+
File open/close is a returned effect (:class:`OpenSource` / :class:`CloseFile`);
|
|
17
|
+
the shell owns the handle (design D10).
|
|
18
|
+
|
|
19
|
+
Class 1 has no timers and no NAKs, so this machine emits no timer effects.
|
|
20
|
+
|
|
21
|
+
Verified by differential trace testing against frozen golden fixtures captured
|
|
22
|
+
from the pre-cutover implementation — see ``tests/test_pure_sender1.py``.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from typing import Any
|
|
26
|
+
|
|
27
|
+
from cfdp.constants import (
|
|
28
|
+
ConditionCode,
|
|
29
|
+
DeliveryCode,
|
|
30
|
+
Direction,
|
|
31
|
+
FileStatus,
|
|
32
|
+
MachineState,
|
|
33
|
+
TransmissionMode,
|
|
34
|
+
)
|
|
35
|
+
from cfdp.event import Event, EventType
|
|
36
|
+
from cfdp.pdu import EofPdu, FiledataPdu, MetadataPdu
|
|
37
|
+
|
|
38
|
+
from .. import indications
|
|
39
|
+
from ..config import TransactionConfig
|
|
40
|
+
from ..outputs import (
|
|
41
|
+
CloseFile,
|
|
42
|
+
InternalEvent,
|
|
43
|
+
OpenSource,
|
|
44
|
+
Output,
|
|
45
|
+
RequestChecksum,
|
|
46
|
+
SendPdu,
|
|
47
|
+
)
|
|
48
|
+
from ..query_port import QueryPort
|
|
49
|
+
from ..transaction import Transaction
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class Sender1:
|
|
53
|
+
"""Pure implementation of the Class 1 (unacknowledged transfer) sender."""
|
|
54
|
+
|
|
55
|
+
def __init__(
|
|
56
|
+
self,
|
|
57
|
+
transaction: Transaction,
|
|
58
|
+
config: TransactionConfig,
|
|
59
|
+
query_port: QueryPort,
|
|
60
|
+
*,
|
|
61
|
+
messages_to_user: list | None = None,
|
|
62
|
+
filestore_requests: list | None = None,
|
|
63
|
+
) -> None:
|
|
64
|
+
self.transaction = transaction
|
|
65
|
+
self.config = config
|
|
66
|
+
self.query_port = query_port
|
|
67
|
+
|
|
68
|
+
# Request payload arrives with the creating put request, not on the
|
|
69
|
+
# frozen Transaction descriptor (design D3, "Data model"). Class-1
|
|
70
|
+
# F-series scenarios carry neither, so both default to None.
|
|
71
|
+
self.messages_to_user = messages_to_user
|
|
72
|
+
self.filestore_requests = filestore_requests
|
|
73
|
+
|
|
74
|
+
self.transmission_mode = TransmissionMode.UNACKNOWLEDGED
|
|
75
|
+
self.state = MachineState.SEND_METADATA
|
|
76
|
+
|
|
77
|
+
# Runtime state (plain data; formerly split across Machine + Transaction).
|
|
78
|
+
self._initialize()
|
|
79
|
+
|
|
80
|
+
# -- state reset (mirrors Machine.initialize) -------------------------- #
|
|
81
|
+
|
|
82
|
+
def _initialize(self):
|
|
83
|
+
# as per CCSDS 720.2-G-3, Page 5-13, g)
|
|
84
|
+
self.condition_code = ConditionCode.NO_ERROR
|
|
85
|
+
self.delivery_code = DeliveryCode.DATA_INCOMPLETE
|
|
86
|
+
self.frozen = False
|
|
87
|
+
self.suspended = False
|
|
88
|
+
self.file_status = FileStatus.UNREPORTED
|
|
89
|
+
self.filestore_responses = None
|
|
90
|
+
self.status_report = None
|
|
91
|
+
|
|
92
|
+
# Send-side file position (formerly Transaction._file_pos / _file_size).
|
|
93
|
+
self._file_pos = 0
|
|
94
|
+
self._file_size = None
|
|
95
|
+
# Whether a source file handle is open (formerly transaction.file_handle
|
|
96
|
+
# truthiness). shutdown() closes the handle only if one was opened, so a
|
|
97
|
+
# metadata-only transfer emits no CloseFile.
|
|
98
|
+
self._source_open = False
|
|
99
|
+
# The Class-1 sender never advances progress (only the receive side does),
|
|
100
|
+
# so resumed/abandoned indications always report 0 — matching the old
|
|
101
|
+
# machine's Transaction.get_progress().
|
|
102
|
+
self._progress = 0
|
|
103
|
+
# Async-checksum cache (issue #21). ``None`` until the shell computes the
|
|
104
|
+
# whole-file checksum and injects E42_CHECKSUM_READY. Cached exactly like
|
|
105
|
+
# the old ``Transaction.get_file_checksum`` memoized it, so every EOF
|
|
106
|
+
# (first send, cancellation) reuses the one value. ``_pending_eof`` holds
|
|
107
|
+
# the "finish sending the EOF" continuation deferred until the value
|
|
108
|
+
# arrives; ``None`` means nothing is waiting.
|
|
109
|
+
self._checksum = None
|
|
110
|
+
self._pending_eof = None
|
|
111
|
+
|
|
112
|
+
# -- the single input door --------------------------------------------- #
|
|
113
|
+
|
|
114
|
+
def update_state(self, event: Event) -> list[Output]:
|
|
115
|
+
"""See state table given in CCSDS 720.2-G-3, Table 5-1.
|
|
116
|
+
|
|
117
|
+
Returns the ordered list of :class:`~cfdp.pure.outputs.Output` produced
|
|
118
|
+
by handling ``event``.
|
|
119
|
+
"""
|
|
120
|
+
self._out: list[Output] = []
|
|
121
|
+
|
|
122
|
+
if self.state == MachineState.SEND_METADATA:
|
|
123
|
+
self._update_send_metadata(event)
|
|
124
|
+
elif self.state == MachineState.SEND_FILE:
|
|
125
|
+
self._update_send_file(event)
|
|
126
|
+
|
|
127
|
+
out, self._out = self._out, []
|
|
128
|
+
return out
|
|
129
|
+
|
|
130
|
+
# -- SEND_METADATA state ----------------------------------------------- #
|
|
131
|
+
|
|
132
|
+
def _update_send_metadata(self, event):
|
|
133
|
+
if event.type == EventType.E0_ENTERED_STATE:
|
|
134
|
+
self._initialize()
|
|
135
|
+
|
|
136
|
+
elif event.type == EventType.E30_RECEIVED_PUT_REQUEST:
|
|
137
|
+
self._issue_transaction_indication()
|
|
138
|
+
self._send_metadata()
|
|
139
|
+
if self._is_file_transfer():
|
|
140
|
+
self.state = MachineState.SEND_FILE
|
|
141
|
+
self._emit(InternalEvent(EventType.E0_ENTERED_STATE))
|
|
142
|
+
else:
|
|
143
|
+
self._send_eof()
|
|
144
|
+
self._issue_eof_sent_indication()
|
|
145
|
+
self._issue_transaction_finished_indication()
|
|
146
|
+
self._shutdown()
|
|
147
|
+
|
|
148
|
+
# -- SEND_FILE state --------------------------------------------------- #
|
|
149
|
+
|
|
150
|
+
def _update_send_file(self, event):
|
|
151
|
+
if event.type == EventType.E0_ENTERED_STATE:
|
|
152
|
+
self._open_sourcefile()
|
|
153
|
+
self._emit(InternalEvent(EventType.E1_SEND_FILE_DATA))
|
|
154
|
+
|
|
155
|
+
elif event.type == EventType.E1_SEND_FILE_DATA:
|
|
156
|
+
if not self.suspended and not self.frozen:
|
|
157
|
+
if self._is_comm_layer_ready():
|
|
158
|
+
self._send_file_data()
|
|
159
|
+
if self._is_entire_file_sent():
|
|
160
|
+
self._begin_eof(self._complete_after_eof)
|
|
161
|
+
return # stop here
|
|
162
|
+
self._emit(InternalEvent(EventType.E1_SEND_FILE_DATA))
|
|
163
|
+
|
|
164
|
+
elif event.type == EventType.E2_ABANDON_TRANSACTION:
|
|
165
|
+
self._issue_abandoned_indication()
|
|
166
|
+
self._shutdown()
|
|
167
|
+
|
|
168
|
+
elif event.type == EventType.E3_NOTICE_OF_CANCELLATION:
|
|
169
|
+
self._begin_eof(self._cancel_after_eof)
|
|
170
|
+
|
|
171
|
+
elif event.type == EventType.E42_CHECKSUM_READY:
|
|
172
|
+
self._checksum = event.checksum
|
|
173
|
+
self._resume_eof()
|
|
174
|
+
|
|
175
|
+
elif event.type == EventType.E4_NOTICE_OF_SUSPENSION:
|
|
176
|
+
if not self.suspended:
|
|
177
|
+
self._issue_suspended_indication()
|
|
178
|
+
self.suspended = True
|
|
179
|
+
|
|
180
|
+
elif event.type == EventType.E31_RECEIVED_SUSPEND_REQUEST:
|
|
181
|
+
self._emit(InternalEvent(EventType.E4_NOTICE_OF_SUSPENSION))
|
|
182
|
+
|
|
183
|
+
elif event.type == EventType.E32_RECEIVED_RESUME_REQUEST:
|
|
184
|
+
if self.suspended:
|
|
185
|
+
self._issue_resumed_indication()
|
|
186
|
+
self.suspended = False
|
|
187
|
+
if not self.frozen:
|
|
188
|
+
self._emit(InternalEvent(EventType.E1_SEND_FILE_DATA))
|
|
189
|
+
|
|
190
|
+
elif event.type == EventType.E33_RECEIVED_CANCEL_REQUEST:
|
|
191
|
+
self.condition_code = ConditionCode.CANCEL_REQUEST_RECEIVED
|
|
192
|
+
self._emit(InternalEvent(EventType.E3_NOTICE_OF_CANCELLATION))
|
|
193
|
+
|
|
194
|
+
elif event.type == EventType.E34_RECEIVED_REPORT_REQUEST:
|
|
195
|
+
self._issue_report_indication()
|
|
196
|
+
|
|
197
|
+
elif event.type == EventType.E40_RECEIVED_FREEZE:
|
|
198
|
+
self.frozen = True
|
|
199
|
+
|
|
200
|
+
elif event.type == EventType.E41_RECEIVED_THAW:
|
|
201
|
+
if self.frozen:
|
|
202
|
+
self.frozen = False
|
|
203
|
+
if not self.suspended:
|
|
204
|
+
self._emit(InternalEvent(EventType.E1_SEND_FILE_DATA))
|
|
205
|
+
|
|
206
|
+
# -- output helper ----------------------------------------------------- #
|
|
207
|
+
|
|
208
|
+
def _emit(self, output):
|
|
209
|
+
self._out.append(output)
|
|
210
|
+
|
|
211
|
+
# -- queries (predicates over pure state / the query port) ------------- #
|
|
212
|
+
|
|
213
|
+
def _is_file_transfer(self):
|
|
214
|
+
return bool(
|
|
215
|
+
self.transaction.source_filename
|
|
216
|
+
and len(self.transaction.source_filename) > 0
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
def _is_comm_layer_ready(self):
|
|
220
|
+
return self.query_port.transport_ready()
|
|
221
|
+
|
|
222
|
+
def _is_entire_file_sent(self):
|
|
223
|
+
return self._file_pos >= self._file_size_bytes()
|
|
224
|
+
|
|
225
|
+
def _file_size_bytes(self):
|
|
226
|
+
if self._file_size is None:
|
|
227
|
+
if self.transaction.source_filename:
|
|
228
|
+
self._file_size = self.query_port.size(self.transaction.source_filename)
|
|
229
|
+
else:
|
|
230
|
+
self._file_size = 0
|
|
231
|
+
return self._file_size
|
|
232
|
+
|
|
233
|
+
# -- effects: file lifecycle ------------------------------------------- #
|
|
234
|
+
|
|
235
|
+
def _open_sourcefile(self):
|
|
236
|
+
# The old machine runs the filestore fault handler if the source file is
|
|
237
|
+
# missing; Class-1 F-series scenarios only ever send existing files, so
|
|
238
|
+
# the happy path is a single OpenSource. (Fault handling on a missing
|
|
239
|
+
# source is Class-2 territory / a later increment.)
|
|
240
|
+
self._emit(OpenSource())
|
|
241
|
+
self._source_open = True
|
|
242
|
+
|
|
243
|
+
def _shutdown(self):
|
|
244
|
+
# Mirror Machine.shutdown: close the handle only if one was opened.
|
|
245
|
+
if self._source_open:
|
|
246
|
+
self._emit(CloseFile())
|
|
247
|
+
self._source_open = False
|
|
248
|
+
self.state = MachineState.COMPLETED
|
|
249
|
+
|
|
250
|
+
# -- effects: PDU builders (pure) -------------------------------------- #
|
|
251
|
+
|
|
252
|
+
def _header_fields(self) -> dict[str, Any]:
|
|
253
|
+
"""The identity fields shared by every PDU this machine sends.
|
|
254
|
+
|
|
255
|
+
Direction, transmission mode, and the three transaction-identity fields
|
|
256
|
+
are the same on every outbound PDU; naming them once keeps each PDU
|
|
257
|
+
builder to only its distinguishing fields (splat with ``**``).
|
|
258
|
+
"""
|
|
259
|
+
return {
|
|
260
|
+
"direction": Direction.TOWARD_RECEIVER,
|
|
261
|
+
"transmission_mode": self.transmission_mode,
|
|
262
|
+
"source_entity_id": self.transaction.source_entity_id,
|
|
263
|
+
"transaction_seq_number": self.transaction.seq_number,
|
|
264
|
+
"destination_entity_id": self.transaction.destination_entity_id,
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
def _send_metadata(self):
|
|
268
|
+
pdu = MetadataPdu(
|
|
269
|
+
**self._header_fields(),
|
|
270
|
+
file_size=self._file_size_bytes(),
|
|
271
|
+
source_filename=self.transaction.source_filename,
|
|
272
|
+
destination_filename=self.transaction.destination_filename,
|
|
273
|
+
checksum_type=self.transaction.checksum_type,
|
|
274
|
+
filestore_requests=self.filestore_requests,
|
|
275
|
+
messages_to_user=self.messages_to_user,
|
|
276
|
+
)
|
|
277
|
+
self._emit(SendPdu(pdu=pdu))
|
|
278
|
+
|
|
279
|
+
def _send_file_data(self):
|
|
280
|
+
offset = self._file_pos
|
|
281
|
+
_, data = self.query_port.read_segment(
|
|
282
|
+
offset, self.config.maximum_file_segment_length
|
|
283
|
+
)
|
|
284
|
+
# Advance by the bytes actually read, not the requested segment length: a
|
|
285
|
+
# backend that returns a short read for a non-final segment must not let
|
|
286
|
+
# _file_pos overshoot the real data end (which _is_entire_file_sent()'s
|
|
287
|
+
# >= would read as "done" and send EOF on a truncated transfer). For the
|
|
288
|
+
# native filestore every non-final read is full-length, so the emitted
|
|
289
|
+
# segment offsets are unchanged.
|
|
290
|
+
self._file_pos += len(data)
|
|
291
|
+
pdu = FiledataPdu(
|
|
292
|
+
**self._header_fields(),
|
|
293
|
+
segment_offset=offset,
|
|
294
|
+
file_data=data,
|
|
295
|
+
)
|
|
296
|
+
self._emit(SendPdu(pdu=pdu))
|
|
297
|
+
|
|
298
|
+
# -- async-checksum-gated EOF (issue #21) ------------------------------ #
|
|
299
|
+
|
|
300
|
+
def _begin_eof(self, continuation):
|
|
301
|
+
"""Send the EOF, then run ``continuation`` — deferring on the checksum.
|
|
302
|
+
|
|
303
|
+
For a file transfer the EOF carries the whole-file checksum. Rather than
|
|
304
|
+
computing it synchronously on the event thread (design R1), the machine
|
|
305
|
+
emits :class:`RequestChecksum` and parks ``continuation`` until the shell
|
|
306
|
+
injects ``E42_CHECKSUM_READY``. A metadata-only transfer needs no real
|
|
307
|
+
checksum (value 0), so it proceeds immediately.
|
|
308
|
+
"""
|
|
309
|
+
if self._is_file_transfer() and self._checksum is None:
|
|
310
|
+
if self._pending_eof is not None:
|
|
311
|
+
# A checksum request is already outstanding and we are being
|
|
312
|
+
# asked to send another EOF — the only re-entry is an E3
|
|
313
|
+
# cancellation, which includes the checksum-failure fallback
|
|
314
|
+
# where the shell injects E33 (not E42) because the computation
|
|
315
|
+
# raised. Do NOT re-park waiting for a second E42 that may never
|
|
316
|
+
# arrive: a Sender1 has no inactivity timer, so a missing event
|
|
317
|
+
# would strand the machine forever (defeating the issue #24
|
|
318
|
+
# "exactly one terminating event" guarantee). Resolve now with
|
|
319
|
+
# the best-available checksum (0 if it was never computed); a
|
|
320
|
+
# later E42, if any, finds nothing parked and no-ops.
|
|
321
|
+
self._pending_eof = None
|
|
322
|
+
self._send_eof()
|
|
323
|
+
continuation()
|
|
324
|
+
return
|
|
325
|
+
# First EOF: emit exactly one RequestChecksum and park until
|
|
326
|
+
# E42_CHECKSUM_READY arrives (a second request would spawn a
|
|
327
|
+
# duplicate worker and a redundant E42 — issue #21 review).
|
|
328
|
+
self._pending_eof = continuation
|
|
329
|
+
self._emit(RequestChecksum(checksum_type=self.transaction.checksum_type))
|
|
330
|
+
return
|
|
331
|
+
self._send_eof()
|
|
332
|
+
continuation()
|
|
333
|
+
|
|
334
|
+
def _resume_eof(self):
|
|
335
|
+
continuation, self._pending_eof = self._pending_eof, None
|
|
336
|
+
if continuation is None:
|
|
337
|
+
return
|
|
338
|
+
self._send_eof()
|
|
339
|
+
continuation()
|
|
340
|
+
|
|
341
|
+
def _complete_after_eof(self):
|
|
342
|
+
self._issue_eof_sent_indication()
|
|
343
|
+
self._issue_transaction_finished_indication()
|
|
344
|
+
self._shutdown()
|
|
345
|
+
|
|
346
|
+
def _cancel_after_eof(self):
|
|
347
|
+
self._issue_transaction_finished_indication()
|
|
348
|
+
self._shutdown()
|
|
349
|
+
|
|
350
|
+
def _send_eof(self):
|
|
351
|
+
pdu = EofPdu(
|
|
352
|
+
**self._header_fields(),
|
|
353
|
+
condition_code=self.condition_code,
|
|
354
|
+
file_checksum=self._file_checksum(),
|
|
355
|
+
file_size=self._file_size_bytes(),
|
|
356
|
+
)
|
|
357
|
+
self._emit(SendPdu(pdu=pdu))
|
|
358
|
+
|
|
359
|
+
def _file_checksum(self):
|
|
360
|
+
# The value is computed asynchronously (RequestChecksum -> E42) and
|
|
361
|
+
# cached; a metadata-only transfer has no file and checksums to 0.
|
|
362
|
+
if self.transaction.source_filename:
|
|
363
|
+
return self._checksum if self._checksum is not None else 0
|
|
364
|
+
return 0
|
|
365
|
+
|
|
366
|
+
# -- effects: indications ---------------------------------------------- #
|
|
367
|
+
|
|
368
|
+
def _issue_transaction_indication(self):
|
|
369
|
+
self._emit(indications.transaction(self.transaction.id))
|
|
370
|
+
|
|
371
|
+
def _issue_eof_sent_indication(self):
|
|
372
|
+
self._emit(indications.eof_sent(self.transaction.id))
|
|
373
|
+
|
|
374
|
+
def _issue_transaction_finished_indication(self):
|
|
375
|
+
self._emit(
|
|
376
|
+
indications.transaction_finished(
|
|
377
|
+
self.transaction.id,
|
|
378
|
+
self.condition_code,
|
|
379
|
+
self.file_status,
|
|
380
|
+
self.delivery_code,
|
|
381
|
+
self.filestore_responses,
|
|
382
|
+
self.status_report,
|
|
383
|
+
)
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
def _issue_suspended_indication(self):
|
|
387
|
+
self._emit(indications.suspended(self.transaction.id, self.condition_code))
|
|
388
|
+
|
|
389
|
+
def _issue_resumed_indication(self):
|
|
390
|
+
self._emit(indications.resumed(self.transaction.id, self._progress))
|
|
391
|
+
|
|
392
|
+
def _issue_report_indication(self):
|
|
393
|
+
self._emit(indications.report(self.transaction.id, self.status_report))
|
|
394
|
+
|
|
395
|
+
def _issue_abandoned_indication(self):
|
|
396
|
+
self._emit(
|
|
397
|
+
indications.abandoned(
|
|
398
|
+
self.transaction.id, self.condition_code, self._progress
|
|
399
|
+
)
|
|
400
|
+
)
|