Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ cmake_install.cmake
CMakeOutput.log
CMakeError.log
CMakeLists.txt.user
compile_commands.json

# Doxygen docs
/api-docs/_build
Expand Down
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
cmake_minimum_required(VERSION 3.27)

# This is the current version of this C++ project
project(c2pa-c VERSION 0.23.15)
project(c2pa-c VERSION 0.24.0)
Comment thread
tmathern marked this conversation as resolved.

# Set the version of the c2pa_rs library used
set(C2PA_VERSION "0.86.1")
Expand Down
53 changes: 49 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ If you want to view the documentation in GitHub, see:
- Using [working stores and archvies](docs/working-stores.md)
- Selectively constructing manifests by [filtering actions and ingredients](docs/selective-manifests.md)
- Using the [embeddable API for low-level control over embedding manifests](docs/embeddable-api.md)
- [Frequently-asked questions (FAQs)](docs/faqs.md)
- [Frequently-asked questions (FAQs)](docs/faqs.md)
- [Release notes](docs/release-notes.md)

</div>
Expand Down Expand Up @@ -64,7 +64,7 @@ Building the library holding the C++ SDK requires [GNU make](https://www.gnu.org

Enter this command to build the SDK:

```
```sh
make release
```

Expand Down Expand Up @@ -129,6 +129,51 @@ Build the [unit tests](https://github.com/contentauth/c2pa-cpp/tree/main/tests)
make test
```

The Rust `c2pa_c` library does not need to be sanitizer-instrumented for this to work.

### Troubleshooting

#### Sanitizer test builds fail on macOS

`make test-san` builds the tests with AddressSanitizer/UBSan. On some recent macOS versions the
AddressSanitizer runtime shipped with Xcode's AppleClang can abort at process start with a message like:

```sh
AddressSanitizer: CHECK failed: sanitizer_malloc_mac.inc:189 "((!asan_init_is_running)) != (0)"
```

This is an incompatibility between an older AppleClang sanitizer runtime and the host
malloc. The fix is to build the sanitizer target with a newer LLVM/Clang (for example, Homebrew's `llvm`).

To do so, install (or update) an `llvm` version. For instance, using homebrew::

```sh
brew install llvm
```

Then, build and run the tests with the sanitizers activated. First, configure the build:

```sh
cmake -S . -B build/debug -G Ninja -DCMAKE_BUILD_TYPE=Debug -DENABLE_SANITIZERS=ON \
-DCMAKE_C_COMPILER="$(brew --prefix llvm)/bin/clang" \
-DCMAKE_CXX_COMPILER="$(brew --prefix llvm)/bin/clang++"
```

Note that the llvm compiler from `brew --prefix llvm` must properly resolve for this command to work.

Then run the build scripts:

```sh
cmake --build build/debug
```

And finally run the test executable:

```sh
ctest --test-dir build/debug --output-on-failure
```


### Building API documentation

API documentation generated by Doxygen is automatically built on each PR.
Expand All @@ -141,14 +186,14 @@ To generate API docs locally, these are the main files:

Install Doxygen if needed:

```
```sh
macOS: brew install doxygen
Ubuntu/Debian: sudo apt-get install doxygen
```

To generate docs, enter the command:

```
```sh
./scripts/generate_api_docs.sh
```

Expand Down
2 changes: 1 addition & 1 deletion docs/selective-manifests.md
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,7 @@ for (auto& assertion : manifest["assertions"]) {
| --- | --- | --- |
| **Who controls it** | Caller (any string) | Caller (any string, or from XMP metadata) |
| **Priority for linking** | Primary: checked first | Fallback: used when label is absent/empty |
| **When to use** | JSON-defined manifests where the caller controls the ingredient definition | Programmatic workflows using `read_ingredient_file()` or XMP-based IDs |
| **When to use** | JSON-defined manifests where the caller controls the ingredient definition | Programmatic workflows using `Builder::add_ingredient()` or XMP-based IDs |
| **Survives signing** | SDK may reassign the actual assertion label | Unchanged |
| **Stable across rebuilds** | The caller controls the build-time value; the post-signing label may change | Yes, always the same set value |

Expand Down
42 changes: 6 additions & 36 deletions include/c2pa.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -541,39 +541,6 @@ namespace c2pa
[[deprecated("Use Context::from_json() or Context::from_settings() instead")]]
void C2PA_CPP_API load_settings(const std::string& data, const std::string& format);

/// @brief Read a file and return the manifest JSON.
/// @param source_path The path to the file to read.
/// @param data_dir Optional directory to store binary resources.
/// @return Optional string containing the manifest JSON if a manifest was found.
/// @throws C2paException for errors encountered by the C2PA library.
/// @deprecated Use Reader object instead.
[[deprecated("Use Reader object instead")]]
std::optional<std::string> C2PA_CPP_API read_file(const std::filesystem::path &source_path, const std::optional<std::filesystem::path> data_dir = std::nullopt);

/// @brief Read a file and return an ingredient JSON.
/// @param source_path The path to the file to read.
/// @param data_dir The directory to store binary resources.
/// @return String containing the ingredient JSON.
/// @throws C2paException for errors encountered by the C2PA library.
/// @deprecated Use Reader and Builder.add_ingredient instead.
[[deprecated("Use Reader and Builder.add_ingredient")]]
std::string C2PA_CPP_API read_ingredient_file(const std::filesystem::path &source_path, const std::filesystem::path &data_dir);

/// @brief Add a manifest and sign a file.
/// @param source_path The path to the asset to be signed.
/// @param dest_path The path to write the signed file to.
/// @param manifest The manifest JSON to add to the file.
/// @param signer_info The signer info to use for signing.
/// @param data_dir Optional directory to store binary resources.
/// @throws C2paException for errors encountered by the C2PA library.
/// @deprecated Use Builder.sign instead.
[[deprecated("Use Builder.sign instead")]]
void C2PA_CPP_API sign_file(const std::filesystem::path &source_path,
const std::filesystem::path &dest_path,
const char *manifest,
SignerInfo *signer_info,
const std::optional<std::filesystem::path> data_dir = std::nullopt);

/// @defgroup StreamWrappers Stream wrappers for C2PA C API
/// @brief C++ stream types that adapt stream types to C2paStream.
///
Expand All @@ -600,7 +567,8 @@ namespace c2pa
explicit CppIStream(IStream &istream) {
static_assert(std::is_base_of<std::istream, IStream>::value,
"Stream must be derived from std::istream");
c_stream = c2pa_create_stream(reinterpret_cast<StreamContext *>(&istream), reader, seeker, writer, flusher);
// Upcast to std::istream* before type erasure; the callbacks cast the context back to std::istream*
c_stream = c2pa_create_stream(reinterpret_cast<StreamContext *>(static_cast<std::istream *>(&istream)), reader, seeker, writer, flusher);
if (c_stream == nullptr) {
throw C2paException("Failed to create input stream wrapper: is stream open and valid?");
}
Expand Down Expand Up @@ -661,7 +629,8 @@ namespace c2pa
template <typename OStream>
explicit CppOStream(OStream &ostream) {
static_assert(std::is_base_of<std::ostream, OStream>::value, "Stream must be derived from std::ostream");
c_stream = c2pa_create_stream(reinterpret_cast<StreamContext *>(&ostream), reader, seeker, writer, flusher);
// Upcast to std::ostream* before type erasure; the callbacks cast the context back to std::ostream*
c_stream = c2pa_create_stream(reinterpret_cast<StreamContext *>(static_cast<std::ostream *>(&ostream)), reader, seeker, writer, flusher);
if (c_stream == nullptr) {
throw C2paException("Failed to create output stream wrapper: is stream open and valid?");
}
Expand Down Expand Up @@ -720,7 +689,8 @@ namespace c2pa
template <typename IOStream>
CppIOStream(IOStream &iostream) {
static_assert(std::is_base_of<std::iostream, IOStream>::value, "Stream must be derived from std::iostream");
c_stream = c2pa_create_stream(reinterpret_cast<StreamContext *>(&iostream), reader, seeker, writer, flusher);
// Upcast to std::iostream* before type erasure; the callbacks cast the context back to std::iostream*
c_stream = c2pa_create_stream(reinterpret_cast<StreamContext *>(static_cast<std::iostream *>(&iostream)), reader, seeker, writer, flusher);
if (c_stream == nullptr) {
throw C2paException("Failed to create I/O stream wrapper: is stream open and valid?");
}
Expand Down
5 changes: 3 additions & 2 deletions src/c2pa_builder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ namespace c2pa
init_from_context(context);

// Apply the manifest definition to the Builder.
// Note: c2pa_builder_with_definition always consumes the builder pointer,
// so the original pointer is invalid after this call regardless of success/error.
// Note: c2pa_builder_with_definition consumes the builder pointer on success
// and on operation failure.
C2paBuilder* updated = c2pa_builder_with_definition(builder, manifest_json.c_str());
builder = nullptr;
if (updated == nullptr) {
Expand Down Expand Up @@ -117,6 +117,7 @@ namespace c2pa
}

builder = c2pa_builder_with_archive(base, c_archive.c_stream);
base = nullptr;
if (builder == nullptr) {
throw C2paException();
}
Expand Down
6 changes: 5 additions & 1 deletion src/c2pa_context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,11 @@ namespace c2pa
if (!raw) {
throw C2paException("Signer is not valid");
}
c2pa_context_builder_set_signer(context_builder, raw);
// On error the signer may not have been consumed by the C API,
// surface an error
if (c2pa_context_builder_set_signer(context_builder, raw) != 0) {
throw C2paException();
}
return *this;
}

Expand Down
80 changes: 0 additions & 80 deletions src/c2pa_core.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,84 +63,4 @@ namespace c2pa
}
}

/// Reads a file and returns the manifest json as a C2pa::String.
/// @param source_path the path to the file to read.
/// @param data_dir the directory to store binary resources (optional).
/// @return a std::string containing the manifest json if a manifest was found.
/// @throws a C2pa::C2paException for errors encountered by the C2PA library.
[[deprecated("Use stream APIs instead: Reader to read data, combined with Builder to manage ingredients")]]
std::optional<std::string> read_file(const std::filesystem::path &source_path, const std::optional<std::filesystem::path> data_dir)
{
const char* dir_ptr = nullptr;
std::string dir_str;
if (data_dir.has_value()) {
auto u = data_dir.value().u8string();
dir_str = std::string(u.begin(), u.end());
dir_ptr = dir_str.c_str();
}

auto src_u8 = source_path.u8string();
std::string src_str(src_u8.begin(), src_u8.end());
char *result = c2pa_read_file(src_str.c_str(), dir_ptr);

if (result == nullptr)
{
auto C2paException = c2pa::C2paException();
if (detail::error_indicates_manifest_not_found(C2paException.what()))
{
return std::nullopt;
}
throw c2pa::C2paException();
}
std::string str(result);
c2pa_free(result);
return str;
}

/// Reads a file and returns an ingredient JSON as a C2pa::String.
/// @param source_path the path to the file to read.
/// @param data_dir the directory to store binary resources.
/// @return a std::string containing the ingredient json.
/// @throws a C2pa::C2paException for errors encountered by the C2PA library.
[[deprecated("Use stream APIs instead: add_ingredient on the Builder")]]
std::string read_ingredient_file(const std::filesystem::path &source_path, const std::filesystem::path &data_dir)
{
auto src_u8 = source_path.u8string();
auto dir_u8 = data_dir.u8string();
return detail::c_string_to_string(
c2pa_read_ingredient_file(std::string(src_u8.begin(), src_u8.end()).c_str(),
std::string(dir_u8.begin(), dir_u8.end()).c_str()));
}

/// Adds the manifest and signs a file.
// source_path: path to the asset to be signed
// dest_path: the path to write the signed file to
// manifest: the manifest json to add to the file
// signer_info: the signer info to use for signing
// data_dir: the directory to store binary resources (optional)
// Throws a C2pa::C2paException for errors encountered by the C2PA library
[[deprecated("Use stream APIs instead: sign on Builder")]]
void sign_file(const std::filesystem::path &source_path,
const std::filesystem::path &dest_path,
const char *manifest,
c2pa::SignerInfo *signer_info,
const std::optional<std::filesystem::path> data_dir)
{
auto src_u8 = source_path.u8string();
auto dst_u8 = dest_path.u8string();
std::string src_str(src_u8.begin(), src_u8.end());
std::string dst_str(dst_u8.begin(), dst_u8.end());
std::string dir_str;
if (data_dir.has_value()) {
auto u = data_dir.value().u8string();
dir_str = std::string(u.begin(), u.end());
}

char *result = c2pa_sign_file(src_str.c_str(), dst_str.c_str(), manifest, signer_info, dir_str.c_str());
if (result == nullptr)
{
throw c2pa::C2paException();
}
c2pa_free(result);
}
} // namespace c2pa
Loading
Loading