Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(dash): Enable Stateful Mode and rsq Corruptions for DASH #66

Merged
merged 10 commits into from
Jul 2, 2024
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ node_modules/
dist/
.env
dev
.history/
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
dist/
*.yml
README.md
.history/
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,8 @@ e.i. `https://<chaos-proxy>/api/v2/manifests/hls/proxy-master.m3u8?url=<some_url
Across all corruptions, there are 3 ways to target a segment in a playlist for corruption.

1. `i`: The segment's list index in any Media Playlist, with HLS segments starting at 0 and MPEG-DASH segments starting at 1. For a Media Playlist with 12 segments, `i`=11, would target the last segment for HLS and `i`=12, would target the last segment for MPEG-DASH.
2. `sq`: The segment's Media Sequence Number for HLS, or the "$Number$" or "$Time$" part of a segment URL for DASH. For an HLS Media Playlist with 12 segments, and where `#EXT-X-MEDIA-SEQUENCE` is 100, `sq`=111 would target the last segment. When corrupting a live HLS stream it is recommended to target with `rsq`.
3. `rsq`: A relative sequence number, counted from where the live stream is currently at when requesting manifest. Can also use a negative integer, which enables counting backwards from the end of the manifest instead. (**HLS SUPPORTED ONLY IN STATEFUL MODE**)
2. `sq`: The segment's Media Sequence Number for HLS, or the "$Number$" or "$Time$" part of a segment URL for DASH. For an HLS Media Playlist with 12 segments, and where `#EXT-X-MEDIA-SEQUENCE` is 100, `sq`=111 would target the last segment. When corrupting a live HLS or DASH stream it is recommended to target with `rsq`.
3. `rsq`: A relative sequence number, counted from where the live stream is currently at when requesting manifest. Can also use a negative integer, which enables counting backwards from the end of the manifest instead. (**SUPPORTED ONLY IN STATEFUL MODE FOR BOTH HLS AND DASH**)

Below are configuration JSON object templates for the currently supported corruptions. A query should have its value be an array consisting of any one of these 3 types of items:

Expand Down Expand Up @@ -258,6 +258,11 @@ https://<chaos-proxy>/api/v2/manifests/dash/proxy-master.mpd?url=https://livesim
https://<chaos-proxy>/api/v2/manifests/dash/proxy-master.mpd?url=https://f53accc45b7aded64ed8085068f31881.egress.mediapackage-vod.eu-north-1.amazonaws.com/out/v1/1c63bf88e2664639a6c293b4d055e6bb/64651f16da554640930b7ce2cd9f758b/66d211307b7d43d3bd515a3bfb654e1c/manifest.mpd&throttle=[{i:*,rate:10000}]
```

7. LIVE: Example of MPEG-DASH with a segment delay of 5000ms on segment with relative sequence number equal to 2:

```
https://<chaos-proxy>/api/v2/manifests/dash/proxy-master.mpd?url=https://livesim.dashif.org/livesim/testpic_2s/Manifest.mpd&delay=[{rsq:2, ms:5000}]
```
## Development Environment

To deploy and update development environment create and push a tag with the suffix `-dev`, for example `my-feat-test-dev`. If you run `npm run deploy:dev` it will automatically create a tag based on git revision with the `-dev` suffix and push it.
Expand Down
14 changes: 7 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
"eslint-plugin-prettier": "^4.2.1",
"husky": "^2.7.0",
"lint-staged": "^12.3.4",
"prettier": "^2.8.7",
"prettier": "2.8.8",
"ts-jest": "^27.1.3",
"tsc-watch": "^5.0.3",
"typescript": "^3.7.3"
Expand Down
11 changes: 9 additions & 2 deletions src/manifests/handlers/dash/segment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import {
composeALBEvent,
generateErrorResponse,
isValidUrl,
segmentUrlParamString
segmentUrlParamString,
STATEFUL,
newState
} from '../../../shared/utils';
import delaySCC from '../../utils/corruptions/delay';
import statusCodeSCC from '../../utils/corruptions/statusCode';
Expand Down Expand Up @@ -56,6 +58,10 @@ export default async function dashSegmentHandler(
}
const reqSegmentIndexInt = parseInt(reqSegmentIndexOrTimeStr);

const stateKey = STATEFUL
? newState({ initialSequenceNumber: undefined })
: undefined;

// Replace RepresentationID in url if present
if (representationIdStr) {
segmentUrl = segmentUrl.replace(
Expand Down Expand Up @@ -85,7 +91,8 @@ export default async function dashSegmentHandler(
const dashUtils = dashManifestUtils();
const mergedMaps = dashUtils.utils.mergeMap(
reqSegmentIndexInt,
allMutations
allMutations,
stateKey
);
const segUrl = new URL(segmentUrl);
const cleanSegUrl = segUrl.origin + segUrl.pathname;
Expand Down
54 changes: 54 additions & 0 deletions src/manifests/utils/configs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,60 @@ describe('configs', () => {
);
});
});

describe('not in stateful mode', () => {
let nonStatefulConfig;
beforeAll(() => {
delete process.env.STATEFUL; // Ensure STATEFUL is not set
nonStatefulConfig = require('./configs');
});

it('should handle DASH mode without stateful', () => {
// Arrange
const configs = nonStatefulConfig.corruptorConfigUtils(
new URLSearchParams(
'statusCode=[{rsq:15,code:400}]&throttle=[{sq:15,rate:1000}]'
)
);

configs.register(statusCodeConfig).register(throttleConfig);

// Act

const [err, actual] = configs.getAllManifestConfigs(0, true);

// Assert
expect(err).toEqual({
status: 400,
message:
'Relative sequence numbers on DASH are only supported when proxy is running in stateful mode'
});
expect(actual).toBeNull();
});

it('should handle HLS mode without stateful', () => {
// Arrange
const configs = nonStatefulConfig.corruptorConfigUtils(
new URLSearchParams(
'statusCode=[{rsq:15,code:400}]&throttle=[{sq:15,rate:1000}]'
)
);

configs.register(statusCodeConfig).register(throttleConfig);

// Act

const [err, actual] = configs.getAllManifestConfigs(0, false);

// Assert
expect(err).toEqual({
status: 400,
message:
'Relative sequence numbers on HLS are only supported when proxy is running in stateful mode'
});
expect(actual).toBeNull();
});
});
});

describe('getAllSegmentConfigs', () => {
Expand Down
10 changes: 10 additions & 0 deletions src/manifests/utils/configs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,16 @@ export const corruptorConfigUtils = function (
null
];
}
if (hasRelativeSequences && !STATEFUL && isDash) {
return [
{
status: 400,
message:
'Relative sequence numbers on DASH are only supported when proxy is running in stateful mode'
},
null
];
}

// If bitrate is set, filter out segments that doesn't match
params = params.filter(
Expand Down
18 changes: 14 additions & 4 deletions src/manifests/utils/dashManifestUtils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,8 @@ describe('utils.mergeMap', () => {
// Act
const actual = dashManifestUtils().utils.mergeMap(
mockReqSegIndex,
mockAllCorruptions
mockAllCorruptions,
undefined
);
const expected = new Map<string, CorruptorConfig>()
.set('a', { fields: { ms: 100 } })
Expand Down Expand Up @@ -191,7 +192,11 @@ describe('utils.mergeMap', () => {
const actual: CorruptorConfigMap[] = [];
for (let segIdx = 0; segIdx < 3; segIdx++) {
actual.push(
dashManifestUtils().utils.mergeMap(segIdx, mockAllCorruptions)
dashManifestUtils().utils.mergeMap(
segIdx,
mockAllCorruptions,
undefined
)
);
}

Expand Down Expand Up @@ -226,7 +231,11 @@ describe('utils.mergeMap', () => {
const actual: CorruptorConfigMap[] = [];
for (let segIdx = 0; segIdx < 3; segIdx++) {
actual.push(
dashManifestUtils().utils.mergeMap(segIdx, mockAllCorruptions)
dashManifestUtils().utils.mergeMap(
segIdx,
mockAllCorruptions,
undefined
)
);
}
const expected = [
Expand All @@ -252,7 +261,8 @@ describe('utils.mergeMap', () => {
// Act
const actual = dashManifestUtils().utils.mergeMap(
mockReqSegIndex,
mockAllCorruptions
mockAllCorruptions,
undefined
);
const expected = new Map<string, CorruptorConfig>().set('a', {
fields: {}
Expand Down
3 changes: 2 additions & 1 deletion src/manifests/utils/dashManifestUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
interface DASHManifestUtils {
mergeMap: (
segmentListSize: number,
configsMap: IndexedCorruptorConfigMap
configsMap: IndexedCorruptorConfigMap,
stateKey: string | undefined
) => CorruptorConfigMap;
}

Expand Down Expand Up @@ -185,8 +186,8 @@
}

function convertRelativeToAbsoluteSegmentOffsets(
mpd: any,

Check warning on line 189 in src/manifests/utils/dashManifestUtils.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
segmentTemplate: any,

Check warning on line 190 in src/manifests/utils/dashManifestUtils.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
originalUrlQuery: URLSearchParams,
segmentTemplateTimelineFormat: boolean
): [URLSearchParams, boolean] {
Expand Down
2 changes: 1 addition & 1 deletion src/shared/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
FastifyInstance
} from 'fastify';
import { addSSMUrlParametersToUrl } from './aws.utils';

import { URLSearchParams } from 'url';
import dotenv from 'dotenv';
import { Readable } from 'stream';
import NodeCache from 'node-cache';
Expand Down
Loading