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

Synchronizes SEIs at beginning of a recording #274

Merged
merged 1 commit into from
Dec 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 25 additions & 20 deletions lib/src/signed_video_h26x_auth.c
Original file line number Diff line number Diff line change
Expand Up @@ -710,9 +710,9 @@ validate_authenticity(signed_video_t *self)
valid = SV_AUTH_RESULT_SIGNATURE_PRESENT;
num_expected_nalus = -1;
num_received_nalus = -1;
// If validation was tried with the very first SEI in stream it cannot be part at.
// Reset the first validation to be able to validate a segment in the middle of the stream.
self->validation_flags.reset_first_validation = (self->gop_info->num_sent_nalus == 1);
// If no valid NAL Units were found, reset validation to be able to make more attepts to
// synchronize the SEIs.
self->validation_flags.reset_first_validation = !has_valid_nalus;
}
}
if (latest->public_key_has_changed) valid = SV_AUTH_RESULT_NOT_OK;
Expand Down Expand Up @@ -835,6 +835,7 @@ prepare_for_validation(signed_video_t *self)
SV_TRY()
h26x_nalu_list_item_t *sei = h26x_nalu_list_get_next_sei_item(nalu_list);
if (sei) {
sei->in_validation = true;
if (!sei->has_been_decoded) {
// Decode the SEI and set signature->hash
self->latest_validation->public_key_has_changed = false;
Expand Down Expand Up @@ -1047,21 +1048,20 @@ maybe_validate_gop(signed_video_t *self, h26x_nalu_t *nalu)

svrc_t status = SV_UNKNOWN_FAILURE;
SV_TRY()
bool update_validation_status = false;
bool public_key_has_changed = false;
char sei_validation_status = 'U';
// Keep validating as long as there are pending GOPs.
bool stop_validating = false;
while (has_pending_gop(self) && !stop_validating) {
// Initialize latest validation.
if (!self->validation_flags.has_auth_result) {
if (!self->validation_flags.has_auth_result || validation_flags->is_first_validation) {
latest->authenticity = SV_AUTH_RESULT_SIGNATURE_PRESENT;
latest->number_of_expected_picture_nalus = 0;
latest->number_of_received_picture_nalus = 0;
latest->number_of_pending_picture_nalus = -1;
latest->public_key_has_changed = public_key_has_changed;
}
if (validation_flags->is_first_validation) {
// Reset in_validation.
// Reset |in_validation|.
update_sei_in_validation(self, true, NULL, NULL);
}

Expand All @@ -1081,30 +1081,35 @@ maybe_validate_gop(signed_video_t *self, h26x_nalu_t *nalu)
// Fetch the |tmp_validation_status| for later use.
update_sei_in_validation(self, false, &sei_validation_status, NULL);
}
// Update |validation_status|.
SV_THROW(h26x_nalu_list_update_status(nalu_list, true));

// The flag |is_first_validation| is used to ignore the first validation if we start the
// validation in the middle of a stream. Now it is time to reset it.
validation_flags->is_first_validation = !validation_flags->signing_present;

if (validation_flags->reset_first_validation) {
validation_flags->is_first_validation = true;
validation_flags->reset_first_validation = false;
} else {
update_validation_status = true;
}
self->gop_info->verified_signature_hash = -1;
self->validation_flags.has_auth_result = true;
public_key_has_changed |= latest->public_key_has_changed; // Pass on public key failure.
}

if (validation_flags->is_first_validation) {
// Reset any set linked hashes if the session is still waiting for a first validation.
reset_linked_hash(self);
}
// All statistics but pending NALUs have already been collected.
latest->number_of_pending_picture_nalus = h26x_nalu_list_num_pending_items(nalu_list);

DEBUG_LOG("Validated GOP as %s", kAuthResultValidStr[latest->authenticity]);
DEBUG_LOG("Expected number of NALUs = %d", latest->number_of_expected_picture_nalus);
DEBUG_LOG("Received number of NALUs = %d", latest->number_of_received_picture_nalus);
DEBUG_LOG("Number of pending NALUs = %d", latest->number_of_pending_picture_nalus);
SV_THROW(h26x_nalu_list_update_status(nalu_list, update_validation_status));
if (validation_flags->is_first_validation) {
update_sei_in_validation(self, false, NULL, &sei_validation_status);
// Reset any set linked hashes if the session is still waiting for a first validation.
reset_linked_hash(self);
}

// All statistics but pending NALUs have already been collected.
latest->number_of_pending_picture_nalus = h26x_nalu_list_num_pending_items(nalu_list);
DEBUG_LOG("Validated GOP as %s", kAuthResultValidStr[latest->authenticity]);
DEBUG_LOG("Expected number of NALUs = %d", latest->number_of_expected_picture_nalus);
DEBUG_LOG("Received number of NALUs = %d", latest->number_of_received_picture_nalus);
DEBUG_LOG("Number of pending NALUs = %d", latest->number_of_pending_picture_nalus);
SV_CATCH()
SV_DONE(status)

Expand Down
27 changes: 13 additions & 14 deletions lib/src/signed_video_h26x_nalu_list.c
Original file line number Diff line number Diff line change
Expand Up @@ -415,32 +415,25 @@ h26x_nalu_list_remove_missing_items(h26x_nalu_list_t *list)

bool found_first_pending_nalu = false;
bool found_decoded_sei = false;
int num_removed_items = 0;
h26x_nalu_list_item_t *item = list->first_item;
while (item && !(found_first_pending_nalu && found_decoded_sei)) {
// Reset the invalid verification failure if we have not past the first pending item.
// Remove the missing NALU in the front.
if (item->tmp_validation_status == 'M' && (item == list->first_item)) {
if (item->tmp_validation_status == 'M' && item->in_validation) {
const h26x_nalu_list_item_t *item_to_remove = item;
item = item->next;
h26x_nalu_list_remove_and_free_item(list, item_to_remove);
num_removed_items++;
continue;
}
if (item->has_been_decoded && item->tmp_validation_status != 'U') {
// Usually, these items were added because we verified hashes with a SEI not associated with
// this recording. This can happen if we export to file or fast forward in a recording. The
// SEI used to generate these missing items is set to 'U'.
item->tmp_validation_status = 'U';
if (item->has_been_decoded && item->tmp_validation_status != 'U' && item->in_validation) {
found_decoded_sei = true;
}
// TODO: Resetting the item validation in the current GOP may affect the validation of the next
// GOP. This needs to be fixed.
if (item->tmp_validation_status == 'N') {
// Reset validation status to 'P' for the next validation.
item->tmp_validation_status = 'P';
}
item = item->next;
if (item && item->tmp_validation_status == 'P') found_first_pending_nalu = true;
}
if (num_removed_items > 0) DEBUG_LOG("Removed %d missing items to list", num_removed_items);
}

/* Searches for, and returns, the next pending SEI item. */
Expand Down Expand Up @@ -482,8 +475,14 @@ h26x_nalu_list_get_stats(const h26x_nalu_list_t *list,
continue;
}
if (item->tmp_validation_status == 'M') local_num_missing_nalus++;
if (item->tmp_validation_status == 'N' || item->tmp_validation_status == 'E')
local_num_invalid_nalus++;
if (item->nalu && item->nalu->is_gop_sei) {
if (item->in_validation &&
(item->tmp_validation_status == 'N' || item->tmp_validation_status == 'E'))
local_num_invalid_nalus++;
} else {
if (item->tmp_validation_status == 'N' || item->tmp_validation_status == 'E')
local_num_invalid_nalus++;
}
if (item->tmp_validation_status == '.') {
// Do not count SEIs, since they are marked valid if the signature could be verified, which
// happens for out-of-sync SEIs for example.
Expand Down
94 changes: 19 additions & 75 deletions tests/check/check_signed_video_auth.c
Original file line number Diff line number Diff line change
Expand Up @@ -880,15 +880,6 @@ START_TEST(all_seis_arrive_late_two_gops_scrapped)

// IPPSPIPPSPIPPSSPISPISP
//
// IPPS -> (signature) -> PPPU 3 pending
// IPPSPIPPS -> (invalid) -> NNNUNPPP. 3 pending
// IPPSPIPPS -> (invalid) -> NNN.NPPP. 3 pending
// IPPSS -> (invalid) -> NMNN.. 0 pending, 1 missing
// PIS -> (invalid) -> MMMNP. 1 pending, 3 missing
// ISPIS -> (valid) -> ...P. 1 pending
// 11 pending
// IPPSPIPPSPIPPSSPISPISP (future)
//
// IPPS -> (signature) -> PPPU 3 pending
// IPPSPIPPS -> (signature) -> PPPUPPPPU 7 pending
// IPPSPIPPSPIPPS -> (valid) -> .....PPPUPPPP. 7 pending
Expand All @@ -897,24 +888,11 @@ START_TEST(all_seis_arrive_late_two_gops_scrapped)
// ISPIS -> (valid) -> ...P. 1 pending
// 22 pending
signed_video_accumulated_validation_t final_validation = {
SV_AUTH_RESULT_NOT_OK, false, 22, 19, 3, SV_PUBKEY_VALIDATION_NOT_FEASIBLE, true, 0, 0};
struct validation_stats expected = {.valid_gops = 1,
.invalid_gops = 4,
.missed_nalus = 4,
.pending_nalus = 11,
.has_signature = 1,
SV_AUTH_RESULT_OK, false, 22, 19, 3, SV_PUBKEY_VALIDATION_NOT_FEASIBLE, true, 0, 0};
const struct validation_stats expected = {.valid_gops = 4,
.pending_nalus = 22,
.has_signature = 2,
.final_validation = &final_validation};
if (settings[_i].auth_level == SV_AUTHENTICITY_LEVEL_FRAME) {
// IPPS -> (signature) -> PMMPPU 3 pending, 2 missing
// IPPSPIPPS -> (invalid) -> NMMNNUNPPP. 3 pending, 2 missing
// IPPSPIPPS -> (invalid) -> NNN.NPPP. 3 pending
// IPPSS -> (invalid) -> NMNN.. 0 pending, 1 missing
// PIS -> (missing) -> MMM.P. 1 pending, 3 missing
// ISPIS -> (valid) -> ...P. 1 pending
// 11 pending
expected.valid_gops_with_missing_info = 1;
expected.invalid_gops = 3;
}
validate_nalu_list(NULL, list, expected, true);

test_stream_free(list);
Expand Down Expand Up @@ -1120,33 +1098,19 @@ START_TEST(remove_two_gop_in_start_of_stream)
test_stream_check_types(list, "ISPPSPISPPPPISPPISP");

signed_video_accumulated_validation_t final_validation = {
SV_AUTH_RESULT_NOT_OK, false, 19, 16, 3, SV_PUBKEY_VALIDATION_NOT_FEASIBLE, true, 0, 0};
SV_AUTH_RESULT_OK, false, 19, 16, 3, SV_PUBKEY_VALIDATION_NOT_FEASIBLE, true, 0, 0};
// ISPPSPISPPPPISPPISP
//
// IS PU -> (signature)
// ISPPS NUNN. -> (invalid)
// PIS MMMNP. -> (invalid)
// ISPPPPIS ......P. -> (valid)
// ISPPIS ....P. -> (valid)
// TODO: There is a flaw in this scenario. It would be best to expect no invalid GOPs in the
// stream, as the first validation should reset the validation status to ensure consistency.
struct validation_stats expected = {.valid_gops = 2,
.invalid_gops = 2,
.pending_nalus = 4,
.missed_nalus = 2,
.has_signature = 1,
// IS PU -> (signature) 1 pending
// ISPPS PUPPU -> (signature) 3 pending
// ISPPSPIS .U..U.P. -> (valid) 1 pending
// ISPPPPIS ......P. -> (valid) 1 pending
// ISPPIS ....P. -> (valid) 1 pending
// 7 pending
const struct validation_stats expected = {.valid_gops = 3,
.pending_nalus = 7,
.has_signature = 2,
.final_validation = &final_validation};
// ISPPSPISPPPPISPPISP
// IS PMU -> (signature)
// ISPPS NMUNN. -> (invalid)
// PIS MMM.P. -> (valid with missing info)
// ISPPPPIS ......P. -> (valid)
// ISPPIS ....P. -> (valid)
if (settings[_i].auth_level == SV_AUTHENTICITY_LEVEL_FRAME) {
expected.valid_gops_with_missing_info = 1;
expected.invalid_gops = 1;
expected.final_validation->authenticity = SV_AUTH_RESULT_NOT_OK;
}
validate_nalu_list(NULL, list, expected, true);

test_stream_free(list);
Expand Down Expand Up @@ -1540,45 +1504,25 @@ END_TEST
START_TEST(file_export_with_two_useless_seis)
{
test_stream_t *list = generate_delayed_sei_list(settings[_i], true);
// Remove the first three GOPs.
// IPPPPIPPPIPPSP IPPSPIPPSSPISPISP
test_stream_t *scrapped = test_stream_pop(list, 14);
test_stream_free(scrapped);

// IPPSPIPPSSPISPISP
//
// IPPS PPPU -> (signature) -> 3 pending
// IPPSPIPPS NNNUNPPP. -> (invalid) -> 3 pending
// IPPSS NMNN.. -> (invalid) -> 0 pending
// PIS MMMNP. -> (invalid) -> 1 pending
// ISPIS ...P. -> (valid) -> 1 pending
// 8 pending
//
// IPPSPIPPSSPISPISP (future)
//
// IPPS PPPU -> (signature) -> 3 pending
// IPPSPIPPS PPPUPPPPU -> (signature) -> 7 pending
// IPPSPIPPSS ...U.PPPU. -> (valid) -> 3 pending
// IPPSSPIS ...U..P. -> (valid) -> 1 pending
// ISPIS ...P. -> (valid) -> 1 pending
// 15 pending
signed_video_accumulated_validation_t final_validation = {
SV_AUTH_RESULT_NOT_OK, false, 17, 14, 3, SV_PUBKEY_VALIDATION_NOT_FEASIBLE, true, 0, 0};
struct validation_stats expected = {.valid_gops = 1,
.invalid_gops = 3,
.missed_nalus = 4,
.pending_nalus = 8,
.has_signature = 1,
SV_AUTH_RESULT_OK, false, 17, 14, 3, SV_PUBKEY_VALIDATION_NOT_FEASIBLE, true, 0, 0};
const struct validation_stats expected = {.valid_gops = 3,
.pending_nalus = 15,
.has_signature = 2,
.final_validation = &final_validation};
if (settings[_i].auth_level == SV_AUTHENTICITY_LEVEL_FRAME) {
// IPPS PMPPU -> (signature) -> 3 pending, 1 missing
// IPPSPIPPS NMNNUNPPP. -> (invalid) -> 3 pending
// IPPSS NMNN.. -> (invalid) -> 0 pending, 1 missing
// PIS MMM.P. -> (missing) -> 1 pending, 3 missing
// ISPIS ...P. -> (valid) -> 1 pending
// 8 pending
expected.valid_gops_with_missing_info = 1;
expected.invalid_gops = 2;
}

validate_nalu_list(NULL, list, expected, true);

Expand Down
Loading