Skip to content

fix(scte35): Condition descriptor-based signals correctly - #10

Merged
renato-freis merged 1 commit into
mainfrom
fix/descriptor-signal-conditioning
Aug 20, 2026
Merged

fix(scte35): Condition descriptor-based signals correctly#10
renato-freis merged 1 commit into
mainfrom
fix/descriptor-signal-conditioning

Conversation

@renato-freis

Copy link
Copy Markdown
Contributor

Summary

Fixes two defects that made SCTE-35 conditioning unreliable for signals carrying a segmentation descriptor, which is how modern SCTE-35 usually arrives: a time_signal plus a descriptor rather than a splice_insert.

Both were found while verifying the example documented in the README, after PR #9 aligned the documentation with the implementation. The example did not work on the signal it describes.

Defect 1: duration read in the wrong unit

The parser stores the two duration carriers in different units:

Field Stored as
BreakDuration.duration 90kHz ticks (parser multiplies by 90000)
SegmentationDescriptor.segmentation_duration seconds (as threefive returns it)

_get_duration() and calculate_break_expiry_time() divided both by 90000. A 60 second placement opportunity was therefore read as 60.0 // 90000 == 0 seconds.

Consequences:

  • Every duration condition failed to match on descriptor-based signals. A rule such as duration > 30 could never fire, so the signal fell through to the channel's default action.
  • Stateful mode calculated no expiry for those breaks, because a duration of 0 returns None.

The splice_insert path was unaffected, which is why this went unnoticed.

Defect 2: placement opportunity Start descriptors failed to encode

threefive requires sub_segment_num and sub_segments_expected to be set when segmentation_type_id is 0x34, 0x36, 0x38 or 0x3A:

if self.segmentation_type_id in [0x34, 0x36, 0x38, 0x3A]:
    self._chk_var(int, nbin.add_int, "sub_segment_num", 8)

Sources are not required to send those fields, and decoding leaves them unset. Encoding then raised ValueError: sub_segment_num is not set, and encode_scte35 caught it and returned the unmodified signal:

ERROR Failed to encode SCTE-35: sub_segment_num is not set, it should be type <class 'int'>
WARNING Returning original signal due to encoding failure

The response still reported replace and the log still said "Signal modified", so the conditioning silently did nothing. They now default to 0, the value the specification uses for "not used".

Documentation

The README example matched on segmentationTypeId but modified breakDuration. Those belong to different signal forms, so the modification did nothing on the very signal the example describes. It now targets segmentationDuration, and the guidance warns that choosing the wrong target leaves the payload unchanged while the response still reports replace.

This also completes the manual administrator fallback, which listed only admin-create-user. That command leaves the account outside the admin group, and the handlers authorize writes from the cognito:groups claim, so the operator could read channels and logs but could not change them, manage users, or view encoder credentials. Confirmed against a Cognito pool configured like the deployment: group membership is empty until admin-add-user-to-group runs.

Testing

New regression suite in backend/tests/unit/test_descriptor_signal_conditioning.py covering the duration units, the expiry calculation, encoding of all four Start types, and the documented rule end to end.

Verified that the new tests fail without these fixes:

without the fixes: 9 failed, 1 passed
with the fixes:    10 passed

Full suite:

186 passed
coverage 72.30% (gate 65%)
ruff, black: clean

Behavior confirmed on a real payload, a time_signal with a 0x34 descriptor of 60 seconds:

Modification target Before After
segmentationDuration rule never matched matches, 60s becomes 30s
breakDuration rule never matched matches, payload unchanged (expected: wrong carrier)

Two defects made the conditioning path unreliable for signals that carry a
segmentation descriptor, which is how modern SCTE-35 usually arrives: a
time_signal plus a descriptor rather than a splice_insert. Both were found
while verifying the example documented in the README.

Duration was read in the wrong unit. The parser stores
BreakDuration.duration in 90kHz ticks but keeps
SegmentationDescriptor.segmentation_duration in seconds, as threefive returns
it. The rule engine and the stateful break expiry divided both by 90000, so a
60 second placement opportunity was read as 0 seconds. Every "duration"
condition therefore failed to match on descriptor-based signals, and stateful
mode calculated no expiry for those breaks.

Placement opportunity Start descriptors failed to encode. threefive requires
sub_segment_num and sub_segments_expected to be set when the segmentation type
is 0x34, 0x36, 0x38 or 0x3A. Sources are not required to send those fields and
decoding leaves them unset, so encoding raised, the encoder fell back to
returning the unmodified signal, and the response still reported the
modification as applied. They now default to 0, the value the specification
uses for "not used".

The README example also targeted breakDuration while matching on
segmentationTypeId. Those belong to different signal forms, so the
modification silently did nothing on the very signal the example describes. It
now targets segmentationDuration, and the guidance warns that choosing the
wrong target leaves the payload unchanged while the response still reports
"replace".

This change also completes the manual administrator fallback, which listed
only admin-create-user. That command leaves the account outside the admin
group, and the handlers authorize writes from the "cognito:groups" claim, so
the operator could read channels and logs but could not change them, manage
users, or view encoder credentials.

Adds regression coverage for the units, the expiry calculation, encoding of
all four Start types, and the documented rule end to end. Verified that the
new tests fail without these fixes.
@renato-freis
renato-freis merged commit 150cfe3 into main Aug 20, 2026
6 checks passed
@renato-freis
renato-freis deleted the fix/descriptor-signal-conditioning branch August 20, 2026 01:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant