Case C - One large image, multi-pass delivery

Two candidates presented for CONOPS-call alignment, neither picked yet. WIP

The actual problem isn't "doesn't fit" - it's control

Images are stored compressed on the satellite by default - that's not a step chosen specifically for this case, it's just the normal state of any image data. A single CFDP transaction can span as many passes as needed via ordinary Freeze/Thaw - the protocol itself imposes no ceiling: freeze/thaw fires unconditionally on every pass boundary with no counter anywhere in its definition, and the one mechanism that could turn "long gap" into a timeout fault - the inactivity timer - is explicitly suspended during a freeze. So a large image, left alone as one ongoing transaction, will eventually complete regardless of size - CFDP itself never breaks this.

That doesn't mean truly unlimited in practice. Real constraints exist outside the protocol: the transaction's state occupies a slot in onboard/ground storage for however long it stays open (Q5's ~50,000-concurrent-tx budget), and nobody would actually want an image dragging on for an enormous number of passes anyway, since it keeps competing for bandwidth against everything else that whole time (Case B's theme). The point isn't that Case C is dodging a protocol wall that doesn't exist - it's that the actual problem is scheduling/pacing, not fitting.

The problem this case actually solves is control, not fit: ground/PPS wants to deliberately decide how much of a given pass's bandwidth this transaction consumes, typically because other content (other images, repairs, etc.) is competing for that same pass's capacity. Left alone, the transaction would just opportunistically grab whatever it can each pass, with no coordination against everything else that pass also needs to carry.

CCSDS 727.0-B-5 §4.12.2.1/.3: freeze/thaw fires unconditionally on every pass boundary, no counter in the definition. §4.11.2.6.2(b), §4.11.2.7(c): the inactivity timer is explicitly suspended during a freeze - the one mechanism that could otherwise turn elapsed time into a fault.

The options for getting that control

Two candidates are on the table, both written up here for the CONOPS call - lean toward whichever is cheaper unless there's a real need the other one specifically serves.

The arithmetic that makes both candidates actually plannable

Once an image is compressed, its size is known onboard. X-band's downlink bitrate is a known link parameter (real value still pending Q11). Combined: time needed = compressed size / bitrate - simple arithmetic, computable entirely in advance, no telemetry feedback required. This is what turns "control" from a vague goal into something ground can actually pre-plan:

  • For Tiling: knowing a given tile/band's compressed size tells ground exactly how long that piece takes to downlink, which directly feeds the "which tile goes in which pass" scheduling decision.
  • For Pause/Resume's byte-threshold extension: ground doesn't have to guess at a byte count - it computes the threshold as bitrate × (time allocated to this Tx in this pass), using the same known quantities. "15MB in Pass 1" and "6MB in Pass 2" aren't arbitrary numbers ground picks; they fall out of how much pass time ground decides to give this transaction, converted through a known rate.

This is the same real number (X-band bitrate) already needed for Q11 - Case C just gives it a second, more granular use beyond Q5's original capacity-budget context.

Candidate 1 - Tiling (split into multiple files)

Split the image into multiple smaller files before any downlink activity, minimizing file count, along boundaries meaningful enough that each chunk is independently useful even on its own - not an arbitrary byte-count slice. Each resulting file is its own ordinary CFDP transaction, closing independently. This isn't a new mechanism - it's exactly Case A's already-solved multi-transaction dispatch pattern, just spread deliberately across multiple passes.

Splitting strategy: still genuinely open, not settled here. What we actually know for sure: the payload is multispectral, which means a small, fixed number of bands, known well in advance of any actual image being captured - a structural property of the sensor, not something that depends on knowing what's actually in a given image (content like cloud cover or features of interest genuinely isn't known ahead of time, so any strategy that needed that knowledge wouldn't work).

What we don't actually know, and shouldn't assert as settled: whether splitting by band is the right choice for "each chunk independently meaningful." That rests on whether a single band alone has standalone analytical value for how this data actually gets used downstream - we haven't confirmed that, so band-wise is a plausible candidate, not a recommendation. Tile-wise (spatial) splitting remains equally plausible and needs the same confirmation before either gets picked.

Case C tiling diagram: PPS names multiple tile files, each dispatched as its own independent CFDP transaction reusing Case A's dispatch pattern, closing independently, reassembled on the ground afterward

Rendered from diagram/case_c_tiling.puml (PlantUML)

How do we actually get a piece out of compressed storage?

Since the image is already compressed as a whole by default, splitting it isn't free - genuinely open, a few candidate approaches, per CONOPS:

  • Decompress the full image, extract the needed band/tile, then re-compress just that piece before sending it as its own file. Costs onboard decompress+recompress cycles per tile.
  • Store the image in a pre-decided split layout from the start (e.g., each band compressed separately at capture/processing time, before it's ever combined into one blob) - so a piece is already separately accessible and compressed, no decompress/recompress needed later, at the cost of losing whatever compression efficiency comes from compressing bands together.
  • Any other way that fits - not settled, this is a real design question Tiling introduces that Pause/Resume never has to answer.

Which approach is workable depends on onboard compute budget and the actual compression scheme in use - outside what this project can settle on its own.

Reassembly needs its own application-layer logic, not just a "cost": raw CFDP has no concept of "these N files are pieces of one image" - each tile transaction closes independently with no relationship to the others as far as CFDP itself is concerned. Something above CFDP has to know the splitting scheme (which band/tile each file represents) and use that to correctly place each received piece back into the final image once all of them arrive. This is a real piece of software to design and build, not just an afterthought line item.

Candidate 2 - Pause/Resume (single file, sequential)

Keep one single file/transaction for the compressed image, even if it doesn't fit one pass. The simplest version needs literally nothing beyond ordinary Freeze/Thaw. If ground additionally wants finer timing control - a deliberate stand-down mid-pass, not just waiting for the next natural pass boundary - that uses CFDP's own already-defined Suspend/Resume primitives.

  • Suspend.request saves the transaction's transmission progress - just the ordinary sequential send cursor, no byte-range bookkeeping needed.
  • Resume.request continues transmission of "Metadata PDU, file segments, and EOF PDU" from exactly where it left off - sequential, no offset selection, no gaps introduced.
  • Neither primitive has any byte-count or offset parameter - this specific ceiling is real, straight from the book: Resume has no way to say "resume, but only for N bytes then stop." Left to CFDP's own primitives alone, the amount sent per window is just whatever throughput happens between one Suspend and the next Resume - not a pre-plannable exact quantity.
CCSDS 727.0-B-5 §4.11.2.6.1(b): Suspend saves transaction status. §4.6.7.2.1(a): Resume continues transmission of Metadata/file segments/EOF - no offset parameter exists anywhere in these primitives.
But this ceiling applies only to CFDP's native primitives, not to what this candidate can actually achieve (confirmed via PAVI): the onboard payload app can track its own storage-read/send progress - the same capability already used elsewhere in this project. A ground command can carry a byte threshold ("send up to N more bytes of Tx X, then pause") using the same custom-command architecture as Option B below - the payload app watches its own progress against that threshold and invokes Suspend.request locally once it's hit. This isn't a CFDP protocol feature; it's application logic layered on top, the exact same pattern already used for the cheap Suspend/Resume shortcut.

Result: ground can pre-plan an exact quantity per pass this way (e.g., "15MB of Tx X in Pass 1, 6MB of Tx X plus other content in Pass 2") - not arbitrary numbers, but the direct output of the size/bitrate arithmetic above (bitrate × time allocated to this Tx = byte threshold to command). Genuinely useful for exactly the kind of mixed-content, multi-pass bandwidth sharing this case is actually about. What this still cannot do: pick a specific non-sequential byte range out of order - the payload app is still just reading/sending the file forward from wherever it is; only Tiling gets you out-of-order or independently-meaningful-on-arrival chunks. Quantity per pass, yes; which specific bytes, still no.

Only Option B can do this - Option A cannot: CFDP's native Remote Suspend Request message has a fixed format - just source entity ID and transaction sequence number, no room for a byte-count field. Since it's a CCSDS-defined Reserved Message, we can't add one. Option B's command is ours, not CFDP's, so extending it with a threshold parameter costs nothing - one more reason favoring Option B below.

One more nuance this surfaces: once the payload app locally invokes Suspend.request to stop early mid-pass, the transaction becomes formally suspended - not just frozen. And "thawing transmission for a suspended transaction shall have no effect whatsoever." So the ordinary automatic pass-boundary thaw will not wake this back up on its own - something has to explicitly invoke Resume.request for each subsequent controlled pass, not just the initial Suspend.

Correction: this does not mean ground has to send a live command every single pass. If the whole multi-pass plan (every pass's threshold, including which passes to skip and when to resume) is loaded onto the satellite once, upfront, the payload app can invoke both Suspend.request and Resume.request locally, on its own, for every subsequent pass - driven by its own pass-boundary observations, its own byte-progress tracking, and (for something like "wait for another Tx to finish first") locally-observable conditions it can evaluate onboard without any ground involvement at all. Ground's job is the one upfront schedule-loading command; everything after that is the payload app executing its own plan. See the worked example below.

Worked example: a single upfront command loads a 5-pass schedule for Tx X - Pass 1 continues freely, Pass 2 sends a threshold then locally suspends, Pass 3 is skipped entirely (thaw cannot wake a suspended transaction), Pass 4 waits for another transaction's local completion then locally resumes, Pass 5 locally resumes and continues freely - all autonomous after the one upfront command

Rendered from diagram/case_c_5pass_mixed_schedule.puml (PlantUML)

CCSDS 727.0-B-5 §4.12.2.4(b): "thawing transmission for a suspended transaction shall have no effect whatsoever."
NAK doesn't give you segment selection either - this is a hard protocol rule, not just a design choice: before EOF has been received, a NAK sequence's scope is capped at "the current reception progress at the time of the event that caused issuance of the NAK sequence" - it cannot extend into data that hasn't been sent yet. Combined with the gap-detection conditions themselves, a "gap" is only ever defined between two already-received extents - there's no protocol concept of a gap ahead of current progress. So NAK can repair a confirmed loss within what's already been attempted, but it cannot be used to ask the sender to jump ahead and send a specific not-yet-reached segment early. Without Tiling, Freeze/Thaw and Suspend/Resume really are the only two levers available here - there's no third option hiding in NAK.
CCSDS 727.0-B-5 §4.6.4.3.3(d)(3): NAK scope end is "the current reception progress" pre-EOF. §4.6.4.3.1: gap-detection conditions, all defined relative to already-received extents.
Case C pause/resume diagram: single transaction sent sequentially, freezing automatically at each pass boundary; ground sends an explicit byte-threshold command each controlled pass, the payload app locally invokes Suspend.request once the threshold is hit, and ordinary thaw cannot wake a suspended transaction back up - a new explicit command is needed every such pass, closing normally once complete

Rendered from diagram/case_c_pause_resume.puml (PlantUML)

Open dependency: this only matters for the reactive case - when ground wants to suspend/resume something that wasn't already part of a pre-loaded plan (an emergency, a mid-stream repriority, correcting a plan that turned out wrong - Case B/D territory). For the nominal, fully-pre-planned execution above, this doesn't come up at all - the payload app handles it autonomously. Still needs Q7.12 answered for the reactive case though - see the detailed breakdown below.

Q7.12 - reactively triggering suspend/resume for something NOT in a pre-loaded plan

Scope check first: if the whole plan was pre-loaded upfront (the normal case above), neither option below is needed at all - the payload app just executes its own plan. This section is specifically about ground wanting to intervene live, mid-stream, on something the original plan didn't cover. Two real options for that, verified against the book, both diagrammed:

Option A - CFDP's own native mechanism (click to expand the full mechanics)

CFDP actually defines this: Remote Suspend Request/Response and Remote Resume Request/Response. Mechanically:

  • To request a remote suspend, the requesting CFDP user issues a Put.request (Acknowledged mode) whose Metadata carries a Reserved CFDP Message identifying the target transaction. That's a whole new CFDP transaction just to carry this one instruction.
  • The remote entity applies Suspend.request locally on receipt, then issues its own Put.request carrying a Remote Suspend Response back to the requester - a second full transaction.
  • Resume works identically but separately - another 2 transactions.
  • Total: 4 full mini Class-2 transactions for one pause+resume cycle, each with its own Metadata+EOF+Finished+ACK overhead and its own multi-pass queueing delay - a simple scheduling decision could take several passes just to fully round-trip and confirm.
CCSDS 727.0-B-5 §6.5.3/6.5.4 (Remote Suspend): Put.request carries the request; remote entity applies Suspend.request and responds with its own Put.request. §6.6.3/6.6.4 (Remote Resume): identical structure for resume.
The bigger cost, not just transaction count: to issue the Suspend/Resume request, PLDP (ground) has to act as a CFDP sender, and the satellite has to act as a CFDP receiver for that mini transaction. This project has never assumed either side needs that reversed capability - PLDP has only ever received, the satellite has only ever sent. Adopting Option A doesn't just cost 4 extra transactions; it requires both ends to be genuinely bidirectional CFDP entities, which may not even be true of the current design.
Diagram: PLDP acting as CFDP sender to originate a Remote Suspend Request transaction to SAT, which applies Suspend.request and responds with its own Remote Suspend Response transaction; identical pattern repeats for Resume

Rendered from diagram/case_c_suspend_native.puml (PlantUML)

Option B - cheap ground-side shortcut

A plain, non-CFDP command - "Suspend Tx X" / "Resume Tx X" - riding the same ordinary-PUS-APID path already established in the TC vs X-band concept. The command processor delivers it to the onboard payload app, which locally invokes Suspend.request/Resume.request on the CFDP entity - the underlying CFDP effect is identical to Option A's; only how the instruction gets there differs. Cost: 2 simple commands, no new CFDP transactions, no role reversal - PLDP stays receive-only throughout, exactly as everywhere else in this project.

Diagram: a plain PUS command routed through the satellite's command processor to its payload app, which locally invokes Suspend.request/Resume.request - no new CFDP transaction, no confirmation message for Suspend

Rendered from diagram/case_c_suspend_shortcut.puml (PlantUML)

The trade-off: Option A gets a built-in, protocol-level confirmation (the Remote Suspend/Resume Response message actually tells you it worked). Option B gets no such confirmation for Suspend specifically - ground would have to infer it from telemetry (File Data PDUs stopping), a weak signal since a pass simply ending looks identical from ground's side. Resume is less of a problem either way - it mostly self-confirms, since ground directly sees File Data flowing again once it does.

Leaning: Option B is very likely the right call - it reuses infrastructure already being built anyway, avoids a real architectural extension (bidirectional CFDP on both ends) that nothing else in this project needs, and only gives up Suspend's confirmation, which a simple telemetry check can likely substitute for well enough. Q7.12 itself is not locked in - this is a leaning, not a decision.

Comparison, for the call

TilingPause/Resume
New mechanism neededNone (reuses Case A)None (reuses existing CFDP primitives)
Extra costSplitting-out-of-compressed-storage mechanics, splitting strategy, application-layer reassemblyNothing extra - no compressed-storage-splitting problem to solve
Partial/early valueYes - each piece usable on arrivalNo - only once fully complete
Exact quantity per passYes, at whole-piece granularityYes, with the byte-threshold extension (PAVI)
Non-sequential/out-of-order selectionYes, at band granularityNo

If there's no real need for partial/early value per band or for picking a specific non-sequential region, Pause/Resume (with the threshold extension) covers exact per-pass quantity control at much lower cost. Tiling only earns its extra cost if per-band partial value or true out-of-order selection genuinely matters.

Open items surfaced by this case

Which candidate to actually build - explicitly not decided here, intended for the CONOPS call.
If Tiling is chosen: the splitting strategy itself (band-wise vs. tile-wise) is genuinely open, not just the fallback threshold - neither is confirmed as the right choice yet. Also still needed: how a piece actually gets extracted from compressed storage (decompress+recompress per piece, vs. a pre-decided split layout from capture time, vs. something else) - genuinely undecided, not just a detail to fill in later.
Whoever builds Tiling needs to build the application-layer reassembly logic too - CFDP itself has no concept of multiple files belonging to one image; nothing tracks that relationship for you.
If Pause/Resume is chosen: for the nominal, pre-planned case, Q7.12 doesn't come up at all - the payload app executes its own plan autonomously (see the 5-pass worked example above). Q7.12 only matters for reactive deviations from a plan (an emergency, a mid-stream repriority) - for that, both real options are laid out (native CFDP Remote Suspend/Resume vs. a cheap ground-side shortcut command) with a leaning toward the shortcut - still not locked in, needs an actual team decision. The byte-threshold-aware pause extension (PAVI's storage-read-progress insight) also needs actual design/confirmation - this page describes the concept, not a built mechanism.
Q23 (light, unconfirmed) still underlies transaction initiation for both candidates regardless of which is picked.
Q8 (tiled vs single tx) is no longer just theoretical - this case's choice between Candidate 1 and 2 is the real-world instance of it.
Q11 (still open) - the actual X-band bitrate. The entire "arithmetic that makes both candidates plannable" section is built on this number - without it, ground can reason about the shape of the scheduling problem but can't compute real byte thresholds or tile-timing numbers yet.
Revision note

An earlier version of this page assumed ground would pick exact, out-of-order byte ranges within one ongoing transaction. A real team discussion showed that's not what's being proposed - this page reflects the actual framing throughout.