Skip to content

Commit 03e4815

Browse files
authored
Fixes signing with partial GOPs (#512)
This functionality has not worked properly if BUs were split in parts or if the stream had FH or AUDs. Updates of the linked hash failed. Various tests for this has now been added. Also, a API name change from signed_viedo_set_max_signing_frames to signed_video_set_max_signing_frames. This has no impact on API breaks since it has not been used anywhere. The version is bumped to v2.3.1. Co-authored-by: bjornvolcker <[email protected]>
1 parent b9b04d9 commit 03e4815

File tree

10 files changed

+100
-17
lines changed

10 files changed

+100
-17
lines changed

lib/src/includes/signed_video_sign.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -511,7 +511,7 @@ signed_video_set_hash_algo(signed_video_t *self, const char *name_or_oid);
511511
* @return An appropriate Signed Video Return Code.
512512
*/
513513
SignedVideoReturnCode
514-
signed_viedo_set_max_signing_frames(signed_video_t *self, unsigned max_signing_frames);
514+
signed_video_set_max_signing_frames(signed_video_t *self, unsigned max_signing_frames);
515515

516516
/**
517517
* @brief Sets an attestation report to the Signed Video session

lib/src/sv_auth.c

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -279,8 +279,10 @@ compute_gop_hash(signed_video_t *self, bu_list_item_t *sei)
279279
}
280280

281281
// Stop adding Bitstream Units when exceeding the amount that the SEI has reported
282-
// in the partial GOP if the SEI was triggered by a partial GOP.
283-
if (gop_info->triggered_partial_gop && (num_in_partial_gop >= gop_info->num_sent)) {
282+
// in the partial GOP if the SEI was triggered by a partial GOP. By design, one
283+
// cannot break at a TG, since they are hashed together with an FH.
284+
if (gop_info->triggered_partial_gop && (num_in_partial_gop >= gop_info->num_sent) &&
285+
(item->bu->bu_type != BU_TYPE_TG)) {
284286
break;
285287
}
286288
// Skip TGs since they do not have their own hash.

lib/src/sv_common.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -945,6 +945,10 @@ sv_add_ongoing_hash(signed_video_t *self,
945945
// Hash |reference_hash| together with the |hash| and store in |bu_hash|.
946946
SV_THROW(sv_openssl_hash_data(
947947
self->crypto_handle, gop_info->hash_buddies, hash_size * 2, bu_hash));
948+
if (gop_info->triggered_partial_gop && !self->authentication_started) {
949+
// If the BU triggered a partial GOP the linked hash has to be updated.
950+
SV_THROW(sv_update_linked_hash(self, bu_hash, hash_size));
951+
}
948952
}
949953
#ifdef SIGNED_VIDEO_DEBUG
950954
sv_print_hex_data(bu_hash, hash_size, "Hash of %s: ", bu_type_to_str(prev_bu));

lib/src/sv_internal.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
#define DEFAULT_HASH_SIZE (256 / 8)
5151

5252
#define SV_VERSION_BYTES 3
53-
#define SIGNED_VIDEO_VERSION "v2.3.0"
53+
#define SIGNED_VIDEO_VERSION "v2.3.1"
5454
#define SV_VERSION_MAX_STRLEN 19 // Longest possible string including 'ONVIF' prefix
5555

5656
#define DEFAULT_AUTHENTICITY_LEVEL SV_AUTHENTICITY_LEVEL_FRAME

lib/src/sv_sign.c

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -632,6 +632,8 @@ signed_video_add_nalu_part_for_signing_with_timestamp(signed_video_t *self,
632632
}
633633
bu_info.hashable_data_size = bu_data_size;
634634
}
635+
// Only completed primary slices, unless FH, can trigger actions.
636+
bool is_actionable = bu_info.is_primary_slice && bu_info.is_last_bu_part && !bu_info.is_fh;
635637

636638
status = SV_UNKNOWN_FAILURE;
637639
SV_TRY()
@@ -642,24 +644,23 @@ signed_video_add_nalu_part_for_signing_with_timestamp(signed_video_t *self,
642644
// Note that |recurrence| is counted in frames and not in BUs, hence we only increment the
643645
// counter for primary slices. Frame counting is updated at the end of a frame, hence
644646
// shall be ignored for FH.
645-
if (bu_info.is_primary_slice && bu_info.is_last_bu_part && !bu_info.is_fh) {
647+
if (is_actionable) {
646648
if ((self->frame_count % self->recurrence) == 0) {
647649
self->has_recurrent_data = true;
648650
}
649651
self->frame_count++; // It is ok for this variable to wrap around
650652
}
651653

652654
// Determine if a SEI should be generated.
653-
bool new_gop = (bu_info.is_first_bu_in_gop && bu_info.is_last_bu_part);
654-
// Do not trigger on FH.
655-
new_gop &= !bu_info.is_fh;
655+
// Check for a new GOP.
656+
bool new_gop = bu_info.is_first_bu_in_gop && is_actionable;
656657
// Trigger signing if number of frames exceeds the limit for a partial GOP.
657658
bool trigger_signing = ((self->max_signing_frames > 0) &&
658659
(gop_info->num_frames_in_partial_gop >= self->max_signing_frames));
659660
// Only trigger if this Bitstream Unit is hashable, hence will be added to the hash
660661
// list. Also, trigger on a primary slice. Otherwise two slices belonging to the same
661662
// frame will be part of different SEIs.
662-
trigger_signing &= bu_info.is_hashable && bu_info.is_primary_slice;
663+
trigger_signing &= bu_info.is_hashable && is_actionable;
663664
gop_info->triggered_partial_gop = false;
664665
// Depending on the input Bitstream Unit, different actions are taken. If the input is
665666
// an I-frame there is a transition to a new GOP. That triggers generating a SEI. If
@@ -709,7 +710,7 @@ signed_video_add_nalu_part_for_signing_with_timestamp(signed_video_t *self,
709710
}
710711
SV_THROW(sv_hash_and_add(self, &bu_info));
711712
// Increment frame counter after the incoming Bitstream Unit has been processed.
712-
if (bu_info.is_primary_slice && bu_info.is_last_bu_part) {
713+
if (is_actionable) {
713714
gop_info->num_frames_in_partial_gop++;
714715
}
715716

@@ -1089,7 +1090,7 @@ signed_video_set_hash_algo(signed_video_t *self, const char *name_or_oid)
10891090
}
10901091

10911092
SignedVideoReturnCode
1092-
signed_viedo_set_max_signing_frames(signed_video_t *self, unsigned max_signing_frames)
1093+
signed_video_set_max_signing_frames(signed_video_t *self, unsigned max_signing_frames)
10931094
{
10941095
if (!self) {
10951096
return SV_INVALID_PARAMETER;

meson.build

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
project('signed-video-framework', 'c',
2-
version : '2.3.0',
2+
version : '2.3.1',
33
meson_version : '>= 0.53.0',
44
default_options : [ 'warning_level=2',
55
'werror=true',

tests/check/check_codec_av1.c

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,36 @@ START_TEST(scrap_first_gop_mixed_with_fh_td_and_obu_metdata)
159159
}
160160
END_TEST
161161

162+
START_TEST(partial_gop_with_fh_in_parts)
163+
{
164+
// Device side
165+
struct sv_setting setting = settings_av1[_i];
166+
const unsigned max_signing_frames = 4; // Trigger signing after reaching 4 frames.
167+
setting.max_signing_frames = max_signing_frames;
168+
test_stream_t *list = create_signed_stream_splitted_bu(
169+
"|It|Pt|Pt|Pt|Pt|It|Pt|It|Pt|Pt|Pt|Pt|Pt|Pt|Pt|Pt|Pt|It|Pt", setting);
170+
test_stream_check_types(list, "|It|Pt|Pt|Pt|Pt|SIt|SPt|It|SPt|Pt|Pt|Pt|SPt|Pt|Pt|Pt|SPt|It|SPt");
171+
172+
// |It|Pt|Pt|Pt|Pt|SIt|SPt|It|SPt|Pt|Pt|Pt|SPt|Pt|Pt|Pt|SPt|It|SPt
173+
//
174+
// |It|Pt|Pt|Pt|Pt|S _.._.._.._.._PP_. (valid, 2 pending)
175+
// Pt|SIt|S .._.PP_. (v, 2 p)
176+
// It|SPt|It|S .._..._PP_. (v, 2 p)
177+
// It|SPt|Pt|Pt|Pt|S .._..._.._.._PP_. (v, 2 p)
178+
// Pt|SPt|Pt|Pt|Pt|S .._..._.._.._PP_. (v, 2 p)
179+
// Pt|SPt|It|S .._..._PP_. (v, 2 p)
180+
// 12 pend
181+
// It|SPt PP_.PP (v, 6 p)
182+
signed_video_accumulated_validation_t final_validation = {
183+
SV_AUTH_RESULT_OK, false, 63, 57, 6, SV_PUBKEY_VALIDATION_NOT_FEASIBLE, true, 0, 0};
184+
const struct validation_stats expected = {
185+
.valid_gops = 6, .invalid_gops = 0, .pending_bu = 12, .final_validation = &final_validation};
186+
validate_stream(NULL, list, expected, true);
187+
188+
test_stream_free(list);
189+
}
190+
END_TEST
191+
162192
static Suite *
163193
signed_video_suite(void)
164194
{
@@ -178,6 +208,7 @@ signed_video_suite(void)
178208
tcase_add_loop_test(tc, signed_stream_in_parts, s, e);
179209
tcase_add_loop_test(tc, has_fh_without_tg, s, e);
180210
tcase_add_loop_test(tc, scrap_first_gop_mixed_with_fh_td_and_obu_metdata, s, e);
211+
tcase_add_loop_test(tc, partial_gop_with_fh_in_parts, s, e);
181212

182213
// Add test case to suit
183214
suite_add_tcase(suite, tc);

tests/check/check_signed_video_auth.c

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2180,6 +2180,35 @@ START_TEST(sign_multislice_stream_partial_gops)
21802180
}
21812181
END_TEST
21822182

2183+
START_TEST(sign_partial_gops_with_bu_in_parts)
2184+
{
2185+
// Device side
2186+
struct sv_setting setting = settings[_i];
2187+
const unsigned max_signing_frames = 4; // Trigger signing after reaching 4 frames.
2188+
setting.max_signing_frames = max_signing_frames;
2189+
test_stream_t *list = create_signed_stream_splitted_bu("IPPPPPIPPIPPPPPPPPPIP", setting);
2190+
test_stream_check_types(list, "IPPPPSPISPPISPPPPSPPPPSPISP");
2191+
2192+
// IPPPPSPISPPISPPPPSPPPPSPISP
2193+
//
2194+
// IPPPPS ....P. (valid, 1 pending)
2195+
// PSPIS ...P. (valid, 1 pending)
2196+
// ISPPIS ....P. (valid, 1 pending)
2197+
// ISPPPPS .....P. (valid, 1 pending)
2198+
// PSPPPPS .....P. (valid, 1 pending)
2199+
// PSPIS ...P. (valid, 1 pending)
2200+
// 6 pending
2201+
// ISP P.P (valid, 3 pending)
2202+
signed_video_accumulated_validation_t final_validation = {
2203+
SV_AUTH_RESULT_OK, false, 27, 24, 3, SV_PUBKEY_VALIDATION_NOT_FEASIBLE, true, 0, 0};
2204+
const struct validation_stats expected = {
2205+
.valid_gops = 6, .pending_bu = 6, .final_validation = &final_validation};
2206+
validate_stream(NULL, list, expected, true);
2207+
2208+
test_stream_free(list);
2209+
}
2210+
END_TEST
2211+
21832212
START_TEST(all_seis_arrive_late_partial_gops)
21842213
{
21852214
// Device side
@@ -3121,6 +3150,7 @@ signed_video_suite(void)
31213150
// Signed partial GOPs
31223151
tcase_add_loop_test(tc, sign_partial_gops, s, e);
31233152
tcase_add_loop_test(tc, sign_multislice_stream_partial_gops, s, e);
3153+
tcase_add_loop_test(tc, sign_partial_gops_with_bu_in_parts, s, e);
31243154
tcase_add_loop_test(tc, all_seis_arrive_late_partial_gops, s, e);
31253155
tcase_add_loop_test(tc, file_export_and_scrubbing_partial_gops, s, e);
31263156
tcase_add_loop_test(tc, modify_one_p_frame_partial_gops, s, e);

tests/check/check_signed_video_sign.c

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -220,9 +220,9 @@ START_TEST(api_inputs)
220220
ck_assert_int_eq(sv_rc, SV_OK);
221221

222222
// Check setting max signing frames
223-
sv_rc = signed_viedo_set_max_signing_frames(NULL, 1);
223+
sv_rc = signed_video_set_max_signing_frames(NULL, 1);
224224
ck_assert_int_eq(sv_rc, SV_INVALID_PARAMETER);
225-
sv_rc = signed_viedo_set_max_signing_frames(sv, 1);
225+
sv_rc = signed_video_set_max_signing_frames(sv, 1);
226226
ck_assert_int_eq(sv_rc, SV_OK);
227227

228228
// Setting validation level.
@@ -942,6 +942,20 @@ START_TEST(signing_mulitslice_stream_partial_gops)
942942
}
943943
END_TEST
944944

945+
START_TEST(signing_partial_gops_with_bu_in_parts)
946+
{
947+
// Device side
948+
struct sv_setting setting = settings[_i];
949+
const unsigned max_signing_frames = 4; // Trigger signing after reaching 4 frames.
950+
setting.max_signing_frames = max_signing_frames;
951+
test_stream_t *list = create_signed_stream_splitted_bu("IPPPPPIPPIPPPPPPPPPIP", setting);
952+
test_stream_check_types(list, "IPPPPSPISPPISPPPPSPPPPSPISP");
953+
verify_seis(list, setting);
954+
955+
test_stream_free(list);
956+
}
957+
END_TEST
958+
945959
static Suite *
946960
signed_video_suite(void)
947961
{
@@ -976,6 +990,7 @@ signed_video_suite(void)
976990
tcase_add_loop_test(tc, signing_multiple_gops, s, e);
977991
tcase_add_loop_test(tc, signing_partial_gops, s, e);
978992
tcase_add_loop_test(tc, signing_mulitslice_stream_partial_gops, s, e);
993+
tcase_add_loop_test(tc, signing_partial_gops_with_bu_in_parts, s, e);
979994

980995
// Add test case to suit
981996
suite_add_tcase(suite, tc);

tests/check/test_helpers.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -404,8 +404,8 @@ create_signed_stream_with_sv(signed_video_t *sv,
404404
if (!(!item->prev && sv->using_golden_sei)) {
405405
ck_assert(!signed_video_is_golden_sei(sv, item->data, item->data_size));
406406
}
407-
// Only split Bitstream Units that are not generated SEIs.
408-
if (split_bu && pulled_seis == 0) {
407+
// Only split Bitstream Units that are not generated SEIs and large enough to be split.
408+
if (split_bu && pulled_seis == 0 && item->data_size > 2) {
409409
// Split the Bitstream Unit into 2 parts, where the last part inlcudes the ID and the stop
410410
// bit.
411411
rc = signed_video_add_nalu_part_for_signing_with_timestamp(
@@ -519,7 +519,7 @@ get_initialized_signed_video(struct sv_setting settings, bool new_private_key)
519519
ck_assert_int_eq(signed_video_set_product_info(sv, HW_ID, FW_VER, SER_NO, MANUFACT, ADDR), SV_OK);
520520
ck_assert_int_eq(signed_video_set_authenticity_level(sv, settings.auth_level), SV_OK);
521521
ck_assert_int_eq(signed_video_set_max_sei_payload_size(sv, settings.max_sei_payload_size), SV_OK);
522-
ck_assert_int_eq(signed_viedo_set_max_signing_frames(sv, settings.max_signing_frames), SV_OK);
522+
ck_assert_int_eq(signed_video_set_max_signing_frames(sv, settings.max_signing_frames), SV_OK);
523523
ck_assert_int_eq(signed_video_set_signing_frequency(sv, settings.signing_frequency), SV_OK);
524524
ck_assert_int_eq(signed_video_set_hash_algo(sv, settings.hash_algo_name), SV_OK);
525525
if (settings.codec != SV_CODEC_AV1) {

0 commit comments

Comments
 (0)