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
2 changes: 1 addition & 1 deletion library/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# C2PA Native Library Version
# Update this to use a different release from https://github.com/contentauth/c2pa-rs/releases
c2paVersion=v0.85.2
c2paVersion=v0.90.0
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,12 @@ class AndroidBuilderTests : BuilderTests() {
assertTrue(result.success, "Context Builder Callback Error Paths test failed: ${result.message}")
}

@Test
fun runTestReaderCrJson() = runBlocking {
val result = testReaderCrJson()
assertTrue(result.success, "Reader crJSON test failed: ${result.message}")
}

@Test
fun runTestBuilderFromArchive() = runBlocking {
val result = testBuilderFromArchive()
Expand Down
38 changes: 35 additions & 3 deletions library/src/main/jni/c2pa_jni.c
Original file line number Diff line number Diff line change
Expand Up @@ -770,6 +770,26 @@ JNIEXPORT jstring JNICALL Java_org_contentauth_c2pa_Reader_toDetailedJsonNative(
return result;
}

JNIEXPORT jstring JNICALL Java_org_contentauth_c2pa_Reader_crjsonNative(JNIEnv *env, jobject obj, jlong readerPtr) {
if (readerPtr == 0) {
(*env)->ThrowNew(env, (*env)->FindClass(env, "java/lang/IllegalStateException"),
"Reader is not initialized");
return NULL;
}

struct C2paReader *reader = (struct C2paReader*)(uintptr_t)readerPtr;
char *json = c2pa_reader_crjson(reader);

if (json == NULL) {
throw_c2pa_exception(env, "Failed to generate crJSON from reader");
return NULL;
}

jstring result = cstring_to_jstring(env, json);
c2pa_free(json);
return result;
}

JNIEXPORT jstring JNICALL Java_org_contentauth_c2pa_Reader_remoteUrlNative(JNIEnv *env, jobject obj, jlong readerPtr) {
if (readerPtr == 0) {
(*env)->ThrowNew(env, (*env)->FindClass(env, "java/lang/IllegalStateException"),
Expand Down Expand Up @@ -853,13 +873,25 @@ JNIEXPORT jlong JNICALL Java_org_contentauth_c2pa_Builder_nativeFromArchive(JNIE
}

struct C2paStream *stream = (struct C2paStream*)(uintptr_t)streamPtr;
struct C2paBuilder *builder = c2pa_builder_from_archive(stream);


// Create a builder from a default context, then attach the archive. The
// context can be released once the builder has been created from it.
struct C2paContext *ctx = c2pa_context_new();
struct C2paBuilder *builder = NULL;
if (ctx != NULL) {
struct C2paBuilder *base = c2pa_builder_from_context(ctx);
if (base != NULL) {
// with_archive consumes `base` and returns a new builder.
builder = c2pa_builder_with_archive(base, stream);
}
c2pa_free(ctx);
}

if (builder == NULL) {
throw_c2pa_exception(env, "Failed to create builder from archive");
return 0;
}

return (jlong)(uintptr_t)builder;
}

Expand Down
22 changes: 22 additions & 0 deletions library/src/main/kotlin/org/contentauth/c2pa/Reader.kt
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,27 @@ class Reader internal constructor(private var ptr: Long) : Closeable {
return json
}

/**
* Returns the manifest store as a crJSON string.
*
* crJSON is the Content Credentials JSON export format defined by the crJSON specification.
* Use [json] for the standard representation or [detailedJson] for the verbose one.
*
* @return The manifest as a crJSON string
* @throws C2PAError.Api if the manifest cannot be serialized
*
* @see json
* @see detailedJson
*/
@Throws(C2PAError::class)
fun crJSON(): String {
val json = crjsonNative(ptr)
if (json == null) {
throw C2PAError.Api(C2PA.getError() ?: "Failed to convert to crJSON")
}
return json
}

/**
* Returns the remote URL where the manifest is hosted, if available.
*
Expand Down Expand Up @@ -395,6 +416,7 @@ class Reader internal constructor(private var ptr: Long) : Closeable {
private external fun withFragmentNative(handle: Long, format: String, streamHandle: Long, fragmentHandle: Long): Long
private external fun toJsonNative(handle: Long): String?
private external fun toDetailedJsonNative(handle: Long): String?
private external fun crjsonNative(handle: Long): String?
private external fun remoteUrlNative(handle: Long): String?
private external fun isEmbeddedNative(handle: Long): Boolean
private external fun resourceToStreamNative(handle: Long, uri: String, streamHandle: Long): Long
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ private suspend fun runAllTests(context: Context): List<TestResult> = withContex
results.add(builderTests.testContextHttpResolverRemoteFetch())
results.add(builderTests.testContextHttpResolverOkHttpFetch())
results.add(builderTests.testContextBuilderCallbackErrorPaths())
results.add(builderTests.testReaderCrJson())
results.add(builderTests.testBuilderFromArchive())
results.add(builderTests.testReaderWithManifestData())
results.add(builderTests.testJsonRoundTrip())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1239,6 +1239,47 @@ abstract class BuilderTests : TestBase() {
}
}

suspend fun testReaderCrJson(): TestResult = withContext(Dispatchers.IO) {
runTest("Reader crJSON") {
try {
val certPem = loadResourceAsString("es256_certs")
val keyPem = loadResourceAsString("es256_private")
val sourceImageData = loadResourceAsBytes("pexels_asadphoto_457882")

val signedData = Builder.fromJson(TEST_MANIFEST_JSON).use { builder ->
ByteArrayStream(sourceImageData).use { source ->
ByteArrayStream().use { dest ->
Signer.fromInfo(SignerInfo(SigningAlgorithm.ES256, certPem, keyPem)).use { signer ->
builder.sign("image/jpeg", source, dest, signer)
}
dest.getData()
}
}
}

val crjson = ByteArrayStream(signedData).use { signedStream ->
Reader.fromStream("image/jpeg", signedStream).use { reader ->
reader.crJSON()
}
}

val success = crjson.isNotEmpty()
TestResult(
"Reader crJSON",
success,
if (success) {
"crJSON returned ${crjson.length} chars"
} else {
"crJSON was empty"
},
"crJSON length: ${crjson.length}",
)
} catch (e: C2PAError) {
TestResult("Reader crJSON", false, "crJSON flow threw", e.toString())
}
}
}

suspend fun testBuilderFromArchive(): TestResult = withContext(Dispatchers.IO) {
runTest("Builder from Archive") {
val manifestJson = TEST_MANIFEST_JSON
Expand Down
Loading