blob: adb8a6558111a2bdb5aa6e9a2365a2c0fa314f81 [file] [edit]
// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#include "formats/jpeg/writer.h"
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "absl/base/nullability.h"
#include "absl/functional/any_invocable.h"
#include "absl/log/die_if_null.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "absl/synchronization/notification.h"
#include "async/async_context.h"
#include "crypto/algorithms.h"
#include "formats/jpeg/app11_io.h"
#include "formats/jpeg/hasher.h"
#include "formats/jpeg/reader.h"
#include "generator/generator.h"
#include "proto/data_hash_assertion.pb.h"
#include "proto/generator.pb.h"
#include "riegeli/bytes/reader.h"
#include "riegeli/bytes/string_writer.h"
#include "riegeli/bytes/writer.h"
#include "utils/byte_instruction.h"
namespace credentio {
namespace {
void AdjustDataHashExclusions(DataHashAssertion* absl_nonnull assertion,
int64_t c2pa_offset, int64_t c2pa_size) {
for (auto& exclusion : *assertion->mutable_exclusions()) {
if (exclusion.start() < c2pa_offset) {
// Before the C2PA segment, so no adjustment needed.
continue;
}
if (exclusion.start() == c2pa_offset) {
// This is the C2PA segment, just need to fill in the length.
exclusion.set_length(c2pa_size);
continue;
}
// This is after the C2PA segment, so we need to add the manifest size to
// the offset.
exclusion.set_start(exclusion.start() + c2pa_size);
}
}
} // namespace
JpegWriter::JpegWriter(const ManifestStoreGenerator* absl_nonnull generator,
Options options)
: generator_(*ABSL_DIE_IF_NULL(generator)), options_(std::move(options)) {}
absl::Status JpegWriter::Write(
GenerateManifestStoreRequest request, riegeli::Reader* absl_nonnull source,
riegeli::Writer* absl_nonnull destination,
OutputMetadata* absl_nullable output_metadata) const {
absl::Notification done;
absl::Status status;
Write(std::move(request), source, destination, output_metadata,
AsyncContext(), [&](absl::Status s) {
status = std::move(s);
done.Notify();
});
done.WaitForNotification();
return status;
}
void JpegWriter::Write(GenerateManifestStoreRequest request,
riegeli::Reader* absl_nonnull source,
riegeli::Writer* absl_nonnull destination,
OutputMetadata* absl_nullable output_metadata,
AsyncContext context,
absl::AnyInvocable<void(absl::Status) &&> cb) const {
auto byte_instructions = std::make_shared<std::vector<ByteInstruction>>();
WriteInternal(
std::move(request), source, output_metadata, byte_instructions, context,
[resolver = AsyncResolver<void>(context, std::move(cb)), source,
destination, byte_instructions](absl::Status status) mutable {
if (!status.ok()) {
resolver.Return(std::move(status));
return;
}
status = ApplyByteInstructions(source, *byte_instructions, destination);
resolver.Return(std::move(status));
});
}
absl::Status JpegWriter::Write(
GenerateManifestStoreRequest request, riegeli::Writer* absl_nonnull asset,
OutputMetadata* absl_nullable output_metadata) const {
absl::Notification done;
absl::Status status;
Write(std::move(request), asset, output_metadata, AsyncContext(),
[&](absl::Status s) {
status = std::move(s);
done.Notify();
});
done.WaitForNotification();
return status;
}
void JpegWriter::Write(GenerateManifestStoreRequest request,
riegeli::Writer* absl_nonnull asset,
OutputMetadata* absl_nullable output_metadata,
AsyncContext context,
absl::AnyInvocable<void(absl::Status) &&> cb) const {
AsyncResolver<void> resolver(context, std::move(cb));
if (!asset->SupportsRandomAccess()) {
resolver.Return(
absl::InvalidArgumentError("asset does not support random access"));
return;
}
if (!asset->SupportsReadMode()) {
resolver.Return(
absl::InvalidArgumentError("asset does not support read mode"));
return;
}
riegeli::Reader* source = asset->ReadMode(0);
if (source == nullptr) {
resolver.Return(asset->ok()
? absl::InternalError("failed to create source reader")
: asset->status());
return;
}
if (!source->ok()) {
resolver.Return(source->status());
return;
}
auto byte_instructions = std::make_shared<std::vector<ByteInstruction>>();
WriteInternal(std::move(request), source, output_metadata, byte_instructions,
context,
[resolver = std::move(resolver), asset,
byte_instructions](absl::Status status) mutable {
if (!status.ok()) {
resolver.Return(std::move(status));
return;
}
status = ApplyByteInstructions(*byte_instructions, asset);
resolver.Return(std::move(status));
});
}
void JpegWriter::WriteInternal(
GenerateManifestStoreRequest request, riegeli::Reader* absl_nonnull source,
OutputMetadata* absl_nullable output_metadata,
std::shared_ptr<std::vector<ByteInstruction>> byte_instructions,
AsyncContext context, absl::AnyInvocable<void(absl::Status) &&> cb) const {
AsyncResolver<void> resolver(context, std::move(cb));
if (request.has_hard_binding()) {
resolver.Return(absl::InvalidArgumentError(
"request must not include `hard_binding` (binding is computed by "
"JpegWriter)"));
return;
}
auto exclusion_fn = [this, &request](riegeli::Reader& reader,
const JpegSegment& segment) {
return options_.exclusion_processor(reader, segment, request);
};
std::optional<JpegExclusionProcessor> exclusion_processor = std::nullopt;
if (options_.exclusion_processor != nullptr) {
exclusion_processor.emplace(std::move(exclusion_fn));
}
auto digest_result = DigestJpeg(*source, HashAlgorithm::kSha256,
request.options().generate_multi_asset_hash(),
exclusion_processor);
if (!digest_result.ok()) {
resolver.Return(digest_result.status());
return;
}
request.mutable_hard_binding()->Swap(&digest_result->hard_binding);
auto insertion_point = std::move(digest_result->insertion_point);
generator_.GenerateManifestStoreTemplate(
request, context,
[this, request, insertion_point = std::move(insertion_point),
output_metadata, byte_instructions, context,
resolver = std::move(resolver)](
absl::StatusOr<ManifestStoreTemplate> template_response) mutable {
if (!template_response.ok()) {
resolver.Return(template_response.status());
return;
}
// Do things with Generate Template Response
constexpr int64_t kMaxManifestStoreSizeBytes =
100 * 1024 * 1024; // 100 MiB
if (template_response->manifest_store_size() <= 0 ||
template_response->manifest_store_size() >
kMaxManifestStoreSizeBytes) {
resolver.Return(absl::InvalidArgumentError(
absl::StrCat("invalid manifest store template size: ",
template_response->manifest_store_size())));
return;
}
uint64_t embedded_size = CalculateTotalJpegApp11SegmentsSize(
template_response->manifest_store_size());
if (embedded_size > UINT32_MAX) {
resolver.Return(
absl::InternalError("estimated manifest store size is too large "
"to fit in a uint32_t"));
return;
}
FinalizeManifestStoreRequest finalize_request;
finalize_request.mutable_manifest_store_template()->Swap(
&*template_response);
finalize_request.mutable_hard_binding()->MergeFrom(
request.hard_binding());
AdjustDataHashExclusions(
finalize_request.mutable_hard_binding()->mutable_data_hash(),
insertion_point.offset, embedded_size);
if (finalize_request.hard_binding().part_bindings_size() > 0) {
AdjustDataHashExclusions(finalize_request.mutable_hard_binding()
->mutable_part_bindings(0)
->mutable_data_hash(),
insertion_point.offset, embedded_size);
for (int i = 0;
i < finalize_request.hard_binding().part_bindings_size(); ++i) {
auto* location = finalize_request.mutable_hard_binding()
->mutable_part_bindings(i)
->mutable_location();
if (location->byte_offset() == 0) {
location->set_length(location->length() + embedded_size);
} else {
location->set_byte_offset(location->byte_offset() +
embedded_size);
}
}
}
generator_.FinalizeManifestStore(
finalize_request, context,
[insertion_point, output_metadata, byte_instructions,
resolver = std::move(resolver)](
absl::StatusOr<FinalizeManifestStoreResponse>
finalize_response) mutable {
if (!finalize_response.ok()) {
resolver.Return(finalize_response.status());
return;
}
std::string app11_payload;
riegeli::StringWriter app11_writer(&app11_payload);
auto write_status = WriteJpegApp11Payload(
insertion_point.segment_id,
finalize_response->manifest_store(), app11_writer);
if (!write_status.ok()) {
resolver.Return(std::move(write_status));
return;
}
if (!app11_writer.Close()) {
resolver.Return(app11_writer.status());
return;
}
byte_instructions->push_back(ByteInstruction{
.operation = ByteInstruction::Operation::kInsert,
.offset = static_cast<uint64_t>(insertion_point.offset),
.bytes = std::move(app11_payload)});
if (output_metadata != nullptr) {
output_metadata->timestamp_included =
finalize_response->is_timestamped();
}
resolver.Return(absl::OkStatus());
});
});
}
} // namespace credentio