| // 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_ |