Skip to content
Merged
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
7 changes: 5 additions & 2 deletions c2pa/c2pa_api/c2pa_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,12 @@
# reader = Reader("image/jpeg", open("test.jpg", "rb"))
# json = reader.json()
class Reader(api.Reader):
def __init__(self, format, stream):
def __init__(self, format, stream, manifest_data=None):
super().__init__()
self.from_stream(format, C2paStream(stream))
if manifest_data is not None:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any chance that the manifest_data could be blank (e.g. or any variation of whatever could be considered blank). Do we need special care around that?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This option is allow verifying a sidecar or remote manifest. If we have retrieved a .c2pa manifest store from our cloud or a sidecar, we can pass it in along with an associated asset to see if it verifies. If the manifest_data is not a correctly structured binary .c2pa we will return errors reporting it.
I should add a ticket somewhere to do more type checking at a higher level, If you pass the wrong type of parameter, it will get caught, but the error messages are obscure ones from the binding layer.

self.from_manifest_data_and_stream(format, manifest_data, stream)
else:
self.from_stream(format, C2paStream(stream))

@classmethod
def from_file(cls, path: str, format=None):
Expand Down
9 changes: 9 additions & 0 deletions src/c2pa.udl
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ interface Reader {
[Throws=Error]
string from_stream([ByRef] string format, [ByRef] Stream reader);

[Throws=Error]
string from_manifest_data_and_stream([ByRef] bytes manifest_data, [ByRef] string format, [ByRef] Stream reader);

[Throws=Error]
string json();

Expand All @@ -79,6 +82,12 @@ interface Builder {
[Throws=Error]
void with_json([ByRef] string json);

[Throws=Error]
void set_no_embed();

[Throws=Error]
void set_remote_url([ByRef] string url);

[Throws=Error]
void add_resource([ByRef] string uri, [ByRef] Stream stream );

Expand Down
32 changes: 32 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,19 @@ impl Reader {
Ok(json)
}

pub fn from_manifest_data_and_stream(&self, manifest_data: &[u8], format: &str, stream: &dyn Stream) -> Result<String> {
// uniffi doesn't allow mutable parameters, so we we use an adapter
let mut stream = StreamAdapter::from(stream);
let reader = c2pa::Reader::from_manifest_data_and_stream(manifest_data, format, &mut stream)?;
let json = reader.to_string();
if let Ok(mut st) = self.reader.try_write() {
*st = reader;
} else {
return Err(Error::RwLock);
};
Ok(json)
}

pub fn json(&self) -> Result<String> {
if let Ok(st) = self.reader.try_read() {
Ok(st.json())
Expand Down Expand Up @@ -121,6 +134,25 @@ impl Builder {
Ok(())
}

/// Set to true to disable embedding a manifest
pub fn set_no_embed(&self) -> Result<()> {
if let Ok(mut builder) = self.builder.try_write() {
builder.set_no_embed(true);
} else {
return Err(Error::RwLock);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: Is this to poison the lock or... ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is here because the uniffi tool we use to generate python bindings does not support mutable parameters. So we add an internal check to ensure there is only one mutable access at a time.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could use atomics here in Buidler to get around this eventually, if we end up having to have these binding layers hold on to a Mutex because of the Uniffi requirements of no mut vars as params. Not something that needs to be done for this PR, but it may make our lives easier down the road.

use std::sync::atomic::{AtomicBool, Ordering};

struct Builder {
    no_embed: AtomicBool
}

impl Builder {
    pub fn set_no_embed(&self, value: bool) {
        self.no_embed.store(value, Ordering::Relaxed)
    }
}


fn main() {
    let b = Builder {
        no_embed: AtomicBool::new(true)
    };
    
    
    b.set_no_embed(true);
    
    // note how b, defined above, is not mutable. 
    std::thread::scope(|s| {
        let _ = s.spawn(|| b.set_no_embed(false));
        let _ = s.spawn(|| b.set_no_embed(true));
        let _ = s.spawn(|| b.set_no_embed(false));
    });
}

};
Ok(())
}

pub fn set_remote_url(&self, remote_url: &str) -> Result<()> {
if let Ok(mut builder) = self.builder.try_write() {
builder.set_remote_url(remote_url);
} else {
return Err(Error::RwLock);
};
Ok(())
}

/// Add a resource to the builder
pub fn add_resource(&self, uri: &str, stream: &dyn Stream) -> Result<()> {
if let Ok(mut builder) = self.builder.try_write() {
Expand Down
15 changes: 13 additions & 2 deletions tests/unit_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ def test_stream_read_and_parse(self):
title = manifest = manifest_store["manifests"][manifest_store["active_manifest"]]["title"]
self.assertEqual(title, "C.jpg")


def test_json_decode_err(self):
with self.assertRaises(Error.Io):
manifest_store = Reader("image/jpeg","foo")
Expand Down Expand Up @@ -92,7 +93,7 @@ def sign(data: bytes) -> bytes:
# Create a local Ps256 signer with certs and a timestamp server
signer = create_signer(sign, SigningAlg.PS256, certs, "http://timestamp.digicert.com")

def test_streams_build(self):
def test_streams_sign(self):
with open(testPath, "rb") as file:
builder = Builder(TestBuilder.manifestDefinition)
output = io.BytesIO(bytearray())
Expand All @@ -101,7 +102,7 @@ def test_streams_build(self):
reader = Reader("image/jpeg", output)
self.assertIn("Python Test", reader.json())

def test_streams_build(self):
def test_archive_sign(self):
with open(testPath, "rb") as file:
builder = Builder(TestBuilder.manifestDefinition)
archive = byte_array = io.BytesIO(bytearray())
Expand All @@ -113,5 +114,15 @@ def test_streams_build(self):
reader = Reader("image/jpeg", output)
self.assertIn("Python Test", reader.json())

def test_remote_sign(self):
with open(testPath, "rb") as file:
builder = Builder(TestBuilder.manifestDefinition)
builder.set_no_embed()
output = io.BytesIO(bytearray())
manifest_data = builder.sign(TestBuilder.signer, "image/jpeg", file, output)
output.seek(0)
reader = Reader.from_manifest_data_and_stream(manifest_data, "image/jpeg", output)
self.assertIn("Python Test", reader.json())

if __name__ == '__main__':
unittest.main()
Loading