blob: d9be3d10cad5cda8194ed52d8036544528de6c99 [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.
//
#ifndef THIRD_PARTY_CREDENTIO_FORMATS_PNG_WRITER_H_
#define THIRD_PARTY_CREDENTIO_FORMATS_PNG_WRITER_H_
#include <vector>
#include "absl/base/nullability.h"
#include "absl/functional/any_invocable.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "async/async_context.h"
#include "formats/byte_range.h"
#include "formats/png/reader.h"
#include "generator/generator.h"
#include "proto/generator.pb.h"
#include "riegeli/bytes/reader.h"
#include "riegeli/bytes/writer.h"
namespace credentio {
// Writes C2PA manifest stores into PNG images.
//
// The manifest store is embedded as a `caBX` chunk immediately before the
// first `IDAT` chunk. If the source already contains a `caBX` chunk, that
// chunk is replaced.
//
// The writer applies no exclusion policy of its own: unless an
// `ExclusionProcessor` is supplied, every byte of the source, other than the
// manifest store chunk itself, is covered by the hard binding hash.
//
// `Write()` does not mutate the writer, so a single instance may serve
// concurrent calls as long as the exclusion processor, if any, is thread-safe.
class PngWriter {
public:
// Information about the C2PA manifest store embedded in the output asset.
struct OutputMetadata {
// Whether the C2PA active manifest embedded in the output asset has a
// timestamp.
bool timestamp_included = false;
};
// A function that determines byte ranges within a given PNG chunk to be
// excluded from the hard binding hash.
//
// It is invoked once per chunk of the source asset, in file order, for every
// chunk except an already existing `caBX` chunk. The reader is positioned
// arbitrarily and may be freely sought by the processor.
//
// The returned ByteRanges are absolute file offsets. They must be sorted by
// offset, must not overlap, and must lie within the chunk, that is within
// [chunk.offset, chunk.offset + chunk.length), which spans the whole chunk
// record including its length, type and CRC fields. Returning ranges that
// violate these rules fails the write with `kInvalidArgument`. Return an
// empty vector to hash the chunk in full.
using ExclusionProcessor =
absl::AnyInvocable<absl::StatusOr<std::vector<ByteRange>>(
riegeli::Reader&, const PngChunk&,
const GenerateManifestStoreRequest&) const>;
struct Options {
// If null, no bytes, other than the manifest store chunk itself, are
// excluded from the hard binding hash.
ExclusionProcessor exclusion_processor = nullptr;
};
// Creates a PNG writer.
//
// The generator must outlive the writer.
explicit PngWriter(const ManifestStoreGenerator* absl_nonnull generator)
: PngWriter(generator, Options()) {}
PngWriter(const ManifestStoreGenerator* absl_nonnull generator,
Options options);
// Generates a C2PA manifest store for `source` and writes a copy of `source`
// with the manifest store embedded to `destination`.
//
// `source` and `destination` must not be backed by the same data; embedding
// in place is not supported. `request` must not include `hard_binding`; the
// hard binding is computed here over the whole asset, minus the manifest
// store chunk and any ranges reported by the exclusion processor.
//
// If `output_metadata` is not null, it is populated before `cb` runs, and it
// must stay alive until then.
//
// Returns (via `cb`):
// - `kInvalidArgument` if `request` carries a `hard_binding`, if the
// source is not a PNG holding an `IDAT` or `caBX` chunk, or if the
// exclusion processor reports unusable ranges.
// - `kAlreadyExists` if the source already holds a `caBX` chunk and
// `request` carries no `opened` action.
// - whatever the generator or the underlying reader and writer report.
void 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);
// Blocking version. Blocks until the write completes.
absl::Status Write(GenerateManifestStoreRequest request,
riegeli::Reader* absl_nonnull source,
riegeli::Writer* absl_nonnull destination,
OutputMetadata* absl_nullable output_metadata);
private:
const ManifestStoreGenerator& generator_;
Options options_;
};
} // namespace credentio
#endif // THIRD_PARTY_CREDENTIO_FORMATS_PNG_WRITER_H_