diff --git a/include/c2pa.hpp b/include/c2pa.hpp index 997567e7..bf335106 100644 --- a/include/c2pa.hpp +++ b/include/c2pa.hpp @@ -46,6 +46,7 @@ namespace c2pa { + // TODO-TMN: Remove this and do full naming using namespace std; typedef C2paSignerInfo SignerInfo; @@ -77,17 +78,17 @@ namespace c2pa /// @details Implement this interface to make your own context class C2PA_CPP_API IContextProvider { public: - virtual ~IContextProvider() = default; - + virtual ~IContextProvider() noexcept = default; + /// @brief Get the underlying C2PA context pointer for FFI operations. /// @return Pointer to C2paContext, or nullptr if not available. /// @note Provider retains ownership; pointer valid for provider's lifetime. [[nodiscard]] virtual C2paContext* c_context() const = 0; - + /// @brief Check if this provider has a valid context. /// @return true if context is available, false otherwise. [[nodiscard]] virtual bool has_context() const noexcept = 0; - + protected: IContextProvider() = default; IContextProvider(const IContextProvider&) = default; @@ -104,41 +105,41 @@ namespace c2pa public: /// @brief Create default settings. Settings(); - + /// @brief Create settings from a configuration string. /// @param data Configuration data in JSON or TOML format. /// @param format Format of the data ("json" or "toml"). /// @throws C2paException if parsing fails. Settings(const string& data, const string& format); - + // Move semantics Settings(Settings&&) noexcept; Settings& operator=(Settings&&) noexcept; - + // Non-copyable Settings(const Settings&) = delete; Settings& operator=(const Settings&) = delete; - - ~Settings(); - + + ~Settings() noexcept; + /// @brief Set a single configuration value by path. /// @param path Dot-separated path to the setting (e.g., "verify.verify_after_sign"). /// @param json_value JSON-encoded value to set. /// @return Reference to this Settings for method chaining. /// @throws C2paException if the path or value is invalid. Settings& set(const string& path, const string& json_value); - + /// @brief Merge configuration from a string. /// @param data Configuration data in JSON or TOML format. /// @param format Format of the data ("json" or "toml"). /// @return Reference to this Settings for method chaining. /// @throws C2paException if parsing fails. Settings& update(const string& data, const string& format); - + /// @brief Get the raw C FFI settings pointer. /// @return Pointer to C2paSettings, or nullptr if not initialized. [[nodiscard]] C2paSettings* c_settings() const noexcept; - + private: C2paSettings* settings_; }; @@ -153,79 +154,78 @@ namespace c2pa class C2PA_CPP_API ContextBuilder { public: ContextBuilder(); - ~ContextBuilder(); - + ~ContextBuilder() noexcept; + // Move semantics ContextBuilder(ContextBuilder&&) noexcept; ContextBuilder& operator=(ContextBuilder&&) noexcept; - + // Non-copyable ContextBuilder(const ContextBuilder&) = delete; ContextBuilder& operator=(const ContextBuilder&) = delete; - + /// @brief Configure with Settings object. /// @param settings Settings to use (will be copied). /// @return Reference to this ContextBuilder for method chaining. ContextBuilder& with_settings(const Settings& settings); - + /// @brief Configure with JSON string. /// @param json JSON configuration string. /// @return Reference to this ContextBuilder for method chaining. /// @throws C2paException if JSON is invalid. ContextBuilder& with_json(const string& json); - + /// @brief Configure with TOML string. /// @param toml TOML configuration string. /// @return Reference to this ContextBuilder for method chaining. /// @throws C2paException if TOML is invalid. ContextBuilder& with_toml(const string& toml); - + /// @brief Build the immutable Context. /// @return Shared pointer to the new Context. /// @throws C2paException if context creation fails. /// @note This consumes the builder. After calling create_context(), the builder is in a moved-from state. - [[nodiscard]] ContextProviderPtr create_context(); - + [[nodiscard]] ContextProviderPtr create_context(); // TODO-TMN: Make consuming? + private: - C2paContextBuilder* context_builder_; + C2paContextBuilder* context_builder; }; /// @brief Create a Context with default settings. /// @return Shared pointer to the new Context. /// @throws C2paException if context creation fails. [[nodiscard]] static ContextProviderPtr create(); - + /// @brief Create a Context from JSON configuration. /// @param json JSON configuration string. /// @return Shared pointer to the new Context. /// @throws C2paException if JSON is invalid or context creation fails. [[nodiscard]] static ContextProviderPtr from_json(const string& json); - + /// @brief Create a Context from TOML configuration. /// @param toml TOML configuration string. /// @return Shared pointer to the new Context. /// @throws C2paException if TOML is invalid or context creation fails. [[nodiscard]] static ContextProviderPtr from_toml(const string& toml); - + // Non-copyable, non-moveable (managed via shared_ptr) Context(const Context&) = delete; Context& operator=(const Context&) = delete; Context(Context&&) = delete; Context& operator=(Context&&) = delete; - - ~Context() override; - + + ~Context() noexcept override; + // IContextProvider implementation [[nodiscard]] C2paContext* c_context() const override; [[nodiscard]] bool has_context() const noexcept override; - + /// @brief Internal constructor (use static factory methods instead). - /// @note This is public to allow std::make_shared but should not be called directly. explicit Context(C2paContext* ctx); - + private: C2paContext* context_; - + friend class Builder; }; @@ -272,12 +272,12 @@ namespace c2pa class C2PA_CPP_API CppIStream : public C2paStream { public: - C2paStream *c_stream; + C2paStream *c_stream; //TODO-TMN: Review this to encapsulate + grant specific access on need basis + look into smart pointer instead template explicit CppIStream(IStream &istream) { static_assert(std::is_base_of::value, "Stream must be derived from std::istream"); - c_stream = c2pa_create_stream(reinterpret_cast(&istream), reader, seeker, writer, flusher); + c_stream = c2pa_create_stream(reinterpret_cast(&istream), reader, seeker, writer, flusher); } CppIStream(const CppIStream &) = delete; @@ -301,7 +301,7 @@ namespace c2pa class C2PA_CPP_API CppOStream : public C2paStream { public: - C2paStream *c_stream; + C2paStream *c_stream; //TODO-TMN: Review this to encapsulate + grant specific access on need basis + look into smart pointer instead template explicit CppOStream(OStream &ostream) { static_assert(std::is_base_of::value, "Stream must be derived from std::ostream"); @@ -327,7 +327,7 @@ namespace c2pa class C2PA_CPP_API CppIOStream : public C2paStream { public: - C2paStream *c_stream; + C2paStream *c_stream; //TODO-TMN: Review this to encapsulate + grant specific access on need basis + look into smart pointer instead template CppIOStream(IOStream &iostream) { static_assert(std::is_base_of::value, "Stream must be derived from std::iostream"); @@ -355,40 +355,36 @@ namespace c2pa private: C2paReader *c2pa_reader; CppIStream *cpp_stream = nullptr; - ContextProviderPtr context_; // Keeps context alive + ContextProviderPtr context_; // Keeps context alive - TODO-TMN: Rename public: - // ===== Context-based constructors ===== - /// @brief Create a Reader from a context and stream. /// @param context Context provider to use for this reader. /// @param format The mime format of the stream. /// @param stream The input stream to read from. /// @throws C2pa::C2paException for errors encountered by the C2PA library. Reader(ContextProviderPtr context, const std::string &format, std::istream &stream); - + /// @brief Create a Reader from a context and file path. /// @param context Context provider to use for this reader. /// @param source_path The path to the file to read. /// @throws C2pa::C2paException for errors encountered by the C2PA library. Reader(ContextProviderPtr context, const std::filesystem::path &source_path); - - // ===== Legacy constructors (DEPRECATED) ===== - + /// @brief Create a Reader from a stream (uses global settings). /// @details The validation_status field in the json contains validation results. /// @param format The mime format of the stream. /// @param stream The input stream to read from. /// @throws C2pa::C2paException for errors encountered by the C2PA library. /// @deprecated Use Reader(ContextProviderPtr, format, stream) instead. - [[deprecated("Use Reader(ContextProviderPtr, format, stream) for better thread safety")]] + [[deprecated("Use Reader(ContextProviderPtr, format, stream) instead")]] Reader(const std::string &format, std::istream &stream); /// @brief Create a Reader from a file path (uses global settings). /// @param source_path The path to the file to read. /// @throws C2pa::C2paException for errors encountered by the C2PA library. /// @deprecated Use Reader(ContextProviderPtr, source_path) instead. - [[deprecated("Use Reader(ContextProviderPtr, source_path) for better thread safety")]] + [[deprecated("Use Reader(ContextProviderPtr, source_path) instead")]] Reader(const std::filesystem::path &source_path); // Non-copyable @@ -403,7 +399,7 @@ namespace c2pa } Reader& operator=(Reader&& other) noexcept { if (this != &other) { - c2pa_reader_free(c2pa_reader); + c2pa_free(c2pa_reader); delete cpp_stream; c2pa_reader = other.c2pa_reader; cpp_stream = other.cpp_stream; @@ -415,9 +411,9 @@ namespace c2pa } ~Reader(); - + /// @brief Get the context associated with this Reader. - /// @return Shared pointer to the context, or nullptr if using legacy API. + /// @return Shared pointer to the context, or nullptr if using legacy/context-free API. [[nodiscard]] inline ContextProviderPtr context() const noexcept { return context_; } @@ -504,7 +500,7 @@ namespace c2pa } Signer& operator=(Signer&& other) noexcept { if (this != &other) { - c2pa_signer_free(signer); + c2pa_free(signer); signer = other.signer; other.signer = nullptr; } @@ -527,11 +523,9 @@ namespace c2pa { private: C2paBuilder *builder; - ContextProviderPtr context_; // Keeps context alive + ContextProviderPtr context_; // Keeps context alive: TODO-TMN: Rename public: - // ===== Context-based constructors ===== - /// @brief Create a Builder from a context with an empty manifest. /// @param context Context provider to use for this builder. /// @throws C2pa::C2paException for errors encountered by the C2PA library. @@ -543,13 +537,11 @@ namespace c2pa /// @throws C2pa::C2paException for errors encountered by the C2PA library. Builder(ContextProviderPtr context, const std::string &manifest_json); - // ===== Legacy constructor (DEPRECATED) ===== - /// @brief Create a Builder from a manifest JSON string (uses global settings). /// @param manifest_json The manifest JSON string. /// @throws C2pa::C2paException for errors encountered by the C2PA library. /// @deprecated Use Builder(ContextProviderPtr, manifest_json) instead. - [[deprecated("Use Builder(ContextProviderPtr, manifest_json) for better thread safety")]] + [[deprecated("Use Builder(ContextProviderPtr, manifest_json) instead")]] Builder(const std::string &manifest_json); // Non-copyable @@ -562,7 +554,7 @@ namespace c2pa } Builder& operator=(Builder&& other) noexcept { if (this != &other) { - c2pa_builder_free(builder); + c2pa_free(builder); builder = other.builder; context_ = std::move(other.context_); other.builder = nullptr; @@ -573,7 +565,7 @@ namespace c2pa ~Builder(); /// @brief Get the context associated with this Builder. - /// @return Shared pointer to the context, or nullptr if using legacy API. + /// @return Shared pointer to the context, or nullptr if using legacy/context-free API. [[nodiscard]] inline ContextProviderPtr context() const noexcept { return context_; } diff --git a/src/c2pa.cpp b/src/c2pa.cpp index b46a2ece..6688317d 100644 --- a/src/c2pa.cpp +++ b/src/c2pa.cpp @@ -95,7 +95,7 @@ namespace c2pa { auto result = c2pa_error(); message = string(result); - c2pa_string_free(result); + c2pa_free(result); } C2paException::C2paException(string what) : message(std::move(what)) @@ -140,7 +140,7 @@ namespace c2pa return *this; } - Settings::~Settings() { + Settings::~Settings() noexcept { if (settings_) { c2pa_free(settings_); } @@ -172,7 +172,7 @@ namespace c2pa } } - Context::~Context() { + Context::~Context() noexcept { if (context_) { c2pa_free(context_); } @@ -204,35 +204,35 @@ namespace c2pa // ===== Context::Builder Implementation ===== - Context::ContextBuilder::ContextBuilder() : context_builder_(c2pa_context_builder_new()) { - if (!context_builder_) { + Context::ContextBuilder::ContextBuilder() : context_builder(c2pa_context_builder_new()) { + if (!context_builder) { throw C2paException("Failed to create context builder"); } } - Context::ContextBuilder::ContextBuilder(ContextBuilder&& other) noexcept : context_builder_(other.context_builder_) { - other.context_builder_ = nullptr; + Context::ContextBuilder::ContextBuilder(ContextBuilder&& other) noexcept : context_builder(other.context_builder) { + other.context_builder = nullptr; } Context::ContextBuilder& Context::ContextBuilder::operator=(ContextBuilder&& other) noexcept { if (this != &other) { - if (context_builder_) { - c2pa_free(context_builder_); + if (context_builder) { + c2pa_free(context_builder); } - context_builder_ = other.context_builder_; - other.context_builder_ = nullptr; + context_builder = other.context_builder; + other.context_builder = nullptr; } return *this; } - Context::ContextBuilder::~ContextBuilder() { - if (context_builder_) { - c2pa_free(context_builder_); + Context::ContextBuilder::~ContextBuilder() noexcept { + if (context_builder) { + c2pa_free(context_builder); } } Context::ContextBuilder& Context::ContextBuilder::with_settings(const Settings& settings) { - if (c2pa_context_builder_set_settings(context_builder_, settings.c_settings()) != 0) { + if (c2pa_context_builder_set_settings(context_builder, settings.c_settings()) != 0) { throw C2paException(); } return *this; @@ -249,14 +249,14 @@ namespace c2pa } ContextProviderPtr Context::ContextBuilder::create_context() { - if (!context_builder_) { + if (!context_builder) { throw C2paException("Builder already consumed"); } - C2paContext* ctx = c2pa_context_builder_build(context_builder_); - context_builder_ = nullptr; // Builder is consumed + C2paContext* ctx = c2pa_context_builder_build(context_builder); if (!ctx) { throw C2paException("Failed to build context"); } + context_builder = nullptr; // Builder is consumed return std::make_shared(ctx); } @@ -265,7 +265,7 @@ namespace c2pa { auto result = c2pa_version(); std::string str(result); - c2pa_string_free(result); + c2pa_free(result); return str; } @@ -296,6 +296,7 @@ namespace c2pa /// @param data_dir the directory to store binary resources (optional). /// @return a 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 combined with Builder to manage ingredients")]] optional read_file(const std::filesystem::path &source_path, const optional data_dir) { const char* dir_ptr = nullptr; @@ -317,7 +318,7 @@ namespace c2pa throw c2pa::C2paException(); } std::string str(result); - c2pa_string_free(result); + c2pa_free(result); return str; } @@ -326,6 +327,7 @@ namespace c2pa /// @param data_dir the directory to store binary resources. /// @return a 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")]] string read_ingredient_file(const std::filesystem::path &source_path, const std::filesystem::path &data_dir) { char *result = c2pa_read_ingredient_file(path_to_string(source_path).c_str(), path_to_string(data_dir).c_str()); @@ -334,7 +336,7 @@ namespace c2pa throw c2pa::C2paException(); } std::string str(result); - c2pa_string_free(result); + c2pa_free(result); return str; } @@ -345,6 +347,7 @@ namespace c2pa // 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, @@ -360,7 +363,7 @@ namespace c2pa throw c2pa::C2paException(); } // Result contains JSON manifest on success - c2pa_string_free(result); + c2pa_free(result); } /// IStream Class wrapper for C2paStream. @@ -697,7 +700,7 @@ namespace c2pa C2paReader* updated = c2pa_reader_with_stream(c2pa_reader, format.c_str(), cpp_stream->c_stream); if (updated == nullptr) { delete cpp_stream; - c2pa_reader_free(c2pa_reader); + c2pa_free(c2pa_reader); throw C2paException("Failed to configure reader with stream"); } c2pa_reader = updated; @@ -717,7 +720,7 @@ namespace c2pa std::ifstream file_stream(source_path, std::ios::binary); if (!file_stream.is_open()) { - c2pa_reader_free(c2pa_reader); + c2pa_free(c2pa_reader); throw std::system_error(errno, std::system_category(), "Failed to open file: " + source_path.string()); } @@ -730,7 +733,7 @@ namespace c2pa C2paReader* updated = c2pa_reader_with_stream(c2pa_reader, extension.c_str(), cpp_stream->c_stream); if (updated == nullptr) { delete cpp_stream; - c2pa_reader_free(c2pa_reader); + c2pa_free(c2pa_reader); throw C2paException("Failed to configure reader with stream"); } c2pa_reader = updated; @@ -778,7 +781,7 @@ namespace c2pa Reader::~Reader() { - c2pa_reader_free(c2pa_reader); + c2pa_free(c2pa_reader); if (cpp_stream != nullptr) { delete cpp_stream; @@ -793,7 +796,7 @@ namespace c2pa throw C2paException(); } std::string str(result); - c2pa_string_free(result); + c2pa_free(result); return str; } @@ -807,7 +810,7 @@ namespace c2pa // // TODO: Revisit after determining how we want c2pa-rs to handle // strings that shouldn't be modified by our bindings. - c2pa_string_free(const_cast(url)); + c2pa_free(const_cast(url)); return url_str; } @@ -853,7 +856,7 @@ namespace c2pa Signer::~Signer() { - c2pa_signer_free(signer); + c2pa_free(signer); } /// @brief Get the C2paSigner @@ -898,7 +901,7 @@ namespace c2pa // Apply the manifest definition C2paBuilder* updated = c2pa_builder_with_definition(builder, manifest_json.c_str()); if (updated == nullptr) { - c2pa_builder_free(builder); + c2pa_free(builder); throw C2paException("Failed to set builder definition"); } builder = updated; @@ -932,7 +935,7 @@ namespace c2pa Builder::~Builder() { - c2pa_builder_free(builder); + c2pa_free(builder); } C2paBuilder *Builder::c2pa_builder() const noexcept @@ -1041,7 +1044,7 @@ namespace c2pa } auto manifest_bytes = std::vector(c2pa_manifest_bytes, c2pa_manifest_bytes + result); - c2pa_manifest_bytes_free(c2pa_manifest_bytes); + c2pa_free(c2pa_manifest_bytes); return manifest_bytes; } @@ -1057,7 +1060,7 @@ namespace c2pa } auto manifest_bytes = std::vector(c2pa_manifest_bytes, c2pa_manifest_bytes + result); - c2pa_manifest_bytes_free(c2pa_manifest_bytes); + c2pa_free(c2pa_manifest_bytes); return manifest_bytes; } @@ -1158,7 +1161,7 @@ namespace c2pa } auto data = std::vector(c2pa_manifest_bytes, c2pa_manifest_bytes + result); - c2pa_manifest_bytes_free(c2pa_manifest_bytes); + c2pa_free(c2pa_manifest_bytes); return data; } @@ -1181,7 +1184,7 @@ namespace c2pa } auto data = std::vector(c2pa_manifest_bytes, c2pa_manifest_bytes + result); - c2pa_manifest_bytes_free(c2pa_manifest_bytes); + c2pa_free(c2pa_manifest_bytes); return data; } @@ -1195,7 +1198,7 @@ namespace c2pa } auto formatted_data = std::vector(c2pa_manifest_bytes, c2pa_manifest_bytes + result); - c2pa_manifest_bytes_free(c2pa_manifest_bytes); + c2pa_free(c2pa_manifest_bytes); return formatted_data; } diff --git a/tests/test.c b/tests/test.c index 8e9a0d1c..3c4aad98 100644 --- a/tests/test.c +++ b/tests/test.c @@ -61,7 +61,7 @@ int main(void) free(uri); assert_int("c2pa_reader_resource", res); - c2pa_reader_free(reader); + c2pa_free(reader); char *certs = load_file("tests/fixtures/es256_certs.pem"); char *private_key = load_file("tests/fixtures/es256_private.key"); @@ -117,15 +117,15 @@ int main(void) const unsigned char *formatted_bytes = NULL; int64_t result3 = c2pa_format_embeddable("image/jpeg", manifest_bytes, result2, (const unsigned char **)&formatted_bytes); assert_int("c2pa_format_embeddable", result3); - c2pa_manifest_bytes_free(manifest_bytes); - c2pa_manifest_bytes_free(formatted_bytes); + c2pa_free(manifest_bytes); + c2pa_free(formatted_bytes); close_file_stream(source); close_file_stream(dest); - c2pa_builder_free(builder2); - c2pa_builder_free(builder); - c2pa_signer_free(signer); + c2pa_free(builder2); + c2pa_free(builder); + c2pa_free(signer); free(certs); free(private_key); diff --git a/tests/unit_test.h b/tests/unit_test.h index 77c82da4..e29fece1 100644 --- a/tests/unit_test.h +++ b/tests/unit_test.h @@ -64,7 +64,7 @@ int save_file(const char* filename, const unsigned char* data, size_t len) { void passed(const char *what, char *c2pa_str) { printf("PASSED: %s\n", what); - c2pa_string_free(c2pa_str); + c2pa_free(c2pa_str); } // assert that c2pa_str contains substr or exit @@ -73,11 +73,11 @@ void assert_contains(const char *what, char *c2pa_str, const char *substr) if (strstr(c2pa_str, substr) == NULL) { fprintf(stderr, "FAILED %s: %s not found in %s\n", what, c2pa_str, substr); - c2pa_string_free(c2pa_str); + c2pa_free(c2pa_str); exit(1); } printf("PASSED: %s\n", what); - c2pa_string_free(c2pa_str); + c2pa_free(c2pa_str); } // assert that c2pa is not NULL or exit @@ -87,7 +87,7 @@ void assert_not_null(const char *what, void *val) { char *err = c2pa_error(); fprintf(stderr, "FAILED %s: %s\n", what, err); - c2pa_string_free(err); + c2pa_free(err); exit(1); } printf("PASSED: %s\n", what); @@ -97,7 +97,7 @@ void assert_not_null(const char *what, void *val) void assert_str_not_null(const char *what, char *c2pa_str) { assert_not_null(what, c2pa_str); - c2pa_string_free(c2pa_str); + c2pa_free(c2pa_str); } // assert that c2pa is not NULL or exit @@ -111,7 +111,7 @@ void assert_null(const char *what, char *c2pa_str, const char *err_str) if (strstr(err, err_str) == NULL) { fprintf(stderr, "FAILED %s: \"%s\" not found in \"%s\"\n", what, err_str, err); - c2pa_string_free(err); + c2pa_free(err); exit(1); } printf("PASSED: %s: \n", what); @@ -119,7 +119,7 @@ void assert_null(const char *what, char *c2pa_str, const char *err_str) else { fprintf(stderr, "FAILED %s: expected NULL\n", what); - c2pa_string_free(c2pa_str); + c2pa_free(c2pa_str); exit(1); } } @@ -130,7 +130,7 @@ void assert_int(const char *what, int result) { char *err = c2pa_error(); fprintf(stderr, "FAILED %s: %s\n", what, err); - c2pa_string_free(err); + c2pa_free(err); exit(1); } printf("PASSED: %s\n", what);