Validate Fragmented Files

Validation of fragmented (fmp4, DASH, HLS) files utilizes the ClaimValidator, but the workflow differs from that described in Validation. Rather than a single call to validate_media(), there are several supporting objects and methods involved in a validation of multiple fragment files.

NOTE: This process applies to a fragmented MP4 file where the media fragments are spread across multiple files. For fmp4 files where all the fragments are in the same file ("flat" fragmented files) the standard Validation procedure should be used.

Call Flow Overview

Here is a sample call flow for validating a fragmented file:

#include "C2PA/C2PAFactory.hpp"

auto cv = c2pa::C2PAFactory::new_claim_validator();

// Validate the initialization segment
auto init_result = cv->start_fragmented_file_validation(
  c2pa::C2PAFactory::new_read_only_file_asset(
    "video/initialization_segment.mp4"));

// Check the result
if (!init_result.is_initialization_segment_valid) {
  // Handle error
}

// Preliminary manifest store report JSON
std::cout << init_result.validation_report << std::endl;

// Validate the fragments
auto fv = init_result.fragment_validator_factory->make_new_fragment_validator();

// The result structure is updated when each fragment is validated
FragmentValidationResult fragmets_result;
for (auto i = 0; i < num_fragments; i++) {
  auto fragment_asset = c2pa::C2PAFactory::new_read_only_file_asset(
    "video/segment" + std::to_string(i) + ".m4s");
  fragments_result = fv->validate_fragment(fragment_asset);
  
  if (!fragments_result.is_fragment_valid) {
    // Handle error
  }
  
  if (fragments_result.validated_fragment_numbers.back() != i) {
    // Handle out-of-order fragments
  }
}

// Final manifest store report JSON
std::cout << fragments_result.validation_report << std::endl;

Order of operations

In order to validate a fragmented file, the following steps must be performed, in order:

  1. Create a ClaimValidator instance.
  2. Call start_fragmented_file_validation() passing in the initialization segment.
  3. The result of the above call will contain a FragmentValidatorFactory object in it. Use that factory to create one or more instances of FragmentValidator object(s).
  4. For each fragment, call the validate_fragment() method of the FragmentValidator.
  5. Each call will update the validation report (JSON string). After your final call to validate_fragment(), the returned validation report will represent the final validation status.

Continuity of Fragments

According to the C2PA specification, "the validator must validate each portion of the content before it is rendered ... [including] validating that the location values for a given Merkle tree start at zero and increments by one for each following chunk."

Checks performed by libc2pa

The FragmentValidator will perform some continuity checking. If the same instance of FragmentValidator is called twice with discontinuous fragments, then it will fail the validation of the second fragment. However, this continuity validation is not performed across separate FragmentValidator instances, and the FragmentValidator also doesn't fail validation if the first fragment presented to it has a non-zero location value.

Fragment validation scenarios

Here are a few use cases where continuity validation will be treated differently:

  1. Validating a fragmented file sequentially from start to finish.
  2. Validating random portions of a fragmented file (presumably) because the user is scrubbing through the media.
  3. Validating multiple fragments of a file in parallel.

In the first case, it makes sense to create a single FragmentValidator instance and feed each fragment, sequentially, to that single instance.

In the second example, the client should create a new FragmentValidator instance whenever a discontinuity is expected. Each new instance will begin monitoring continuity of fragments starting with the first fragment that it receives.

In the third example, it's a good idea to create multiple FragmentValidator instances, one for each fragment being processed. The library will perform no continuity validation because each instance is only provided a single fragment. It's up to the client code to ensure that all fragments are present and in the correct order.

Information provided by libc2pa

In the cases where continuity will be enforced by the client code and not by libc2pa, the client can make use of the validated_fragment_numbers property returned by the FragmentValidator. This is a vector indicating all the fragments that have been validated so far by that specific instance of FragmentValidator.

Starting Fragmented File Validation

Fragmented file validation must always start with the initialization segment. The main C2PA manifest (with all the hash validation info in it) is contained in the initialization segment. It is impossible to validate fragments without first validating the initialization segment.

This can be done with the start_fragmented_file_validation() method of the ClaimValidator object. Similar to how other methods work on the ClaimValidator, there are three forms of this method: buffer, stream, and file.

start_fragmented_file_validation()

This returns a FragmentValidationInitialResult structure containing information about the validation. This structure also contains a FragmentValidatorFactory that can be used to create FragmentValidator instances to validate the file fragments.

  • c2pa::ReadOnlyAssetPtr media - An Asset object containing the raw data of the initialization segment for the file to validate.
auto initial_result = cv->start_fragmented_file_validation(init_segment_asset);

FragmentValidationInitialResult structure

The start_fragmented_file_validation() method returns a FragmentValidationInitialResult structure. This structure has the following form:

struct FragmentValidationInitialResult {
  bool is_initialization_segment_valid;
  std::string validation_report;
  std::vector<std::unique_ptr<C2PAThumbnailInfo>> thumbnails;
  std::unique_ptr<FragmentValidatorFactory> fragment_validator_factory;
};
  • is_initialization_segment_valid - This is a single value indicating the overall validation status. If there were any parts of the Manifest Store marked as INVALID, or if there were any other serious errors encountered during the validation process, then this value will be false.
  • validation_report - This will be populated with a serialized JSON structure with detailed information about the C2PA Manifest Store and the status of its validation.
  • thumbnails - This will be populated with an array of thumbnail info structures; one for each thumbnail assertion found in the C2PA Manifest Store.
  • fragment_validator_factory - This is a factory object that can be used to create one or more instances of the FragmentValidator class. These FragmentValidator instances can then be used to validate the file fragments.

    NOTE: The fragment_validator_factory will be nullptr if is_initialization_segment_valid is false. The library will not allow fragment validation if the initialization segment is invalid.

Validating File Fragments

File fragments can only be validated once the initialization segment has been validated with the start_fragmented_file_validation() method. The result of that call will contain a FragmentValidatorFactory object that can be used to create FragmentValidator instances.

make_new_fragment_validator()

This is a method of the FragmentValidatorFactory class. It is used to create instances of the FragmentValidator class. Each FragmentValidator instance contains all the required information from the C2PA manifest in the initialization segment in order to validate file fragments.

auto fv = initial_result.fragment_validator_factory->make_new_fragment_validator();

validate_fragment()

This is a method of the FileValidator class. It is used to perform validation of a file fragment and it returns a FragmentValidationResult structure containing information about the validation.

  • c2pa::ReadOnlyAssetPtr fragment - An Asset object containing the raw data of the file fragment to validate.

FragmentValidationResult structure

The validate_fragment() method returns a FragmentValidationResult structure. This structure has the following form:

struct FragmentValidationResult {
  bool is_fragment_valid;
  std::string validation_report;
  std::vector<uint32_t> validated_fragment_numbers;
};
  • is_fragment_valid - This is a single value indicating the overall validation status. If there were any parts of the Manifest Store marked as INVALID, or if there were any other serious errors encountered during the validation process, then this value will be false.
  • validation_report - This will be populated with a serialized JSON structure with detailed information about the C2PA Manifest Store and the status of its validation.
  • validated_fragment_numbers - This is an array of numbers indicating the location field of the C2PA manifest in each of the fragments processed by this particular instance of FragmentValidator. This can be used by the client to verify continuity of the fragments in the file.

Partial Fragment Validation

Similar to how partial validation can be performed on "flat" files, it can also be performed on fragmented files. The initialization segment and the fragments can each be passed to the library in small chunks. This can be handy when working with streamed content.

The buffer version of start_fragmented_file_validation() can be called with just the beginning of the initialization segment, and if the ClaimValidator is able to find the C2PA Manifest Store in the provided data, then it will go ahead and perform validation on it. The hash assertion, which relies on a hash of the entire file, is almost guaranteed to fail, but the rest of the validation_report might provide some valuable information about the media.

If no Manifest Store is found, or if only a part of the Manifest Store is found, then the start_fragmented_file_validation() call will throw a common::runtime_error_with_context exception, and the structure returned by the manifest_parser_info() method of the exception may provide enough information to determine the size of the Manifest Store. The client can then use the update_initialization_segment_data() method of the ClaimValidator to add more media data and attempt to revalidate. See libc2pa Exceptions

If update_initialization_segment_data() is being called in a loop in one part of a program, to validate a media file in piecemeal chunks, then the set_progress_update_method() method can be used to notify another part of the program of the progress of the validation.

In a similar fashion, the buffer version of the validate_fragment() method of the FragmentValidator class can be called with just the beginning of a fragment. Then update_fragment_data() can be called repeatedly to add more data and attempt to revalidate the fragment.

Validating Files in a Set

When multiple fragmented files are signed together in a set , it is possible to determine later if those signed files all belong to the same set.

initialization_segments_are_in_set()

This function takes in an array of initialization segments and returns an array of bools, one boolean for each input asset. The returned boolean values will only be true if the corresponding asset validates correctly and if it belongs to a set with the first asset in the array.

  • std::vector<c2pa::ReadOnlyAssetPtr> initialization_segments - The list of assets to validate.
auto result_vector = cv->initialization_segments_are_in_set({
  init_segment_1,
  init_segment_2,
});