From 90423333c4dbb73ab23d0575257e9d995d72b618 Mon Sep 17 00:00:00 2001 From: Eli Mallon Date: Thu, 8 Aug 2024 14:55:05 -0700 Subject: [PATCH 01/42] golang proof of concept! --- Cargo.toml | 9 +- Makefile | 8 + go.mod | 3 + pkg/c2pa/.gitignore | 1 + pkg/c2pa/c2pa.go | 79 ++ pkg/c2pa/demo/demo.go | 44 + src/c2pa/c2pa.c | 8 + src/c2pa/c2pa.go | 1971 +++++++++++++++++++++++++++++++++++++++++ src/c2pa/c2pa.h | 559 ++++++++++++ src/streams.rs | 4 + 10 files changed, 2681 insertions(+), 5 deletions(-) create mode 100644 Makefile create mode 100644 go.mod create mode 100644 pkg/c2pa/.gitignore create mode 100644 pkg/c2pa/c2pa.go create mode 100644 pkg/c2pa/demo/demo.go create mode 100644 src/c2pa/c2pa.c create mode 100644 src/c2pa/c2pa.go create mode 100644 src/c2pa/c2pa.h diff --git a/Cargo.toml b/Cargo.toml index ddffa900..d082ae52 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,17 +7,16 @@ authors = ["Gavin Peacock + +// This file exists beacause of +// https://github.com/golang/go/issues/11263 + +void cgo_rust_task_callback_bridge_c2pa(RustTaskCallback cb, const void * taskData, int8_t status) { + cb(taskData, status); +} \ No newline at end of file diff --git a/src/c2pa/c2pa.go b/src/c2pa/c2pa.go new file mode 100644 index 00000000..8be98d1c --- /dev/null +++ b/src/c2pa/c2pa.go @@ -0,0 +1,1971 @@ +package c2pa + +// #include +import "C" + +import ( + "bytes" + "encoding/binary" + "fmt" + "io" + "math" + "runtime" + "sync" + "sync/atomic" + "unsafe" +) + +type RustBuffer = C.RustBuffer + +type RustBufferI interface { + AsReader() *bytes.Reader + Free() + ToGoBytes() []byte + Data() unsafe.Pointer + Len() int + Capacity() int +} + +func RustBufferFromExternal(b RustBufferI) RustBuffer { + return RustBuffer{ + capacity: C.int(b.Capacity()), + len: C.int(b.Len()), + data: (*C.uchar)(b.Data()), + } +} + +func (cb RustBuffer) Capacity() int { + return int(cb.capacity) +} + +func (cb RustBuffer) Len() int { + return int(cb.len) +} + +func (cb RustBuffer) Data() unsafe.Pointer { + return unsafe.Pointer(cb.data) +} + +func (cb RustBuffer) AsReader() *bytes.Reader { + b := unsafe.Slice((*byte)(cb.data), C.int(cb.len)) + return bytes.NewReader(b) +} + +func (cb RustBuffer) Free() { + rustCall(func(status *C.RustCallStatus) bool { + C.ffi_c2pa_rustbuffer_free(cb, status) + return false + }) +} + +func (cb RustBuffer) ToGoBytes() []byte { + return C.GoBytes(unsafe.Pointer(cb.data), C.int(cb.len)) +} + +func stringToRustBuffer(str string) RustBuffer { + return bytesToRustBuffer([]byte(str)) +} + +func bytesToRustBuffer(b []byte) RustBuffer { + if len(b) == 0 { + return RustBuffer{} + } + // We can pass the pointer along here, as it is pinned + // for the duration of this call + foreign := C.ForeignBytes{ + len: C.int(len(b)), + data: (*C.uchar)(unsafe.Pointer(&b[0])), + } + + return rustCall(func(status *C.RustCallStatus) RustBuffer { + return C.ffi_c2pa_rustbuffer_from_bytes(foreign, status) + }) +} + +type BufLifter[GoType any] interface { + Lift(value RustBufferI) GoType +} + +type BufLowerer[GoType any] interface { + Lower(value GoType) RustBuffer +} + +type FfiConverter[GoType any, FfiType any] interface { + Lift(value FfiType) GoType + Lower(value GoType) FfiType +} + +type BufReader[GoType any] interface { + Read(reader io.Reader) GoType +} + +type BufWriter[GoType any] interface { + Write(writer io.Writer, value GoType) +} + +type FfiRustBufConverter[GoType any, FfiType any] interface { + FfiConverter[GoType, FfiType] + BufReader[GoType] +} + +func LowerIntoRustBuffer[GoType any](bufWriter BufWriter[GoType], value GoType) RustBuffer { + // This might be not the most efficient way but it does not require knowing allocation size + // beforehand + var buffer bytes.Buffer + bufWriter.Write(&buffer, value) + + bytes, err := io.ReadAll(&buffer) + if err != nil { + panic(fmt.Errorf("reading written data: %w", err)) + } + return bytesToRustBuffer(bytes) +} + +func LiftFromRustBuffer[GoType any](bufReader BufReader[GoType], rbuf RustBufferI) GoType { + defer rbuf.Free() + reader := rbuf.AsReader() + item := bufReader.Read(reader) + if reader.Len() > 0 { + // TODO: Remove this + leftover, _ := io.ReadAll(reader) + panic(fmt.Errorf("Junk remaining in buffer after lifting: %s", string(leftover))) + } + return item +} + +func rustCallWithError[U any](converter BufLifter[error], callback func(*C.RustCallStatus) U) (U, error) { + var status C.RustCallStatus + returnValue := callback(&status) + err := checkCallStatus(converter, status) + + return returnValue, err +} + +func checkCallStatus(converter BufLifter[error], status C.RustCallStatus) error { + switch status.code { + case 0: + return nil + case 1: + return converter.Lift(status.errorBuf) + case 2: + // when the rust code sees a panic, it tries to construct a rustbuffer + // with the message. but if that code panics, then it just sends back + // an empty buffer. + if status.errorBuf.len > 0 { + panic(fmt.Errorf("%s", FfiConverterStringINSTANCE.Lift(status.errorBuf))) + } else { + panic(fmt.Errorf("Rust panicked while handling Rust panic")) + } + default: + return fmt.Errorf("unknown status code: %d", status.code) + } +} + +func checkCallStatusUnknown(status C.RustCallStatus) error { + switch status.code { + case 0: + return nil + case 1: + panic(fmt.Errorf("function not returning an error returned an error")) + case 2: + // when the rust code sees a panic, it tries to construct a rustbuffer + // with the message. but if that code panics, then it just sends back + // an empty buffer. + if status.errorBuf.len > 0 { + panic(fmt.Errorf("%s", FfiConverterStringINSTANCE.Lift(status.errorBuf))) + } else { + panic(fmt.Errorf("Rust panicked while handling Rust panic")) + } + default: + return fmt.Errorf("unknown status code: %d", status.code) + } +} + +func rustCall[U any](callback func(*C.RustCallStatus) U) U { + returnValue, err := rustCallWithError(nil, callback) + if err != nil { + panic(err) + } + return returnValue +} + +func writeInt8(writer io.Writer, value int8) { + if err := binary.Write(writer, binary.BigEndian, value); err != nil { + panic(err) + } +} + +func writeUint8(writer io.Writer, value uint8) { + if err := binary.Write(writer, binary.BigEndian, value); err != nil { + panic(err) + } +} + +func writeInt16(writer io.Writer, value int16) { + if err := binary.Write(writer, binary.BigEndian, value); err != nil { + panic(err) + } +} + +func writeUint16(writer io.Writer, value uint16) { + if err := binary.Write(writer, binary.BigEndian, value); err != nil { + panic(err) + } +} + +func writeInt32(writer io.Writer, value int32) { + if err := binary.Write(writer, binary.BigEndian, value); err != nil { + panic(err) + } +} + +func writeUint32(writer io.Writer, value uint32) { + if err := binary.Write(writer, binary.BigEndian, value); err != nil { + panic(err) + } +} + +func writeInt64(writer io.Writer, value int64) { + if err := binary.Write(writer, binary.BigEndian, value); err != nil { + panic(err) + } +} + +func writeUint64(writer io.Writer, value uint64) { + if err := binary.Write(writer, binary.BigEndian, value); err != nil { + panic(err) + } +} + +func writeFloat32(writer io.Writer, value float32) { + if err := binary.Write(writer, binary.BigEndian, value); err != nil { + panic(err) + } +} + +func writeFloat64(writer io.Writer, value float64) { + if err := binary.Write(writer, binary.BigEndian, value); err != nil { + panic(err) + } +} + +func readInt8(reader io.Reader) int8 { + var result int8 + if err := binary.Read(reader, binary.BigEndian, &result); err != nil { + panic(err) + } + return result +} + +func readUint8(reader io.Reader) uint8 { + var result uint8 + if err := binary.Read(reader, binary.BigEndian, &result); err != nil { + panic(err) + } + return result +} + +func readInt16(reader io.Reader) int16 { + var result int16 + if err := binary.Read(reader, binary.BigEndian, &result); err != nil { + panic(err) + } + return result +} + +func readUint16(reader io.Reader) uint16 { + var result uint16 + if err := binary.Read(reader, binary.BigEndian, &result); err != nil { + panic(err) + } + return result +} + +func readInt32(reader io.Reader) int32 { + var result int32 + if err := binary.Read(reader, binary.BigEndian, &result); err != nil { + panic(err) + } + return result +} + +func readUint32(reader io.Reader) uint32 { + var result uint32 + if err := binary.Read(reader, binary.BigEndian, &result); err != nil { + panic(err) + } + return result +} + +func readInt64(reader io.Reader) int64 { + var result int64 + if err := binary.Read(reader, binary.BigEndian, &result); err != nil { + panic(err) + } + return result +} + +func readUint64(reader io.Reader) uint64 { + var result uint64 + if err := binary.Read(reader, binary.BigEndian, &result); err != nil { + panic(err) + } + return result +} + +func readFloat32(reader io.Reader) float32 { + var result float32 + if err := binary.Read(reader, binary.BigEndian, &result); err != nil { + panic(err) + } + return result +} + +func readFloat64(reader io.Reader) float64 { + var result float64 + if err := binary.Read(reader, binary.BigEndian, &result); err != nil { + panic(err) + } + return result +} + +func init() { + + (&FfiConverterCallbackInterfaceSignerCallback{}).register() + (&FfiConverterCallbackInterfaceStream{}).register() + uniffiCheckChecksums() +} + +func uniffiCheckChecksums() { + // Get the bindings contract version from our ComponentInterface + bindingsContractVersion := 24 + // Get the scaffolding contract version by calling the into the dylib + scaffoldingContractVersion := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint32_t { + return C.ffi_c2pa_uniffi_contract_version(uniffiStatus) + }) + if bindingsContractVersion != int(scaffoldingContractVersion) { + // If this happens try cleaning and rebuilding your project + panic(fmt.Sprintf("c2pa: UniFFI contract version mismatch bindingsContractVersion=%d scaffoldingContractVersion=%d", bindingsContractVersion, int(scaffoldingContractVersion))) + } + { + checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_c2pa_checksum_func_sdk_version(uniffiStatus) + }) + if checksum != 37245 { + // If this happens try cleaning and rebuilding your project + panic("c2pa: uniffi_c2pa_checksum_func_sdk_version: UniFFI API checksum mismatch") + } + } + { + checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_c2pa_checksum_func_version(uniffiStatus) + }) + if checksum != 61576 { + // If this happens try cleaning and rebuilding your project + panic("c2pa: uniffi_c2pa_checksum_func_version: UniFFI API checksum mismatch") + } + } + { + checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_c2pa_checksum_method_builder_add_ingredient(uniffiStatus) + }) + if checksum != 54967 { + // If this happens try cleaning and rebuilding your project + panic("c2pa: uniffi_c2pa_checksum_method_builder_add_ingredient: UniFFI API checksum mismatch") + } + } + { + checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_c2pa_checksum_method_builder_add_resource(uniffiStatus) + }) + if checksum != 12018 { + // If this happens try cleaning and rebuilding your project + panic("c2pa: uniffi_c2pa_checksum_method_builder_add_resource: UniFFI API checksum mismatch") + } + } + { + checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_c2pa_checksum_method_builder_from_archive(uniffiStatus) + }) + if checksum != 17341 { + // If this happens try cleaning and rebuilding your project + panic("c2pa: uniffi_c2pa_checksum_method_builder_from_archive: UniFFI API checksum mismatch") + } + } + { + checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_c2pa_checksum_method_builder_sign(uniffiStatus) + }) + if checksum != 8729 { + // If this happens try cleaning and rebuilding your project + panic("c2pa: uniffi_c2pa_checksum_method_builder_sign: UniFFI API checksum mismatch") + } + } + { + checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_c2pa_checksum_method_builder_to_archive(uniffiStatus) + }) + if checksum != 44718 { + // If this happens try cleaning and rebuilding your project + panic("c2pa: uniffi_c2pa_checksum_method_builder_to_archive: UniFFI API checksum mismatch") + } + } + { + checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_c2pa_checksum_method_builder_with_json(uniffiStatus) + }) + if checksum != 29392 { + // If this happens try cleaning and rebuilding your project + panic("c2pa: uniffi_c2pa_checksum_method_builder_with_json: UniFFI API checksum mismatch") + } + } + { + checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_c2pa_checksum_method_reader_from_stream(uniffiStatus) + }) + if checksum != 3255 { + // If this happens try cleaning and rebuilding your project + panic("c2pa: uniffi_c2pa_checksum_method_reader_from_stream: UniFFI API checksum mismatch") + } + } + { + checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_c2pa_checksum_method_reader_json(uniffiStatus) + }) + if checksum != 33242 { + // If this happens try cleaning and rebuilding your project + panic("c2pa: uniffi_c2pa_checksum_method_reader_json: UniFFI API checksum mismatch") + } + } + { + checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_c2pa_checksum_method_reader_resource_to_stream(uniffiStatus) + }) + if checksum != 44049 { + // If this happens try cleaning and rebuilding your project + panic("c2pa: uniffi_c2pa_checksum_method_reader_resource_to_stream: UniFFI API checksum mismatch") + } + } + { + checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_c2pa_checksum_constructor_builder_new(uniffiStatus) + }) + if checksum != 8924 { + // If this happens try cleaning and rebuilding your project + panic("c2pa: uniffi_c2pa_checksum_constructor_builder_new: UniFFI API checksum mismatch") + } + } + { + checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_c2pa_checksum_constructor_callbacksigner_new(uniffiStatus) + }) + if checksum != 51503 { + // If this happens try cleaning and rebuilding your project + panic("c2pa: uniffi_c2pa_checksum_constructor_callbacksigner_new: UniFFI API checksum mismatch") + } + } + { + checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_c2pa_checksum_constructor_reader_new(uniffiStatus) + }) + if checksum != 7340 { + // If this happens try cleaning and rebuilding your project + panic("c2pa: uniffi_c2pa_checksum_constructor_reader_new: UniFFI API checksum mismatch") + } + } + { + checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_c2pa_checksum_method_signercallback_sign(uniffiStatus) + }) + if checksum != 15928 { + // If this happens try cleaning and rebuilding your project + panic("c2pa: uniffi_c2pa_checksum_method_signercallback_sign: UniFFI API checksum mismatch") + } + } + { + checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_c2pa_checksum_method_stream_read_stream(uniffiStatus) + }) + if checksum != 4594 { + // If this happens try cleaning and rebuilding your project + panic("c2pa: uniffi_c2pa_checksum_method_stream_read_stream: UniFFI API checksum mismatch") + } + } + { + checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_c2pa_checksum_method_stream_seek_stream(uniffiStatus) + }) + if checksum != 32219 { + // If this happens try cleaning and rebuilding your project + panic("c2pa: uniffi_c2pa_checksum_method_stream_seek_stream: UniFFI API checksum mismatch") + } + } + { + checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_c2pa_checksum_method_stream_write_stream(uniffiStatus) + }) + if checksum != 37641 { + // If this happens try cleaning and rebuilding your project + panic("c2pa: uniffi_c2pa_checksum_method_stream_write_stream: UniFFI API checksum mismatch") + } + } +} + +type FfiConverterUint64 struct{} + +var FfiConverterUint64INSTANCE = FfiConverterUint64{} + +func (FfiConverterUint64) Lower(value uint64) C.uint64_t { + return C.uint64_t(value) +} + +func (FfiConverterUint64) Write(writer io.Writer, value uint64) { + writeUint64(writer, value) +} + +func (FfiConverterUint64) Lift(value C.uint64_t) uint64 { + return uint64(value) +} + +func (FfiConverterUint64) Read(reader io.Reader) uint64 { + return readUint64(reader) +} + +type FfiDestroyerUint64 struct{} + +func (FfiDestroyerUint64) Destroy(_ uint64) {} + +type FfiConverterInt64 struct{} + +var FfiConverterInt64INSTANCE = FfiConverterInt64{} + +func (FfiConverterInt64) Lower(value int64) C.int64_t { + return C.int64_t(value) +} + +func (FfiConverterInt64) Write(writer io.Writer, value int64) { + writeInt64(writer, value) +} + +func (FfiConverterInt64) Lift(value C.int64_t) int64 { + return int64(value) +} + +func (FfiConverterInt64) Read(reader io.Reader) int64 { + return readInt64(reader) +} + +type FfiDestroyerInt64 struct{} + +func (FfiDestroyerInt64) Destroy(_ int64) {} + +type FfiConverterString struct{} + +var FfiConverterStringINSTANCE = FfiConverterString{} + +func (FfiConverterString) Lift(rb RustBufferI) string { + defer rb.Free() + reader := rb.AsReader() + b, err := io.ReadAll(reader) + if err != nil { + panic(fmt.Errorf("reading reader: %w", err)) + } + return string(b) +} + +func (FfiConverterString) Read(reader io.Reader) string { + length := readInt32(reader) + buffer := make([]byte, length) + read_length, err := reader.Read(buffer) + if err != nil { + panic(err) + } + if read_length != int(length) { + panic(fmt.Errorf("bad read length when reading string, expected %d, read %d", length, read_length)) + } + return string(buffer) +} + +func (FfiConverterString) Lower(value string) RustBuffer { + return stringToRustBuffer(value) +} + +func (FfiConverterString) Write(writer io.Writer, value string) { + if len(value) > math.MaxInt32 { + panic("String is too large to fit into Int32") + } + + writeInt32(writer, int32(len(value))) + write_length, err := io.WriteString(writer, value) + if err != nil { + panic(err) + } + if write_length != len(value) { + panic(fmt.Errorf("bad write length when writing string, expected %d, written %d", len(value), write_length)) + } +} + +type FfiDestroyerString struct{} + +func (FfiDestroyerString) Destroy(_ string) {} + +type FfiConverterBytes struct{} + +var FfiConverterBytesINSTANCE = FfiConverterBytes{} + +func (c FfiConverterBytes) Lower(value []byte) RustBuffer { + return LowerIntoRustBuffer[[]byte](c, value) +} + +func (c FfiConverterBytes) Write(writer io.Writer, value []byte) { + if len(value) > math.MaxInt32 { + panic("[]byte is too large to fit into Int32") + } + + writeInt32(writer, int32(len(value))) + write_length, err := writer.Write(value) + if err != nil { + panic(err) + } + if write_length != len(value) { + panic(fmt.Errorf("bad write length when writing []byte, expected %d, written %d", len(value), write_length)) + } +} + +func (c FfiConverterBytes) Lift(rb RustBufferI) []byte { + return LiftFromRustBuffer[[]byte](c, rb) +} + +func (c FfiConverterBytes) Read(reader io.Reader) []byte { + length := readInt32(reader) + buffer := make([]byte, length) + read_length, err := reader.Read(buffer) + if err != nil { + panic(err) + } + if read_length != int(length) { + panic(fmt.Errorf("bad read length when reading []byte, expected %d, read %d", length, read_length)) + } + return buffer +} + +type FfiDestroyerBytes struct{} + +func (FfiDestroyerBytes) Destroy(_ []byte) {} + +// Below is an implementation of synchronization requirements outlined in the link. +// https://github.com/mozilla/uniffi-rs/blob/0dc031132d9493ca812c3af6e7dd60ad2ea95bf0/uniffi_bindgen/src/bindings/kotlin/templates/ObjectRuntime.kt#L31 + +type FfiObject struct { + pointer unsafe.Pointer + callCounter atomic.Int64 + freeFunction func(unsafe.Pointer, *C.RustCallStatus) + destroyed atomic.Bool +} + +func newFfiObject(pointer unsafe.Pointer, freeFunction func(unsafe.Pointer, *C.RustCallStatus)) FfiObject { + return FfiObject{ + pointer: pointer, + freeFunction: freeFunction, + } +} + +func (ffiObject *FfiObject) incrementPointer(debugName string) unsafe.Pointer { + for { + counter := ffiObject.callCounter.Load() + if counter <= -1 { + panic(fmt.Errorf("%v object has already been destroyed", debugName)) + } + if counter == math.MaxInt64 { + panic(fmt.Errorf("%v object call counter would overflow", debugName)) + } + if ffiObject.callCounter.CompareAndSwap(counter, counter+1) { + break + } + } + + return ffiObject.pointer +} + +func (ffiObject *FfiObject) decrementPointer() { + if ffiObject.callCounter.Add(-1) == -1 { + ffiObject.freeRustArcPtr() + } +} + +func (ffiObject *FfiObject) destroy() { + if ffiObject.destroyed.CompareAndSwap(false, true) { + if ffiObject.callCounter.Add(-1) == -1 { + ffiObject.freeRustArcPtr() + } + } +} + +func (ffiObject *FfiObject) freeRustArcPtr() { + rustCall(func(status *C.RustCallStatus) int32 { + ffiObject.freeFunction(ffiObject.pointer, status) + return 0 + }) +} + +type Builder struct { + ffiObject FfiObject +} + +func NewBuilder() *Builder { + return FfiConverterBuilderINSTANCE.Lift(rustCall(func(_uniffiStatus *C.RustCallStatus) unsafe.Pointer { + return C.uniffi_c2pa_fn_constructor_builder_new(_uniffiStatus) + })) +} + +func (_self *Builder) AddIngredient(ingredientJson string, format string, stream Stream) error { + _pointer := _self.ffiObject.incrementPointer("*Builder") + defer _self.ffiObject.decrementPointer() + _, _uniffiErr := rustCallWithError(FfiConverterTypeError{}, func(_uniffiStatus *C.RustCallStatus) bool { + C.uniffi_c2pa_fn_method_builder_add_ingredient( + _pointer, FfiConverterStringINSTANCE.Lower(ingredientJson), FfiConverterStringINSTANCE.Lower(format), FfiConverterCallbackInterfaceStreamINSTANCE.Lower(stream), _uniffiStatus) + return false + }) + return _uniffiErr +} + +func (_self *Builder) AddResource(uri string, stream Stream) error { + _pointer := _self.ffiObject.incrementPointer("*Builder") + defer _self.ffiObject.decrementPointer() + _, _uniffiErr := rustCallWithError(FfiConverterTypeError{}, func(_uniffiStatus *C.RustCallStatus) bool { + C.uniffi_c2pa_fn_method_builder_add_resource( + _pointer, FfiConverterStringINSTANCE.Lower(uri), FfiConverterCallbackInterfaceStreamINSTANCE.Lower(stream), _uniffiStatus) + return false + }) + return _uniffiErr +} + +func (_self *Builder) FromArchive(stream Stream) error { + _pointer := _self.ffiObject.incrementPointer("*Builder") + defer _self.ffiObject.decrementPointer() + _, _uniffiErr := rustCallWithError(FfiConverterTypeError{}, func(_uniffiStatus *C.RustCallStatus) bool { + C.uniffi_c2pa_fn_method_builder_from_archive( + _pointer, FfiConverterCallbackInterfaceStreamINSTANCE.Lower(stream), _uniffiStatus) + return false + }) + return _uniffiErr +} + +func (_self *Builder) Sign(format string, input Stream, output Stream, signer *CallbackSigner) ([]byte, error) { + _pointer := _self.ffiObject.incrementPointer("*Builder") + defer _self.ffiObject.decrementPointer() + _uniffiRV, _uniffiErr := rustCallWithError(FfiConverterTypeError{}, func(_uniffiStatus *C.RustCallStatus) RustBufferI { + return C.uniffi_c2pa_fn_method_builder_sign( + _pointer, FfiConverterStringINSTANCE.Lower(format), FfiConverterCallbackInterfaceStreamINSTANCE.Lower(input), FfiConverterCallbackInterfaceStreamINSTANCE.Lower(output), FfiConverterCallbackSignerINSTANCE.Lower(signer), _uniffiStatus) + }) + if _uniffiErr != nil { + var _uniffiDefaultValue []byte + return _uniffiDefaultValue, _uniffiErr + } else { + return FfiConverterBytesINSTANCE.Lift(_uniffiRV), _uniffiErr + } +} + +func (_self *Builder) ToArchive(stream Stream) error { + _pointer := _self.ffiObject.incrementPointer("*Builder") + defer _self.ffiObject.decrementPointer() + _, _uniffiErr := rustCallWithError(FfiConverterTypeError{}, func(_uniffiStatus *C.RustCallStatus) bool { + C.uniffi_c2pa_fn_method_builder_to_archive( + _pointer, FfiConverterCallbackInterfaceStreamINSTANCE.Lower(stream), _uniffiStatus) + return false + }) + return _uniffiErr +} + +func (_self *Builder) WithJson(json string) error { + _pointer := _self.ffiObject.incrementPointer("*Builder") + defer _self.ffiObject.decrementPointer() + _, _uniffiErr := rustCallWithError(FfiConverterTypeError{}, func(_uniffiStatus *C.RustCallStatus) bool { + C.uniffi_c2pa_fn_method_builder_with_json( + _pointer, FfiConverterStringINSTANCE.Lower(json), _uniffiStatus) + return false + }) + return _uniffiErr +} + +func (object *Builder) Destroy() { + runtime.SetFinalizer(object, nil) + object.ffiObject.destroy() +} + +type FfiConverterBuilder struct{} + +var FfiConverterBuilderINSTANCE = FfiConverterBuilder{} + +func (c FfiConverterBuilder) Lift(pointer unsafe.Pointer) *Builder { + result := &Builder{ + newFfiObject( + pointer, + func(pointer unsafe.Pointer, status *C.RustCallStatus) { + C.uniffi_c2pa_fn_free_builder(pointer, status) + }), + } + runtime.SetFinalizer(result, (*Builder).Destroy) + return result +} + +func (c FfiConverterBuilder) Read(reader io.Reader) *Builder { + return c.Lift(unsafe.Pointer(uintptr(readUint64(reader)))) +} + +func (c FfiConverterBuilder) Lower(value *Builder) unsafe.Pointer { + // TODO: this is bad - all synchronization from ObjectRuntime.go is discarded here, + // because the pointer will be decremented immediately after this function returns, + // and someone will be left holding onto a non-locked pointer. + pointer := value.ffiObject.incrementPointer("*Builder") + defer value.ffiObject.decrementPointer() + return pointer +} + +func (c FfiConverterBuilder) Write(writer io.Writer, value *Builder) { + writeUint64(writer, uint64(uintptr(c.Lower(value)))) +} + +type FfiDestroyerBuilder struct{} + +func (_ FfiDestroyerBuilder) Destroy(value *Builder) { + value.Destroy() +} + +type CallbackSigner struct { + ffiObject FfiObject +} + +func NewCallbackSigner(callback SignerCallback, alg SigningAlg, certs []byte, taUrl *string) *CallbackSigner { + return FfiConverterCallbackSignerINSTANCE.Lift(rustCall(func(_uniffiStatus *C.RustCallStatus) unsafe.Pointer { + return C.uniffi_c2pa_fn_constructor_callbacksigner_new(FfiConverterCallbackInterfaceSignerCallbackINSTANCE.Lower(callback), FfiConverterTypeSigningAlgINSTANCE.Lower(alg), FfiConverterBytesINSTANCE.Lower(certs), FfiConverterOptionalStringINSTANCE.Lower(taUrl), _uniffiStatus) + })) +} + +func (object *CallbackSigner) Destroy() { + runtime.SetFinalizer(object, nil) + object.ffiObject.destroy() +} + +type FfiConverterCallbackSigner struct{} + +var FfiConverterCallbackSignerINSTANCE = FfiConverterCallbackSigner{} + +func (c FfiConverterCallbackSigner) Lift(pointer unsafe.Pointer) *CallbackSigner { + result := &CallbackSigner{ + newFfiObject( + pointer, + func(pointer unsafe.Pointer, status *C.RustCallStatus) { + C.uniffi_c2pa_fn_free_callbacksigner(pointer, status) + }), + } + runtime.SetFinalizer(result, (*CallbackSigner).Destroy) + return result +} + +func (c FfiConverterCallbackSigner) Read(reader io.Reader) *CallbackSigner { + return c.Lift(unsafe.Pointer(uintptr(readUint64(reader)))) +} + +func (c FfiConverterCallbackSigner) Lower(value *CallbackSigner) unsafe.Pointer { + // TODO: this is bad - all synchronization from ObjectRuntime.go is discarded here, + // because the pointer will be decremented immediately after this function returns, + // and someone will be left holding onto a non-locked pointer. + pointer := value.ffiObject.incrementPointer("*CallbackSigner") + defer value.ffiObject.decrementPointer() + return pointer +} + +func (c FfiConverterCallbackSigner) Write(writer io.Writer, value *CallbackSigner) { + writeUint64(writer, uint64(uintptr(c.Lower(value)))) +} + +type FfiDestroyerCallbackSigner struct{} + +func (_ FfiDestroyerCallbackSigner) Destroy(value *CallbackSigner) { + value.Destroy() +} + +type Reader struct { + ffiObject FfiObject +} + +func NewReader() *Reader { + return FfiConverterReaderINSTANCE.Lift(rustCall(func(_uniffiStatus *C.RustCallStatus) unsafe.Pointer { + return C.uniffi_c2pa_fn_constructor_reader_new(_uniffiStatus) + })) +} + +func (_self *Reader) FromStream(format string, reader Stream) (string, error) { + _pointer := _self.ffiObject.incrementPointer("*Reader") + defer _self.ffiObject.decrementPointer() + _uniffiRV, _uniffiErr := rustCallWithError(FfiConverterTypeError{}, func(_uniffiStatus *C.RustCallStatus) RustBufferI { + return C.uniffi_c2pa_fn_method_reader_from_stream( + _pointer, FfiConverterStringINSTANCE.Lower(format), FfiConverterCallbackInterfaceStreamINSTANCE.Lower(reader), _uniffiStatus) + }) + if _uniffiErr != nil { + var _uniffiDefaultValue string + return _uniffiDefaultValue, _uniffiErr + } else { + return FfiConverterStringINSTANCE.Lift(_uniffiRV), _uniffiErr + } +} + +func (_self *Reader) Json() (string, error) { + _pointer := _self.ffiObject.incrementPointer("*Reader") + defer _self.ffiObject.decrementPointer() + _uniffiRV, _uniffiErr := rustCallWithError(FfiConverterTypeError{}, func(_uniffiStatus *C.RustCallStatus) RustBufferI { + return C.uniffi_c2pa_fn_method_reader_json( + _pointer, _uniffiStatus) + }) + if _uniffiErr != nil { + var _uniffiDefaultValue string + return _uniffiDefaultValue, _uniffiErr + } else { + return FfiConverterStringINSTANCE.Lift(_uniffiRV), _uniffiErr + } +} + +func (_self *Reader) ResourceToStream(uri string, stream Stream) (uint64, error) { + _pointer := _self.ffiObject.incrementPointer("*Reader") + defer _self.ffiObject.decrementPointer() + _uniffiRV, _uniffiErr := rustCallWithError(FfiConverterTypeError{}, func(_uniffiStatus *C.RustCallStatus) C.uint64_t { + return C.uniffi_c2pa_fn_method_reader_resource_to_stream( + _pointer, FfiConverterStringINSTANCE.Lower(uri), FfiConverterCallbackInterfaceStreamINSTANCE.Lower(stream), _uniffiStatus) + }) + if _uniffiErr != nil { + var _uniffiDefaultValue uint64 + return _uniffiDefaultValue, _uniffiErr + } else { + return FfiConverterUint64INSTANCE.Lift(_uniffiRV), _uniffiErr + } +} + +func (object *Reader) Destroy() { + runtime.SetFinalizer(object, nil) + object.ffiObject.destroy() +} + +type FfiConverterReader struct{} + +var FfiConverterReaderINSTANCE = FfiConverterReader{} + +func (c FfiConverterReader) Lift(pointer unsafe.Pointer) *Reader { + result := &Reader{ + newFfiObject( + pointer, + func(pointer unsafe.Pointer, status *C.RustCallStatus) { + C.uniffi_c2pa_fn_free_reader(pointer, status) + }), + } + runtime.SetFinalizer(result, (*Reader).Destroy) + return result +} + +func (c FfiConverterReader) Read(reader io.Reader) *Reader { + return c.Lift(unsafe.Pointer(uintptr(readUint64(reader)))) +} + +func (c FfiConverterReader) Lower(value *Reader) unsafe.Pointer { + // TODO: this is bad - all synchronization from ObjectRuntime.go is discarded here, + // because the pointer will be decremented immediately after this function returns, + // and someone will be left holding onto a non-locked pointer. + pointer := value.ffiObject.incrementPointer("*Reader") + defer value.ffiObject.decrementPointer() + return pointer +} + +func (c FfiConverterReader) Write(writer io.Writer, value *Reader) { + writeUint64(writer, uint64(uintptr(c.Lower(value)))) +} + +type FfiDestroyerReader struct{} + +func (_ FfiDestroyerReader) Destroy(value *Reader) { + value.Destroy() +} + +type Error struct { + err error +} + +func (err Error) Error() string { + return fmt.Sprintf("Error: %s", err.err.Error()) +} + +func (err Error) Unwrap() error { + return err.err +} + +// Err* are used for checking error type with `errors.Is` +var ErrErrorAssertion = fmt.Errorf("ErrorAssertion") +var ErrErrorAssertionNotFound = fmt.Errorf("ErrorAssertionNotFound") +var ErrErrorDecoding = fmt.Errorf("ErrorDecoding") +var ErrErrorEncoding = fmt.Errorf("ErrorEncoding") +var ErrErrorFileNotFound = fmt.Errorf("ErrorFileNotFound") +var ErrErrorIo = fmt.Errorf("ErrorIo") +var ErrErrorJson = fmt.Errorf("ErrorJson") +var ErrErrorManifest = fmt.Errorf("ErrorManifest") +var ErrErrorManifestNotFound = fmt.Errorf("ErrorManifestNotFound") +var ErrErrorNotSupported = fmt.Errorf("ErrorNotSupported") +var ErrErrorOther = fmt.Errorf("ErrorOther") +var ErrErrorRemoteManifest = fmt.Errorf("ErrorRemoteManifest") +var ErrErrorResourceNotFound = fmt.Errorf("ErrorResourceNotFound") +var ErrErrorRwLock = fmt.Errorf("ErrorRwLock") +var ErrErrorSignature = fmt.Errorf("ErrorSignature") +var ErrErrorVerify = fmt.Errorf("ErrorVerify") + +// Variant structs +type ErrorAssertion struct { + Reason string +} + +func NewErrorAssertion( + reason string, +) *Error { + return &Error{ + err: &ErrorAssertion{ + Reason: reason, + }, + } +} + +func (err ErrorAssertion) Error() string { + return fmt.Sprint("Assertion", + ": ", + + "Reason=", + err.Reason, + ) +} + +func (self ErrorAssertion) Is(target error) bool { + return target == ErrErrorAssertion +} + +type ErrorAssertionNotFound struct { + Reason string +} + +func NewErrorAssertionNotFound( + reason string, +) *Error { + return &Error{ + err: &ErrorAssertionNotFound{ + Reason: reason, + }, + } +} + +func (err ErrorAssertionNotFound) Error() string { + return fmt.Sprint("AssertionNotFound", + ": ", + + "Reason=", + err.Reason, + ) +} + +func (self ErrorAssertionNotFound) Is(target error) bool { + return target == ErrErrorAssertionNotFound +} + +type ErrorDecoding struct { + Reason string +} + +func NewErrorDecoding( + reason string, +) *Error { + return &Error{ + err: &ErrorDecoding{ + Reason: reason, + }, + } +} + +func (err ErrorDecoding) Error() string { + return fmt.Sprint("Decoding", + ": ", + + "Reason=", + err.Reason, + ) +} + +func (self ErrorDecoding) Is(target error) bool { + return target == ErrErrorDecoding +} + +type ErrorEncoding struct { + Reason string +} + +func NewErrorEncoding( + reason string, +) *Error { + return &Error{ + err: &ErrorEncoding{ + Reason: reason, + }, + } +} + +func (err ErrorEncoding) Error() string { + return fmt.Sprint("Encoding", + ": ", + + "Reason=", + err.Reason, + ) +} + +func (self ErrorEncoding) Is(target error) bool { + return target == ErrErrorEncoding +} + +type ErrorFileNotFound struct { + Reason string +} + +func NewErrorFileNotFound( + reason string, +) *Error { + return &Error{ + err: &ErrorFileNotFound{ + Reason: reason, + }, + } +} + +func (err ErrorFileNotFound) Error() string { + return fmt.Sprint("FileNotFound", + ": ", + + "Reason=", + err.Reason, + ) +} + +func (self ErrorFileNotFound) Is(target error) bool { + return target == ErrErrorFileNotFound +} + +type ErrorIo struct { + Reason string +} + +func NewErrorIo( + reason string, +) *Error { + return &Error{ + err: &ErrorIo{ + Reason: reason, + }, + } +} + +func (err ErrorIo) Error() string { + return fmt.Sprint("Io", + ": ", + + "Reason=", + err.Reason, + ) +} + +func (self ErrorIo) Is(target error) bool { + return target == ErrErrorIo +} + +type ErrorJson struct { + Reason string +} + +func NewErrorJson( + reason string, +) *Error { + return &Error{ + err: &ErrorJson{ + Reason: reason, + }, + } +} + +func (err ErrorJson) Error() string { + return fmt.Sprint("Json", + ": ", + + "Reason=", + err.Reason, + ) +} + +func (self ErrorJson) Is(target error) bool { + return target == ErrErrorJson +} + +type ErrorManifest struct { + Reason string +} + +func NewErrorManifest( + reason string, +) *Error { + return &Error{ + err: &ErrorManifest{ + Reason: reason, + }, + } +} + +func (err ErrorManifest) Error() string { + return fmt.Sprint("Manifest", + ": ", + + "Reason=", + err.Reason, + ) +} + +func (self ErrorManifest) Is(target error) bool { + return target == ErrErrorManifest +} + +type ErrorManifestNotFound struct { + Reason string +} + +func NewErrorManifestNotFound( + reason string, +) *Error { + return &Error{ + err: &ErrorManifestNotFound{ + Reason: reason, + }, + } +} + +func (err ErrorManifestNotFound) Error() string { + return fmt.Sprint("ManifestNotFound", + ": ", + + "Reason=", + err.Reason, + ) +} + +func (self ErrorManifestNotFound) Is(target error) bool { + return target == ErrErrorManifestNotFound +} + +type ErrorNotSupported struct { + Reason string +} + +func NewErrorNotSupported( + reason string, +) *Error { + return &Error{ + err: &ErrorNotSupported{ + Reason: reason, + }, + } +} + +func (err ErrorNotSupported) Error() string { + return fmt.Sprint("NotSupported", + ": ", + + "Reason=", + err.Reason, + ) +} + +func (self ErrorNotSupported) Is(target error) bool { + return target == ErrErrorNotSupported +} + +type ErrorOther struct { + Reason string +} + +func NewErrorOther( + reason string, +) *Error { + return &Error{ + err: &ErrorOther{ + Reason: reason, + }, + } +} + +func (err ErrorOther) Error() string { + return fmt.Sprint("Other", + ": ", + + "Reason=", + err.Reason, + ) +} + +func (self ErrorOther) Is(target error) bool { + return target == ErrErrorOther +} + +type ErrorRemoteManifest struct { + Reason string +} + +func NewErrorRemoteManifest( + reason string, +) *Error { + return &Error{ + err: &ErrorRemoteManifest{ + Reason: reason, + }, + } +} + +func (err ErrorRemoteManifest) Error() string { + return fmt.Sprint("RemoteManifest", + ": ", + + "Reason=", + err.Reason, + ) +} + +func (self ErrorRemoteManifest) Is(target error) bool { + return target == ErrErrorRemoteManifest +} + +type ErrorResourceNotFound struct { + Reason string +} + +func NewErrorResourceNotFound( + reason string, +) *Error { + return &Error{ + err: &ErrorResourceNotFound{ + Reason: reason, + }, + } +} + +func (err ErrorResourceNotFound) Error() string { + return fmt.Sprint("ResourceNotFound", + ": ", + + "Reason=", + err.Reason, + ) +} + +func (self ErrorResourceNotFound) Is(target error) bool { + return target == ErrErrorResourceNotFound +} + +type ErrorRwLock struct { +} + +func NewErrorRwLock() *Error { + return &Error{ + err: &ErrorRwLock{}, + } +} + +func (err ErrorRwLock) Error() string { + return fmt.Sprint("RwLock") +} + +func (self ErrorRwLock) Is(target error) bool { + return target == ErrErrorRwLock +} + +type ErrorSignature struct { + Reason string +} + +func NewErrorSignature( + reason string, +) *Error { + return &Error{ + err: &ErrorSignature{ + Reason: reason, + }, + } +} + +func (err ErrorSignature) Error() string { + return fmt.Sprint("Signature", + ": ", + + "Reason=", + err.Reason, + ) +} + +func (self ErrorSignature) Is(target error) bool { + return target == ErrErrorSignature +} + +type ErrorVerify struct { + Reason string +} + +func NewErrorVerify( + reason string, +) *Error { + return &Error{ + err: &ErrorVerify{ + Reason: reason, + }, + } +} + +func (err ErrorVerify) Error() string { + return fmt.Sprint("Verify", + ": ", + + "Reason=", + err.Reason, + ) +} + +func (self ErrorVerify) Is(target error) bool { + return target == ErrErrorVerify +} + +type FfiConverterTypeError struct{} + +var FfiConverterTypeErrorINSTANCE = FfiConverterTypeError{} + +func (c FfiConverterTypeError) Lift(eb RustBufferI) error { + return LiftFromRustBuffer[error](c, eb) +} + +func (c FfiConverterTypeError) Lower(value *Error) RustBuffer { + return LowerIntoRustBuffer[*Error](c, value) +} + +func (c FfiConverterTypeError) Read(reader io.Reader) error { + errorID := readUint32(reader) + + switch errorID { + case 1: + return &Error{&ErrorAssertion{ + Reason: FfiConverterStringINSTANCE.Read(reader), + }} + case 2: + return &Error{&ErrorAssertionNotFound{ + Reason: FfiConverterStringINSTANCE.Read(reader), + }} + case 3: + return &Error{&ErrorDecoding{ + Reason: FfiConverterStringINSTANCE.Read(reader), + }} + case 4: + return &Error{&ErrorEncoding{ + Reason: FfiConverterStringINSTANCE.Read(reader), + }} + case 5: + return &Error{&ErrorFileNotFound{ + Reason: FfiConverterStringINSTANCE.Read(reader), + }} + case 6: + return &Error{&ErrorIo{ + Reason: FfiConverterStringINSTANCE.Read(reader), + }} + case 7: + return &Error{&ErrorJson{ + Reason: FfiConverterStringINSTANCE.Read(reader), + }} + case 8: + return &Error{&ErrorManifest{ + Reason: FfiConverterStringINSTANCE.Read(reader), + }} + case 9: + return &Error{&ErrorManifestNotFound{ + Reason: FfiConverterStringINSTANCE.Read(reader), + }} + case 10: + return &Error{&ErrorNotSupported{ + Reason: FfiConverterStringINSTANCE.Read(reader), + }} + case 11: + return &Error{&ErrorOther{ + Reason: FfiConverterStringINSTANCE.Read(reader), + }} + case 12: + return &Error{&ErrorRemoteManifest{ + Reason: FfiConverterStringINSTANCE.Read(reader), + }} + case 13: + return &Error{&ErrorResourceNotFound{ + Reason: FfiConverterStringINSTANCE.Read(reader), + }} + case 14: + return &Error{&ErrorRwLock{}} + case 15: + return &Error{&ErrorSignature{ + Reason: FfiConverterStringINSTANCE.Read(reader), + }} + case 16: + return &Error{&ErrorVerify{ + Reason: FfiConverterStringINSTANCE.Read(reader), + }} + default: + panic(fmt.Sprintf("Unknown error code %d in FfiConverterTypeError.Read()", errorID)) + } +} + +func (c FfiConverterTypeError) Write(writer io.Writer, value *Error) { + switch variantValue := value.err.(type) { + case *ErrorAssertion: + writeInt32(writer, 1) + FfiConverterStringINSTANCE.Write(writer, variantValue.Reason) + case *ErrorAssertionNotFound: + writeInt32(writer, 2) + FfiConverterStringINSTANCE.Write(writer, variantValue.Reason) + case *ErrorDecoding: + writeInt32(writer, 3) + FfiConverterStringINSTANCE.Write(writer, variantValue.Reason) + case *ErrorEncoding: + writeInt32(writer, 4) + FfiConverterStringINSTANCE.Write(writer, variantValue.Reason) + case *ErrorFileNotFound: + writeInt32(writer, 5) + FfiConverterStringINSTANCE.Write(writer, variantValue.Reason) + case *ErrorIo: + writeInt32(writer, 6) + FfiConverterStringINSTANCE.Write(writer, variantValue.Reason) + case *ErrorJson: + writeInt32(writer, 7) + FfiConverterStringINSTANCE.Write(writer, variantValue.Reason) + case *ErrorManifest: + writeInt32(writer, 8) + FfiConverterStringINSTANCE.Write(writer, variantValue.Reason) + case *ErrorManifestNotFound: + writeInt32(writer, 9) + FfiConverterStringINSTANCE.Write(writer, variantValue.Reason) + case *ErrorNotSupported: + writeInt32(writer, 10) + FfiConverterStringINSTANCE.Write(writer, variantValue.Reason) + case *ErrorOther: + writeInt32(writer, 11) + FfiConverterStringINSTANCE.Write(writer, variantValue.Reason) + case *ErrorRemoteManifest: + writeInt32(writer, 12) + FfiConverterStringINSTANCE.Write(writer, variantValue.Reason) + case *ErrorResourceNotFound: + writeInt32(writer, 13) + FfiConverterStringINSTANCE.Write(writer, variantValue.Reason) + case *ErrorRwLock: + writeInt32(writer, 14) + case *ErrorSignature: + writeInt32(writer, 15) + FfiConverterStringINSTANCE.Write(writer, variantValue.Reason) + case *ErrorVerify: + writeInt32(writer, 16) + FfiConverterStringINSTANCE.Write(writer, variantValue.Reason) + default: + _ = variantValue + panic(fmt.Sprintf("invalid error value `%v` in FfiConverterTypeError.Write", value)) + } +} + +type SeekMode uint + +const ( + SeekModeStart SeekMode = 1 + SeekModeEnd SeekMode = 2 + SeekModeCurrent SeekMode = 3 +) + +type FfiConverterTypeSeekMode struct{} + +var FfiConverterTypeSeekModeINSTANCE = FfiConverterTypeSeekMode{} + +func (c FfiConverterTypeSeekMode) Lift(rb RustBufferI) SeekMode { + return LiftFromRustBuffer[SeekMode](c, rb) +} + +func (c FfiConverterTypeSeekMode) Lower(value SeekMode) RustBuffer { + return LowerIntoRustBuffer[SeekMode](c, value) +} +func (FfiConverterTypeSeekMode) Read(reader io.Reader) SeekMode { + id := readInt32(reader) + return SeekMode(id) +} + +func (FfiConverterTypeSeekMode) Write(writer io.Writer, value SeekMode) { + writeInt32(writer, int32(value)) +} + +type FfiDestroyerTypeSeekMode struct{} + +func (_ FfiDestroyerTypeSeekMode) Destroy(value SeekMode) { +} + +type SigningAlg uint + +const ( + SigningAlgEs256 SigningAlg = 1 + SigningAlgEs384 SigningAlg = 2 + SigningAlgEs512 SigningAlg = 3 + SigningAlgPs256 SigningAlg = 4 + SigningAlgPs384 SigningAlg = 5 + SigningAlgPs512 SigningAlg = 6 + SigningAlgEd25519 SigningAlg = 7 +) + +type FfiConverterTypeSigningAlg struct{} + +var FfiConverterTypeSigningAlgINSTANCE = FfiConverterTypeSigningAlg{} + +func (c FfiConverterTypeSigningAlg) Lift(rb RustBufferI) SigningAlg { + return LiftFromRustBuffer[SigningAlg](c, rb) +} + +func (c FfiConverterTypeSigningAlg) Lower(value SigningAlg) RustBuffer { + return LowerIntoRustBuffer[SigningAlg](c, value) +} +func (FfiConverterTypeSigningAlg) Read(reader io.Reader) SigningAlg { + id := readInt32(reader) + return SigningAlg(id) +} + +func (FfiConverterTypeSigningAlg) Write(writer io.Writer, value SigningAlg) { + writeInt32(writer, int32(value)) +} + +type FfiDestroyerTypeSigningAlg struct{} + +func (_ FfiDestroyerTypeSigningAlg) Destroy(value SigningAlg) { +} + +type uniffiCallbackResult C.int32_t + +const ( + uniffiIdxCallbackFree uniffiCallbackResult = 0 + uniffiCallbackResultSuccess uniffiCallbackResult = 0 + uniffiCallbackResultError uniffiCallbackResult = 1 + uniffiCallbackUnexpectedResultError uniffiCallbackResult = 2 + uniffiCallbackCancelled uniffiCallbackResult = 3 +) + +type concurrentHandleMap[T any] struct { + leftMap map[uint64]*T + rightMap map[*T]uint64 + currentHandle uint64 + lock sync.RWMutex +} + +func newConcurrentHandleMap[T any]() *concurrentHandleMap[T] { + return &concurrentHandleMap[T]{ + leftMap: map[uint64]*T{}, + rightMap: map[*T]uint64{}, + } +} + +func (cm *concurrentHandleMap[T]) insert(obj *T) uint64 { + cm.lock.Lock() + defer cm.lock.Unlock() + + if existingHandle, ok := cm.rightMap[obj]; ok { + return existingHandle + } + cm.currentHandle = cm.currentHandle + 1 + cm.leftMap[cm.currentHandle] = obj + cm.rightMap[obj] = cm.currentHandle + return cm.currentHandle +} + +func (cm *concurrentHandleMap[T]) remove(handle uint64) bool { + cm.lock.Lock() + defer cm.lock.Unlock() + + if val, ok := cm.leftMap[handle]; ok { + delete(cm.leftMap, handle) + delete(cm.rightMap, val) + } + return false +} + +func (cm *concurrentHandleMap[T]) tryGet(handle uint64) (*T, bool) { + cm.lock.RLock() + defer cm.lock.RUnlock() + + val, ok := cm.leftMap[handle] + return val, ok +} + +type FfiConverterCallbackInterface[CallbackInterface any] struct { + handleMap *concurrentHandleMap[CallbackInterface] +} + +func (c *FfiConverterCallbackInterface[CallbackInterface]) drop(handle uint64) RustBuffer { + c.handleMap.remove(handle) + return RustBuffer{} +} + +func (c *FfiConverterCallbackInterface[CallbackInterface]) Lift(handle uint64) CallbackInterface { + val, ok := c.handleMap.tryGet(handle) + if !ok { + panic(fmt.Errorf("no callback in handle map: %d", handle)) + } + return *val +} + +func (c *FfiConverterCallbackInterface[CallbackInterface]) Read(reader io.Reader) CallbackInterface { + return c.Lift(readUint64(reader)) +} + +func (c *FfiConverterCallbackInterface[CallbackInterface]) Lower(value CallbackInterface) C.uint64_t { + return C.uint64_t(c.handleMap.insert(&value)) +} + +func (c *FfiConverterCallbackInterface[CallbackInterface]) Write(writer io.Writer, value CallbackInterface) { + writeUint64(writer, uint64(c.Lower(value))) +} + +type SignerCallback interface { + Sign(data []byte) ([]byte, *Error) +} + +// foreignCallbackCallbackInterfaceSignerCallback cannot be callable be a compiled function at a same time +type foreignCallbackCallbackInterfaceSignerCallback struct{} + +//export c2pa_cgo_SignerCallback +func c2pa_cgo_SignerCallback(handle C.uint64_t, method C.int32_t, argsPtr *C.uint8_t, argsLen C.int32_t, outBuf *C.RustBuffer) C.int32_t { + cb := FfiConverterCallbackInterfaceSignerCallbackINSTANCE.Lift(uint64(handle)) + switch method { + case 0: + // 0 means Rust is done with the callback, and the callback + // can be dropped by the foreign language. + *outBuf = FfiConverterCallbackInterfaceSignerCallbackINSTANCE.drop(uint64(handle)) + // See docs of ForeignCallback in `uniffi/src/ffi/foreigncallbacks.rs` + return C.int32_t(uniffiIdxCallbackFree) + + case 1: + var result uniffiCallbackResult + args := unsafe.Slice((*byte)(argsPtr), argsLen) + result = foreignCallbackCallbackInterfaceSignerCallback{}.InvokeSign(cb, args, outBuf) + return C.int32_t(result) + + default: + // This should never happen, because an out of bounds method index won't + // ever be used. Once we can catch errors, we should return an InternalException. + // https://github.com/mozilla/uniffi-rs/issues/351 + return C.int32_t(uniffiCallbackUnexpectedResultError) + } +} + +func (foreignCallbackCallbackInterfaceSignerCallback) InvokeSign(callback SignerCallback, args []byte, outBuf *C.RustBuffer) uniffiCallbackResult { + reader := bytes.NewReader(args) + result, err := callback.Sign(FfiConverterBytesINSTANCE.Read(reader)) + + if err != nil { + // The only way to bypass an unexpected error is to bypass pointer to an empty + // instance of the error + if err.err == nil { + return uniffiCallbackUnexpectedResultError + } + *outBuf = LowerIntoRustBuffer[*Error](FfiConverterTypeErrorINSTANCE, err) + return uniffiCallbackResultError + } + *outBuf = LowerIntoRustBuffer[[]byte](FfiConverterBytesINSTANCE, result) + return uniffiCallbackResultSuccess +} + +type FfiConverterCallbackInterfaceSignerCallback struct { + FfiConverterCallbackInterface[SignerCallback] +} + +var FfiConverterCallbackInterfaceSignerCallbackINSTANCE = &FfiConverterCallbackInterfaceSignerCallback{ + FfiConverterCallbackInterface: FfiConverterCallbackInterface[SignerCallback]{ + handleMap: newConcurrentHandleMap[SignerCallback](), + }, +} + +// This is a static function because only 1 instance is supported for registering +func (c *FfiConverterCallbackInterfaceSignerCallback) register() { + rustCall(func(status *C.RustCallStatus) int32 { + C.uniffi_c2pa_fn_init_callback_signercallback(C.ForeignCallback(C.c2pa_cgo_SignerCallback), status) + return 0 + }) +} + +type FfiDestroyerCallbackInterfaceSignerCallback struct{} + +func (FfiDestroyerCallbackInterfaceSignerCallback) Destroy(value SignerCallback) { +} + +type Stream interface { + ReadStream(length uint64) ([]byte, *Error) + + SeekStream(pos int64, mode SeekMode) (uint64, *Error) + + WriteStream(data []byte) (uint64, *Error) +} + +// foreignCallbackCallbackInterfaceStream cannot be callable be a compiled function at a same time +type foreignCallbackCallbackInterfaceStream struct{} + +//export c2pa_cgo_Stream +func c2pa_cgo_Stream(handle C.uint64_t, method C.int32_t, argsPtr *C.uint8_t, argsLen C.int32_t, outBuf *C.RustBuffer) C.int32_t { + cb := FfiConverterCallbackInterfaceStreamINSTANCE.Lift(uint64(handle)) + switch method { + case 0: + // 0 means Rust is done with the callback, and the callback + // can be dropped by the foreign language. + *outBuf = FfiConverterCallbackInterfaceStreamINSTANCE.drop(uint64(handle)) + // See docs of ForeignCallback in `uniffi/src/ffi/foreigncallbacks.rs` + return C.int32_t(uniffiIdxCallbackFree) + + case 1: + var result uniffiCallbackResult + args := unsafe.Slice((*byte)(argsPtr), argsLen) + result = foreignCallbackCallbackInterfaceStream{}.InvokeReadStream(cb, args, outBuf) + return C.int32_t(result) + case 2: + var result uniffiCallbackResult + args := unsafe.Slice((*byte)(argsPtr), argsLen) + result = foreignCallbackCallbackInterfaceStream{}.InvokeSeekStream(cb, args, outBuf) + return C.int32_t(result) + case 3: + var result uniffiCallbackResult + args := unsafe.Slice((*byte)(argsPtr), argsLen) + result = foreignCallbackCallbackInterfaceStream{}.InvokeWriteStream(cb, args, outBuf) + return C.int32_t(result) + + default: + // This should never happen, because an out of bounds method index won't + // ever be used. Once we can catch errors, we should return an InternalException. + // https://github.com/mozilla/uniffi-rs/issues/351 + return C.int32_t(uniffiCallbackUnexpectedResultError) + } +} + +func (foreignCallbackCallbackInterfaceStream) InvokeReadStream(callback Stream, args []byte, outBuf *C.RustBuffer) uniffiCallbackResult { + reader := bytes.NewReader(args) + result, err := callback.ReadStream(FfiConverterUint64INSTANCE.Read(reader)) + + if err != nil { + // The only way to bypass an unexpected error is to bypass pointer to an empty + // instance of the error + if err.err == nil { + return uniffiCallbackUnexpectedResultError + } + *outBuf = LowerIntoRustBuffer[*Error](FfiConverterTypeErrorINSTANCE, err) + return uniffiCallbackResultError + } + *outBuf = LowerIntoRustBuffer[[]byte](FfiConverterBytesINSTANCE, result) + return uniffiCallbackResultSuccess +} +func (foreignCallbackCallbackInterfaceStream) InvokeSeekStream(callback Stream, args []byte, outBuf *C.RustBuffer) uniffiCallbackResult { + reader := bytes.NewReader(args) + result, err := callback.SeekStream(FfiConverterInt64INSTANCE.Read(reader), FfiConverterTypeSeekModeINSTANCE.Read(reader)) + + if err != nil { + // The only way to bypass an unexpected error is to bypass pointer to an empty + // instance of the error + if err.err == nil { + return uniffiCallbackUnexpectedResultError + } + *outBuf = LowerIntoRustBuffer[*Error](FfiConverterTypeErrorINSTANCE, err) + return uniffiCallbackResultError + } + *outBuf = LowerIntoRustBuffer[uint64](FfiConverterUint64INSTANCE, result) + return uniffiCallbackResultSuccess +} +func (foreignCallbackCallbackInterfaceStream) InvokeWriteStream(callback Stream, args []byte, outBuf *C.RustBuffer) uniffiCallbackResult { + reader := bytes.NewReader(args) + result, err := callback.WriteStream(FfiConverterBytesINSTANCE.Read(reader)) + + if err != nil { + // The only way to bypass an unexpected error is to bypass pointer to an empty + // instance of the error + if err.err == nil { + return uniffiCallbackUnexpectedResultError + } + *outBuf = LowerIntoRustBuffer[*Error](FfiConverterTypeErrorINSTANCE, err) + return uniffiCallbackResultError + } + *outBuf = LowerIntoRustBuffer[uint64](FfiConverterUint64INSTANCE, result) + return uniffiCallbackResultSuccess +} + +type FfiConverterCallbackInterfaceStream struct { + FfiConverterCallbackInterface[Stream] +} + +var FfiConverterCallbackInterfaceStreamINSTANCE = &FfiConverterCallbackInterfaceStream{ + FfiConverterCallbackInterface: FfiConverterCallbackInterface[Stream]{ + handleMap: newConcurrentHandleMap[Stream](), + }, +} + +// This is a static function because only 1 instance is supported for registering +func (c *FfiConverterCallbackInterfaceStream) register() { + rustCall(func(status *C.RustCallStatus) int32 { + C.uniffi_c2pa_fn_init_callback_stream(C.ForeignCallback(C.c2pa_cgo_Stream), status) + return 0 + }) +} + +type FfiDestroyerCallbackInterfaceStream struct{} + +func (FfiDestroyerCallbackInterfaceStream) Destroy(value Stream) { +} + +type FfiConverterOptionalString struct{} + +var FfiConverterOptionalStringINSTANCE = FfiConverterOptionalString{} + +func (c FfiConverterOptionalString) Lift(rb RustBufferI) *string { + return LiftFromRustBuffer[*string](c, rb) +} + +func (_ FfiConverterOptionalString) Read(reader io.Reader) *string { + if readInt8(reader) == 0 { + return nil + } + temp := FfiConverterStringINSTANCE.Read(reader) + return &temp +} + +func (c FfiConverterOptionalString) Lower(value *string) RustBuffer { + return LowerIntoRustBuffer[*string](c, value) +} + +func (_ FfiConverterOptionalString) Write(writer io.Writer, value *string) { + if value == nil { + writeInt8(writer, 0) + } else { + writeInt8(writer, 1) + FfiConverterStringINSTANCE.Write(writer, *value) + } +} + +type FfiDestroyerOptionalString struct{} + +func (_ FfiDestroyerOptionalString) Destroy(value *string) { + if value != nil { + FfiDestroyerString{}.Destroy(*value) + } +} + +func SdkVersion() string { + return FfiConverterStringINSTANCE.Lift(rustCall(func(_uniffiStatus *C.RustCallStatus) RustBufferI { + return C.uniffi_c2pa_fn_func_sdk_version(_uniffiStatus) + })) +} + +func Version() string { + return FfiConverterStringINSTANCE.Lift(rustCall(func(_uniffiStatus *C.RustCallStatus) RustBufferI { + return C.uniffi_c2pa_fn_func_version(_uniffiStatus) + })) +} diff --git a/src/c2pa/c2pa.h b/src/c2pa/c2pa.h new file mode 100644 index 00000000..39ea6801 --- /dev/null +++ b/src/c2pa/c2pa.h @@ -0,0 +1,559 @@ + + +// This file was autogenerated by some hot garbage in the `uniffi` crate. +// Trust me, you don't want to mess with it! + + + +#include +#include + +// The following structs are used to implement the lowest level +// of the FFI, and thus useful to multiple uniffied crates. +// We ensure they are declared exactly once, with a header guard, UNIFFI_SHARED_H. +#ifdef UNIFFI_SHARED_H + // We also try to prevent mixing versions of shared uniffi header structs. + // If you add anything to the #else block, you must increment the version suffix in UNIFFI_SHARED_HEADER_V6 + #ifndef UNIFFI_SHARED_HEADER_V6 + #error Combining helper code from multiple versions of uniffi is not supported + #endif // ndef UNIFFI_SHARED_HEADER_V6 +#else +#define UNIFFI_SHARED_H +#define UNIFFI_SHARED_HEADER_V6 +// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️ +// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V6 in this file. ⚠️ + +typedef struct RustBuffer { + int32_t capacity; + int32_t len; + uint8_t *data; +} RustBuffer; + +typedef int32_t (*ForeignCallback)(uint64_t, int32_t, uint8_t *, int32_t, RustBuffer *); + +// Task defined in Rust that Go executes +typedef void (*RustTaskCallback)(const void *, int8_t); + +// Callback to execute Rust tasks using a Go routine +// +// Args: +// executor: ForeignExecutor lowered into a uint64_t value +// delay: Delay in MS +// task: RustTaskCallback to call +// task_data: data to pass the task callback +typedef int8_t (*ForeignExecutorCallback)(uint64_t, uint32_t, RustTaskCallback, void *); + +typedef struct ForeignBytes { + int32_t len; + const uint8_t *data; +} ForeignBytes; + +// Error definitions +typedef struct RustCallStatus { + int8_t code; + RustBuffer errorBuf; +} RustCallStatus; + +// Continuation callback for UniFFI Futures +typedef void (*RustFutureContinuation)(void * , int8_t); + +// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️ +// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V6 in this file. ⚠️ +#endif // def UNIFFI_SHARED_H + +// Needed because we can't execute the callback directly from go. +void cgo_rust_task_callback_bridge_c2pa(RustTaskCallback, const void *, int8_t); + +int8_t uniffiForeignExecutorCallbackc2pa(uint64_t, uint32_t, RustTaskCallback, void*); + +void uniffiFutureContinuationCallbackc2pa(void*, int8_t); + +void uniffi_c2pa_fn_free_builder( + void* ptr, + RustCallStatus* out_status +); + +void* uniffi_c2pa_fn_constructor_builder_new( + RustCallStatus* out_status +); + +void uniffi_c2pa_fn_method_builder_add_ingredient( + void* ptr, + RustBuffer ingredient_json, + RustBuffer format, + uint64_t stream, + RustCallStatus* out_status +); + +void uniffi_c2pa_fn_method_builder_add_resource( + void* ptr, + RustBuffer uri, + uint64_t stream, + RustCallStatus* out_status +); + +void uniffi_c2pa_fn_method_builder_from_archive( + void* ptr, + uint64_t stream, + RustCallStatus* out_status +); + +RustBuffer uniffi_c2pa_fn_method_builder_sign( + void* ptr, + RustBuffer format, + uint64_t input, + uint64_t output, + void* signer, + RustCallStatus* out_status +); + +void uniffi_c2pa_fn_method_builder_to_archive( + void* ptr, + uint64_t stream, + RustCallStatus* out_status +); + +void uniffi_c2pa_fn_method_builder_with_json( + void* ptr, + RustBuffer json, + RustCallStatus* out_status +); + +void uniffi_c2pa_fn_free_callbacksigner( + void* ptr, + RustCallStatus* out_status +); + +void* uniffi_c2pa_fn_constructor_callbacksigner_new( + uint64_t callback, + RustBuffer alg, + RustBuffer certs, + RustBuffer ta_url, + RustCallStatus* out_status +); + +void uniffi_c2pa_fn_free_reader( + void* ptr, + RustCallStatus* out_status +); + +void* uniffi_c2pa_fn_constructor_reader_new( + RustCallStatus* out_status +); + +RustBuffer uniffi_c2pa_fn_method_reader_from_stream( + void* ptr, + RustBuffer format, + uint64_t reader, + RustCallStatus* out_status +); + +RustBuffer uniffi_c2pa_fn_method_reader_json( + void* ptr, + RustCallStatus* out_status +); + +uint64_t uniffi_c2pa_fn_method_reader_resource_to_stream( + void* ptr, + RustBuffer uri, + uint64_t stream, + RustCallStatus* out_status +); + +void uniffi_c2pa_fn_init_callback_signercallback( + ForeignCallback callback_stub, + RustCallStatus* out_status +); + +void uniffi_c2pa_fn_init_callback_stream( + ForeignCallback callback_stub, + RustCallStatus* out_status +); + +RustBuffer uniffi_c2pa_fn_func_sdk_version( + RustCallStatus* out_status +); + +RustBuffer uniffi_c2pa_fn_func_version( + RustCallStatus* out_status +); + +RustBuffer ffi_c2pa_rustbuffer_alloc( + int32_t size, + RustCallStatus* out_status +); + +RustBuffer ffi_c2pa_rustbuffer_from_bytes( + ForeignBytes bytes, + RustCallStatus* out_status +); + +void ffi_c2pa_rustbuffer_free( + RustBuffer buf, + RustCallStatus* out_status +); + +RustBuffer ffi_c2pa_rustbuffer_reserve( + RustBuffer buf, + int32_t additional, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_continuation_callback_set( + RustFutureContinuation callback, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_poll_u8( + void* handle, + void* uniffi_callback, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_cancel_u8( + void* handle, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_free_u8( + void* handle, + RustCallStatus* out_status +); + +uint8_t ffi_c2pa_rust_future_complete_u8( + void* handle, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_poll_i8( + void* handle, + void* uniffi_callback, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_cancel_i8( + void* handle, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_free_i8( + void* handle, + RustCallStatus* out_status +); + +int8_t ffi_c2pa_rust_future_complete_i8( + void* handle, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_poll_u16( + void* handle, + void* uniffi_callback, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_cancel_u16( + void* handle, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_free_u16( + void* handle, + RustCallStatus* out_status +); + +uint16_t ffi_c2pa_rust_future_complete_u16( + void* handle, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_poll_i16( + void* handle, + void* uniffi_callback, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_cancel_i16( + void* handle, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_free_i16( + void* handle, + RustCallStatus* out_status +); + +int16_t ffi_c2pa_rust_future_complete_i16( + void* handle, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_poll_u32( + void* handle, + void* uniffi_callback, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_cancel_u32( + void* handle, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_free_u32( + void* handle, + RustCallStatus* out_status +); + +uint32_t ffi_c2pa_rust_future_complete_u32( + void* handle, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_poll_i32( + void* handle, + void* uniffi_callback, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_cancel_i32( + void* handle, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_free_i32( + void* handle, + RustCallStatus* out_status +); + +int32_t ffi_c2pa_rust_future_complete_i32( + void* handle, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_poll_u64( + void* handle, + void* uniffi_callback, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_cancel_u64( + void* handle, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_free_u64( + void* handle, + RustCallStatus* out_status +); + +uint64_t ffi_c2pa_rust_future_complete_u64( + void* handle, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_poll_i64( + void* handle, + void* uniffi_callback, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_cancel_i64( + void* handle, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_free_i64( + void* handle, + RustCallStatus* out_status +); + +int64_t ffi_c2pa_rust_future_complete_i64( + void* handle, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_poll_f32( + void* handle, + void* uniffi_callback, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_cancel_f32( + void* handle, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_free_f32( + void* handle, + RustCallStatus* out_status +); + +float ffi_c2pa_rust_future_complete_f32( + void* handle, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_poll_f64( + void* handle, + void* uniffi_callback, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_cancel_f64( + void* handle, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_free_f64( + void* handle, + RustCallStatus* out_status +); + +double ffi_c2pa_rust_future_complete_f64( + void* handle, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_poll_pointer( + void* handle, + void* uniffi_callback, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_cancel_pointer( + void* handle, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_free_pointer( + void* handle, + RustCallStatus* out_status +); + +void* ffi_c2pa_rust_future_complete_pointer( + void* handle, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_poll_rust_buffer( + void* handle, + void* uniffi_callback, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_cancel_rust_buffer( + void* handle, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_free_rust_buffer( + void* handle, + RustCallStatus* out_status +); + +RustBuffer ffi_c2pa_rust_future_complete_rust_buffer( + void* handle, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_poll_void( + void* handle, + void* uniffi_callback, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_cancel_void( + void* handle, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_free_void( + void* handle, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_complete_void( + void* handle, + RustCallStatus* out_status +); + +uint16_t uniffi_c2pa_checksum_func_sdk_version( + RustCallStatus* out_status +); + +uint16_t uniffi_c2pa_checksum_func_version( + RustCallStatus* out_status +); + +uint16_t uniffi_c2pa_checksum_method_builder_add_ingredient( + RustCallStatus* out_status +); + +uint16_t uniffi_c2pa_checksum_method_builder_add_resource( + RustCallStatus* out_status +); + +uint16_t uniffi_c2pa_checksum_method_builder_from_archive( + RustCallStatus* out_status +); + +uint16_t uniffi_c2pa_checksum_method_builder_sign( + RustCallStatus* out_status +); + +uint16_t uniffi_c2pa_checksum_method_builder_to_archive( + RustCallStatus* out_status +); + +uint16_t uniffi_c2pa_checksum_method_builder_with_json( + RustCallStatus* out_status +); + +uint16_t uniffi_c2pa_checksum_method_reader_from_stream( + RustCallStatus* out_status +); + +uint16_t uniffi_c2pa_checksum_method_reader_json( + RustCallStatus* out_status +); + +uint16_t uniffi_c2pa_checksum_method_reader_resource_to_stream( + RustCallStatus* out_status +); + +uint16_t uniffi_c2pa_checksum_constructor_builder_new( + RustCallStatus* out_status +); + +uint16_t uniffi_c2pa_checksum_constructor_callbacksigner_new( + RustCallStatus* out_status +); + +uint16_t uniffi_c2pa_checksum_constructor_reader_new( + RustCallStatus* out_status +); + +uint16_t uniffi_c2pa_checksum_method_signercallback_sign( + RustCallStatus* out_status +); + +uint16_t uniffi_c2pa_checksum_method_stream_read_stream( + RustCallStatus* out_status +); + +uint16_t uniffi_c2pa_checksum_method_stream_seek_stream( + RustCallStatus* out_status +); + +uint16_t uniffi_c2pa_checksum_method_stream_write_stream( + RustCallStatus* out_status +); + +uint32_t ffi_c2pa_uniffi_contract_version( + RustCallStatus* out_status +); + + +int32_t c2pa_cgo_SignerCallback(uint64_t, int32_t, uint8_t *, int32_t, RustBuffer *); +int32_t c2pa_cgo_Stream(uint64_t, int32_t, uint8_t *, int32_t, RustBuffer *); + diff --git a/src/streams.rs b/src/streams.rs index 960c9b43..62001e0f 100644 --- a/src/streams.rs +++ b/src/streams.rs @@ -51,6 +51,10 @@ impl Stream for Box { } } +unsafe impl uniffi::LiftRef for Box { + type LiftType = Box; +} + impl AsMut for dyn Stream { fn as_mut(&mut self) -> &mut Self { self From 8f34404f2776933d28a5c427ad9134e4effdaa09 Mon Sep 17 00:00:00 2001 From: Eli Mallon Date: Thu, 8 Aug 2024 22:19:16 +0000 Subject: [PATCH 02/42] Update README.md --- README.md | 358 ++++++------------------------------------------------ 1 file changed, 36 insertions(+), 322 deletions(-) diff --git a/README.md b/README.md index f668bb59..92fac1f3 100644 --- a/README.md +++ b/README.md @@ -1,335 +1,49 @@ -# C2PA Python +# C2PA Go -Python bindings for the C2PA Content Authenticity Initiative (CAI) library. +This is a proof-of-concept of Golang bindings to the C2PA library; it's forked from the Python repo. -This library enables you to read and validate C2PA data in supported media files and add signed manifests to supported media files. +### Building -**NOTE**: This is a completely different API from 0.4.0. Check [Release notes](#release-notes) for changes. +You'll need cargo and go. -**WARNING**: This is an prerelease version of this library. There may be bugs and unimplemented features, and the API is subject to change. - -## Installation - -Install from PyPI by entering this command: - -``` -pip install -U c2pa-python -``` - -This is a platform wheel built with Rust that works on Windows, macOS, and most Linux distributions (using [manylinux](https://github.com/pypa/manylinux)). If you need to run on another platform, see [Development](#development) for information on how to build from source. - -### Reinstalling - -If you tried unsuccessfully to install this package before the [0.40 release](https://github.com/contentauth/c2pa-python/releases/tag/v0.4), then use this command to reinstall: - -``` -pip install --upgrade --force-reinstall c2pa-python -``` - -## Usage - -### Import - -Import the API as follows: - -```py -from c2pa import * -``` - -### Read and validate C2PA data in a file or stream - -Use the `Reader` to read C2PA data from the specified file. -This examines the specified media file for C2PA data and generates a report of any data it finds. If there are validation errors, the report includes a `validation_status` field. For a summary of supported media types, see [Supported file formats](#supported-file-formats). - -A media file may contain many manifests in a manifest store. The most recent manifest is identified by the value of the `active_manifest` field in the manifests map. - -The manifests may contain binary resources such as thumbnails which can be retrieved with `resource_to_stream` or `resource_to_file` using the associated `identifier` field values and a `uri`. - -NOTE: For a comprehensive reference to the JSON manifest structure, see the [Manifest store reference](https://opensource.contentauthenticity.org/docs/manifest/manifest-ref). -```py -try: - # Create a reader from a file path - reader = c2pa.Reader.from_file("path/to/media_file.jpg") - # It's also possible to create a reader from a format and stream - # Note that these two readers are functionally equivalent - stream = open("path/to/media_file.jpg", "rb") - reader = c2pa.Reader("image/jpeg", stream) - - # Print the JSON for a manifest. - print("manifest store:", reader.json()) - - # Get the active manifest. - manifest = reader.get_active_manifest() - if manifest != None: - - # get the uri to the manifest's thumbnail and write it to a file - uri = manifest["thumbnail"]["identifier"] - reader.resource_to_file(uri, "thumbnail_v2.jpg") - -except Exception as err: - print(err) ``` +make +[ ... ] -### Add a signed manifest to a media file or stream - -Use a `Builder` to add a manifest to an asset. - -```py -try: - # Define a function to sign the claim bytes - # In this case we are using a pre-defined sign_ps256 method, passing in our private cert - # Normally this cert would be kept safe in some other location - def private_sign(data: bytes) -> bytes: - return sign_ps256(data, "tests/fixtures/ps256.pem") - - # read our public certs into memory - certs = open(data_dir + "ps256.pub", "rb").read() - - # Create a signer from the private signer, certs and a time stamp service url - signer = create_signer(private_sign, SigningAlg.PS256, certs, "http://timestamp.digicert.com") - - # Define a manifest with thumbnail and an assertion. - manifest_json = { - "claim_generator_info": [{ - "name": "python_test", - "version": "0.1" - }], - "title": "Do Not Train Example", +./dist/go-demo ~/testvids/screenshot-signed.jpg +{ + "active_manifest": "urn:uuid:019cfdc5-8d91-4b2a-bb0d-f94afb9badef", + "manifests": { + "urn:uuid:019cfdc5-8d91-4b2a-bb0d-f94afb9badef": { + "claim_generator": "Aquareum c2patool/0.9.6 c2pa-rs/0.33.1", + "title": "Video File", + "format": "image/jpeg", + "instance_id": "xmp:iid:755324ba-0c38-4d9a-b7c1-be8a41035ac6", "thumbnail": { - "format": "image/jpeg", - "identifier": "thumbnail" + "format": "image/jpeg", + "identifier": "self#jumbf=c2pa.assertions/c2pa.thumbnail.claim.jpeg" }, + "ingredients": [], "assertions": [ - { - "label": "c2pa.training-mining", - "data": { - "entries": { - "c2pa.ai_generative_training": { "use": "notAllowed" }, - "c2pa.ai_inference": { "use": "notAllowed" }, - "c2pa.ai_training": { "use": "notAllowed" }, - "c2pa.data_mining": { "use": "notAllowed" } + { + "label": "c2pa.actions", + "data": { + "actions": [ + { + "action": "c2pa.published" + } + ] } } - } - ] - } - - # Create a builder add a thumbnail resource and an ingredient file. - builder = Builder(manifest_json) - - # The uri provided here "thumbnail" must match an identifier in the manifest definition. - builder.add_resource_file("thumbnail", "tests/fixtures/A_thumbnail.jpg") - - # Or add the resource from a stream - a_thumbnail_jpg_stream = open("tests/fixtures/A_thumbnail.jpg", "rb") - builder.add_resource("image/jpeg", a_thumbnail_jpg_stream) - - # Define an ingredient, in this case a parent ingredient named A.jpg, with a thumbnail - ingredient_json = { - "title": "A.jpg", - "relationship": "parentOf", # "parentOf", "componentOf" or "inputTo" - "thumbnail": { - "identifier": "thumbnail", - "format": "image/jpeg" + ], + "signature_info": { + "alg": "Es256", + "issuer": "Internet Widgits Pty Ltd", + "cert_serial_number": "421347483195564801015437680009080750481212048692", + "time": "2024-08-08T17:36:42+00:00" + }, + "label": "urn:uuid:019cfdc5-8d91-4b2a-bb0d-f94afb9badef" } } - - # Add the ingredient to the builder loading information from a source file. - builder.add_ingredient_file(ingredient_json, "tests/fixtures/A.jpg") - - # Or add the ingredient from a stream - a_jpg_stream = open("tests/fixtures/A.jpg", "rb") - builder.add_ingredient("image/jpeg", a_jpg_stream) - - # At this point we could archive or unarchive our Builder to continue later. - # In this example we use a bytearray for the archive stream. - # all ingredients and resources will be saved in the archive - archive = io.BytesIO(bytearray()) - builder.to_archive(archive) - archive.seek() - builder = builder.from_archive(archive) - - # Sign and add our manifest to a source file, writing it to an output file. - # This returns the binary manifest data that could be uploaded to cloud storage. - c2pa_data = builder.sign_file(signer, "tests/fixtures/A.jpg", "target/out.jpg") - - # Or sign the builder with a stream and output it to a stream - input_stream = open("tests/fixtures/A.jpg", "rb") - output_stream = open("target/out.jpg", "wb") - c2pa_data = builder.sign(signer, "image/jpeg", input_stream, output_stream) - -except Exception as err: - print(err) - ``` - -### Creating a manifest JSON definition file - -The manifest JSON string defines the C2PA manifest to add to the file. - -```py -manifest_json = json.dumps({ - "claim_generator": "python_test/0.1", - "assertions": [ - { - "label": "c2pa.training-mining", - "data": { - "entries": { - "c2pa.ai_generative_training": { "use": "notAllowed" }, - "c2pa.ai_inference": { "use": "notAllowed" }, - "c2pa.ai_training": { "use": "notAllowed" }, - "c2pa.data_mining": { "use": "notAllowed" } - } - } - } - ] - }) -``` - -## Supported file formats - - | Extensions | MIME type | - | ------------- | --------------------------------------------------- | - | `avi` | `video/msvideo`, `video/avi`, `application-msvideo` | - | `avif` | `image/avif` | - | `c2pa` | `application/x-c2pa-manifest-store` | - | `dng` | `image/x-adobe-dng` | - | `heic` | `image/heic` | - | `heif` | `image/heif` | - | `jpg`, `jpeg` | `image/jpeg` | - | `m4a` | `audio/mp4` | - | `mp4` | `video/mp4`, `application/mp4` | - | `mov` | `video/quicktime` | - | `png` | `image/png` | - | `svg` | `image/svg+xml` | - | `tif`,`tiff` | `image/tiff` | - | `wav` | `audio/x-wav` | - | `webp` | `image/webp` | - - -## Development - -It is best to [set up a virtual environment](https://virtualenv.pypa.io/en/latest/installation.html) for development and testing. To build from source on Linux, install `curl` and `rustup` then set up Python. - -First update `apt` then (if needed) install `curl`: - -``` -apt update -apt install curl -``` - -Install Rust: - -``` -curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -source "$HOME/.cargo/env" -``` - -Install Python, `pip`, and `venv`: - -``` -apt install python3 -apt install pip -apt install python3.11-venv -python3 -m venv .venv -``` - -Build the wheel for your platform: - -``` -source .venv/bin/activate -pip install maturin -pip install uniffi-bindgen -python3 -m pip install build -pip install -U pytest - -python3 -m build --wheel -``` - -### ManyLinux build - -Build using [manylinux](https://github.com/pypa/manylinux) by using a Docker image as follows: - -``` -docker run -it quay.io/pypa/manylinux_2_28_aarch64 bash -curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -source "$HOME/.cargo/env" -export PATH=/opt/python/cp312-cp312/bin:$PATH -pip install maturin -pip install venv -pip install build -pip install -U pytest - -cd home -git clone https://github.com/contentauth/c2pa-python.git -cd c2pa-python -python3 -m build --wheel -auditwheel repair target/wheels/c2pa_python-0.4.0-py3-none-linux_aarch64.whl -``` - -### Testing - -We use [PyTest](https://docs.pytest.org/) for testing. - -Run tests by entering this command: - -``` -source .venv/bin/activate -maturin develop -pytest -deactivate -``` - -For example: - -``` -source .venv/bin/activate -maturin develop -python3 tests/training.py -deactivate -``` - -## Release notes - -### Version 0.5.0 - -- This release rewrites the API to be stream based using a Builder and Reader model. -- The functions now support throwing c2pa.Error values, caught with try/except. -- Instead of `c2pa.read_file` you now call `c2pa_api.Reader.from_file` and `reader.json`. -- Read thumbnails and other resources use `reader.resource_to_stream` or `reader.resource.to_file`. -- Instead of `c2pa.sign_file` use `c2pa_api.Builder.from_json` and `builder.sign` or `builder.sign_file`. -- Add thumbnails or other resources with `builder.add_resource` or `builder.add_resource_file`. -- Add Ingredients with `builder.add_ingredient` or `builder.add_ingredient_file`. -- You can archive a `Builder` using `builder.to_archive` and reconstruct it with `builder.from_archive`. -- Signers can be constructed with `c2pa_api.create_signer`. -- The signer now requires a signing function to keep private keys private. -- Example signing functions are provided in c2pa_api.py - -### Version 0.4.0 - -This release: - -- Changes the name of the import from `c2pa-python` to `c2pa`. -- Supports pre-built platform wheels for macOS, Windows and [manylinux](https://github.com/pypa/manylinux). - -### Version 0.3.0 - -This release includes some breaking changes to align with future APIs: -- `C2paSignerInfo` moves the `alg` to the first parameter from the 3rd. -- `c2pa.verify_from_file_json` is now `c2pa.read_file`. -- `c2pa.ingredient_from_file_json` is now `c2pa.read_ingredient_file`. -- `c2pa.add_manifest_to_file_json` is now `c2pa.sign_file`. -- There are many more specific errors types now, and Error messages always start with the name of the error i.e (str(err.value).startswith("ManifestNotFound")). -- The ingredient thumbnail identifier may be jumbf uri reference if a valid thumb already exists in the active manifest. -- Extracted file paths for read_file now use a folder structure and different naming conventions. - -## License - -This package is distributed under the terms of both the [MIT license](https://github.com/contentauth/c2pa-python/blob/main/LICENSE-MIT) and the [Apache License (Version 2.0)](https://github.com/contentauth/c2pa-python/blob/main/LICENSE-APACHE). - -Note that some components and dependent crates are licensed under different terms; please check the license terms for each crate and component for details. - -### Contributions and feedback - -We welcome contributions to this project. For information on contributing, providing feedback, and about ongoing work, see [Contributing](https://github.com/contentauth/c2pa-python/blob/main/CONTRIBUTING.md). - - +} +``` \ No newline at end of file From 5e334de452e0952f199635bfe4fb87ba51d86467 Mon Sep 17 00:00:00 2001 From: Eli Mallon Date: Thu, 8 Aug 2024 15:21:04 -0700 Subject: [PATCH 03/42] remove old generated files --- src/c2pa/c2pa.c | 8 - src/c2pa/c2pa.go | 1971 ---------------------------------------------- src/c2pa/c2pa.h | 559 ------------- 3 files changed, 2538 deletions(-) delete mode 100644 src/c2pa/c2pa.c delete mode 100644 src/c2pa/c2pa.go delete mode 100644 src/c2pa/c2pa.h diff --git a/src/c2pa/c2pa.c b/src/c2pa/c2pa.c deleted file mode 100644 index f9a0d0f9..00000000 --- a/src/c2pa/c2pa.c +++ /dev/null @@ -1,8 +0,0 @@ -#include - -// This file exists beacause of -// https://github.com/golang/go/issues/11263 - -void cgo_rust_task_callback_bridge_c2pa(RustTaskCallback cb, const void * taskData, int8_t status) { - cb(taskData, status); -} \ No newline at end of file diff --git a/src/c2pa/c2pa.go b/src/c2pa/c2pa.go deleted file mode 100644 index 8be98d1c..00000000 --- a/src/c2pa/c2pa.go +++ /dev/null @@ -1,1971 +0,0 @@ -package c2pa - -// #include -import "C" - -import ( - "bytes" - "encoding/binary" - "fmt" - "io" - "math" - "runtime" - "sync" - "sync/atomic" - "unsafe" -) - -type RustBuffer = C.RustBuffer - -type RustBufferI interface { - AsReader() *bytes.Reader - Free() - ToGoBytes() []byte - Data() unsafe.Pointer - Len() int - Capacity() int -} - -func RustBufferFromExternal(b RustBufferI) RustBuffer { - return RustBuffer{ - capacity: C.int(b.Capacity()), - len: C.int(b.Len()), - data: (*C.uchar)(b.Data()), - } -} - -func (cb RustBuffer) Capacity() int { - return int(cb.capacity) -} - -func (cb RustBuffer) Len() int { - return int(cb.len) -} - -func (cb RustBuffer) Data() unsafe.Pointer { - return unsafe.Pointer(cb.data) -} - -func (cb RustBuffer) AsReader() *bytes.Reader { - b := unsafe.Slice((*byte)(cb.data), C.int(cb.len)) - return bytes.NewReader(b) -} - -func (cb RustBuffer) Free() { - rustCall(func(status *C.RustCallStatus) bool { - C.ffi_c2pa_rustbuffer_free(cb, status) - return false - }) -} - -func (cb RustBuffer) ToGoBytes() []byte { - return C.GoBytes(unsafe.Pointer(cb.data), C.int(cb.len)) -} - -func stringToRustBuffer(str string) RustBuffer { - return bytesToRustBuffer([]byte(str)) -} - -func bytesToRustBuffer(b []byte) RustBuffer { - if len(b) == 0 { - return RustBuffer{} - } - // We can pass the pointer along here, as it is pinned - // for the duration of this call - foreign := C.ForeignBytes{ - len: C.int(len(b)), - data: (*C.uchar)(unsafe.Pointer(&b[0])), - } - - return rustCall(func(status *C.RustCallStatus) RustBuffer { - return C.ffi_c2pa_rustbuffer_from_bytes(foreign, status) - }) -} - -type BufLifter[GoType any] interface { - Lift(value RustBufferI) GoType -} - -type BufLowerer[GoType any] interface { - Lower(value GoType) RustBuffer -} - -type FfiConverter[GoType any, FfiType any] interface { - Lift(value FfiType) GoType - Lower(value GoType) FfiType -} - -type BufReader[GoType any] interface { - Read(reader io.Reader) GoType -} - -type BufWriter[GoType any] interface { - Write(writer io.Writer, value GoType) -} - -type FfiRustBufConverter[GoType any, FfiType any] interface { - FfiConverter[GoType, FfiType] - BufReader[GoType] -} - -func LowerIntoRustBuffer[GoType any](bufWriter BufWriter[GoType], value GoType) RustBuffer { - // This might be not the most efficient way but it does not require knowing allocation size - // beforehand - var buffer bytes.Buffer - bufWriter.Write(&buffer, value) - - bytes, err := io.ReadAll(&buffer) - if err != nil { - panic(fmt.Errorf("reading written data: %w", err)) - } - return bytesToRustBuffer(bytes) -} - -func LiftFromRustBuffer[GoType any](bufReader BufReader[GoType], rbuf RustBufferI) GoType { - defer rbuf.Free() - reader := rbuf.AsReader() - item := bufReader.Read(reader) - if reader.Len() > 0 { - // TODO: Remove this - leftover, _ := io.ReadAll(reader) - panic(fmt.Errorf("Junk remaining in buffer after lifting: %s", string(leftover))) - } - return item -} - -func rustCallWithError[U any](converter BufLifter[error], callback func(*C.RustCallStatus) U) (U, error) { - var status C.RustCallStatus - returnValue := callback(&status) - err := checkCallStatus(converter, status) - - return returnValue, err -} - -func checkCallStatus(converter BufLifter[error], status C.RustCallStatus) error { - switch status.code { - case 0: - return nil - case 1: - return converter.Lift(status.errorBuf) - case 2: - // when the rust code sees a panic, it tries to construct a rustbuffer - // with the message. but if that code panics, then it just sends back - // an empty buffer. - if status.errorBuf.len > 0 { - panic(fmt.Errorf("%s", FfiConverterStringINSTANCE.Lift(status.errorBuf))) - } else { - panic(fmt.Errorf("Rust panicked while handling Rust panic")) - } - default: - return fmt.Errorf("unknown status code: %d", status.code) - } -} - -func checkCallStatusUnknown(status C.RustCallStatus) error { - switch status.code { - case 0: - return nil - case 1: - panic(fmt.Errorf("function not returning an error returned an error")) - case 2: - // when the rust code sees a panic, it tries to construct a rustbuffer - // with the message. but if that code panics, then it just sends back - // an empty buffer. - if status.errorBuf.len > 0 { - panic(fmt.Errorf("%s", FfiConverterStringINSTANCE.Lift(status.errorBuf))) - } else { - panic(fmt.Errorf("Rust panicked while handling Rust panic")) - } - default: - return fmt.Errorf("unknown status code: %d", status.code) - } -} - -func rustCall[U any](callback func(*C.RustCallStatus) U) U { - returnValue, err := rustCallWithError(nil, callback) - if err != nil { - panic(err) - } - return returnValue -} - -func writeInt8(writer io.Writer, value int8) { - if err := binary.Write(writer, binary.BigEndian, value); err != nil { - panic(err) - } -} - -func writeUint8(writer io.Writer, value uint8) { - if err := binary.Write(writer, binary.BigEndian, value); err != nil { - panic(err) - } -} - -func writeInt16(writer io.Writer, value int16) { - if err := binary.Write(writer, binary.BigEndian, value); err != nil { - panic(err) - } -} - -func writeUint16(writer io.Writer, value uint16) { - if err := binary.Write(writer, binary.BigEndian, value); err != nil { - panic(err) - } -} - -func writeInt32(writer io.Writer, value int32) { - if err := binary.Write(writer, binary.BigEndian, value); err != nil { - panic(err) - } -} - -func writeUint32(writer io.Writer, value uint32) { - if err := binary.Write(writer, binary.BigEndian, value); err != nil { - panic(err) - } -} - -func writeInt64(writer io.Writer, value int64) { - if err := binary.Write(writer, binary.BigEndian, value); err != nil { - panic(err) - } -} - -func writeUint64(writer io.Writer, value uint64) { - if err := binary.Write(writer, binary.BigEndian, value); err != nil { - panic(err) - } -} - -func writeFloat32(writer io.Writer, value float32) { - if err := binary.Write(writer, binary.BigEndian, value); err != nil { - panic(err) - } -} - -func writeFloat64(writer io.Writer, value float64) { - if err := binary.Write(writer, binary.BigEndian, value); err != nil { - panic(err) - } -} - -func readInt8(reader io.Reader) int8 { - var result int8 - if err := binary.Read(reader, binary.BigEndian, &result); err != nil { - panic(err) - } - return result -} - -func readUint8(reader io.Reader) uint8 { - var result uint8 - if err := binary.Read(reader, binary.BigEndian, &result); err != nil { - panic(err) - } - return result -} - -func readInt16(reader io.Reader) int16 { - var result int16 - if err := binary.Read(reader, binary.BigEndian, &result); err != nil { - panic(err) - } - return result -} - -func readUint16(reader io.Reader) uint16 { - var result uint16 - if err := binary.Read(reader, binary.BigEndian, &result); err != nil { - panic(err) - } - return result -} - -func readInt32(reader io.Reader) int32 { - var result int32 - if err := binary.Read(reader, binary.BigEndian, &result); err != nil { - panic(err) - } - return result -} - -func readUint32(reader io.Reader) uint32 { - var result uint32 - if err := binary.Read(reader, binary.BigEndian, &result); err != nil { - panic(err) - } - return result -} - -func readInt64(reader io.Reader) int64 { - var result int64 - if err := binary.Read(reader, binary.BigEndian, &result); err != nil { - panic(err) - } - return result -} - -func readUint64(reader io.Reader) uint64 { - var result uint64 - if err := binary.Read(reader, binary.BigEndian, &result); err != nil { - panic(err) - } - return result -} - -func readFloat32(reader io.Reader) float32 { - var result float32 - if err := binary.Read(reader, binary.BigEndian, &result); err != nil { - panic(err) - } - return result -} - -func readFloat64(reader io.Reader) float64 { - var result float64 - if err := binary.Read(reader, binary.BigEndian, &result); err != nil { - panic(err) - } - return result -} - -func init() { - - (&FfiConverterCallbackInterfaceSignerCallback{}).register() - (&FfiConverterCallbackInterfaceStream{}).register() - uniffiCheckChecksums() -} - -func uniffiCheckChecksums() { - // Get the bindings contract version from our ComponentInterface - bindingsContractVersion := 24 - // Get the scaffolding contract version by calling the into the dylib - scaffoldingContractVersion := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint32_t { - return C.ffi_c2pa_uniffi_contract_version(uniffiStatus) - }) - if bindingsContractVersion != int(scaffoldingContractVersion) { - // If this happens try cleaning and rebuilding your project - panic(fmt.Sprintf("c2pa: UniFFI contract version mismatch bindingsContractVersion=%d scaffoldingContractVersion=%d", bindingsContractVersion, int(scaffoldingContractVersion))) - } - { - checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { - return C.uniffi_c2pa_checksum_func_sdk_version(uniffiStatus) - }) - if checksum != 37245 { - // If this happens try cleaning and rebuilding your project - panic("c2pa: uniffi_c2pa_checksum_func_sdk_version: UniFFI API checksum mismatch") - } - } - { - checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { - return C.uniffi_c2pa_checksum_func_version(uniffiStatus) - }) - if checksum != 61576 { - // If this happens try cleaning and rebuilding your project - panic("c2pa: uniffi_c2pa_checksum_func_version: UniFFI API checksum mismatch") - } - } - { - checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { - return C.uniffi_c2pa_checksum_method_builder_add_ingredient(uniffiStatus) - }) - if checksum != 54967 { - // If this happens try cleaning and rebuilding your project - panic("c2pa: uniffi_c2pa_checksum_method_builder_add_ingredient: UniFFI API checksum mismatch") - } - } - { - checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { - return C.uniffi_c2pa_checksum_method_builder_add_resource(uniffiStatus) - }) - if checksum != 12018 { - // If this happens try cleaning and rebuilding your project - panic("c2pa: uniffi_c2pa_checksum_method_builder_add_resource: UniFFI API checksum mismatch") - } - } - { - checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { - return C.uniffi_c2pa_checksum_method_builder_from_archive(uniffiStatus) - }) - if checksum != 17341 { - // If this happens try cleaning and rebuilding your project - panic("c2pa: uniffi_c2pa_checksum_method_builder_from_archive: UniFFI API checksum mismatch") - } - } - { - checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { - return C.uniffi_c2pa_checksum_method_builder_sign(uniffiStatus) - }) - if checksum != 8729 { - // If this happens try cleaning and rebuilding your project - panic("c2pa: uniffi_c2pa_checksum_method_builder_sign: UniFFI API checksum mismatch") - } - } - { - checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { - return C.uniffi_c2pa_checksum_method_builder_to_archive(uniffiStatus) - }) - if checksum != 44718 { - // If this happens try cleaning and rebuilding your project - panic("c2pa: uniffi_c2pa_checksum_method_builder_to_archive: UniFFI API checksum mismatch") - } - } - { - checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { - return C.uniffi_c2pa_checksum_method_builder_with_json(uniffiStatus) - }) - if checksum != 29392 { - // If this happens try cleaning and rebuilding your project - panic("c2pa: uniffi_c2pa_checksum_method_builder_with_json: UniFFI API checksum mismatch") - } - } - { - checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { - return C.uniffi_c2pa_checksum_method_reader_from_stream(uniffiStatus) - }) - if checksum != 3255 { - // If this happens try cleaning and rebuilding your project - panic("c2pa: uniffi_c2pa_checksum_method_reader_from_stream: UniFFI API checksum mismatch") - } - } - { - checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { - return C.uniffi_c2pa_checksum_method_reader_json(uniffiStatus) - }) - if checksum != 33242 { - // If this happens try cleaning and rebuilding your project - panic("c2pa: uniffi_c2pa_checksum_method_reader_json: UniFFI API checksum mismatch") - } - } - { - checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { - return C.uniffi_c2pa_checksum_method_reader_resource_to_stream(uniffiStatus) - }) - if checksum != 44049 { - // If this happens try cleaning and rebuilding your project - panic("c2pa: uniffi_c2pa_checksum_method_reader_resource_to_stream: UniFFI API checksum mismatch") - } - } - { - checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { - return C.uniffi_c2pa_checksum_constructor_builder_new(uniffiStatus) - }) - if checksum != 8924 { - // If this happens try cleaning and rebuilding your project - panic("c2pa: uniffi_c2pa_checksum_constructor_builder_new: UniFFI API checksum mismatch") - } - } - { - checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { - return C.uniffi_c2pa_checksum_constructor_callbacksigner_new(uniffiStatus) - }) - if checksum != 51503 { - // If this happens try cleaning and rebuilding your project - panic("c2pa: uniffi_c2pa_checksum_constructor_callbacksigner_new: UniFFI API checksum mismatch") - } - } - { - checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { - return C.uniffi_c2pa_checksum_constructor_reader_new(uniffiStatus) - }) - if checksum != 7340 { - // If this happens try cleaning and rebuilding your project - panic("c2pa: uniffi_c2pa_checksum_constructor_reader_new: UniFFI API checksum mismatch") - } - } - { - checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { - return C.uniffi_c2pa_checksum_method_signercallback_sign(uniffiStatus) - }) - if checksum != 15928 { - // If this happens try cleaning and rebuilding your project - panic("c2pa: uniffi_c2pa_checksum_method_signercallback_sign: UniFFI API checksum mismatch") - } - } - { - checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { - return C.uniffi_c2pa_checksum_method_stream_read_stream(uniffiStatus) - }) - if checksum != 4594 { - // If this happens try cleaning and rebuilding your project - panic("c2pa: uniffi_c2pa_checksum_method_stream_read_stream: UniFFI API checksum mismatch") - } - } - { - checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { - return C.uniffi_c2pa_checksum_method_stream_seek_stream(uniffiStatus) - }) - if checksum != 32219 { - // If this happens try cleaning and rebuilding your project - panic("c2pa: uniffi_c2pa_checksum_method_stream_seek_stream: UniFFI API checksum mismatch") - } - } - { - checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { - return C.uniffi_c2pa_checksum_method_stream_write_stream(uniffiStatus) - }) - if checksum != 37641 { - // If this happens try cleaning and rebuilding your project - panic("c2pa: uniffi_c2pa_checksum_method_stream_write_stream: UniFFI API checksum mismatch") - } - } -} - -type FfiConverterUint64 struct{} - -var FfiConverterUint64INSTANCE = FfiConverterUint64{} - -func (FfiConverterUint64) Lower(value uint64) C.uint64_t { - return C.uint64_t(value) -} - -func (FfiConverterUint64) Write(writer io.Writer, value uint64) { - writeUint64(writer, value) -} - -func (FfiConverterUint64) Lift(value C.uint64_t) uint64 { - return uint64(value) -} - -func (FfiConverterUint64) Read(reader io.Reader) uint64 { - return readUint64(reader) -} - -type FfiDestroyerUint64 struct{} - -func (FfiDestroyerUint64) Destroy(_ uint64) {} - -type FfiConverterInt64 struct{} - -var FfiConverterInt64INSTANCE = FfiConverterInt64{} - -func (FfiConverterInt64) Lower(value int64) C.int64_t { - return C.int64_t(value) -} - -func (FfiConverterInt64) Write(writer io.Writer, value int64) { - writeInt64(writer, value) -} - -func (FfiConverterInt64) Lift(value C.int64_t) int64 { - return int64(value) -} - -func (FfiConverterInt64) Read(reader io.Reader) int64 { - return readInt64(reader) -} - -type FfiDestroyerInt64 struct{} - -func (FfiDestroyerInt64) Destroy(_ int64) {} - -type FfiConverterString struct{} - -var FfiConverterStringINSTANCE = FfiConverterString{} - -func (FfiConverterString) Lift(rb RustBufferI) string { - defer rb.Free() - reader := rb.AsReader() - b, err := io.ReadAll(reader) - if err != nil { - panic(fmt.Errorf("reading reader: %w", err)) - } - return string(b) -} - -func (FfiConverterString) Read(reader io.Reader) string { - length := readInt32(reader) - buffer := make([]byte, length) - read_length, err := reader.Read(buffer) - if err != nil { - panic(err) - } - if read_length != int(length) { - panic(fmt.Errorf("bad read length when reading string, expected %d, read %d", length, read_length)) - } - return string(buffer) -} - -func (FfiConverterString) Lower(value string) RustBuffer { - return stringToRustBuffer(value) -} - -func (FfiConverterString) Write(writer io.Writer, value string) { - if len(value) > math.MaxInt32 { - panic("String is too large to fit into Int32") - } - - writeInt32(writer, int32(len(value))) - write_length, err := io.WriteString(writer, value) - if err != nil { - panic(err) - } - if write_length != len(value) { - panic(fmt.Errorf("bad write length when writing string, expected %d, written %d", len(value), write_length)) - } -} - -type FfiDestroyerString struct{} - -func (FfiDestroyerString) Destroy(_ string) {} - -type FfiConverterBytes struct{} - -var FfiConverterBytesINSTANCE = FfiConverterBytes{} - -func (c FfiConverterBytes) Lower(value []byte) RustBuffer { - return LowerIntoRustBuffer[[]byte](c, value) -} - -func (c FfiConverterBytes) Write(writer io.Writer, value []byte) { - if len(value) > math.MaxInt32 { - panic("[]byte is too large to fit into Int32") - } - - writeInt32(writer, int32(len(value))) - write_length, err := writer.Write(value) - if err != nil { - panic(err) - } - if write_length != len(value) { - panic(fmt.Errorf("bad write length when writing []byte, expected %d, written %d", len(value), write_length)) - } -} - -func (c FfiConverterBytes) Lift(rb RustBufferI) []byte { - return LiftFromRustBuffer[[]byte](c, rb) -} - -func (c FfiConverterBytes) Read(reader io.Reader) []byte { - length := readInt32(reader) - buffer := make([]byte, length) - read_length, err := reader.Read(buffer) - if err != nil { - panic(err) - } - if read_length != int(length) { - panic(fmt.Errorf("bad read length when reading []byte, expected %d, read %d", length, read_length)) - } - return buffer -} - -type FfiDestroyerBytes struct{} - -func (FfiDestroyerBytes) Destroy(_ []byte) {} - -// Below is an implementation of synchronization requirements outlined in the link. -// https://github.com/mozilla/uniffi-rs/blob/0dc031132d9493ca812c3af6e7dd60ad2ea95bf0/uniffi_bindgen/src/bindings/kotlin/templates/ObjectRuntime.kt#L31 - -type FfiObject struct { - pointer unsafe.Pointer - callCounter atomic.Int64 - freeFunction func(unsafe.Pointer, *C.RustCallStatus) - destroyed atomic.Bool -} - -func newFfiObject(pointer unsafe.Pointer, freeFunction func(unsafe.Pointer, *C.RustCallStatus)) FfiObject { - return FfiObject{ - pointer: pointer, - freeFunction: freeFunction, - } -} - -func (ffiObject *FfiObject) incrementPointer(debugName string) unsafe.Pointer { - for { - counter := ffiObject.callCounter.Load() - if counter <= -1 { - panic(fmt.Errorf("%v object has already been destroyed", debugName)) - } - if counter == math.MaxInt64 { - panic(fmt.Errorf("%v object call counter would overflow", debugName)) - } - if ffiObject.callCounter.CompareAndSwap(counter, counter+1) { - break - } - } - - return ffiObject.pointer -} - -func (ffiObject *FfiObject) decrementPointer() { - if ffiObject.callCounter.Add(-1) == -1 { - ffiObject.freeRustArcPtr() - } -} - -func (ffiObject *FfiObject) destroy() { - if ffiObject.destroyed.CompareAndSwap(false, true) { - if ffiObject.callCounter.Add(-1) == -1 { - ffiObject.freeRustArcPtr() - } - } -} - -func (ffiObject *FfiObject) freeRustArcPtr() { - rustCall(func(status *C.RustCallStatus) int32 { - ffiObject.freeFunction(ffiObject.pointer, status) - return 0 - }) -} - -type Builder struct { - ffiObject FfiObject -} - -func NewBuilder() *Builder { - return FfiConverterBuilderINSTANCE.Lift(rustCall(func(_uniffiStatus *C.RustCallStatus) unsafe.Pointer { - return C.uniffi_c2pa_fn_constructor_builder_new(_uniffiStatus) - })) -} - -func (_self *Builder) AddIngredient(ingredientJson string, format string, stream Stream) error { - _pointer := _self.ffiObject.incrementPointer("*Builder") - defer _self.ffiObject.decrementPointer() - _, _uniffiErr := rustCallWithError(FfiConverterTypeError{}, func(_uniffiStatus *C.RustCallStatus) bool { - C.uniffi_c2pa_fn_method_builder_add_ingredient( - _pointer, FfiConverterStringINSTANCE.Lower(ingredientJson), FfiConverterStringINSTANCE.Lower(format), FfiConverterCallbackInterfaceStreamINSTANCE.Lower(stream), _uniffiStatus) - return false - }) - return _uniffiErr -} - -func (_self *Builder) AddResource(uri string, stream Stream) error { - _pointer := _self.ffiObject.incrementPointer("*Builder") - defer _self.ffiObject.decrementPointer() - _, _uniffiErr := rustCallWithError(FfiConverterTypeError{}, func(_uniffiStatus *C.RustCallStatus) bool { - C.uniffi_c2pa_fn_method_builder_add_resource( - _pointer, FfiConverterStringINSTANCE.Lower(uri), FfiConverterCallbackInterfaceStreamINSTANCE.Lower(stream), _uniffiStatus) - return false - }) - return _uniffiErr -} - -func (_self *Builder) FromArchive(stream Stream) error { - _pointer := _self.ffiObject.incrementPointer("*Builder") - defer _self.ffiObject.decrementPointer() - _, _uniffiErr := rustCallWithError(FfiConverterTypeError{}, func(_uniffiStatus *C.RustCallStatus) bool { - C.uniffi_c2pa_fn_method_builder_from_archive( - _pointer, FfiConverterCallbackInterfaceStreamINSTANCE.Lower(stream), _uniffiStatus) - return false - }) - return _uniffiErr -} - -func (_self *Builder) Sign(format string, input Stream, output Stream, signer *CallbackSigner) ([]byte, error) { - _pointer := _self.ffiObject.incrementPointer("*Builder") - defer _self.ffiObject.decrementPointer() - _uniffiRV, _uniffiErr := rustCallWithError(FfiConverterTypeError{}, func(_uniffiStatus *C.RustCallStatus) RustBufferI { - return C.uniffi_c2pa_fn_method_builder_sign( - _pointer, FfiConverterStringINSTANCE.Lower(format), FfiConverterCallbackInterfaceStreamINSTANCE.Lower(input), FfiConverterCallbackInterfaceStreamINSTANCE.Lower(output), FfiConverterCallbackSignerINSTANCE.Lower(signer), _uniffiStatus) - }) - if _uniffiErr != nil { - var _uniffiDefaultValue []byte - return _uniffiDefaultValue, _uniffiErr - } else { - return FfiConverterBytesINSTANCE.Lift(_uniffiRV), _uniffiErr - } -} - -func (_self *Builder) ToArchive(stream Stream) error { - _pointer := _self.ffiObject.incrementPointer("*Builder") - defer _self.ffiObject.decrementPointer() - _, _uniffiErr := rustCallWithError(FfiConverterTypeError{}, func(_uniffiStatus *C.RustCallStatus) bool { - C.uniffi_c2pa_fn_method_builder_to_archive( - _pointer, FfiConverterCallbackInterfaceStreamINSTANCE.Lower(stream), _uniffiStatus) - return false - }) - return _uniffiErr -} - -func (_self *Builder) WithJson(json string) error { - _pointer := _self.ffiObject.incrementPointer("*Builder") - defer _self.ffiObject.decrementPointer() - _, _uniffiErr := rustCallWithError(FfiConverterTypeError{}, func(_uniffiStatus *C.RustCallStatus) bool { - C.uniffi_c2pa_fn_method_builder_with_json( - _pointer, FfiConverterStringINSTANCE.Lower(json), _uniffiStatus) - return false - }) - return _uniffiErr -} - -func (object *Builder) Destroy() { - runtime.SetFinalizer(object, nil) - object.ffiObject.destroy() -} - -type FfiConverterBuilder struct{} - -var FfiConverterBuilderINSTANCE = FfiConverterBuilder{} - -func (c FfiConverterBuilder) Lift(pointer unsafe.Pointer) *Builder { - result := &Builder{ - newFfiObject( - pointer, - func(pointer unsafe.Pointer, status *C.RustCallStatus) { - C.uniffi_c2pa_fn_free_builder(pointer, status) - }), - } - runtime.SetFinalizer(result, (*Builder).Destroy) - return result -} - -func (c FfiConverterBuilder) Read(reader io.Reader) *Builder { - return c.Lift(unsafe.Pointer(uintptr(readUint64(reader)))) -} - -func (c FfiConverterBuilder) Lower(value *Builder) unsafe.Pointer { - // TODO: this is bad - all synchronization from ObjectRuntime.go is discarded here, - // because the pointer will be decremented immediately after this function returns, - // and someone will be left holding onto a non-locked pointer. - pointer := value.ffiObject.incrementPointer("*Builder") - defer value.ffiObject.decrementPointer() - return pointer -} - -func (c FfiConverterBuilder) Write(writer io.Writer, value *Builder) { - writeUint64(writer, uint64(uintptr(c.Lower(value)))) -} - -type FfiDestroyerBuilder struct{} - -func (_ FfiDestroyerBuilder) Destroy(value *Builder) { - value.Destroy() -} - -type CallbackSigner struct { - ffiObject FfiObject -} - -func NewCallbackSigner(callback SignerCallback, alg SigningAlg, certs []byte, taUrl *string) *CallbackSigner { - return FfiConverterCallbackSignerINSTANCE.Lift(rustCall(func(_uniffiStatus *C.RustCallStatus) unsafe.Pointer { - return C.uniffi_c2pa_fn_constructor_callbacksigner_new(FfiConverterCallbackInterfaceSignerCallbackINSTANCE.Lower(callback), FfiConverterTypeSigningAlgINSTANCE.Lower(alg), FfiConverterBytesINSTANCE.Lower(certs), FfiConverterOptionalStringINSTANCE.Lower(taUrl), _uniffiStatus) - })) -} - -func (object *CallbackSigner) Destroy() { - runtime.SetFinalizer(object, nil) - object.ffiObject.destroy() -} - -type FfiConverterCallbackSigner struct{} - -var FfiConverterCallbackSignerINSTANCE = FfiConverterCallbackSigner{} - -func (c FfiConverterCallbackSigner) Lift(pointer unsafe.Pointer) *CallbackSigner { - result := &CallbackSigner{ - newFfiObject( - pointer, - func(pointer unsafe.Pointer, status *C.RustCallStatus) { - C.uniffi_c2pa_fn_free_callbacksigner(pointer, status) - }), - } - runtime.SetFinalizer(result, (*CallbackSigner).Destroy) - return result -} - -func (c FfiConverterCallbackSigner) Read(reader io.Reader) *CallbackSigner { - return c.Lift(unsafe.Pointer(uintptr(readUint64(reader)))) -} - -func (c FfiConverterCallbackSigner) Lower(value *CallbackSigner) unsafe.Pointer { - // TODO: this is bad - all synchronization from ObjectRuntime.go is discarded here, - // because the pointer will be decremented immediately after this function returns, - // and someone will be left holding onto a non-locked pointer. - pointer := value.ffiObject.incrementPointer("*CallbackSigner") - defer value.ffiObject.decrementPointer() - return pointer -} - -func (c FfiConverterCallbackSigner) Write(writer io.Writer, value *CallbackSigner) { - writeUint64(writer, uint64(uintptr(c.Lower(value)))) -} - -type FfiDestroyerCallbackSigner struct{} - -func (_ FfiDestroyerCallbackSigner) Destroy(value *CallbackSigner) { - value.Destroy() -} - -type Reader struct { - ffiObject FfiObject -} - -func NewReader() *Reader { - return FfiConverterReaderINSTANCE.Lift(rustCall(func(_uniffiStatus *C.RustCallStatus) unsafe.Pointer { - return C.uniffi_c2pa_fn_constructor_reader_new(_uniffiStatus) - })) -} - -func (_self *Reader) FromStream(format string, reader Stream) (string, error) { - _pointer := _self.ffiObject.incrementPointer("*Reader") - defer _self.ffiObject.decrementPointer() - _uniffiRV, _uniffiErr := rustCallWithError(FfiConverterTypeError{}, func(_uniffiStatus *C.RustCallStatus) RustBufferI { - return C.uniffi_c2pa_fn_method_reader_from_stream( - _pointer, FfiConverterStringINSTANCE.Lower(format), FfiConverterCallbackInterfaceStreamINSTANCE.Lower(reader), _uniffiStatus) - }) - if _uniffiErr != nil { - var _uniffiDefaultValue string - return _uniffiDefaultValue, _uniffiErr - } else { - return FfiConverterStringINSTANCE.Lift(_uniffiRV), _uniffiErr - } -} - -func (_self *Reader) Json() (string, error) { - _pointer := _self.ffiObject.incrementPointer("*Reader") - defer _self.ffiObject.decrementPointer() - _uniffiRV, _uniffiErr := rustCallWithError(FfiConverterTypeError{}, func(_uniffiStatus *C.RustCallStatus) RustBufferI { - return C.uniffi_c2pa_fn_method_reader_json( - _pointer, _uniffiStatus) - }) - if _uniffiErr != nil { - var _uniffiDefaultValue string - return _uniffiDefaultValue, _uniffiErr - } else { - return FfiConverterStringINSTANCE.Lift(_uniffiRV), _uniffiErr - } -} - -func (_self *Reader) ResourceToStream(uri string, stream Stream) (uint64, error) { - _pointer := _self.ffiObject.incrementPointer("*Reader") - defer _self.ffiObject.decrementPointer() - _uniffiRV, _uniffiErr := rustCallWithError(FfiConverterTypeError{}, func(_uniffiStatus *C.RustCallStatus) C.uint64_t { - return C.uniffi_c2pa_fn_method_reader_resource_to_stream( - _pointer, FfiConverterStringINSTANCE.Lower(uri), FfiConverterCallbackInterfaceStreamINSTANCE.Lower(stream), _uniffiStatus) - }) - if _uniffiErr != nil { - var _uniffiDefaultValue uint64 - return _uniffiDefaultValue, _uniffiErr - } else { - return FfiConverterUint64INSTANCE.Lift(_uniffiRV), _uniffiErr - } -} - -func (object *Reader) Destroy() { - runtime.SetFinalizer(object, nil) - object.ffiObject.destroy() -} - -type FfiConverterReader struct{} - -var FfiConverterReaderINSTANCE = FfiConverterReader{} - -func (c FfiConverterReader) Lift(pointer unsafe.Pointer) *Reader { - result := &Reader{ - newFfiObject( - pointer, - func(pointer unsafe.Pointer, status *C.RustCallStatus) { - C.uniffi_c2pa_fn_free_reader(pointer, status) - }), - } - runtime.SetFinalizer(result, (*Reader).Destroy) - return result -} - -func (c FfiConverterReader) Read(reader io.Reader) *Reader { - return c.Lift(unsafe.Pointer(uintptr(readUint64(reader)))) -} - -func (c FfiConverterReader) Lower(value *Reader) unsafe.Pointer { - // TODO: this is bad - all synchronization from ObjectRuntime.go is discarded here, - // because the pointer will be decremented immediately after this function returns, - // and someone will be left holding onto a non-locked pointer. - pointer := value.ffiObject.incrementPointer("*Reader") - defer value.ffiObject.decrementPointer() - return pointer -} - -func (c FfiConverterReader) Write(writer io.Writer, value *Reader) { - writeUint64(writer, uint64(uintptr(c.Lower(value)))) -} - -type FfiDestroyerReader struct{} - -func (_ FfiDestroyerReader) Destroy(value *Reader) { - value.Destroy() -} - -type Error struct { - err error -} - -func (err Error) Error() string { - return fmt.Sprintf("Error: %s", err.err.Error()) -} - -func (err Error) Unwrap() error { - return err.err -} - -// Err* are used for checking error type with `errors.Is` -var ErrErrorAssertion = fmt.Errorf("ErrorAssertion") -var ErrErrorAssertionNotFound = fmt.Errorf("ErrorAssertionNotFound") -var ErrErrorDecoding = fmt.Errorf("ErrorDecoding") -var ErrErrorEncoding = fmt.Errorf("ErrorEncoding") -var ErrErrorFileNotFound = fmt.Errorf("ErrorFileNotFound") -var ErrErrorIo = fmt.Errorf("ErrorIo") -var ErrErrorJson = fmt.Errorf("ErrorJson") -var ErrErrorManifest = fmt.Errorf("ErrorManifest") -var ErrErrorManifestNotFound = fmt.Errorf("ErrorManifestNotFound") -var ErrErrorNotSupported = fmt.Errorf("ErrorNotSupported") -var ErrErrorOther = fmt.Errorf("ErrorOther") -var ErrErrorRemoteManifest = fmt.Errorf("ErrorRemoteManifest") -var ErrErrorResourceNotFound = fmt.Errorf("ErrorResourceNotFound") -var ErrErrorRwLock = fmt.Errorf("ErrorRwLock") -var ErrErrorSignature = fmt.Errorf("ErrorSignature") -var ErrErrorVerify = fmt.Errorf("ErrorVerify") - -// Variant structs -type ErrorAssertion struct { - Reason string -} - -func NewErrorAssertion( - reason string, -) *Error { - return &Error{ - err: &ErrorAssertion{ - Reason: reason, - }, - } -} - -func (err ErrorAssertion) Error() string { - return fmt.Sprint("Assertion", - ": ", - - "Reason=", - err.Reason, - ) -} - -func (self ErrorAssertion) Is(target error) bool { - return target == ErrErrorAssertion -} - -type ErrorAssertionNotFound struct { - Reason string -} - -func NewErrorAssertionNotFound( - reason string, -) *Error { - return &Error{ - err: &ErrorAssertionNotFound{ - Reason: reason, - }, - } -} - -func (err ErrorAssertionNotFound) Error() string { - return fmt.Sprint("AssertionNotFound", - ": ", - - "Reason=", - err.Reason, - ) -} - -func (self ErrorAssertionNotFound) Is(target error) bool { - return target == ErrErrorAssertionNotFound -} - -type ErrorDecoding struct { - Reason string -} - -func NewErrorDecoding( - reason string, -) *Error { - return &Error{ - err: &ErrorDecoding{ - Reason: reason, - }, - } -} - -func (err ErrorDecoding) Error() string { - return fmt.Sprint("Decoding", - ": ", - - "Reason=", - err.Reason, - ) -} - -func (self ErrorDecoding) Is(target error) bool { - return target == ErrErrorDecoding -} - -type ErrorEncoding struct { - Reason string -} - -func NewErrorEncoding( - reason string, -) *Error { - return &Error{ - err: &ErrorEncoding{ - Reason: reason, - }, - } -} - -func (err ErrorEncoding) Error() string { - return fmt.Sprint("Encoding", - ": ", - - "Reason=", - err.Reason, - ) -} - -func (self ErrorEncoding) Is(target error) bool { - return target == ErrErrorEncoding -} - -type ErrorFileNotFound struct { - Reason string -} - -func NewErrorFileNotFound( - reason string, -) *Error { - return &Error{ - err: &ErrorFileNotFound{ - Reason: reason, - }, - } -} - -func (err ErrorFileNotFound) Error() string { - return fmt.Sprint("FileNotFound", - ": ", - - "Reason=", - err.Reason, - ) -} - -func (self ErrorFileNotFound) Is(target error) bool { - return target == ErrErrorFileNotFound -} - -type ErrorIo struct { - Reason string -} - -func NewErrorIo( - reason string, -) *Error { - return &Error{ - err: &ErrorIo{ - Reason: reason, - }, - } -} - -func (err ErrorIo) Error() string { - return fmt.Sprint("Io", - ": ", - - "Reason=", - err.Reason, - ) -} - -func (self ErrorIo) Is(target error) bool { - return target == ErrErrorIo -} - -type ErrorJson struct { - Reason string -} - -func NewErrorJson( - reason string, -) *Error { - return &Error{ - err: &ErrorJson{ - Reason: reason, - }, - } -} - -func (err ErrorJson) Error() string { - return fmt.Sprint("Json", - ": ", - - "Reason=", - err.Reason, - ) -} - -func (self ErrorJson) Is(target error) bool { - return target == ErrErrorJson -} - -type ErrorManifest struct { - Reason string -} - -func NewErrorManifest( - reason string, -) *Error { - return &Error{ - err: &ErrorManifest{ - Reason: reason, - }, - } -} - -func (err ErrorManifest) Error() string { - return fmt.Sprint("Manifest", - ": ", - - "Reason=", - err.Reason, - ) -} - -func (self ErrorManifest) Is(target error) bool { - return target == ErrErrorManifest -} - -type ErrorManifestNotFound struct { - Reason string -} - -func NewErrorManifestNotFound( - reason string, -) *Error { - return &Error{ - err: &ErrorManifestNotFound{ - Reason: reason, - }, - } -} - -func (err ErrorManifestNotFound) Error() string { - return fmt.Sprint("ManifestNotFound", - ": ", - - "Reason=", - err.Reason, - ) -} - -func (self ErrorManifestNotFound) Is(target error) bool { - return target == ErrErrorManifestNotFound -} - -type ErrorNotSupported struct { - Reason string -} - -func NewErrorNotSupported( - reason string, -) *Error { - return &Error{ - err: &ErrorNotSupported{ - Reason: reason, - }, - } -} - -func (err ErrorNotSupported) Error() string { - return fmt.Sprint("NotSupported", - ": ", - - "Reason=", - err.Reason, - ) -} - -func (self ErrorNotSupported) Is(target error) bool { - return target == ErrErrorNotSupported -} - -type ErrorOther struct { - Reason string -} - -func NewErrorOther( - reason string, -) *Error { - return &Error{ - err: &ErrorOther{ - Reason: reason, - }, - } -} - -func (err ErrorOther) Error() string { - return fmt.Sprint("Other", - ": ", - - "Reason=", - err.Reason, - ) -} - -func (self ErrorOther) Is(target error) bool { - return target == ErrErrorOther -} - -type ErrorRemoteManifest struct { - Reason string -} - -func NewErrorRemoteManifest( - reason string, -) *Error { - return &Error{ - err: &ErrorRemoteManifest{ - Reason: reason, - }, - } -} - -func (err ErrorRemoteManifest) Error() string { - return fmt.Sprint("RemoteManifest", - ": ", - - "Reason=", - err.Reason, - ) -} - -func (self ErrorRemoteManifest) Is(target error) bool { - return target == ErrErrorRemoteManifest -} - -type ErrorResourceNotFound struct { - Reason string -} - -func NewErrorResourceNotFound( - reason string, -) *Error { - return &Error{ - err: &ErrorResourceNotFound{ - Reason: reason, - }, - } -} - -func (err ErrorResourceNotFound) Error() string { - return fmt.Sprint("ResourceNotFound", - ": ", - - "Reason=", - err.Reason, - ) -} - -func (self ErrorResourceNotFound) Is(target error) bool { - return target == ErrErrorResourceNotFound -} - -type ErrorRwLock struct { -} - -func NewErrorRwLock() *Error { - return &Error{ - err: &ErrorRwLock{}, - } -} - -func (err ErrorRwLock) Error() string { - return fmt.Sprint("RwLock") -} - -func (self ErrorRwLock) Is(target error) bool { - return target == ErrErrorRwLock -} - -type ErrorSignature struct { - Reason string -} - -func NewErrorSignature( - reason string, -) *Error { - return &Error{ - err: &ErrorSignature{ - Reason: reason, - }, - } -} - -func (err ErrorSignature) Error() string { - return fmt.Sprint("Signature", - ": ", - - "Reason=", - err.Reason, - ) -} - -func (self ErrorSignature) Is(target error) bool { - return target == ErrErrorSignature -} - -type ErrorVerify struct { - Reason string -} - -func NewErrorVerify( - reason string, -) *Error { - return &Error{ - err: &ErrorVerify{ - Reason: reason, - }, - } -} - -func (err ErrorVerify) Error() string { - return fmt.Sprint("Verify", - ": ", - - "Reason=", - err.Reason, - ) -} - -func (self ErrorVerify) Is(target error) bool { - return target == ErrErrorVerify -} - -type FfiConverterTypeError struct{} - -var FfiConverterTypeErrorINSTANCE = FfiConverterTypeError{} - -func (c FfiConverterTypeError) Lift(eb RustBufferI) error { - return LiftFromRustBuffer[error](c, eb) -} - -func (c FfiConverterTypeError) Lower(value *Error) RustBuffer { - return LowerIntoRustBuffer[*Error](c, value) -} - -func (c FfiConverterTypeError) Read(reader io.Reader) error { - errorID := readUint32(reader) - - switch errorID { - case 1: - return &Error{&ErrorAssertion{ - Reason: FfiConverterStringINSTANCE.Read(reader), - }} - case 2: - return &Error{&ErrorAssertionNotFound{ - Reason: FfiConverterStringINSTANCE.Read(reader), - }} - case 3: - return &Error{&ErrorDecoding{ - Reason: FfiConverterStringINSTANCE.Read(reader), - }} - case 4: - return &Error{&ErrorEncoding{ - Reason: FfiConverterStringINSTANCE.Read(reader), - }} - case 5: - return &Error{&ErrorFileNotFound{ - Reason: FfiConverterStringINSTANCE.Read(reader), - }} - case 6: - return &Error{&ErrorIo{ - Reason: FfiConverterStringINSTANCE.Read(reader), - }} - case 7: - return &Error{&ErrorJson{ - Reason: FfiConverterStringINSTANCE.Read(reader), - }} - case 8: - return &Error{&ErrorManifest{ - Reason: FfiConverterStringINSTANCE.Read(reader), - }} - case 9: - return &Error{&ErrorManifestNotFound{ - Reason: FfiConverterStringINSTANCE.Read(reader), - }} - case 10: - return &Error{&ErrorNotSupported{ - Reason: FfiConverterStringINSTANCE.Read(reader), - }} - case 11: - return &Error{&ErrorOther{ - Reason: FfiConverterStringINSTANCE.Read(reader), - }} - case 12: - return &Error{&ErrorRemoteManifest{ - Reason: FfiConverterStringINSTANCE.Read(reader), - }} - case 13: - return &Error{&ErrorResourceNotFound{ - Reason: FfiConverterStringINSTANCE.Read(reader), - }} - case 14: - return &Error{&ErrorRwLock{}} - case 15: - return &Error{&ErrorSignature{ - Reason: FfiConverterStringINSTANCE.Read(reader), - }} - case 16: - return &Error{&ErrorVerify{ - Reason: FfiConverterStringINSTANCE.Read(reader), - }} - default: - panic(fmt.Sprintf("Unknown error code %d in FfiConverterTypeError.Read()", errorID)) - } -} - -func (c FfiConverterTypeError) Write(writer io.Writer, value *Error) { - switch variantValue := value.err.(type) { - case *ErrorAssertion: - writeInt32(writer, 1) - FfiConverterStringINSTANCE.Write(writer, variantValue.Reason) - case *ErrorAssertionNotFound: - writeInt32(writer, 2) - FfiConverterStringINSTANCE.Write(writer, variantValue.Reason) - case *ErrorDecoding: - writeInt32(writer, 3) - FfiConverterStringINSTANCE.Write(writer, variantValue.Reason) - case *ErrorEncoding: - writeInt32(writer, 4) - FfiConverterStringINSTANCE.Write(writer, variantValue.Reason) - case *ErrorFileNotFound: - writeInt32(writer, 5) - FfiConverterStringINSTANCE.Write(writer, variantValue.Reason) - case *ErrorIo: - writeInt32(writer, 6) - FfiConverterStringINSTANCE.Write(writer, variantValue.Reason) - case *ErrorJson: - writeInt32(writer, 7) - FfiConverterStringINSTANCE.Write(writer, variantValue.Reason) - case *ErrorManifest: - writeInt32(writer, 8) - FfiConverterStringINSTANCE.Write(writer, variantValue.Reason) - case *ErrorManifestNotFound: - writeInt32(writer, 9) - FfiConverterStringINSTANCE.Write(writer, variantValue.Reason) - case *ErrorNotSupported: - writeInt32(writer, 10) - FfiConverterStringINSTANCE.Write(writer, variantValue.Reason) - case *ErrorOther: - writeInt32(writer, 11) - FfiConverterStringINSTANCE.Write(writer, variantValue.Reason) - case *ErrorRemoteManifest: - writeInt32(writer, 12) - FfiConverterStringINSTANCE.Write(writer, variantValue.Reason) - case *ErrorResourceNotFound: - writeInt32(writer, 13) - FfiConverterStringINSTANCE.Write(writer, variantValue.Reason) - case *ErrorRwLock: - writeInt32(writer, 14) - case *ErrorSignature: - writeInt32(writer, 15) - FfiConverterStringINSTANCE.Write(writer, variantValue.Reason) - case *ErrorVerify: - writeInt32(writer, 16) - FfiConverterStringINSTANCE.Write(writer, variantValue.Reason) - default: - _ = variantValue - panic(fmt.Sprintf("invalid error value `%v` in FfiConverterTypeError.Write", value)) - } -} - -type SeekMode uint - -const ( - SeekModeStart SeekMode = 1 - SeekModeEnd SeekMode = 2 - SeekModeCurrent SeekMode = 3 -) - -type FfiConverterTypeSeekMode struct{} - -var FfiConverterTypeSeekModeINSTANCE = FfiConverterTypeSeekMode{} - -func (c FfiConverterTypeSeekMode) Lift(rb RustBufferI) SeekMode { - return LiftFromRustBuffer[SeekMode](c, rb) -} - -func (c FfiConverterTypeSeekMode) Lower(value SeekMode) RustBuffer { - return LowerIntoRustBuffer[SeekMode](c, value) -} -func (FfiConverterTypeSeekMode) Read(reader io.Reader) SeekMode { - id := readInt32(reader) - return SeekMode(id) -} - -func (FfiConverterTypeSeekMode) Write(writer io.Writer, value SeekMode) { - writeInt32(writer, int32(value)) -} - -type FfiDestroyerTypeSeekMode struct{} - -func (_ FfiDestroyerTypeSeekMode) Destroy(value SeekMode) { -} - -type SigningAlg uint - -const ( - SigningAlgEs256 SigningAlg = 1 - SigningAlgEs384 SigningAlg = 2 - SigningAlgEs512 SigningAlg = 3 - SigningAlgPs256 SigningAlg = 4 - SigningAlgPs384 SigningAlg = 5 - SigningAlgPs512 SigningAlg = 6 - SigningAlgEd25519 SigningAlg = 7 -) - -type FfiConverterTypeSigningAlg struct{} - -var FfiConverterTypeSigningAlgINSTANCE = FfiConverterTypeSigningAlg{} - -func (c FfiConverterTypeSigningAlg) Lift(rb RustBufferI) SigningAlg { - return LiftFromRustBuffer[SigningAlg](c, rb) -} - -func (c FfiConverterTypeSigningAlg) Lower(value SigningAlg) RustBuffer { - return LowerIntoRustBuffer[SigningAlg](c, value) -} -func (FfiConverterTypeSigningAlg) Read(reader io.Reader) SigningAlg { - id := readInt32(reader) - return SigningAlg(id) -} - -func (FfiConverterTypeSigningAlg) Write(writer io.Writer, value SigningAlg) { - writeInt32(writer, int32(value)) -} - -type FfiDestroyerTypeSigningAlg struct{} - -func (_ FfiDestroyerTypeSigningAlg) Destroy(value SigningAlg) { -} - -type uniffiCallbackResult C.int32_t - -const ( - uniffiIdxCallbackFree uniffiCallbackResult = 0 - uniffiCallbackResultSuccess uniffiCallbackResult = 0 - uniffiCallbackResultError uniffiCallbackResult = 1 - uniffiCallbackUnexpectedResultError uniffiCallbackResult = 2 - uniffiCallbackCancelled uniffiCallbackResult = 3 -) - -type concurrentHandleMap[T any] struct { - leftMap map[uint64]*T - rightMap map[*T]uint64 - currentHandle uint64 - lock sync.RWMutex -} - -func newConcurrentHandleMap[T any]() *concurrentHandleMap[T] { - return &concurrentHandleMap[T]{ - leftMap: map[uint64]*T{}, - rightMap: map[*T]uint64{}, - } -} - -func (cm *concurrentHandleMap[T]) insert(obj *T) uint64 { - cm.lock.Lock() - defer cm.lock.Unlock() - - if existingHandle, ok := cm.rightMap[obj]; ok { - return existingHandle - } - cm.currentHandle = cm.currentHandle + 1 - cm.leftMap[cm.currentHandle] = obj - cm.rightMap[obj] = cm.currentHandle - return cm.currentHandle -} - -func (cm *concurrentHandleMap[T]) remove(handle uint64) bool { - cm.lock.Lock() - defer cm.lock.Unlock() - - if val, ok := cm.leftMap[handle]; ok { - delete(cm.leftMap, handle) - delete(cm.rightMap, val) - } - return false -} - -func (cm *concurrentHandleMap[T]) tryGet(handle uint64) (*T, bool) { - cm.lock.RLock() - defer cm.lock.RUnlock() - - val, ok := cm.leftMap[handle] - return val, ok -} - -type FfiConverterCallbackInterface[CallbackInterface any] struct { - handleMap *concurrentHandleMap[CallbackInterface] -} - -func (c *FfiConverterCallbackInterface[CallbackInterface]) drop(handle uint64) RustBuffer { - c.handleMap.remove(handle) - return RustBuffer{} -} - -func (c *FfiConverterCallbackInterface[CallbackInterface]) Lift(handle uint64) CallbackInterface { - val, ok := c.handleMap.tryGet(handle) - if !ok { - panic(fmt.Errorf("no callback in handle map: %d", handle)) - } - return *val -} - -func (c *FfiConverterCallbackInterface[CallbackInterface]) Read(reader io.Reader) CallbackInterface { - return c.Lift(readUint64(reader)) -} - -func (c *FfiConverterCallbackInterface[CallbackInterface]) Lower(value CallbackInterface) C.uint64_t { - return C.uint64_t(c.handleMap.insert(&value)) -} - -func (c *FfiConverterCallbackInterface[CallbackInterface]) Write(writer io.Writer, value CallbackInterface) { - writeUint64(writer, uint64(c.Lower(value))) -} - -type SignerCallback interface { - Sign(data []byte) ([]byte, *Error) -} - -// foreignCallbackCallbackInterfaceSignerCallback cannot be callable be a compiled function at a same time -type foreignCallbackCallbackInterfaceSignerCallback struct{} - -//export c2pa_cgo_SignerCallback -func c2pa_cgo_SignerCallback(handle C.uint64_t, method C.int32_t, argsPtr *C.uint8_t, argsLen C.int32_t, outBuf *C.RustBuffer) C.int32_t { - cb := FfiConverterCallbackInterfaceSignerCallbackINSTANCE.Lift(uint64(handle)) - switch method { - case 0: - // 0 means Rust is done with the callback, and the callback - // can be dropped by the foreign language. - *outBuf = FfiConverterCallbackInterfaceSignerCallbackINSTANCE.drop(uint64(handle)) - // See docs of ForeignCallback in `uniffi/src/ffi/foreigncallbacks.rs` - return C.int32_t(uniffiIdxCallbackFree) - - case 1: - var result uniffiCallbackResult - args := unsafe.Slice((*byte)(argsPtr), argsLen) - result = foreignCallbackCallbackInterfaceSignerCallback{}.InvokeSign(cb, args, outBuf) - return C.int32_t(result) - - default: - // This should never happen, because an out of bounds method index won't - // ever be used. Once we can catch errors, we should return an InternalException. - // https://github.com/mozilla/uniffi-rs/issues/351 - return C.int32_t(uniffiCallbackUnexpectedResultError) - } -} - -func (foreignCallbackCallbackInterfaceSignerCallback) InvokeSign(callback SignerCallback, args []byte, outBuf *C.RustBuffer) uniffiCallbackResult { - reader := bytes.NewReader(args) - result, err := callback.Sign(FfiConverterBytesINSTANCE.Read(reader)) - - if err != nil { - // The only way to bypass an unexpected error is to bypass pointer to an empty - // instance of the error - if err.err == nil { - return uniffiCallbackUnexpectedResultError - } - *outBuf = LowerIntoRustBuffer[*Error](FfiConverterTypeErrorINSTANCE, err) - return uniffiCallbackResultError - } - *outBuf = LowerIntoRustBuffer[[]byte](FfiConverterBytesINSTANCE, result) - return uniffiCallbackResultSuccess -} - -type FfiConverterCallbackInterfaceSignerCallback struct { - FfiConverterCallbackInterface[SignerCallback] -} - -var FfiConverterCallbackInterfaceSignerCallbackINSTANCE = &FfiConverterCallbackInterfaceSignerCallback{ - FfiConverterCallbackInterface: FfiConverterCallbackInterface[SignerCallback]{ - handleMap: newConcurrentHandleMap[SignerCallback](), - }, -} - -// This is a static function because only 1 instance is supported for registering -func (c *FfiConverterCallbackInterfaceSignerCallback) register() { - rustCall(func(status *C.RustCallStatus) int32 { - C.uniffi_c2pa_fn_init_callback_signercallback(C.ForeignCallback(C.c2pa_cgo_SignerCallback), status) - return 0 - }) -} - -type FfiDestroyerCallbackInterfaceSignerCallback struct{} - -func (FfiDestroyerCallbackInterfaceSignerCallback) Destroy(value SignerCallback) { -} - -type Stream interface { - ReadStream(length uint64) ([]byte, *Error) - - SeekStream(pos int64, mode SeekMode) (uint64, *Error) - - WriteStream(data []byte) (uint64, *Error) -} - -// foreignCallbackCallbackInterfaceStream cannot be callable be a compiled function at a same time -type foreignCallbackCallbackInterfaceStream struct{} - -//export c2pa_cgo_Stream -func c2pa_cgo_Stream(handle C.uint64_t, method C.int32_t, argsPtr *C.uint8_t, argsLen C.int32_t, outBuf *C.RustBuffer) C.int32_t { - cb := FfiConverterCallbackInterfaceStreamINSTANCE.Lift(uint64(handle)) - switch method { - case 0: - // 0 means Rust is done with the callback, and the callback - // can be dropped by the foreign language. - *outBuf = FfiConverterCallbackInterfaceStreamINSTANCE.drop(uint64(handle)) - // See docs of ForeignCallback in `uniffi/src/ffi/foreigncallbacks.rs` - return C.int32_t(uniffiIdxCallbackFree) - - case 1: - var result uniffiCallbackResult - args := unsafe.Slice((*byte)(argsPtr), argsLen) - result = foreignCallbackCallbackInterfaceStream{}.InvokeReadStream(cb, args, outBuf) - return C.int32_t(result) - case 2: - var result uniffiCallbackResult - args := unsafe.Slice((*byte)(argsPtr), argsLen) - result = foreignCallbackCallbackInterfaceStream{}.InvokeSeekStream(cb, args, outBuf) - return C.int32_t(result) - case 3: - var result uniffiCallbackResult - args := unsafe.Slice((*byte)(argsPtr), argsLen) - result = foreignCallbackCallbackInterfaceStream{}.InvokeWriteStream(cb, args, outBuf) - return C.int32_t(result) - - default: - // This should never happen, because an out of bounds method index won't - // ever be used. Once we can catch errors, we should return an InternalException. - // https://github.com/mozilla/uniffi-rs/issues/351 - return C.int32_t(uniffiCallbackUnexpectedResultError) - } -} - -func (foreignCallbackCallbackInterfaceStream) InvokeReadStream(callback Stream, args []byte, outBuf *C.RustBuffer) uniffiCallbackResult { - reader := bytes.NewReader(args) - result, err := callback.ReadStream(FfiConverterUint64INSTANCE.Read(reader)) - - if err != nil { - // The only way to bypass an unexpected error is to bypass pointer to an empty - // instance of the error - if err.err == nil { - return uniffiCallbackUnexpectedResultError - } - *outBuf = LowerIntoRustBuffer[*Error](FfiConverterTypeErrorINSTANCE, err) - return uniffiCallbackResultError - } - *outBuf = LowerIntoRustBuffer[[]byte](FfiConverterBytesINSTANCE, result) - return uniffiCallbackResultSuccess -} -func (foreignCallbackCallbackInterfaceStream) InvokeSeekStream(callback Stream, args []byte, outBuf *C.RustBuffer) uniffiCallbackResult { - reader := bytes.NewReader(args) - result, err := callback.SeekStream(FfiConverterInt64INSTANCE.Read(reader), FfiConverterTypeSeekModeINSTANCE.Read(reader)) - - if err != nil { - // The only way to bypass an unexpected error is to bypass pointer to an empty - // instance of the error - if err.err == nil { - return uniffiCallbackUnexpectedResultError - } - *outBuf = LowerIntoRustBuffer[*Error](FfiConverterTypeErrorINSTANCE, err) - return uniffiCallbackResultError - } - *outBuf = LowerIntoRustBuffer[uint64](FfiConverterUint64INSTANCE, result) - return uniffiCallbackResultSuccess -} -func (foreignCallbackCallbackInterfaceStream) InvokeWriteStream(callback Stream, args []byte, outBuf *C.RustBuffer) uniffiCallbackResult { - reader := bytes.NewReader(args) - result, err := callback.WriteStream(FfiConverterBytesINSTANCE.Read(reader)) - - if err != nil { - // The only way to bypass an unexpected error is to bypass pointer to an empty - // instance of the error - if err.err == nil { - return uniffiCallbackUnexpectedResultError - } - *outBuf = LowerIntoRustBuffer[*Error](FfiConverterTypeErrorINSTANCE, err) - return uniffiCallbackResultError - } - *outBuf = LowerIntoRustBuffer[uint64](FfiConverterUint64INSTANCE, result) - return uniffiCallbackResultSuccess -} - -type FfiConverterCallbackInterfaceStream struct { - FfiConverterCallbackInterface[Stream] -} - -var FfiConverterCallbackInterfaceStreamINSTANCE = &FfiConverterCallbackInterfaceStream{ - FfiConverterCallbackInterface: FfiConverterCallbackInterface[Stream]{ - handleMap: newConcurrentHandleMap[Stream](), - }, -} - -// This is a static function because only 1 instance is supported for registering -func (c *FfiConverterCallbackInterfaceStream) register() { - rustCall(func(status *C.RustCallStatus) int32 { - C.uniffi_c2pa_fn_init_callback_stream(C.ForeignCallback(C.c2pa_cgo_Stream), status) - return 0 - }) -} - -type FfiDestroyerCallbackInterfaceStream struct{} - -func (FfiDestroyerCallbackInterfaceStream) Destroy(value Stream) { -} - -type FfiConverterOptionalString struct{} - -var FfiConverterOptionalStringINSTANCE = FfiConverterOptionalString{} - -func (c FfiConverterOptionalString) Lift(rb RustBufferI) *string { - return LiftFromRustBuffer[*string](c, rb) -} - -func (_ FfiConverterOptionalString) Read(reader io.Reader) *string { - if readInt8(reader) == 0 { - return nil - } - temp := FfiConverterStringINSTANCE.Read(reader) - return &temp -} - -func (c FfiConverterOptionalString) Lower(value *string) RustBuffer { - return LowerIntoRustBuffer[*string](c, value) -} - -func (_ FfiConverterOptionalString) Write(writer io.Writer, value *string) { - if value == nil { - writeInt8(writer, 0) - } else { - writeInt8(writer, 1) - FfiConverterStringINSTANCE.Write(writer, *value) - } -} - -type FfiDestroyerOptionalString struct{} - -func (_ FfiDestroyerOptionalString) Destroy(value *string) { - if value != nil { - FfiDestroyerString{}.Destroy(*value) - } -} - -func SdkVersion() string { - return FfiConverterStringINSTANCE.Lift(rustCall(func(_uniffiStatus *C.RustCallStatus) RustBufferI { - return C.uniffi_c2pa_fn_func_sdk_version(_uniffiStatus) - })) -} - -func Version() string { - return FfiConverterStringINSTANCE.Lift(rustCall(func(_uniffiStatus *C.RustCallStatus) RustBufferI { - return C.uniffi_c2pa_fn_func_version(_uniffiStatus) - })) -} diff --git a/src/c2pa/c2pa.h b/src/c2pa/c2pa.h deleted file mode 100644 index 39ea6801..00000000 --- a/src/c2pa/c2pa.h +++ /dev/null @@ -1,559 +0,0 @@ - - -// This file was autogenerated by some hot garbage in the `uniffi` crate. -// Trust me, you don't want to mess with it! - - - -#include -#include - -// The following structs are used to implement the lowest level -// of the FFI, and thus useful to multiple uniffied crates. -// We ensure they are declared exactly once, with a header guard, UNIFFI_SHARED_H. -#ifdef UNIFFI_SHARED_H - // We also try to prevent mixing versions of shared uniffi header structs. - // If you add anything to the #else block, you must increment the version suffix in UNIFFI_SHARED_HEADER_V6 - #ifndef UNIFFI_SHARED_HEADER_V6 - #error Combining helper code from multiple versions of uniffi is not supported - #endif // ndef UNIFFI_SHARED_HEADER_V6 -#else -#define UNIFFI_SHARED_H -#define UNIFFI_SHARED_HEADER_V6 -// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️ -// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V6 in this file. ⚠️ - -typedef struct RustBuffer { - int32_t capacity; - int32_t len; - uint8_t *data; -} RustBuffer; - -typedef int32_t (*ForeignCallback)(uint64_t, int32_t, uint8_t *, int32_t, RustBuffer *); - -// Task defined in Rust that Go executes -typedef void (*RustTaskCallback)(const void *, int8_t); - -// Callback to execute Rust tasks using a Go routine -// -// Args: -// executor: ForeignExecutor lowered into a uint64_t value -// delay: Delay in MS -// task: RustTaskCallback to call -// task_data: data to pass the task callback -typedef int8_t (*ForeignExecutorCallback)(uint64_t, uint32_t, RustTaskCallback, void *); - -typedef struct ForeignBytes { - int32_t len; - const uint8_t *data; -} ForeignBytes; - -// Error definitions -typedef struct RustCallStatus { - int8_t code; - RustBuffer errorBuf; -} RustCallStatus; - -// Continuation callback for UniFFI Futures -typedef void (*RustFutureContinuation)(void * , int8_t); - -// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️ -// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V6 in this file. ⚠️ -#endif // def UNIFFI_SHARED_H - -// Needed because we can't execute the callback directly from go. -void cgo_rust_task_callback_bridge_c2pa(RustTaskCallback, const void *, int8_t); - -int8_t uniffiForeignExecutorCallbackc2pa(uint64_t, uint32_t, RustTaskCallback, void*); - -void uniffiFutureContinuationCallbackc2pa(void*, int8_t); - -void uniffi_c2pa_fn_free_builder( - void* ptr, - RustCallStatus* out_status -); - -void* uniffi_c2pa_fn_constructor_builder_new( - RustCallStatus* out_status -); - -void uniffi_c2pa_fn_method_builder_add_ingredient( - void* ptr, - RustBuffer ingredient_json, - RustBuffer format, - uint64_t stream, - RustCallStatus* out_status -); - -void uniffi_c2pa_fn_method_builder_add_resource( - void* ptr, - RustBuffer uri, - uint64_t stream, - RustCallStatus* out_status -); - -void uniffi_c2pa_fn_method_builder_from_archive( - void* ptr, - uint64_t stream, - RustCallStatus* out_status -); - -RustBuffer uniffi_c2pa_fn_method_builder_sign( - void* ptr, - RustBuffer format, - uint64_t input, - uint64_t output, - void* signer, - RustCallStatus* out_status -); - -void uniffi_c2pa_fn_method_builder_to_archive( - void* ptr, - uint64_t stream, - RustCallStatus* out_status -); - -void uniffi_c2pa_fn_method_builder_with_json( - void* ptr, - RustBuffer json, - RustCallStatus* out_status -); - -void uniffi_c2pa_fn_free_callbacksigner( - void* ptr, - RustCallStatus* out_status -); - -void* uniffi_c2pa_fn_constructor_callbacksigner_new( - uint64_t callback, - RustBuffer alg, - RustBuffer certs, - RustBuffer ta_url, - RustCallStatus* out_status -); - -void uniffi_c2pa_fn_free_reader( - void* ptr, - RustCallStatus* out_status -); - -void* uniffi_c2pa_fn_constructor_reader_new( - RustCallStatus* out_status -); - -RustBuffer uniffi_c2pa_fn_method_reader_from_stream( - void* ptr, - RustBuffer format, - uint64_t reader, - RustCallStatus* out_status -); - -RustBuffer uniffi_c2pa_fn_method_reader_json( - void* ptr, - RustCallStatus* out_status -); - -uint64_t uniffi_c2pa_fn_method_reader_resource_to_stream( - void* ptr, - RustBuffer uri, - uint64_t stream, - RustCallStatus* out_status -); - -void uniffi_c2pa_fn_init_callback_signercallback( - ForeignCallback callback_stub, - RustCallStatus* out_status -); - -void uniffi_c2pa_fn_init_callback_stream( - ForeignCallback callback_stub, - RustCallStatus* out_status -); - -RustBuffer uniffi_c2pa_fn_func_sdk_version( - RustCallStatus* out_status -); - -RustBuffer uniffi_c2pa_fn_func_version( - RustCallStatus* out_status -); - -RustBuffer ffi_c2pa_rustbuffer_alloc( - int32_t size, - RustCallStatus* out_status -); - -RustBuffer ffi_c2pa_rustbuffer_from_bytes( - ForeignBytes bytes, - RustCallStatus* out_status -); - -void ffi_c2pa_rustbuffer_free( - RustBuffer buf, - RustCallStatus* out_status -); - -RustBuffer ffi_c2pa_rustbuffer_reserve( - RustBuffer buf, - int32_t additional, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_continuation_callback_set( - RustFutureContinuation callback, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_poll_u8( - void* handle, - void* uniffi_callback, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_cancel_u8( - void* handle, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_free_u8( - void* handle, - RustCallStatus* out_status -); - -uint8_t ffi_c2pa_rust_future_complete_u8( - void* handle, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_poll_i8( - void* handle, - void* uniffi_callback, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_cancel_i8( - void* handle, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_free_i8( - void* handle, - RustCallStatus* out_status -); - -int8_t ffi_c2pa_rust_future_complete_i8( - void* handle, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_poll_u16( - void* handle, - void* uniffi_callback, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_cancel_u16( - void* handle, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_free_u16( - void* handle, - RustCallStatus* out_status -); - -uint16_t ffi_c2pa_rust_future_complete_u16( - void* handle, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_poll_i16( - void* handle, - void* uniffi_callback, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_cancel_i16( - void* handle, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_free_i16( - void* handle, - RustCallStatus* out_status -); - -int16_t ffi_c2pa_rust_future_complete_i16( - void* handle, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_poll_u32( - void* handle, - void* uniffi_callback, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_cancel_u32( - void* handle, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_free_u32( - void* handle, - RustCallStatus* out_status -); - -uint32_t ffi_c2pa_rust_future_complete_u32( - void* handle, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_poll_i32( - void* handle, - void* uniffi_callback, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_cancel_i32( - void* handle, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_free_i32( - void* handle, - RustCallStatus* out_status -); - -int32_t ffi_c2pa_rust_future_complete_i32( - void* handle, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_poll_u64( - void* handle, - void* uniffi_callback, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_cancel_u64( - void* handle, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_free_u64( - void* handle, - RustCallStatus* out_status -); - -uint64_t ffi_c2pa_rust_future_complete_u64( - void* handle, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_poll_i64( - void* handle, - void* uniffi_callback, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_cancel_i64( - void* handle, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_free_i64( - void* handle, - RustCallStatus* out_status -); - -int64_t ffi_c2pa_rust_future_complete_i64( - void* handle, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_poll_f32( - void* handle, - void* uniffi_callback, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_cancel_f32( - void* handle, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_free_f32( - void* handle, - RustCallStatus* out_status -); - -float ffi_c2pa_rust_future_complete_f32( - void* handle, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_poll_f64( - void* handle, - void* uniffi_callback, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_cancel_f64( - void* handle, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_free_f64( - void* handle, - RustCallStatus* out_status -); - -double ffi_c2pa_rust_future_complete_f64( - void* handle, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_poll_pointer( - void* handle, - void* uniffi_callback, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_cancel_pointer( - void* handle, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_free_pointer( - void* handle, - RustCallStatus* out_status -); - -void* ffi_c2pa_rust_future_complete_pointer( - void* handle, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_poll_rust_buffer( - void* handle, - void* uniffi_callback, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_cancel_rust_buffer( - void* handle, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_free_rust_buffer( - void* handle, - RustCallStatus* out_status -); - -RustBuffer ffi_c2pa_rust_future_complete_rust_buffer( - void* handle, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_poll_void( - void* handle, - void* uniffi_callback, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_cancel_void( - void* handle, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_free_void( - void* handle, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_complete_void( - void* handle, - RustCallStatus* out_status -); - -uint16_t uniffi_c2pa_checksum_func_sdk_version( - RustCallStatus* out_status -); - -uint16_t uniffi_c2pa_checksum_func_version( - RustCallStatus* out_status -); - -uint16_t uniffi_c2pa_checksum_method_builder_add_ingredient( - RustCallStatus* out_status -); - -uint16_t uniffi_c2pa_checksum_method_builder_add_resource( - RustCallStatus* out_status -); - -uint16_t uniffi_c2pa_checksum_method_builder_from_archive( - RustCallStatus* out_status -); - -uint16_t uniffi_c2pa_checksum_method_builder_sign( - RustCallStatus* out_status -); - -uint16_t uniffi_c2pa_checksum_method_builder_to_archive( - RustCallStatus* out_status -); - -uint16_t uniffi_c2pa_checksum_method_builder_with_json( - RustCallStatus* out_status -); - -uint16_t uniffi_c2pa_checksum_method_reader_from_stream( - RustCallStatus* out_status -); - -uint16_t uniffi_c2pa_checksum_method_reader_json( - RustCallStatus* out_status -); - -uint16_t uniffi_c2pa_checksum_method_reader_resource_to_stream( - RustCallStatus* out_status -); - -uint16_t uniffi_c2pa_checksum_constructor_builder_new( - RustCallStatus* out_status -); - -uint16_t uniffi_c2pa_checksum_constructor_callbacksigner_new( - RustCallStatus* out_status -); - -uint16_t uniffi_c2pa_checksum_constructor_reader_new( - RustCallStatus* out_status -); - -uint16_t uniffi_c2pa_checksum_method_signercallback_sign( - RustCallStatus* out_status -); - -uint16_t uniffi_c2pa_checksum_method_stream_read_stream( - RustCallStatus* out_status -); - -uint16_t uniffi_c2pa_checksum_method_stream_seek_stream( - RustCallStatus* out_status -); - -uint16_t uniffi_c2pa_checksum_method_stream_write_stream( - RustCallStatus* out_status -); - -uint32_t ffi_c2pa_uniffi_contract_version( - RustCallStatus* out_status -); - - -int32_t c2pa_cgo_SignerCallback(uint64_t, int32_t, uint8_t *, int32_t, RustBuffer *); -int32_t c2pa_cgo_Stream(uint64_t, int32_t, uint8_t *, int32_t, RustBuffer *); - From 0babcb47f836f3618c1eab563235d4c169b64896 Mon Sep 17 00:00:00 2001 From: Eli Mallon Date: Thu, 8 Aug 2024 15:29:24 -0700 Subject: [PATCH 04/42] add darwin build flags --- pkg/c2pa/c2pa.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/c2pa/c2pa.go b/pkg/c2pa/c2pa.go index c530f2ff..4de21d26 100644 --- a/pkg/c2pa/c2pa.go +++ b/pkg/c2pa/c2pa.go @@ -11,6 +11,7 @@ import ( ) // #cgo LDFLAGS: -L../../target/release -lc2pa -lm +// #cgo darwin LDFLAGS: -framework Security import "C" func GetManifest(target io.ReadWriteSeeker, mType string) (string, error) { From 102cea59bb62e4bbff1c458faee78310bc28dcaa Mon Sep 17 00:00:00 2001 From: Eli Mallon Date: Sat, 10 Aug 2024 10:32:02 -0700 Subject: [PATCH 05/42] whoops, actually use provided mime-type --- pkg/c2pa/c2pa.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/c2pa/c2pa.go b/pkg/c2pa/c2pa.go index 4de21d26..637f41a8 100644 --- a/pkg/c2pa/c2pa.go +++ b/pkg/c2pa/c2pa.go @@ -17,7 +17,7 @@ import "C" func GetManifest(target io.ReadWriteSeeker, mType string) (string, error) { stream := C2PAStream{target} r := rustC2PA.NewReader() - r.FromStream("image/jpeg", &stream) + r.FromStream(mType, &stream) ret, err := r.Json() if err != nil { return "", err From 93400c195f3d02e7c700176efd06cea56af679e4 Mon Sep 17 00:00:00 2001 From: Eli Mallon Date: Sat, 10 Aug 2024 10:41:04 -0700 Subject: [PATCH 06/42] Makefile: split up the steps a bit --- Makefile | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 189b0940..6fad5b36 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,14 @@ .PHONY: all -all: - mkdir -p dist +all: rust go + +.PHONY: rust +rust: cargo install uniffi-bindgen-go --git https://github.com/NordSecurity/uniffi-bindgen-go --tag v0.2.1+v0.25.0 cargo build --release + +.PHONY: go +go: + mkdir -p dist uniffi-bindgen-go src/c2pa.udl --out-dir pkg/c2pa/generated go build -o ./dist/go-demo ./pkg/c2pa/demo/... From c8bd69f0528c81d787965a9db555d7c89bf024e3 Mon Sep 17 00:00:00 2001 From: Eli Mallon Date: Sat, 10 Aug 2024 11:34:42 -0700 Subject: [PATCH 07/42] add generated json-schema types --- Makefile | 10 +++++++++- pkg/c2pa/c2pa.go | 13 ++++++++++--- pkg/c2pa/demo/demo.go | 9 ++++++++- 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 6fad5b36..432d4be5 100644 --- a/Makefile +++ b/Makefile @@ -1,12 +1,20 @@ .PHONY: all -all: rust go +all: rust types go .PHONY: rust rust: cargo install uniffi-bindgen-go --git https://github.com/NordSecurity/uniffi-bindgen-go --tag v0.2.1+v0.25.0 cargo build --release +.PHONY: types +types: + go install github.com/atombender/go-jsonschema@latest + cargo install --git https://github.com/aquareum-tv/c2pa-rs export_schema + go-jsonschema -p manifeststore ./target/schema/ManifestStore.schema.json -o pkg/c2pa/generated/manifeststore/manifeststore.go + go-jsonschema -p manifestdefinition ./target/schema/ManifestDefinition.schema.json -o pkg/c2pa/generated/manifestdefinition/manifestdefinition.go + go-jsonschema -p settings ./target/schema/Settings.schema.json -o pkg/c2pa/generated/settings/settings.go + .PHONY: go go: mkdir -p dist diff --git a/pkg/c2pa/c2pa.go b/pkg/c2pa/c2pa.go index 637f41a8..45678161 100644 --- a/pkg/c2pa/c2pa.go +++ b/pkg/c2pa/c2pa.go @@ -3,26 +3,33 @@ package c2pa import ( + "encoding/json" "errors" "fmt" "io" rustC2PA "git.aquareum.tv/aquareum-tv/c2pa-go/pkg/c2pa/generated/c2pa" + "git.aquareum.tv/aquareum-tv/c2pa-go/pkg/c2pa/generated/manifeststore" ) // #cgo LDFLAGS: -L../../target/release -lc2pa -lm // #cgo darwin LDFLAGS: -framework Security import "C" -func GetManifest(target io.ReadWriteSeeker, mType string) (string, error) { +func GetManifest(target io.ReadWriteSeeker, mType string) (*manifeststore.ManifestStoreSchemaJson, error) { stream := C2PAStream{target} r := rustC2PA.NewReader() r.FromStream(mType, &stream) ret, err := r.Json() if err != nil { - return "", err + return nil, err } - return ret, nil + var store manifeststore.ManifestStoreSchemaJson + err = json.Unmarshal([]byte(ret), &store) + if err != nil { + return nil, err + } + return &store, nil } type C2PAStream struct { diff --git a/pkg/c2pa/demo/demo.go b/pkg/c2pa/demo/demo.go index efceac5b..11ddbe69 100644 --- a/pkg/c2pa/demo/demo.go +++ b/pkg/c2pa/demo/demo.go @@ -1,6 +1,7 @@ package main import ( + "encoding/json" "flag" "fmt" "mime" @@ -32,7 +33,13 @@ func Start() error { if err != nil { return err } - fmt.Println(manifest) + + bs, err := json.MarshalIndent(manifest, "", " ") + if err != nil { + return err + } + + fmt.Println(string(bs)) return nil } From d91d8d1ecc22540a390dcee223f4cc0c46bf35e7 Mon Sep 17 00:00:00 2001 From: Eli Mallon Date: Sat, 10 Aug 2024 11:58:34 -0700 Subject: [PATCH 08/42] go: start to outline some actual types --- pkg/c2pa/c2pa.go | 44 +++++++++++++++++++++++++++++++++++++++++-- pkg/c2pa/demo/demo.go | 18 ++++++------------ 2 files changed, 48 insertions(+), 14 deletions(-) diff --git a/pkg/c2pa/c2pa.go b/pkg/c2pa/c2pa.go index 45678161..5ceb389d 100644 --- a/pkg/c2pa/c2pa.go +++ b/pkg/c2pa/c2pa.go @@ -7,6 +7,9 @@ import ( "errors" "fmt" "io" + "mime" + "os" + "path/filepath" rustC2PA "git.aquareum.tv/aquareum-tv/c2pa-go/pkg/c2pa/generated/c2pa" "git.aquareum.tv/aquareum-tv/c2pa-go/pkg/c2pa/generated/manifeststore" @@ -16,7 +19,12 @@ import ( // #cgo darwin LDFLAGS: -framework Security import "C" -func GetManifest(target io.ReadWriteSeeker, mType string) (*manifeststore.ManifestStoreSchemaJson, error) { +type Reader interface { + GetManifest(label string) *manifeststore.Manifest + GetActiveManifest() *manifeststore.Manifest +} + +func FromStream(target io.ReadWriteSeeker, mType string) (Reader, error) { stream := C2PAStream{target} r := rustC2PA.NewReader() r.FromStream(mType, &stream) @@ -29,7 +37,39 @@ func GetManifest(target io.ReadWriteSeeker, mType string) (*manifeststore.Manife if err != nil { return nil, err } - return &store, nil + return &C2PAReader{store: &store}, nil +} + +func FromFile(fname string) (Reader, error) { + mType := mime.TypeByExtension(filepath.Ext(fname)) + if mType == "" { + return nil, fmt.Errorf("couldn't find MIME type for filename %s", fname) + } + f, err := os.Open(fname) + if err != nil { + return nil, err + } + defer f.Close() + return FromStream(f, mType) +} + +type C2PAReader struct { + store *manifeststore.ManifestStoreSchemaJson +} + +func (r *C2PAReader) GetManifest(label string) *manifeststore.Manifest { + m, ok := r.store.Manifests[label] + if !ok { + return nil + } + return &m +} + +func (r *C2PAReader) GetActiveManifest() *manifeststore.Manifest { + if r.store.ActiveManifest == nil { + return nil + } + return r.GetManifest(*r.store.ActiveManifest) } type C2PAStream struct { diff --git a/pkg/c2pa/demo/demo.go b/pkg/c2pa/demo/demo.go index 11ddbe69..6a8dd07b 100644 --- a/pkg/c2pa/demo/demo.go +++ b/pkg/c2pa/demo/demo.go @@ -4,9 +4,7 @@ import ( "encoding/json" "flag" "fmt" - "mime" "os" - "path/filepath" "git.aquareum.tv/aquareum-tv/c2pa-go/pkg/c2pa" ) @@ -20,21 +18,17 @@ func Start() error { return nil } fname := args[1] - f, err := os.Open(fname) + reader, err := c2pa.FromFile(fname) if err != nil { return err } - defer f.Close() - mType := mime.TypeByExtension(filepath.Ext(fname)) - if mType == "" { - return fmt.Errorf("couldn't find MIME type for filename %s", fname) - } - manifest, err := c2pa.GetManifest(f, mType) - if err != nil { - return err + + activeManifest := reader.GetActiveManifest() + if activeManifest == nil { + return fmt.Errorf("could not find active manifest") } - bs, err := json.MarshalIndent(manifest, "", " ") + bs, err := json.MarshalIndent(activeManifest, "", " ") if err != nil { return err } From 06b6694eff682efa8afc2a079831133786c76548 Mon Sep 17 00:00:00 2001 From: Eli Mallon Date: Sat, 10 Aug 2024 14:05:40 -0700 Subject: [PATCH 09/42] checkpoint with working signing --- pkg/c2pa/c2pa.go | 130 +++++++++++++++++++++++++++++++++++++++++- pkg/c2pa/demo/demo.go | 23 +++++++- 2 files changed, 148 insertions(+), 5 deletions(-) diff --git a/pkg/c2pa/c2pa.go b/pkg/c2pa/c2pa.go index 5ceb389d..e016a92b 100644 --- a/pkg/c2pa/c2pa.go +++ b/pkg/c2pa/c2pa.go @@ -3,7 +3,12 @@ package c2pa import ( + "crypto/ecdsa" + "crypto/rand" + "crypto/sha256" + "crypto/x509" "encoding/json" + "encoding/pem" "errors" "fmt" "io" @@ -29,6 +34,7 @@ func FromStream(target io.ReadWriteSeeker, mType string) (Reader, error) { r := rustC2PA.NewReader() r.FromStream(mType, &stream) ret, err := r.Json() + fmt.Println(ret) if err != nil { return nil, err } @@ -122,6 +128,126 @@ func (s *C2PAStream) SeekStream(pos int64, mode rustC2PA.SeekMode) (uint64, *rus } func (s *C2PAStream) WriteStream(data []byte) (uint64, *rustC2PA.Error) { - panic("not implemented") - return 0, nil + wrote, err := s.ReadWriteSeeker.Write(data) + if err != nil { + return uint64(wrote), rustC2PA.NewErrorIo(err.Error()) + } + return uint64(wrote), nil +} + +type Builder interface { + GetManifest(label string) *manifeststore.Manifest + GetActiveManifest() *manifeststore.Manifest +} + +type C2PABuilder struct { + builder *rustC2PA.Builder +} + +var certs = []byte(`-----BEGIN CERTIFICATE----- +MIIChzCCAi6gAwIBAgIUcCTmJHYF8dZfG0d1UdT6/LXtkeYwCgYIKoZIzj0EAwIw +gYwxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTESMBAGA1UEBwwJU29tZXdoZXJl +MScwJQYDVQQKDB5DMlBBIFRlc3QgSW50ZXJtZWRpYXRlIFJvb3QgQ0ExGTAXBgNV +BAsMEEZPUiBURVNUSU5HX09OTFkxGDAWBgNVBAMMD0ludGVybWVkaWF0ZSBDQTAe +Fw0yMjA2MTAxODQ2NDBaFw0zMDA4MjYxODQ2NDBaMIGAMQswCQYDVQQGEwJVUzEL +MAkGA1UECAwCQ0ExEjAQBgNVBAcMCVNvbWV3aGVyZTEfMB0GA1UECgwWQzJQQSBU +ZXN0IFNpZ25pbmcgQ2VydDEZMBcGA1UECwwQRk9SIFRFU1RJTkdfT05MWTEUMBIG +A1UEAwwLQzJQQSBTaWduZXIwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQPaL6R +kAkYkKU4+IryBSYxJM3h77sFiMrbvbI8fG7w2Bbl9otNG/cch3DAw5rGAPV7NWky +l3QGuV/wt0MrAPDoo3gwdjAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsG +AQUFBwMEMA4GA1UdDwEB/wQEAwIGwDAdBgNVHQ4EFgQUFznP0y83joiNOCedQkxT +tAMyNcowHwYDVR0jBBgwFoAUDnyNcma/osnlAJTvtW6A4rYOL2swCgYIKoZIzj0E +AwIDRwAwRAIgOY/2szXjslg/MyJFZ2y7OH8giPYTsvS7UPRP9GI9NgICIDQPMKrE +LQUJEtipZ0TqvI/4mieoyRCeIiQtyuS0LACz +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICajCCAg+gAwIBAgIUfXDXHH+6GtA2QEBX2IvJ2YnGMnUwCgYIKoZIzj0EAwIw +dzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlTb21ld2hlcmUx +GjAYBgNVBAoMEUMyUEEgVGVzdCBSb290IENBMRkwFwYDVQQLDBBGT1IgVEVTVElO +R19PTkxZMRAwDgYDVQQDDAdSb290IENBMB4XDTIyMDYxMDE4NDY0MFoXDTMwMDgy +NzE4NDY0MFowgYwxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTESMBAGA1UEBwwJ +U29tZXdoZXJlMScwJQYDVQQKDB5DMlBBIFRlc3QgSW50ZXJtZWRpYXRlIFJvb3Qg +Q0ExGTAXBgNVBAsMEEZPUiBURVNUSU5HX09OTFkxGDAWBgNVBAMMD0ludGVybWVk +aWF0ZSBDQTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABHllI4O7a0EkpTYAWfPM +D6Rnfk9iqhEmCQKMOR6J47Rvh2GGjUw4CS+aLT89ySukPTnzGsMQ4jK9d3V4Aq4Q +LsOjYzBhMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQW +BBQOfI1yZr+iyeUAlO+1boDitg4vazAfBgNVHSMEGDAWgBRembiG4Xgb2VcVWnUA +UrYpDsuojDAKBggqhkjOPQQDAgNJADBGAiEAtdZ3+05CzFo90fWeZ4woeJcNQC4B +84Ill3YeZVvR8ZECIQDVRdha1xEDKuNTAManY0zthSosfXcvLnZui1A/y/DYeg== +-----END CERTIFICATE----- +`) + +var priv = []byte(`-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgfNJBsaRLSeHizv0m +GL+gcn78QmtfLSm+n+qG9veC2W2hRANCAAQPaL6RkAkYkKU4+IryBSYxJM3h77sF +iMrbvbI8fG7w2Bbl9otNG/cch3DAw5rGAPV7NWkyl3QGuV/wt0MrAPDo +-----END PRIVATE KEY----- +`) + +func NewBuilder() *C2PABuilder { + b := rustC2PA.NewBuilder() + str := `{ + "alg": "ES256", + "private_key": "/home/iameli/testvids/cert.key", + "sign_cert": "/home/iameli/testvids/cert.crt", + "ta_url": "http://timestamp.digicert.com", + "claim_generator": "Aquareum", + "title": "Image File", + "assertions": [ + { + "label": "c2pa.actions", + "data": { "actions": [{ "action": "c2pa.published" }] } + } + ] + }` + err := b.WithJson(str) + if err != nil { + panic(err) + } + infile, err := os.Open("/home/iameli/testvids/screenshot.jpg") + if err != nil { + panic(err) + } + outfile, err := os.Create("/home/iameli/code/c2pa-go/screenshot-signed-go.jpg") + if err != nil { + panic(err) + } + mySigner := &C2PASigner{} + taUrl := "http://timestamp.digicert.com" + signer := rustC2PA.NewCallbackSigner(mySigner, rustC2PA.SigningAlgEs256, certs, &taUrl) + bs, err := b.Sign("image/jpeg", &C2PAStream{infile}, &C2PAStream{outfile}, signer) + if err != nil { + panic(err) + } + fmt.Printf("got %d bytes\n", len(bs)) + return &C2PABuilder{b} +} + +type C2PASigner struct{} + +func (s *C2PASigner) Sign(data []byte) ([]byte, *rustC2PA.Error) { + // fmt.Printf("Sign called len(data)=%d\n", len(data)) + block, _ := pem.Decode(priv) + if block == nil { + return []byte{}, rustC2PA.NewErrorOther("failed to parse PEM block containing the private key") + } + key, err := x509.ParsePKCS8PrivateKey(block.Bytes) + if err != nil { + fmt.Println(err.Error()) + return []byte{}, rustC2PA.NewErrorOther(err.Error()) + } + + h := sha256.New() + + h.Write(data) + + hashbs := h.Sum(nil) + // fmt.Printf("len(hashbs)=%d\n", len(hashbs)) + + bs, err := ecdsa.SignASN1(rand.Reader, key.(*ecdsa.PrivateKey), hashbs) + if err != nil { + fmt.Println(err.Error()) + return []byte{}, rustC2PA.NewErrorOther(err.Error()) + } + return bs, nil } diff --git a/pkg/c2pa/demo/demo.go b/pkg/c2pa/demo/demo.go index 6a8dd07b..46ea911f 100644 --- a/pkg/c2pa/demo/demo.go +++ b/pkg/c2pa/demo/demo.go @@ -11,13 +11,30 @@ import ( func Start() error { fs := flag.NewFlagSet("c2pa-go-demo", flag.ExitOnError) - fs.Parse(os.Args) + manifest := fs.String("manifest", "", "manifest file for signing") + output := fs.String("output", "", "output file for signing") + pass := os.Args[1:] + err := fs.Parse(pass) + if err != nil { + return err + } + if *manifest != "" || *output != "" { + if *manifest == "" { + return fmt.Errorf("missing --manifest") + } + if *output == "" { + return fmt.Errorf("missing --output") + } + c2pa.NewBuilder() + return nil + } args := fs.Args() - if len(args) != 2 { + if len(args) != 1 { + fs.Usage() fmt.Printf("usage: %s [target-file]\n", os.Args[0]) return nil } - fname := args[1] + fname := args[0] reader, err := c2pa.FromFile(fname) if err != nil { return err From d1af42d6b0bf9917b740d660abcb26e889342f36 Mon Sep 17 00:00:00 2001 From: Eli Mallon Date: Sun, 11 Aug 2024 12:52:01 -0700 Subject: [PATCH 10/42] go: working signing flow --- Cargo.toml | 5 +- pkg/c2pa/c2pa.go | 158 +++++++++++++++++++++--------------------- pkg/c2pa/demo/demo.go | 43 +++++++++++- src/c2pa.udl | 1 + 4 files changed, 127 insertions(+), 80 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d082ae52..2234b116 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,10 @@ name = "c2pa" crate-type = ["staticlib"] [dependencies] -c2pa = { version = "0.32.0", features = ["unstable_api", "openssl"] } +c2pa = { git = "https://git.aquareum.tv/aquareum-tv/c2pa-rs.git", branch = "es256k", features = [ + "unstable_api", + "openssl", +] } pem = "3.0.3" serde = { version = "1.0.197", features = ["derive"] } serde_derive = "1.0" diff --git a/pkg/c2pa/c2pa.go b/pkg/c2pa/c2pa.go index e016a92b..ce56968f 100644 --- a/pkg/c2pa/c2pa.go +++ b/pkg/c2pa/c2pa.go @@ -3,8 +3,10 @@ package c2pa import ( + "crypto" "crypto/ecdsa" "crypto/rand" + "crypto/rsa" "crypto/sha256" "crypto/x509" "encoding/json" @@ -17,6 +19,7 @@ import ( "path/filepath" rustC2PA "git.aquareum.tv/aquareum-tv/c2pa-go/pkg/c2pa/generated/c2pa" + "git.aquareum.tv/aquareum-tv/c2pa-go/pkg/c2pa/generated/manifestdefinition" "git.aquareum.tv/aquareum-tv/c2pa-go/pkg/c2pa/generated/manifeststore" ) @@ -136,98 +139,77 @@ func (s *C2PAStream) WriteStream(data []byte) (uint64, *rustC2PA.Error) { } type Builder interface { - GetManifest(label string) *manifeststore.Manifest - GetActiveManifest() *manifeststore.Manifest + Sign(input, output io.ReadWriteSeeker, mimeType string) error + SignFile(infile, outfile string) error +} + +type BuilderParams struct { + Cert []byte + Key []byte + TAURL string + Algorithm string } type C2PABuilder struct { - builder *rustC2PA.Builder -} - -var certs = []byte(`-----BEGIN CERTIFICATE----- -MIIChzCCAi6gAwIBAgIUcCTmJHYF8dZfG0d1UdT6/LXtkeYwCgYIKoZIzj0EAwIw -gYwxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTESMBAGA1UEBwwJU29tZXdoZXJl -MScwJQYDVQQKDB5DMlBBIFRlc3QgSW50ZXJtZWRpYXRlIFJvb3QgQ0ExGTAXBgNV -BAsMEEZPUiBURVNUSU5HX09OTFkxGDAWBgNVBAMMD0ludGVybWVkaWF0ZSBDQTAe -Fw0yMjA2MTAxODQ2NDBaFw0zMDA4MjYxODQ2NDBaMIGAMQswCQYDVQQGEwJVUzEL -MAkGA1UECAwCQ0ExEjAQBgNVBAcMCVNvbWV3aGVyZTEfMB0GA1UECgwWQzJQQSBU -ZXN0IFNpZ25pbmcgQ2VydDEZMBcGA1UECwwQRk9SIFRFU1RJTkdfT05MWTEUMBIG -A1UEAwwLQzJQQSBTaWduZXIwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQPaL6R -kAkYkKU4+IryBSYxJM3h77sFiMrbvbI8fG7w2Bbl9otNG/cch3DAw5rGAPV7NWky -l3QGuV/wt0MrAPDoo3gwdjAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsG -AQUFBwMEMA4GA1UdDwEB/wQEAwIGwDAdBgNVHQ4EFgQUFznP0y83joiNOCedQkxT -tAMyNcowHwYDVR0jBBgwFoAUDnyNcma/osnlAJTvtW6A4rYOL2swCgYIKoZIzj0E -AwIDRwAwRAIgOY/2szXjslg/MyJFZ2y7OH8giPYTsvS7UPRP9GI9NgICIDQPMKrE -LQUJEtipZ0TqvI/4mieoyRCeIiQtyuS0LACz ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIICajCCAg+gAwIBAgIUfXDXHH+6GtA2QEBX2IvJ2YnGMnUwCgYIKoZIzj0EAwIw -dzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlTb21ld2hlcmUx -GjAYBgNVBAoMEUMyUEEgVGVzdCBSb290IENBMRkwFwYDVQQLDBBGT1IgVEVTVElO -R19PTkxZMRAwDgYDVQQDDAdSb290IENBMB4XDTIyMDYxMDE4NDY0MFoXDTMwMDgy -NzE4NDY0MFowgYwxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTESMBAGA1UEBwwJ -U29tZXdoZXJlMScwJQYDVQQKDB5DMlBBIFRlc3QgSW50ZXJtZWRpYXRlIFJvb3Qg -Q0ExGTAXBgNVBAsMEEZPUiBURVNUSU5HX09OTFkxGDAWBgNVBAMMD0ludGVybWVk -aWF0ZSBDQTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABHllI4O7a0EkpTYAWfPM -D6Rnfk9iqhEmCQKMOR6J47Rvh2GGjUw4CS+aLT89ySukPTnzGsMQ4jK9d3V4Aq4Q -LsOjYzBhMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQW -BBQOfI1yZr+iyeUAlO+1boDitg4vazAfBgNVHSMEGDAWgBRembiG4Xgb2VcVWnUA -UrYpDsuojDAKBggqhkjOPQQDAgNJADBGAiEAtdZ3+05CzFo90fWeZ4woeJcNQC4B -84Ill3YeZVvR8ZECIQDVRdha1xEDKuNTAManY0zthSosfXcvLnZui1A/y/DYeg== ------END CERTIFICATE----- -`) - -var priv = []byte(`-----BEGIN PRIVATE KEY----- -MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgfNJBsaRLSeHizv0m -GL+gcn78QmtfLSm+n+qG9veC2W2hRANCAAQPaL6RkAkYkKU4+IryBSYxJM3h77sF -iMrbvbI8fG7w2Bbl9otNG/cch3DAw5rGAPV7NWkyl3QGuV/wt0MrAPDo ------END PRIVATE KEY----- -`) - -func NewBuilder() *C2PABuilder { + builder *rustC2PA.Builder + manifest *ManifestDefinition + params *BuilderParams +} + +type ManifestDefinition manifestdefinition.ManifestDefinitionSchemaJson + +func NewBuilder(manifest *ManifestDefinition, params *BuilderParams) (Builder, error) { b := rustC2PA.NewBuilder() - str := `{ - "alg": "ES256", - "private_key": "/home/iameli/testvids/cert.key", - "sign_cert": "/home/iameli/testvids/cert.crt", - "ta_url": "http://timestamp.digicert.com", - "claim_generator": "Aquareum", - "title": "Image File", - "assertions": [ - { - "label": "c2pa.actions", - "data": { "actions": [{ "action": "c2pa.published" }] } - } - ] - }` - err := b.WithJson(str) + bs, err := json.Marshal(manifest) if err != nil { - panic(err) + return nil, err } - infile, err := os.Open("/home/iameli/testvids/screenshot.jpg") + err = b.WithJson(string(bs)) if err != nil { - panic(err) + return nil, err + } + + return &C2PABuilder{builder: b, manifest: manifest, params: params}, nil +} + +func (b *C2PABuilder) Sign(input, output io.ReadWriteSeeker, mimeType string) error { + mySigner := &C2PACallbackSigner{ + key: b.params.Key, } - outfile, err := os.Create("/home/iameli/code/c2pa-go/screenshot-signed-go.jpg") + signer := rustC2PA.NewCallbackSigner(mySigner, rustC2PA.SigningAlgEs256, b.params.Cert, &b.params.TAURL) + _, err := b.builder.Sign(mimeType, &C2PAStream{input}, &C2PAStream{output}, signer) if err != nil { - panic(err) + return err } - mySigner := &C2PASigner{} - taUrl := "http://timestamp.digicert.com" - signer := rustC2PA.NewCallbackSigner(mySigner, rustC2PA.SigningAlgEs256, certs, &taUrl) - bs, err := b.Sign("image/jpeg", &C2PAStream{infile}, &C2PAStream{outfile}, signer) + return nil +} + +// helper function for operating on files +func (b *C2PABuilder) SignFile(infile, outfile string) error { + mimeType := mime.TypeByExtension(filepath.Ext(infile)) + if mimeType == "" { + return fmt.Errorf("couldn't find MIME type for filename %s", infile) + } + input, err := os.Open(infile) if err != nil { - panic(err) + return err } - fmt.Printf("got %d bytes\n", len(bs)) - return &C2PABuilder{b} + defer input.Close() + + output, err := os.Create(outfile) + if err != nil { + return err + } + defer output.Close() + return b.Sign(input, output, mimeType) } -type C2PASigner struct{} +type C2PACallbackSigner struct { + key []byte +} -func (s *C2PASigner) Sign(data []byte) ([]byte, *rustC2PA.Error) { - // fmt.Printf("Sign called len(data)=%d\n", len(data)) - block, _ := pem.Decode(priv) +func (s *C2PACallbackSigner) Sign(data []byte) ([]byte, *rustC2PA.Error) { + block, _ := pem.Decode(s.key) if block == nil { return []byte{}, rustC2PA.NewErrorOther("failed to parse PEM block containing the private key") } @@ -242,7 +224,6 @@ func (s *C2PASigner) Sign(data []byte) ([]byte, *rustC2PA.Error) { h.Write(data) hashbs := h.Sum(nil) - // fmt.Printf("len(hashbs)=%d\n", len(hashbs)) bs, err := ecdsa.SignASN1(rand.Reader, key.(*ecdsa.PrivateKey), hashbs) if err != nil { @@ -251,3 +232,24 @@ func (s *C2PASigner) Sign(data []byte) ([]byte, *rustC2PA.Error) { } return bs, nil } + +func parsePrivateKey(der []byte) (crypto.Signer, error) { + if key, err := x509.ParsePKCS1PrivateKey(der); err == nil { + return key, nil + } + if key, err := x509.ParsePKCS8PrivateKey(der); err == nil { + switch key := key.(type) { + case *rsa.PrivateKey: + return key, nil + case *ecdsa.PrivateKey: + return key, nil + default: + return nil, errors.New("crypto/tls: found unknown private key type in PKCS#8 wrapping") + } + } + if key, err := x509.ParseECPrivateKey(der); err == nil { + return key, nil + } + + return nil, errors.New("crypto/tls: failed to parse private key") +} diff --git a/pkg/c2pa/demo/demo.go b/pkg/c2pa/demo/demo.go index 46ea911f..11c4e066 100644 --- a/pkg/c2pa/demo/demo.go +++ b/pkg/c2pa/demo/demo.go @@ -12,6 +12,9 @@ import ( func Start() error { fs := flag.NewFlagSet("c2pa-go-demo", flag.ExitOnError) manifest := fs.String("manifest", "", "manifest file for signing") + cert := fs.String("cert", "", "certificate file to use") + key := fs.String("key", "", "private key file to use") + input := fs.String("input", "", "input file for signing") output := fs.String("output", "", "output file for signing") pass := os.Args[1:] err := fs.Parse(pass) @@ -25,7 +28,45 @@ func Start() error { if *output == "" { return fmt.Errorf("missing --output") } - c2pa.NewBuilder() + if *input == "" { + return fmt.Errorf("missing --input") + } + if *cert == "" { + return fmt.Errorf("missing --cert") + } + if *key == "" { + return fmt.Errorf("missing --key") + } + certBytes, err := os.ReadFile(*cert) + if err != nil { + return err + } + keyBytes, err := os.ReadFile(*key) + if err != nil { + return err + } + manifestBytes, err := os.ReadFile(*manifest) + if err != nil { + return err + } + var manifest c2pa.ManifestDefinition + err = json.Unmarshal(manifestBytes, &manifest) + if err != nil { + return err + } + b, err := c2pa.NewBuilder(&manifest, &c2pa.BuilderParams{ + Cert: certBytes, + Key: keyBytes, + TAURL: "http://timestamp.digicert.com", + Algorithm: "es256", + }) + if err != nil { + return err + } + err = b.SignFile(*input, *output) + if err != nil { + return err + } return nil } args := fs.Args() diff --git a/src/c2pa.udl b/src/c2pa.udl index 9bb1989c..7dd3870a 100644 --- a/src/c2pa.udl +++ b/src/c2pa.udl @@ -26,6 +26,7 @@ interface Error { enum SigningAlg { "Es256", + "Es256k", "Es384", "Es512", "Ps256", From 4a67ed9bdd0a197dfe7f7aacbef26ffeb69d1ad2 Mon Sep 17 00:00:00 2001 From: Eli Mallon Date: Sun, 11 Aug 2024 15:18:27 -0700 Subject: [PATCH 11/42] working es256k signing! --- Cargo.toml | 3 +- Makefile | 2 +- go.mod | 4 +++ go.sum | 10 ++++++ pkg/c2pa/c2pa.go | 53 +++++++++++++++++++++++-------- pkg/c2pa/demo/demo.go | 6 +++- tests/fixtures/es256k_certs.pem | 31 ++++++++++++++++++ tests/fixtures/es256k_private.key | 5 +++ 8 files changed, 97 insertions(+), 17 deletions(-) create mode 100644 go.sum create mode 100644 tests/fixtures/es256k_certs.pem create mode 100644 tests/fixtures/es256k_private.key diff --git a/Cargo.toml b/Cargo.toml index 2234b116..6c5e2f12 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,10 +10,11 @@ name = "c2pa" crate-type = ["staticlib"] [dependencies] -c2pa = { git = "https://git.aquareum.tv/aquareum-tv/c2pa-rs.git", branch = "es256k", features = [ +c2pa = { git = "https://git.aquareum.tv/aquareum-tv/c2pa-rs.git", rev = "ac3a37eb1f51b9498b6b2974bd26ed2a362bf517", features = [ "unstable_api", "openssl", ] } +# c2pa = { path = "../c2pa-rs/sdk", features = ["unstable_api", "openssl"] } pem = "3.0.3" serde = { version = "1.0.197", features = ["derive"] } serde_derive = "1.0" diff --git a/Makefile b/Makefile index 432d4be5..5efd76bd 100644 --- a/Makefile +++ b/Makefile @@ -19,4 +19,4 @@ types: go: mkdir -p dist uniffi-bindgen-go src/c2pa.udl --out-dir pkg/c2pa/generated - go build -o ./dist/go-demo ./pkg/c2pa/demo/... + go build -a -o ./dist/go-demo ./pkg/c2pa/demo/... diff --git a/go.mod b/go.mod index 6409daf4..75990f7e 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,7 @@ module git.aquareum.tv/aquareum-tv/c2pa-go go 1.22.2 + +require github.com/decred/dcrd/dcrec/secp256k1 v1.0.4 + +require github.com/decred/dcrd/dcrec/secp256k1/v2 v2.0.0 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 00000000..51004540 --- /dev/null +++ b/go.sum @@ -0,0 +1,10 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/chaincfg/chainhash v1.0.2 h1:rt5Vlq/jM3ZawwiacWjPa+smINyLRN07EO0cNBV6DGU= +github.com/decred/dcrd/chaincfg/chainhash v1.0.2/go.mod h1:BpbrGgrPTr3YJYRN3Bm+D9NuaFd+zGyNeIKgrhCXK60= +github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/dcrec/secp256k1 v1.0.4 h1:0XErmfJBiVbl0NvyclGn4jr+1hIylDf5beFi9W0o7Fc= +github.com/decred/dcrd/dcrec/secp256k1 v1.0.4/go.mod h1:00z7mJdugt+GBAzPN1QrDRGCXxyKUiexEHu6ukxEw3k= +github.com/decred/dcrd/dcrec/secp256k1/v2 v2.0.0 h1:3GIJYXQDAKpLEFriGFN8SbSffak10UXHGdIcFaMPykY= +github.com/decred/dcrd/dcrec/secp256k1/v2 v2.0.0/go.mod h1:3s92l0paYkZoIHuj4X93Teg/HB7eGM9x/zokGw+u4mY= diff --git a/pkg/c2pa/c2pa.go b/pkg/c2pa/c2pa.go index ce56968f..f69814d4 100644 --- a/pkg/c2pa/c2pa.go +++ b/pkg/c2pa/c2pa.go @@ -9,6 +9,8 @@ import ( "crypto/rsa" "crypto/sha256" "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" "encoding/json" "encoding/pem" "errors" @@ -21,6 +23,7 @@ import ( rustC2PA "git.aquareum.tv/aquareum-tv/c2pa-go/pkg/c2pa/generated/c2pa" "git.aquareum.tv/aquareum-tv/c2pa-go/pkg/c2pa/generated/manifestdefinition" "git.aquareum.tv/aquareum-tv/c2pa-go/pkg/c2pa/generated/manifeststore" + "github.com/decred/dcrd/dcrec/secp256k1" ) // #cgo LDFLAGS: -L../../target/release -lc2pa -lm @@ -174,9 +177,9 @@ func NewBuilder(manifest *ManifestDefinition, params *BuilderParams) (Builder, e func (b *C2PABuilder) Sign(input, output io.ReadWriteSeeker, mimeType string) error { mySigner := &C2PACallbackSigner{ - key: b.params.Key, + params: b.params, } - signer := rustC2PA.NewCallbackSigner(mySigner, rustC2PA.SigningAlgEs256, b.params.Cert, &b.params.TAURL) + signer := rustC2PA.NewCallbackSigner(mySigner, rustC2PA.SigningAlgEs256k, b.params.Cert, &b.params.TAURL) _, err := b.builder.Sign(mimeType, &C2PAStream{input}, &C2PAStream{output}, signer) if err != nil { return err @@ -205,30 +208,52 @@ func (b *C2PABuilder) SignFile(infile, outfile string) error { } type C2PACallbackSigner struct { - key []byte + params *BuilderParams +} + +type pkcs8 struct { + Version int + Algo pkix.AlgorithmIdentifier + PrivateKey []byte } func (s *C2PACallbackSigner) Sign(data []byte) ([]byte, *rustC2PA.Error) { - block, _ := pem.Decode(s.key) + h := sha256.New() + + h.Write(data) + + hashbs := h.Sum(nil) + block, _ := pem.Decode(s.params.Key) + if block == nil { return []byte{}, rustC2PA.NewErrorOther("failed to parse PEM block containing the private key") } + if s.params.Algorithm == "es256k" { + var privKey pkcs8 + _, err := asn1.Unmarshal(block.Bytes, &privKey) + if err != nil { + return nil, rustC2PA.NewErrorOther(fmt.Sprintf("asn1.Unmarshal failed: %s", err.Error())) + } + var curvePrivateKey []byte + asn1.Unmarshal(privKey.PrivateKey, &curvePrivateKey) + if err != nil { + return nil, rustC2PA.NewErrorOther(fmt.Sprintf("asn1.Unmarshal for private key failed: %s", err.Error())) + } + priv, _ := secp256k1.PrivKeyFromBytes(curvePrivateKey) + bs, err := ecdsa.SignASN1(rand.Reader, priv.ToECDSA(), hashbs) + if err != nil { + return []byte{}, rustC2PA.NewErrorOther(fmt.Sprintf("ecdsa.SignASN1 failed: %s", err.Error())) + } + return bs, nil + } key, err := x509.ParsePKCS8PrivateKey(block.Bytes) if err != nil { - fmt.Println(err.Error()) - return []byte{}, rustC2PA.NewErrorOther(err.Error()) + return []byte{}, rustC2PA.NewErrorOther(fmt.Sprintf("x509.ParsePKCS8PrivateKey failed: %s", err.Error())) } - h := sha256.New() - - h.Write(data) - - hashbs := h.Sum(nil) - bs, err := ecdsa.SignASN1(rand.Reader, key.(*ecdsa.PrivateKey), hashbs) if err != nil { - fmt.Println(err.Error()) - return []byte{}, rustC2PA.NewErrorOther(err.Error()) + return []byte{}, rustC2PA.NewErrorOther(fmt.Sprintf("ecdsa.SignASN1 failed: %s", err.Error())) } return bs, nil } diff --git a/pkg/c2pa/demo/demo.go b/pkg/c2pa/demo/demo.go index 11c4e066..7efde428 100644 --- a/pkg/c2pa/demo/demo.go +++ b/pkg/c2pa/demo/demo.go @@ -16,6 +16,7 @@ func Start() error { key := fs.String("key", "", "private key file to use") input := fs.String("input", "", "input file for signing") output := fs.String("output", "", "output file for signing") + alg := fs.String("alg", "", "algorithm to use to sign (es256, es256k, es384, es512, ps256, ps384, ps512, ed25519)") pass := os.Args[1:] err := fs.Parse(pass) if err != nil { @@ -37,6 +38,9 @@ func Start() error { if *key == "" { return fmt.Errorf("missing --key") } + if *alg == "" { + return fmt.Errorf("missing --alg") + } certBytes, err := os.ReadFile(*cert) if err != nil { return err @@ -57,8 +61,8 @@ func Start() error { b, err := c2pa.NewBuilder(&manifest, &c2pa.BuilderParams{ Cert: certBytes, Key: keyBytes, + Algorithm: *alg, TAURL: "http://timestamp.digicert.com", - Algorithm: "es256", }) if err != nil { return err diff --git a/tests/fixtures/es256k_certs.pem b/tests/fixtures/es256k_certs.pem new file mode 100644 index 00000000..591bd3a1 --- /dev/null +++ b/tests/fixtures/es256k_certs.pem @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIChjCCAiugAwIBAgIUCnNsaF5RjkLTBFOU9y2k24WKricwCgYIKoZIzj0EAwIw +gYwxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTESMBAGA1UEBwwJU29tZXdoZXJl +MScwJQYDVQQKDB5DMlBBIFRlc3QgSW50ZXJtZWRpYXRlIFJvb3QgQ0ExGTAXBgNV +BAsMEEZPUiBURVNUSU5HX09OTFkxGDAWBgNVBAMMD0ludGVybWVkaWF0ZSBDQTAe +Fw0yNDA4MTExOTU2MjhaFw0zNDA4MDkxOTU2MjhaMIGAMQswCQYDVQQGEwJVUzEL +MAkGA1UECAwCQ0ExEjAQBgNVBAcMCVNvbWV3aGVyZTEfMB0GA1UECgwWQzJQQSBU +ZXN0IFNpZ25pbmcgQ2VydDEZMBcGA1UECwwQRk9SIFRFU1RJTkdfT05MWTEUMBIG +A1UEAwwLQzJQQSBTaWduZXIwVjAQBgcqhkjOPQIBBgUrgQQACgNCAAQ+nDJR8SnH +rM3Xyjr414/xvlmsJ9DpHDUEPupv6AANqY9Ovvd3joP/avrSRie91yjkC9gkRzZj +Kk9QA+nsW1ZHo3gwdjAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUF +BwMEMA4GA1UdDwEB/wQEAwIGwDAdBgNVHQ4EFgQUrym0Mg35E6+/SIj8E6BQuSgQ +vxQwHwYDVR0jBBgwFoAUTTYhdEZFi+hD9NstNz/kGDKos3UwCgYIKoZIzj0EAwID +SQAwRgIhAMueiVdiB+0s8nIPWtImWkTyeVFTKeDGait6qlxrkxK4AiEAjSADhYY0 +HYlugGMFQhVFICJvlqmH92pfByiQIZqYEJY= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICZjCCAgygAwIBAgIUJXk6wN3IvkruFN8UcqPzifrlhbowCgYIKoZIzj0EAwIw +dzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlTb21ld2hlcmUx +GjAYBgNVBAoMEUMyUEEgVGVzdCBSb290IENBMRkwFwYDVQQLDBBGT1IgVEVTVElO +R19PTkxZMRAwDgYDVQQDDAdSb290IENBMB4XDTI0MDgxMTE5NTYyOFoXDTM0MDgw +OTE5NTYyOFowgYwxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTESMBAGA1UEBwwJ +U29tZXdoZXJlMScwJQYDVQQKDB5DMlBBIFRlc3QgSW50ZXJtZWRpYXRlIFJvb3Qg +Q0ExGTAXBgNVBAsMEEZPUiBURVNUSU5HX09OTFkxGDAWBgNVBAMMD0ludGVybWVk +aWF0ZSBDQTBWMBAGByqGSM49AgEGBSuBBAAKA0IABAqHVfoqCxkh3E4s8DwSHmf0 +iD95+F0djOPdddEmPsNDyioMFTiKqqVOGYgoOMM8PNPN/+pmszZDZ6wtgzfi8M2j +YzBhMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBRN +NiF0RkWL6EP02y03P+QYMqizdTAfBgNVHSMEGDAWgBSp8P3J1W2jalobJY0EOGjY +2yypUjAKBggqhkjOPQQDAgNIADBFAiEA+JGHcLQnfVK+aldiyz++fr3Vh5uUiNvt +wgBCY7vBUEwCIAz7npLJHSqCBYP7CnoS4CV4wwi/b2VAmJgmCTKzSnQ0 +-----END CERTIFICATE----- diff --git a/tests/fixtures/es256k_private.key b/tests/fixtures/es256k_private.key new file mode 100644 index 00000000..5e05d908 --- /dev/null +++ b/tests/fixtures/es256k_private.key @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQg3LMRXAay/z8RuAge6879 +IMppovaQ2CjuI8f2PNPFPqOhRANCAAQ+nDJR8SnHrM3Xyjr414/xvlmsJ9DpHDUE +Pupv6AANqY9Ovvd3joP/avrSRie91yjkC9gkRzZjKk9QA+nsW1ZH +-----END PRIVATE KEY----- From f6a9b64afe75ea9067ec92dc32f43a3ea7892b31 Mon Sep 17 00:00:00 2001 From: Eli Mallon Date: Sun, 11 Aug 2024 15:28:18 -0700 Subject: [PATCH 12/42] ACTUALLY working that thing i said --- pkg/c2pa/c2pa.go | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/pkg/c2pa/c2pa.go b/pkg/c2pa/c2pa.go index f69814d4..4be1240f 100644 --- a/pkg/c2pa/c2pa.go +++ b/pkg/c2pa/c2pa.go @@ -217,6 +217,13 @@ type pkcs8 struct { PrivateKey []byte } +type ecPrivateKey struct { + Version int + PrivateKey []byte + NamedCurveOID asn1.ObjectIdentifier `asn1:"optional,explicit,tag:0"` + PublicKey asn1.BitString `asn1:"optional,explicit,tag:1"` +} + func (s *C2PACallbackSigner) Sign(data []byte) ([]byte, *rustC2PA.Error) { h := sha256.New() @@ -234,12 +241,16 @@ func (s *C2PACallbackSigner) Sign(data []byte) ([]byte, *rustC2PA.Error) { if err != nil { return nil, rustC2PA.NewErrorOther(fmt.Sprintf("asn1.Unmarshal failed: %s", err.Error())) } - var curvePrivateKey []byte - asn1.Unmarshal(privKey.PrivateKey, &curvePrivateKey) + namedCurveOID := new(asn1.ObjectIdentifier) + if _, err := asn1.Unmarshal(privKey.Algo.Parameters.FullBytes, namedCurveOID); err != nil { + return nil, rustC2PA.NewErrorOther(fmt.Sprintf("asn1.Unmarshal for oid failed: %s", err.Error())) + } + var curveKey ecPrivateKey + _, err = asn1.Unmarshal(privKey.PrivateKey, &curveKey) if err != nil { return nil, rustC2PA.NewErrorOther(fmt.Sprintf("asn1.Unmarshal for private key failed: %s", err.Error())) } - priv, _ := secp256k1.PrivKeyFromBytes(curvePrivateKey) + priv, _ := secp256k1.PrivKeyFromBytes(curveKey.PrivateKey) bs, err := ecdsa.SignASN1(rand.Reader, priv.ToECDSA(), hashbs) if err != nil { return []byte{}, rustC2PA.NewErrorOther(fmt.Sprintf("ecdsa.SignASN1 failed: %s", err.Error())) From bdeb453bf38b96e81f65bd7448bb47858dc512c5 Mon Sep 17 00:00:00 2001 From: Eli Mallon Date: Sun, 11 Aug 2024 15:32:00 -0700 Subject: [PATCH 13/42] tests: add a bad signature for testing --- tests/fixtures/screenshot-signed-badsig.jpg | Bin 0 -> 90646 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/fixtures/screenshot-signed-badsig.jpg diff --git a/tests/fixtures/screenshot-signed-badsig.jpg b/tests/fixtures/screenshot-signed-badsig.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ba2aeef473b30408b26a8b681da585c4aa366182 GIT binary patch literal 90646 zcmeFZ2|Scv`#*jULPe5DvPGzb8Dn3D%964aWeqX5!4PBLQy;Qdim{GTBqrGEWg{^*}-`E}b%FYJ$cb!VwpiuUnUI=$L6s-YZ@~wgBmj<@(o`2E+{yX&% zIE~`2qs`ZnD|a2f-nnf(jpSu-TUP|i$=;5}Hvb#j!~dAAlMTvA($2=qW=o8~EyfR= z?fu}c-l#3vuH3UndD(b+?R9XqcXRY|+Al3&=VpVnciUp@Ac(YaLpabV;5M#~4k#xZ zX<0eDErzCoVo80P(a>X?+ zY-QzaWx;k32YG3T`7~R1zZibJpu`$gL4>4({P~ zUliu}jnvV{-g8T{4qrKJ{_|P{3gPHx>Etj3;ac-7hfv%yB5(y7`0A0Dz>+xAFgK zs?zX3nKHjWG0g@fmA7o5{hHY}&^?fXB=^B^GrDMSI9&woC!R%52co0fy94FwWOXk7 z1cAZm_M*gn@^8_P#lSllnU6-CdaKGnPY1pd3)u zYrG%4Z;Nmz%Wf?LL&5W!M(1_(wXF^GbmRL>a;lZ6B4r$B5j~bk%ADgr=*3R|=`+!+!{qb>tK${G7 zVBm|A_(ZmHEL&qH&IsDJjftKCylab!ZL6F%f{f3rP!? zb>0cy!SvaZe?UBu| zd6A3G)=Y1j6hv$cUDez3MbHKre}EpKMMsRNnqY<@r_jzPic1Nm5<;g17Oe+mF= z(oAXTX5{5kv0%emXA|3Iwc3!Hj>xpz*U3{afD`a%^=EbI1X)%yZ#D594c~Tkbw|LS zPa=LVnd{u`)zN6t?b0Quy=3k=&{oFtD6hK+FO=7LcNcpaZr_Fb*tnYQQwN9dQvuVK zLx6SzW!z4ix)F=4ASSx3@O_79MS^r7dI%esm2vx}h*KbYdImbsHUNAjoN?zj%I&*B z3|S}!53oBO2=tXIV%?Pq1cv-gBL>0VMt&<%;-etO4}x=3g(_$1ke}$xY(rJJn2m~J zWHmWyE*ZIX?1pumRLu!vQ^%)Qxy-rQq`_a8JZWi&qO`O*9rNzb2qE9g)@QTAIktnt z8DD_I86JI|Lc6zV6jE=O)0vDdzkar%%{fp~H)W^g?a1(W`4D6{(h*~I&-V=`$2J#KdUndCt?*?7y& zwLa2yxO&~m_F47u1AhCFgI#Q&OpmXAC_2`6uCNr#$+&mimYIWq;19CX%V6t$+h!H} zDE};HQ^}j^&IV;CjqC@?C)QEHpSyQ#`pfJN$+@`0R2P8#hVaH+wIh1BChYMsesE}| zv%%$H)u@0BT1si$1@vTb^=QOoprJw@di!0EVpoqsSfzVXN8fIa4IAwpu^ThxxDH&8 z>!F#t6X9>NJXF(fB{GyEE{m}mRFuiJHCZ#?znnd}gTgLp_o-P({0-$W$3dNQA&d?L z<=T~rn+hM!F}=B6(SPF-o@5xU>yvDp#?^%LR0E0Wib6PC6l(IzYixWYs1$;vB3@2kEXXArI8l>R|vv-#Ly z>$s)Rt;tv+y1Yt!*u$=ofyOD(@MsSy?r#0^9@oykOX=cMQ>v%pD_yFBENfoa$UEuy zW>4IBw5L<-H)=fpEz%2h1&rnEEJ+bRk&L6;0SY&c2HwatsrY=p2oYSAst_4^Mj40 zc=R2f^Hoo)>k<2_@LaQ3ulNC*h1qP z-Nrtx#=`{c$VuRGPYXJ8e-!=r`se9#B&g7w1= zN3AUW#$>>@KLZFSm}3hkc5twKpB`Hv_PgiKvD??z_a~c$9Q@++(%_Bz_ZHvb?TgE& zE%U|YlLpf;>kkla-F}O^@0cnjw(M1{FBVu~QV4gj_OtNEf_*vh*5> zoq{SV`79oGEn0}XcY>taurD-!zc}o~N5}Z_bE-2$h z5xreEBDH`(vc~bKk3r5R0W*i0Vsc!75v^%=uefZGvsS|bE(j9aR&(58r z%IWCE2(}+Hk@XjUNY%VlEgWaM_|c{(aYOO>axwUUZs!>Kv?+{0e|@7QfnasUXV1CB zrdSqbi)f*T^`gwndw1`l54IM5W;X!;l+y=Wc;Qv7#=qTXGT&0myZ=(Q>2}azVAr+t zpWlvIzP1AIsnN`TQ&7{GEyGUjX=@o#-!! z6>Jv&Jh9vzou?rL3UMHQIC!G(3Af~KJq|qB=xrVMD(hXL` zW3WrFKkOX3Xwiv`T)8GFA_H-3E#H&m-f&jemXC!w{2^COo-v*i6$cU$d3y(;rtP~* ziz;?*<{LyG3&{Vi^`!OGrSeAxHytvMWqyh-7+R05wiAtXy;YMD^zQPGpy`Lz-Ue0X zi+Q_?Eyp$wpO2d9i1zTYC$-FYW(%)87Yo;ToFaB0{}8`ueMQgFiG|D}|CB4)#u52D zG@E)G;<852tE)K~o`ns#`g&)Nu2&pk+Z)O;t&x$3o*888JoR!D5YSfhdu)5gQhC6{%JFKCKk;nZ46voo~x`o&I4ij+8qZG9Ds`!VHS*;B3 zWBg*rM9J=)f&BYp=41HU8IaTJOc z^|TYz(fA%y`vzJHw4LYgJ1<(O46OJawEh#o`L8gmZukbuspr|Cq5KHu{6vi@qbds;2oqsjZI#4_sqy01HqL+*jb5Fn*PcZQ{v?p#Z}%Z&M4{IZM!vgOZJ9SnXV0cDI-1lC0Gu87VKye zA>G?NmoKE@}6k;-qP)o=K}^uv`GHIzAXE70$b`|w`=zT~#x z>+|8T-8V1iUkE=g-AQIZ9d)}23OcC!zWVV2dT&cVhb|?EMk-Xjj=9rZ=l+Y~ye!A= z`(o914HV1RP4_Jxx~P?4w^)z3?t}JGHM2b76B(6^2skzK>E50L4yj&mdd~2V?1DcY zem{NdmQtqbM_^D>B&s}BW50OF^V_BEHpmHRS7YxP2B0OF_s+|N#e>Dg>=n;KXFFmq zA3J{OQ4yS5;|`CHZOB5{lhskX9B5k9*d#fXD1+yZ#=n#w)Y0z<112qG6WY--na_0IWa7qaOiF zbK+j%+^yzth{U;=+}wj1ROD;6zXL4)?x>#{@L#5+ho+y z*aDU>doM4r=2vurfob@ZMZ>2EneUwbAv*m%8&@#<78^l^9bg)&GcwR_fxlT@=)*xE z;Br>o$%_|86s)tdy$-0Bb9FA(nK0F)7Tk?IHu;-X9?vtOrAQvcd7-KM*L{&SJbULj zP6tRyR47c(ZH7Hvy1O{**>kAckp8v)yQ4iT62|!Dxy1r-Kx#~})q8x~NsY9V2Pcv| z@1816Y0fanWu)iu71MY>5;|)3aHdXbXU^EHjLPI9JudQLyRQ0vSuJP8`6rckjpHZp zcc_jQeO%}LFz0K2@qDcN^xdQgKzPBlXf~J@!*`ewmwxf0i7njn?W5?XR|fmj@#6YJ=D83YNd4OZJ>_ z;5kj-#~8z&ou|s+91g6$R)S%>;)D{+l7`~9wvQ_syyA{)z7D| zdWbQo_i@rax{&WCgiKp$uS^`Rv_F1hhM7mzLNYVul~adXKkR*1q$H&M{c{aN@Y4dB zEI#vtib-RcoTs+cU;9nK!|QV4Z=<}yZ?QW*7DPzCXSia!$bp0#y{Rd+lb6r;KyFuj z5~&%l4Nfe=%~TSLd}Vj-E(%$6qK3>*`7y(=lAnq>&(Sq~Xs{t#>hGpwsXBO1Px7~q z20Dv%deJSe$MS4FFM#wX?mlbl+s2`_zh2^UY?FZVg~unCA~VZ~)ew)o-@s{63@xNj z#i_}!&-HxLR;?Koj@V43?zZJoxh|^_%^5qRpe%#BcSY@)$2s>Myt2B-w_R)KIXTsP zyQ10p(31fxUgfixNpZV%X6vIt<#(sDt?MPr2GoW#_^TMV44g|HlWA z$1ij!1VqD{2Ba#t748W#z7EOpZa~HK?ns&+v$>Ne(w5)9CYwFfv)zkg{z3jSMn%bW zCh9Hs4jrjt#1STjrI6=$o}Yo{U7XfDEOC~y>uyAL%GNox8T*y*$JD>Bzclbm1HUxz zO9Q_&@Jj=~H1JCUzclbm1HUxzO9Q_&@Jj=~H1JCU|1W7^!Bh*ZK_|$${fXex*(@ruc@q&SpPZu|C!SLm%^f5UZ2P=}PVgY7^`-Y7dH0?_-NxxSBAUX#6ZH&8V zEkN{i^bCyKcF;4@Gwh_5(=+U0KX;I08^osQxQEbQ>ARj+P8i;lX%ptGM|s_YG8W5* z+a7AaRbq5<=IRSd;(3ji@cur6CRetdoX9nHTJN+s-RZY6Z%MM%9cT|b-9gB?R87F_>n3ed{3QM$+Pb}W|pAAZ;f!ecwei)y2qr<#RGk2 zyY!4PTy?C)9?36p=2ny*l~e0#?kr69RkE*Io^}y@vya&0Vy7Q(0>!nzYtP=%5<0hh_QT~h*5SD6V^-49r|HVdNT(4KUMMP;+P1e09;J4j-o{~DyZ;1LR* zt|%4nn30r9?TqRxd$eH$v*@fgUqEB*@MS}i!-UxP0cMXXu*-q$F>)5!@ULT9lf6E| zG+(+2oL3oJ#-(v$N#X%5Yxk_GmDko|DPvYaHRL%fOM5fy`h?ni1&yqFyzVwWX?~ik zCP%CbI_2lFXiG`GIo({>{2>{;ERwzcIJ7Ku85vBO-~VzY1h%0z)P+-730SolT7o5$ zs;dTO$h@Hf?SVQ3sn|?ne8OV70FkhMmYO@l?`mA(gq?uSMg?4+rY=Bn5Bz5r-P`=V z?>JN}7Hk5<-gQIj&yUrI>yGpIqjzCr*9xtOugh5Z@$gjZ{2_{MkJJW+LsdDzXMVnW zW^O0RrS^sIL{~yi>G}HpP2hdQWPh+|a0u_z^eSVObL`SVLypsaJxMbatMaZRy)VTC zj@22*TLn0Y&X;b{6Er5tyj1AKWcNq?nvnXSd+c3N38v?mcmXfK`#IA^0AK=`9!8Z? zSSQal`UW|EJbVmmo0&0EIO7HCaiCTIcM?;|9Ofo0aTd@H`)h0@vGTx?Q-0on_I)3d z+-eZ-%`U6V83s>n`+&gb>`i=k#-;)7L%*tiY2)9q4c0GaQ{l)P1n}sGZGE}?Kc7@J zT2#VRDO_{&qq6|kzG_~bpBM1%0eBSve=m@@fJ0pb-#X6*_gUV5*7vsexS;ar_VSwC z_Ohr_{2Sp)x}?_2L*)j$DyQX#;daIjYq$dBK<0B+`z}Z?XHGIU&uJce&&6rEVG%I= zXPjwuD){?rQFE9ZbSnRX@LM&*oj_y0!uvs4pvf;KAhAT9WG;m!rzu#C3&AT$5zzXF zjObotb3|=LZEH)|Nw8pmGwdHy{?ApgvS1J&GwW{9|Ake{%)Iq!zhb|r;}C1B+D75L z$wnB1-w&eD*Jjb3C?f&HSQPBl4875dh4Sa@RGkE8b82})6#3~SR_r~9tGI(KxZ^nh1Yk;*6Bk0&MghZVW_mk2+Twgu{nVQH(xoMnnV0G+VO#9} zEU`?lJ&WWI_W4+vb9%`)qoTlEacTyQb`4EZ`zXAUzX_<*i*_tyk`U0rmoSS6LOj6= z&d(jS(ckU!tjS{>Wg?d-GOL**<%IQ1nbiJ&C<2j@nRLLh>#<1XZ_0FsO#}a zl~<$&?5s;)(}=|rLqs%O^bNj13vY+72KgrU${A1@C+A}QMLpf_>*|`s8u6kyAgZ+z zSouw0FnP^Cbfy4!xwg38Y_ycW5nY`2DJOdkCsVD_+0YGvC6;iFCXo4_bB9QYQdyP; zL}V6I@qQ_(o#+X6L=@(YfPLp%vksKCZ2g013wmm6yHN0#Rj^V`NDytu6@McA|a*bM|{>kw)EnM?0pbC=#s{|dhT5MFZyAS9$IeyPyeO0>Ly;)F3p zFUiSXXuY^BtfFh*3k?bO)ulqo6`@w$VLXC=HauHp^erm}uQ58yfbjEwMDlaFPG>vp ztO@N0EYK>_-jO*yO@j2Wa#(~wHZPt7v4DFH@;+k82Nyjxa%>S7AF93K5~?BWZ3KPT z+(Mn35-3*Zf_Dt&=v2R5-vr!B#+=61l(@Z8$~e;uiy$1h0`1Tikcg|PP;!&`G|nlb zq_x7fjxXOSsvnMttsWKiAj3w7#UrTB2KQSXlnr_T7LT&Dm%Xrk$|3BqqRF)|o-BGP zdgmlETbZW%{jv>VoTsWvI~UVj2Ktkss)=G=6O(QcoDv;?Otoe^yx zZtInRbBP;wFQr@`>z&K}1T>a)U}Y7Bia4(ilxnjt!27m8#a+y_hqzHVIB;)n3NjxZ zE!+2a6sHn$Zc%p@O>{vK&N*Sr8%2eL`&4UH)w#>^Wl~}^8gK;$2>Tuv)6~8u9EVeX zsGY$_;J3$Djhu!d_z~HJpqHZ5V_o1^d^D%?Std*c3D}mkIZHshwt~0@pU3y12@Kb;-`iVV$gHQBh z2%fB(vVdnh6J6STk+(sYknEF0LWGrZDmZVIy z?HH-ZeH9o6-n}*Kmhp+V=I;lPY}K6Tta@LMPFTmgxM5ye8`M={z5Baz%2pNnA)Oln zn}8GDR}5i1o-URE zX$gb*=CwP^_(TumTv4#}6@xc$H0F)Idz6563;@ zg87oYh2x4iRCC%8Q)>>prTM}7!0165!3rBAcAaH5OXQ> zKB;{;*F^qmZKsif746}b&}DOlru_I!f!a@OnDL-Oi~G2KhnF&iP9xr8oiijI;&@q! zu3|~gT-ij6?yBF&G+>f5+wD;N>`7nQ#7cL*z-^MP*lh627LQi(NG=HCLm7iRLwm^thk;QupSRz zFz^F{&TdVL|88Q8H?V%)G_efGKL3}j7$Pv6fXNnA9?#tbIyN3{0`Gu?ADV&6;$c#L zHELkS*WXIxN`vM*Sjn@`Y0UZqw zUe#~4hnskDP4+cngFq~;+HrJ)_U8l$vYh}v9aah3F7R6@i@=TzZYQQ2KZx+tEhxpG zt8iC>S-(T=8`n})BeutH$yn6Xi4N6LVsx$mpUnID!8FvYAC6wu)yR6j-~E>JlhOJ2 z&8g%goYlG%#C#z;sR+6V5oGjU8;D^`w=mQ9gi|C`@7X$(Kkh=uc&IE6EB1wl!4if; zZ0@go=5vciDk^p-dzlNPuV0TB6(&|URwlm%1RU@Mvrq827s9)%#=5i*Cx4pbz zRjhPo={5b4_xR*s)V?eGVb}vI`o3(ethli7Is7LT{FM2(cbkb@&h2*xm+^Dj zT96viX#H#lHYX`Y8&S}JdkY*e$55!DW})+E0+(?YHi49NQfL=o&u_G`t*!tw*AcS` zD2&%p)t(uw3bK9-9hj)g$8X$cacJVIxz|0bd9ORW+M=_MjJZUA9sn56r5Qz&j@rhc z{KzdHYHaSjkjo6^F%sYr?6iupyl=lFQ7rfH4TfQ)uGV7|JQI#a&&P+vYT!aP8o23Y z%aM$MS{Kvqbw4fSn#Nfv8+?2b#$n!VO)TWq<}B=rnqR)FzF!_a4sndN;;TS*;}P+S zWTJ5Pe!z}Yqjjt*NCx|~%!kUW>(S`9gk!M-B|5yfEsxg)K_ z6agL=@+*AsY)w^Fx#8s=z+hZhlY0}mnP$F*^Y6w9VE6~Ws$R?sQtat~(LJA%(CGHA zjj~j)aIYGLBv!zx1GITVPtj{RW`6!cO0P_CoPD$GcG2I1nwPQaXxI7_QzTnA+Tb&+ zn2>>+voeDioRrY2HgzwY8bO#6(!bS!Qjx=OpL_4t6=A z(~RL2P-si5OK0{l!1i!-ji8CqwB)anVAq%H5ppaI#+0-);Bs?0*pdO*a^(2AA@dJy zt;08U$vGdXax*p6K?+MJ2TAVG)(7pw1jEYVlnS>r%?hpYM#rcajR5Uehd_#hwTMWa zi1SmJ+XuTIlO5##LbS{RX^U1o#W1!l0Xg z2vvpW^l<&d7(f5I(=|&|4Gj&x{qS7BO8{GmQ(4HFp`@Cbkyig|4P8AXED`VKL6}Z{ zr7oZ|D*z@q73QTc%W3JQg~UzaIov{89SdNtz@!0R-*cwUy7aY@mYMddrE-$)F%hNlRUtE#y_SJCp;tQ2tp|*<+z{Zs8;c5NvHQY-OF_25+CJF!b=? zEY-}-C~DBvvvm-QCvfo6Jj|6`w0~qWE;-oXOki(RTs5(`u*yG8GZ=p-3>L?;E>Gnh z3o;mbx_ZEU6Ug9NweVTb+$c<^)!pkHEArRRH3&+#ukJ4H)d5d-kt?f^f_rIEv@eJ6|qAl{E$N!qcA(>5JrLMzz zh4s(2e5?hi8J7S+(+_pQJqMTtOsqgDvs2>od6#$Qj zBq00H8c$EwuC46$Qx}Kcp*2YzQJHm5%|&06>RtTciEG1MHZx zK=tbWz#cW)3J>F41HWSv+-maQNdNxk!lcF~*J27db%b&hcxt>dzZfQQp|&RY)6NU- zh1Py_9TpRvJwVy7ieDI*9ta5MJD`2~0FpnnbAq=ioEXA1iw z!u-E+`Y&Jj9os`RXuEh2|9_dq|F(wmMciR~gn)?s|A`e1^tmie*I2m)&rZF;^xHBm z1X|vsfLWgd&UU5CV}+;!T0My^flmiP1^*|jd6Yg%DsNn2?%+V$fw*%Ry^AO>+OOBLdc`+kl$NK(86oiW3apUd zAn5U(oMa!1fwV>+YEOtt$R+0c5qzp8q&5zA&iT?6K$rlz-+k-I#nOtl?v}vXOU#bX zw7Sq^D!^7~Sbi!T5%JVN;em?BYG@d}`kzbl4@(&rgm=?vsPBF$sBoA~kWRbL`X$qg zx(FW7H<$5yLr_HwqwCs|X|dfvr@e%mn%yb-b2Z6l9@g^?y-el}#h)?h-3_#Tr~Yf# zKbxO&bDO{be9e3DC0bNiQZch_Um-2*l{7AHsaF_75D^kh>_pEUcP^&nrM0)IAp33o z6AUiVyQt_oQ8d;hHUWfNi7NEceIh)g4QdsLLyT0kfV6g8s&V0nJk~Dx8g@qM_q>-3-?;>OexuP?(*YP2J2Y3u+K8JNT zyT3R+(LkZ3oO{r8dK{%oD&lw+m2y35#d^W0lYYb$%nIUQ4gH0!fU-W5%s+;>-9K!UYB8{xbu7aQo0R2^I!s zm{GvpPeD4E#V3O@23_zB6wWPT1QwlJ9caI+@yWo7*m$TE!CDDVblBzHz%SJ78{ys<`2N|W2ehR`D& zY+J&PcbdmwIAEU`;}cmu$zoBwtuAM=SX#nu>{{!bi6u`k~`fh!qvED zIM|lJKAoVo5Dy`6V*BBb&!E_=;G?)^ObbXqh#Q-V_R!|&^`b(jG1okEgsLuzRg6_I^I8zoT#7~hXm!xiP@;|s=tQzL}gVb$j>CL&^dQ)cV?(f-vf zBnL;|P^;Q^+b;c$lIV}MSwSwMyf;H_dmtbX8VQyAi0JWS}bhu8o=+-_cDRS zzLf(>AQh2UY(6J9KCHiJQVlN?E5+Xrljv~Gkd(LyvFyGV9I9iSR+ssOEYDaC0{U`1L>cT0nDZQq75MwyzyNY=vP{a#gm-D}v1Q)BGV3AI^V z!Rmgt!gogND6f>YdTDp(u zUfP2$vb~<3;=bU5zprg!@M3>N(h_9(5LtBldy@%k{i0hRmNGK1P}3CjT)fh2I(n%) z1L=xTg!lOlt-~WCJ{Eiv*2HKsv51oNSwOZ8QEk9h9Rq@}_vDRykK6?hf$tY4j_6 z)y9=FEZqNWt!{N_pwD%`ZE(?ZddDX5Gh=J7o=Ww#U+(l?Xf4MTsIQeM>P*v}z4}kg=}XnW9TNLj4FB)1XPwj%WVjx7Fhc$E zS$=hf1e$BYMvJw|&^8J`rR|eSy*srZwoi`W%9Y)_Uv($qy9Vmi6x74yOG&Y*y6;=# z{mR793-eJ6x;{zQ=?^v)qKR({4Gi*I$UfcVe$kN5(ay4G3j_VQu>Gyo1kGE7TN@V9 zo(BG<_D$xx^E3Sls~^Dhp*2T!ySj8R4C#JxJ2XB&8RVF8G`y(;B-H;l9r?YEDXD9=UoB zr$TWzxWxie%<8l_d5airBI2%-qwBLOU$F9m1$4uC-u&YG=ud5(26Gj9)n^7v7czU# z#2-uu`$=(spZT|A6MxU*e_P34b&~)6=k}+aFmsHl3I6mt!@tkwe^ZN|23z3NbvkZ4 zrfsxM$X|Jz;x9fu8+1%fD42GH^T#ofJG_wn_1J()J+`=ej})&<0sE>1;o}X@0Za9S zF1O6VtI0g$3?a>4i-&kCJI@L)SmZtx9C}rCMWDD_wTVem_C51@(%9!yiXM-%v?nVJ zJdZcWdDE%cR|VeMb6XVh?vjwjesP@d&d%HK zWO3il`v0nrN-xni3hp&PTIEX=*~Bb)M{6reTgUsw2Wq>=hCxIxUObHf3+}ZCymgp+?ADzo zv&Z@_0gc&ZVtp#l>AxIy{XqD)w|^glqtmawIU7#P+~vjy1~bRN>o zhdo&emG7>$4_ls)*l=y!1TaqVY5qa`-fbkrt%9PUY{S*o>;1ip2dUk41WMf{@B(73 zBXSd{w%!<2$iQ@L6_q1k=X6$;;%RTam|XX$s>|;TN;Kz#vnxAe22$Z z9tr;K^ZhT)vj$~iQW6PNf7B#ul9j#X>toUfvR?-Y2{PN<;N8n~10+m4M!8B0=&}N{ zKdXUWx@|&+*iP2Wk9noo)DLggU%-VZOk2l=Y)t9N-yWAmXN&H%scvb}SKCu`tJc~fEH(ng0GpIA)@?IRKq0-|tmN!GW6bUXcnL^N7xzO2wC zK)2BWetlx<22-q3w^mG3k@dDUWecN#7K+(axnsfujyCLrmoWHHsPRkL^bckHVv%yu zt$EIrR)2JY8DP6Wbe<1L_eVn0Wb|w?CraLS4GSY(8WtC%>m=%7W`)tt$OO(=n`%Sy zgcB~%Cyu}Y;UI-N(I0I$X)RL}Pp}S`Migrh+njCVGIFEh(#=K1u8Af4Th-}3Zp5f) zy&*YtIm(KPD$qiX?q1Yc;H1Cg(asi6W7~?Ev9!20j-J}YhH{lmf2yhmF7)ohjT?-r zc~qoB32%SvEeRcTrX0MG*DIAM5*G$591gM1n_WXc8N=$HpMjXWa8njj;@7BV>dUkj z5f$o6Y!*+^S-+WkihakUaD5YI7TjfSv;D0 z3u;p~60Nt??*g3}7N&*DZ~4ooIhUo9;P}45DC|;2t}wl3(mQ{#}A z{ocnAi=vmsq9!ULdIFc|SsdkOgZ%D2m3Fz@xosfbUr55mx{!OI&OIRnubpWPTx!9X z47FWl_2x!(o{J!M)r!lcl4*ex$CSwIJy*Hy%&ny;6f*2$DWR`miXy?Gj9n=$@GUKG z&BB?<**gZH6})rlJ4Vk#x1Hf)u zTmhRmwot`blrTY3w69XBTrO8RPv+;#A(m1Db+sq2^Lugo&4EIWp_1kw_ze>pP^0sa z0x`F4kHit!@fx6ktpf#}fz*};%B22iUf+amHMQJRTh%Y>H`@h9h1*Rr+gzZ!t5Y+D zkTS95q_psnqU4jRgN#!nM*g22VGc z%KqE6x|D<<6nimoQCyipD8%bRf#wKI0S8O&cW~^g=&WeW@nL2tN6HupL?s-u&sEWF z&~p$Y?;$8NoO<`W5?Rx1Y7iDJ!3P2iATZ34spSWyNwM#1Qwa;TNs42B8;6q{cHe$b zY%Xp(?BI7@|5L%3N3U#l4DI!IvH2Pa_of`>@E!B1<}R=z6$3xoQk-lT!OK~LOqR$g zEuggcmSNSH7s_7?5l~lm>H600v`*EQ!}co<6zKX)5(Ka5+SbRspp=-mnU#}>HxJ~5 z!dj)7eHKo=tg7AH10SfPy`1WU0OdK85*zQiKxSyCLw3bA?j0dTp&YpGIe&7Adsl*f z;EFiMr3OPku;zqoibM%tpf07Qg?7G{@5J0N_^CcAxt!Lm))=kZR9rzr=e=TPnOY2^ z@<3Cf2$nOar{nstZxG3jeY?03*O~T86`eE#0R#7;g^-0C?0KuUZ37ueO-V?;oQ~+J zsI^u64*Xf|k(ZzJgng#wuwh73p zP04zWp={#ab*@iL2$U;c_3&Y3h}d4wp4f=Z@mb#m<5;}_;_rT$1a5izrl&VsZmwW{ zp#R~H!sw-YB>_|2yts0;#SYoW)jg?!=Kd+TmW?*SgC6}&seTU}2$LI_Yl;_>u?|Iu z>lFnWD>GSg(qpJgUpgbf7aHCeoPqKL>yk6l{NjV<@wxm2*Pa>f(GTBkQ^domWa#lY zQMAU_WGQx%2Ys2W+J9$0Gi20`djiR*JXqelhwO4HDryQ3#S4fyRzeW*`(eR+C4=ec z@>ci8+V>g_ExuD!WZcz#?hb?@0;OL=8-_Vu@;TE&@2bXGT0cC?s%lWN+~L`g4|bw=_P|6dAHYN~{o7*7cyB zq8q$1UkSEKY=F6QE-w-8b)u8XCa`d?r>)UMYOkY13h(7lBq` z@k6>_OiUc@v30B_icQ~!<;xvgjA{(86z3d3Iw0}>nGTNCc<)3DXZnMi0N8EAqZxnS zg#T@tk9KZJre%V3%m6OIEcH1Mj+6@}1ogMeKAIUauG22*mV{UZ*Of|U-L`L2rRjoi!K zZ$HCeH*w8Xc(r^(Cys#l4TJj&4$bVdnfBX*9oG3JB;Ts2B-O6>JgYt6H??ZEb^+`C zL3FNr4P!Rh8UY`H?!MMoFf->rM>`Bao^kJ9@G;2gQKnKO+-rC`Ew9QnCF;eznd3;- zQ;NpNj3s?2@AVDwX`n)TteFG{S>T6*3 z4{|UtUM0oG0iqB=aVoS#rGerc1iKv9;avLEWAbHo&Mpeneb2H4xsSt5;aUg9`Aq+I z3))BI@Qp6DCC_Uaj!H+zOHgN94kaftqAX4<%ndOYhSr;P9UIfeh~RHRBlT`O`zVpbPkzV_IuI zDe?1jX!KlC=w9d6tNgmo)K9Gy^#l_G&XM+oYI3G3(hb9wri*`g@KxCN+5Z2*)xbY= zsbb^N6e}#0j(~jjw4k#y!8+l7$$1{Su$%26lo$%GrZ7_Mw_C{r86_OJTosW?dO<>H zC7Cj`2@Idm3N%p}E=2Uo4RjKN)$XQsWg$*mNM>`Ls^AGA66eduFkSpN3Pw42p7q`D z?~d>c6|pR*h!)}Wg%mX49S!hUe~(THmj(Y6Y<3q{!c_HbItzmT9@>Qz`2^@Kf_Ahg)42V$6j^g~Z92$hY>_--%HM$#!+ahwAeDu#wi4kzF|+M1*svsA8rMxsL{ndu%)Om8NDWR>Fi}#XD_TmY7{i z1b)}Rdle~hhR!G>@qd_}&y9$ZnvFnE!yK<#5imtKWFa?Sj7{G(GCpu|}A5L8% z=E^M=S24b-q9&Nts=5@1+MSSGXwGkvBg<8=za`=C`^;U-NS$O!c!~8k%7oz}6$hvZ z2ARW|7QKwOD&0v7DVZ?YTosC%BD^W6KCY#e_Hw6#e{#}ufZ=oYqNUg-uy6frT&;^W zzY2=|mFh>2iMia&^{$NZtLL6Y{y*%!30M>7zCS(`OKa;=ilE45L7@~8!;U0@-h!wE zsB#3^6%Y&%BC;hcfm7ScqKSe+!jf3ov=AsHB+9A>Fhv3p0s#^MO(Ym%gb>0G{>Con zp0?-SpXYi0_n!Z~zmw$Mj2Rrru#hrkcVCa-&Abe z(ayJN7}kq0T0SJhA4*N7K?iptf57m_ktj2ry4Y)LXP@jYKAp=m!}sGHayvOyQ}0@D zo3_!n*ijzC@}h-E$TYJmKhA655;tQZW24_jx#{3vhqn_CZnteF+ZS>8HS5K0jh=dK z`|LQHV;WAMDxE(*RHGi)A9#{!b&AgAMO#e}x|z2X+fZmV+gS4Ax?|icLa57QQ*IaJ zOj~;MjJs>7$w13W0D+J+lt`Qtbt4*c#+9Npw`fFqY5y=KXYetEO)@XwMuUUTF89-K z&M4IoaBLAizcwDYs=KP{@>3=^IQzd5AX703IkNmY`0tB`q_1TvI461B#;4v^Hbi=w zNr6LBlu|7zr;@pam5N$$zeat>?>l9S!Sao>pBoZZ#zB3S5j3kcLe6+>5}?|#n&+b}FMe^NX!9)79yJ-F6| zkw-rZAq3ja{?+LztAemkf6_;!#++4&qW)_YZ?q!+>WhE!G^;*Y+lPsJp3A`qX1bBKA0)C zOAlt$0=xDA?Z*3wl}7t4juLZS{Aqqf%27N{W2h*D=)~f_2EseQTRQMSn~psU{ZqNr z1~bp>)~P0(Z3xMpFYb!4RmgxdroBlQjlUfd4|OB1HS7C(S+}N>N0#otv$Z6{1}P8g zKzTM1O>7TMQp2_(@)`XCQXQ~Gj~qT1h_UZ34*H6S))jSYHp=o~#Ggvl_qS}#m!>hS zNA6IxqNdM}b5FHKgCmk;K@`VIFLPJN-T`bfA!l@MvXb&{Bsv>I8bc3Ngtb=PR@FIs zh+haE1Agv06uxL39>$vBSQ^78$6z=DI= z0MvobfJPeb+g}6CXHIXgT5i3yarQfgr#!|V*$y*SG>>$&bOb105@%=~qdCO8W6h;4 zi}QGgsmCgL*hmjB95v5ncI%?R2WTub<<2~I>ln6JOCq2z2&W!GeIh(iMR zcFoc_iJD`UKDI3I6Gyh|uAJ|r<022m_&mthwAp3uSf#g&H|H_%%ByN?%jANh^ut?r zn}XGy`e@Hv!>d+oFRiMMsrP$*>f8(U*}FeOZd^wQI@{cnmRtn*Ju3J=9M?b0_&fBu zKP>bA*bJSK%g&qCfiJdXKE3&NE)Z|oK`LU9r|4C_mZ|>qKWwq+&sS*HcvPkv8j&d1 zomD%~2Pn3ivwCukO(`Dpjo9VbEFkLV*8AvuXi*Jz?gUc(EAl=jLIHUVSg6A`(f~u} z|8V{PyQ;ieH~MRUKWix)d-hyZD~Magyiw5A>n>!zr{&Kmm(e{U zV4$pNJrAHh{==N+-7yN?B|prX)&{WEt90Gxv{P6FTan-1i3ny-LL*~&SslbE` z_ZBFCC~AdyUcW!f3Mt&9a-wG-jvpP%^%JIcE-tf9|19Kb7;H2_Rof7k5D2fASN|&R zxjJ}y*7#a;(pqABm2T`5vnKOb$0!wAngfJa*cf5BCR>odR4!%O6pC1B!#O7$=qqmL z7S>XMPd&W~ra@yfgQ^Us*Q)dT2SJHfAHo6R8lfzj0iA6g!b7&RRcc1NE*WD5cp7pc z4IVE6wua+T#%bz(i1<^|H&Dor@7VwQ(b6Fv6dcbp4e^_Lm8n)t(@g@-|NED3KE3S~ z`?sy$X6h~2%qzxUO?)}mdzevjIEG~8l{2y1O@|7R5vLZ+2$N}F_3PhFycV5C^)$>t z-38Fth%$?!u)!BfI|gcmny*>sNJU(q7e=;ga4-w0{vnf`vhRBeaD96406tDP4%kis zw$!;!(g%?AUz3b+6pOhqai8TsnX>nf}D{JIdL1V(bDp!P$<15;# zZN1(z812$Y4zOAAvGaYwdN_WStauc%UpN%j5R+W<^=vxiO$#dKuvZZnQtPwbE$8#ju6B2HyWqo>zkcFqhs4V^~-VG}AIhyK(e_k31${cab#J z0Twl1%w~6e-+S>&A>9Q>fDSLD05GYEUZb{&flfqfoJ2A~;1o;{MN}&np6yHp+D2j} z1>Rq`j^*SWdS12iUHwgb;;D?CcU5e}kU+UZwVr%@qygtWk6Pb~f*&dMjPYI77Aq;? z?7q?Un_E3J4b5W~31#2frO8$xtG-d9>5+O#yZaiVB#}ZhxDG&KU!KxayLDeT{Xz>4T9)Csw01>p-dFCB|NrDl)J87FF>Y z$QH)oBkbwJAluD8xwAB>*k-O1-NA^);2p9lG-whKdivMnVF}YKv$3e4ueVp=BcEY5 z_NN#}Lm>RHmLsZ05Bvtg+EQ6NX9zpIq$&Orv#nUFEM_p9+>fU%bLwP(+f(lFJ`igc z6Q$62Jz2EZU)lyALW`}{Aq0I7Qe;AK^DPHFSE~Ob0E)6M9&nuSiYXD{QHX20GO0%J z7@_V>b^cIFYz;4hP9&Rb?0N69f73ef&+uaL`tvCV71d!={Ht0r~@ z`f`-{lBF9z(XT0{v_4;uy#^3~O~Ojbb8164gPkC=QU{BsFL;M4&xZ?cl4_w?+q2@K zme{OpLL&<@2b}Yys6_?yQ=gCF?K~`9;%z`Hu{t-SkSGOn8@Z+P*S&u?C1zJ0!8nl7 z;;TO*yr$bXQ?zDqFRES3LJ3&c5w-;k6lG{da62D*w6$-i%}%%XP&J2yBq?OMH^OKJ zhzZ%2yQ9~l(7aBsaxmxR8?gWVz`;h#V1_c+YtQC7tE|h!P!r;;Y3EA3-fHa-x3jmR z3L4w>ZOfYYv`W>n7m+a@imoaU^0x&c|Cp)XT(7*5J5^nfqk>)`>P`6`G7VJe^HK!BJjdc943wwJffnP{Jo|#KN{*u z@d53h44=fS!&_|Un=-FHXsx33J2f2Kp#|Vbl(p|*c*)%)j|*{_D?{1Y zL~&{wzv*11?#{uOiM)cG&VZL(ihG=VW<+Ht#iP)Jm3^x24Xt3&h21z8-WK37V}+z- zy_5OtuDyAGTea!mwt2hHe9I!YbU)M0$>#h%Y z`35k*$UjPxi7z)Vro`!@K=}C4CKXsYZ{2rWwSMo!Db|juWS!I%d8cCT<(~mBY!Y+1 z-RTOgb7g1km;g5z$AJcLpMzf@ZQ4(Eeb1t0@F1A9S0XeMZ$c>ye_z$;@$#i1Wbrlx z&Q^CwI3gBcn14~0GKBqeVVRrLw>9wfJfX@f0+Uv(C^5nwG`9mCGQo2vGNh(7>DFjv z{Sjefg&{V_r@qt2(XHvD`+w+^f5ME-dbSzW<0xjbbt7i#4bohesecPYBs&PxO@wp6 z4RF8{*k1rSQLmf6-JheE4m<9=gfekL`&<&M$UHIwHU{OuA7RnuebpE z>0p4`E5m}^ICgOTIWqW{)D*pBmJc+tau`3srJSJ;-uyP7^pHoR@ig^^AS~)Izp#kR z+-JSgjFhHyLB=f}zGy+P(0%HsH+K)mzXqPs+t&+U12ummP)3~*2)SwUpnltBx9UHs z+=xnhgo?(fKYK-~$lx;dUo1erR0|49f{rwB%|n^3_7m!Su0dpi8;g>w{`*q{9SQ;V zx~OciRQ)P*4%mwW9kkNSmd@PNcmC~?5xci)6&@%)erHf%?Y;GmZs=%JGRL4oFvR?u zf}}pOVvhQ!mk;i6zEbep{iwzaa&i4SIi*JpKu^<06R^}v=1Khah7^R?!+piejgiMM zJ(_{do$Y9dNRP~}@rm4+&xBuC#iiSkUQlaYmuc`( zx=~+3o57$$+_lT1I3cpaUR2}{zwR0lR_vk}QBfq1Kv^g2`U6V$A%k{m&Q9q=lUX)G z_!xtaouc?V~9u&IU!zW2u z!0GKb!-;c<_?%WHDSE9!^cwiLg7pu)q@0KSl35^(XdP^ufAaPOYiP}>j9EIqkFlcA zjhiF;x!s7el(aOnF9;a|Ucy>H7TKYtbAgUXIifm+D3czlvmZpxPW7}G%W7ohs0m0D z?n)TNi{PwwVC8UKZ^OCx36HW`sQr0p!s$dyJq4M&P_#4y+Y{3UWPg)P#?Dk-2 zi8@oO$P@vAnaU}Y=5O}`E4bz!nRpb(yj67eay`foQBvjOhK8Y`Ts^17?L=^FzFQZx zN9o8950>lQQWEQ|SXxAx#_-4`Al=;n6xQBmQe z`vzamt)8J+4)<*{GWhfDqBy?2O!89lGOa~r-UEI_QRC#(3sKf2lDLzUz-xL_no}Ye z$R&wa{GgUGnIKPCS>M6-K5&XcBR=rM7>Gnd9sTdwx+z2JK5T8ckc zlSq>JkqyI``w=ShS3<5oAC7D*bBn>&N6?>7rAOO~$7DNm5r{a8;!LXy>z-V#t+tmm zrG@u%1NKSBX?Y5ESENXDX6$PKeox$X@(ypZD@|YD>p8@Q+gX7QHsXO@QT4PVZNfYE zBt=gikZ^cQ0!i3EU#%ySxB&YSi__YBq~A@ri@OWEdK;1tgG3)wPH?{ybL9Si`m=qT zy5E1gg8zMAzOAO|AOFk$(S(J6d<*~MTDck)%2E#$9{1>wRE)#OkKbcEtK4|e!YH}|Q#v%-aD^3X4y1}Y$4bL= zGX)SzxapP`+H#+-sl?!;Km0Kv++t@@cU~qw3QDOM57dvaQnCI^|_b$u$l5 z4pEy<03oP&N^(T?-BbZefGK?fI$AGFSYN(7^1~ztb+>+E-`%Mk9~3lESZ~$wa>JY1 zqdb*xQWzi1Z2Nwr#p{w)AjD~IML@_>efIP}?eG7VFO_}*2&~x)w#s@5#2ns23+l%! z^g?MY*|Se}%T6}9mhHPY)fo12jj;lJ;yr#=v-bN{4B;q|z$3l_uGCU&gvNIp{7nWz z3y74yPp(Fmx*n9YIBA(Zi?{OV*P9}*B>`b}thD$pX_Qx;R(7^XVUf3NQq1R3JW*Cw zi6G{rHMtIb@ zns^DDvz2Nxvn!*fYUQR%bzXkXLb)9Dti#N|Bz1qqzY-k9I+r_+Le5jM%B!1pfXBiG z!Co_JshU?m#S`TS5?lniaV)39sbFM+;gkp2h}2a8PhifX`l`LPO=1ISj#XJ>$ffyL zz@>29*@tW15cRw`-USyNlgS5IzZ@2|p&y=CnB|4Nm?)*Oy`X7TOfjjSZ*^iNz)P@l zN{rhHW=;P2ulUd>S5MbSAQLbVC}Ya`NyfC~yh|yum)S6pHFB6RbxqXZrAEGI7IP16 z&GHiwXQKUanhTkR8a(dP9{eI#U}7&S_TKB}n9@1SJiX}yv=(K@8BoR~|3$`xi{}F$ zWpAm!n3$&B4Utc%vvGa*w>q8}EM?U2!k$4OP7QwFwohmHwP!1@Ot_U6+pZHu_|z5P zAib926?lVzC>X_;R)#u|Nb9>r+75JAC!OLM#O-6_kNA4 ztZtDHk5u;PqNpy@y~No+^AK`X#ECY^88tbF&BhaES8K}1|ZsaN6X&BIV+d*3%7OGdRnMA)kEI+ z@ex%&Mo)3ia^r+PW}K>ln)QCGJzIa>&`UNs8f-#{P6f&0T?6tBgCPhe-G1f; zaA^gvGd+8{<4$dqxQLa#O9tY5cqP44q2F?$>e18{Tp?xcWyW;orlhLvLyF0+zM1X& zB+a1PKN61-gNCHk*ML>_m83e#+8S*YZ3!$=AFq2K*q^$%bhrNbRP4Cj8Vn^FeiaUn zUmee1wWX{$>t9$tobQ!)UaZi?%(1|@r*0dt%hY*m1u0Qs1TboW_BFlg?{B=2JZH#ZA_(Va+7xmeY*k z2a9-%a|CnJQh>KXVHUvcn!JA5y7&7*u6tmqymMgG;@mpw#*C(9e@9>Zi~2~Gc7k{D z@|IMK-MOMGB}rM-Xo%phstWIgdD-RU+G7zN1;%lcRpoN2hQ_$Lo({hq@_=&G!&cAk z&;FBghZ1i7e1tLlMrf*n1Dasc^iNi?a<_SSGKkP9SIMc4{JnBm((I!6HSiRRUO1L% zm^dDE=$NPnLFZhuaJ1wSXY?ECUgfJF_s_TFVAu7M(UEh<*PJ!9&r9O5AqDGU-pW`b z_{4K+VX}h}g$7~iwWWQhd=1EOzOR9Gr=_dFm5#}w!D&nHZ7G6npUp6CDH(^lF!;zA z1+;S^Yf0V{@Of>i=1iY`55e3-9}y3Yt(hmzlkx2S~UNjlNIln*q>zv=e ztIWT@;rpYxy7ZEt7W2`d&}2dIE$pV^!xt#bXyDzs;n+amRXf59;?faS!`HxpbUmLy z+QNCs+&Fhul;sww@b~;sGT#)1G|eYy?g!O{hhZx&pty<#c`s^WAnc~9)?DvvAQ*J` zOTI$j%H5m__1hkP9%BEXO1u)JwqLu_80y!Z){Ipa`Sf>%tJvY2&M8`h-Jf^hAp!g3 z4+C70F;C>WoFhytX!6VDKY=E#;4AVC@OAaQkrv7q){5Oz45llYE9@=PM>5Ao8?H&rKyE&&gEI(VjvX^Vp)iFfCPhK?>SX?s-4^9HAlj-R84 z)xyoxyKwM{BLI3ia>igbC zyS%T*=SfO@r{cb<<&aB6Wz=a|RCr?pcfM{y=CT%g2!gKR+h~y64@W3sVd;_FXKcWB zjk(woBG0Hi`S5$wJ8}sDjV&!v=(6KrOPOk8Ej_*goVn6lkqmy~nuUQsH=Z+O#;Hpy zAg-4H`+2Sx&UD-s>}d1oe=|I$H2%fn_?@;}VW3L~vcF~Jd$oQ(y- zH=FSsF6oTXD8gChf)(1aXaqA0Lxfa$<`&?s}{P}1>va7Voh$sU= zr*B~wz4N`StSP}@vIh@$9=N))e#>05qpEVcHzN9IwlG6@ywES^dJzVL)Q~omAmq>$ z#h&fI=J*Yy4@JXfH2vm{JC1>D|}a2s&3=x8qmY z+s?--^+g4h?!57OUt?lwQdK(_~Pz2&|t+Sg0NwxC4bthHOAAbo9&I*eG% z6=W54x}fm(S@Ta-AQW43xO-?NWk;6@+Ah1Xia>y}s)N)1vapKzA)F}*3wysqEz+Rkgqq=8fo$(hKTxw72~g7SCx*4QCN_JL3q+y78O=5Ga1S)pld6 z>kqL)({oJSE~!>FxSx}SjSYPcq!Q3bv~!(^hoxWH|2rIKQ04Fz&0g(@3%p`8cI>)E zn^V=$E}+|Nt*UU`XjuPL3#%avpZ+3Edk!gU%9rXQyf8$`L4Y_nQInZCLw7#VRg{nxZxPYsD0p+D$nO1$*$&GhW7Obuh@>$=3{qd~1`KRFElbgQ2b1m(iWTnVS~ud<|%52bs| zE1+>U%;*!Tynfm7OXysX(va*TnAE?y8TLo8Ja+o`uoCbo*Y)7jwz7z|>vSK(iHMRn ziaA_wleAvkRX~c?Y?VA*!t;o$j|9IHwAfTvlrVk(!E_-5C|-kI!^ud^3|8(U{&i!TR{Y{hgCNX~W<% zRxzK;=BG#e39vc_dV3Hh9uav(cUU^eJakO5K>r%D&hhS<)0^M0h#5$o=v;k!k^W~% zRHKy$oaLW^vrAB((YxoP=<`EitPOpshs6=f*TF3u{fB!1(%^Jlc&quvcN6Npj!C@J4OHb|-@4C5{g@UP&tsEq=1tO~p#QyS*-|A<7n$y0EUF9M=`v*5U>){pkMYu_UIH{NCfyr%x*l8k>{) zaE0Y@9C+!*xjkU06*{+!i)6$-^*lP!Tq=-(cDhUhhtD&ubm7qzkH$Rq%#WP-#!B4oe7YirMld?Sse!`T*BLfA0*# zop01~SQ^&}`RKPlkYA?$k-Fs8wL>K%W(7nU<6bpwZ=By)aH%lD>A>#4yW;-=Ge5kO zw=`AcKSXXM5iVSP?+%@h1$Bl?9PB?7yz)oe|V$+BKQBH%ifmU zuXg5UKk|a4ad)W)&GjJv+C%f=sx*DrLqxp8PH>{gs%@s^L6z z-u3GvhLuhzYpoYiY}PK#4_XaNn)o0xb|%g z)nwImD_bGghu)zvK&fzY zN(Q!E45(vXKf6+yV~U5kh8Z8NK&3H3*CjgNgqh*>!iN7LmJvdIQAsk|?{gunn8)nG zWM${NRyrmrH!{_}WkyuH<+4qW33~$Dw9b{s`Oyzgj~js`jMcMKA@=hry8a)G-?PNf zimeB*RMf+fDkp8x@N)n=2}Swn&f~*;b2UW-o+xH`im%b;8{=PU>^MoB>bVeLVW7>j zDRyyVc4n>JRB@v`W9>@d=C&X!6ACHIcl`NI%SFLp?&hi!6=^BR=0r_Vn}V6*HBhGS z^n*&7*x<)=3A@pkd3-R4Q*QDC_ek+R#d`2zwO@g9RoxLXKk8Mco3=Ei03zXNaZ{@r z(|9ZF4nfbbj^=cMq-fCH=!qh&9waUes&MRUq1OkZB*#Rho{k>UQ^IBm zM2kqc1Z~90>?$jIUMm4{ zET?raUS$l`QoI*w9e2$0&~R6eHMK5pp$4%?_v#m>Z;i!|$LHCkI8;pSixvvOPG_a? z46(V0Bq7XMePyH)-ExU2BC_@+1aoaVlv z0)!kTXKoJ;$gsZ*3DPs^5aS9j<>n^O%s04YJUUU-dZ4WLAkrM%F&5!n)0pFVjU`yK zh05q?mt@(*2!L0xwW!wEhS19nG#_~p7y?~7>U%gN_a^h4Y z%0{dtia~QnAXb`~JQv`7v^?j;U`3>D(2d5KR5z>fekZr`-M#ylpKwo>H=5@S))_)AHB@7_vUSx^ zNtexv9agNIKH60Bd6sK@Yhk2$uDTW# z)kdoCbV0;-2~ql#UBs(uMt26oht4tri@_3V63*Hl2OC2{uqhg%g>NJ&30Hq1-ELwOyXBuGz>j$t z9`x>HNXQXRlR}erPUfZ~e{XyLjCr=vm(0r&hKoEm7f7W$j@@A z*t=H2cD`EfZl@|hLbkK&@pSof)`BD|UeaU?d-?&Ok`4*)UNB9KlQL-Vr|)iC0qht3 zUlFsG*lsca6C>`5(Kg3FK@8L75)ioIrDT!Fh7P19XVsph1Tb9VV&TSQd#38q$M3!A z0F}lV2h(HEi7R$sV3G#M0JH+kwDB1z=srgcBhlp_0e8;+1|j?5ogdyb^NaG&XABjw z(~ONx7LNQp51f4@>L5nr$XGM(&;%E67cbyBt3Q3SIQSa<9pvM@yGJf+i|+cxtPTgu z++kwUlNhIAMc}pzs@89A+66kPM1tXKpygaINYevpcovt$053+^kq?!z{OGH21Bdxc zwFWoaeE9oQApbN`23ZXaSNWSrpsnm(sN3B;>S`2=&Z-`xct_xG1XpkN>YvI|@p#c7 zInN&+Jj1|d5w;aG1|nYr18EU3M>p};K)L}(;aRq`aFR%kSixaHyCZSBAza-+t*UrK zIti9r%T@lI`7hDb|2Waq)?S#AaZCcmJ-FVQFhICJH8>^8Vn^$pz$Ae` zE2`6%SyjFfn;uJ&X{6g+2qzmQ_z#W2`K;TjETWH{s7IZ31~n8#%1)gN zl9Y-o{Z-j07FBirFlZ%Hyr=yZFC@xv#L z(+2vT8l1I_nXvg~BQ+flvm8VCWqDJC1Bndt$3+I)_?bLI(ey_Iz=w_PkAx3yizLCu z%mseyy*2i0!0n65nic*qa}tE{=qm5!t{uaDw#p6>ddq#sfs(;zZRttJeCw>NB&UQ9 zqy>}2T$#zMdN>(-=Z^CVbUGN#bFpk92SdeiZea%dU_GnUMmF+?dNhFPs_NR67Q<`tK`|Q34@)Q|3*Y>)Abs^+zc97w zC9AwJQJWEQy|lRKvVAqeuRb;>{3jc>P9cPm> zaj)2k`TMHAURAZn1jg<$EKe>(m(AX|683$hnn%;-A`vS;A{Z6$@I@AJH89>_kJ{Ig z*@y~RRh!3%D1p1z&FYkrSTujZW}(jUyhO_O_@$N(_*Op1S(-cB6WtXj!<(wJUoF=? zSS_fQhrR~BR6VvdJK1VlC67Qs5#a*q?5fkY>b0i+pkPxn&7g!A3ZGT$ntf4^7+Ai* zm-A6aYJa3~T4ulLb@$#UMW3S20YwV?tnP0?E7x~Qys97*O|l>;g6|*y@a3s!PG1DD zJE8X5q|OPbKSun-JI1U1+rG3B`m%XWQJVLT(w1>!9vKc*=->Px?uAr&hx?qqmc;(q z>opaTwsZO0qb%x#e&Z-)*#+2x#G~`>ZdjeGo9M8Zm3o9v8?8BQi)T&gQ@}7nZ|`HF zy(uRmqWwhex(mwrtffYCDS)C9i?~?|8nfVd@!j~tekF~Sj}$uX?lT0567aofeTKxm6d;(7cp~?cvdZaz zwjD8e)U>j(io}3Nq_=#}1MS_)H*TwJ4P;iJ{Q05gEQp4*1MOZVmpN9UOn>rri+l>? z4|tS$d|%97O11yl1$O+l%(JRXc>N0T&Vt!%pf{Gb=lz@DN%Yf8V;B9<<)MjVojjbQ zbPfB>?%x)y0k2SMOS3+rKp+f2nW{rz_rzGy^D9FV;XNU-Iu>-It>PE47e#faH+@^? zntu2w_$W7QvNLP!6p@6FWcF59=A3zuezY#b)CY7s_#v!hxahHi2G{4BuXkXh9JgACXKW<+D1<19`*h|Lwk%C_Bi%L=U)a2+p0C`S^R^T#p-lUiXH8a+4i^v#eRX6y> zKs2s8_aq?#6rzOAE{{E&KeuFGG(0M(T*_DtpcX zK3>r~OFvYCmFh6OQWOuT*>N@`VS3o;ren%Dx4Be77;F%Y&@1dreQ2Zs(POK9~cbrv{%7#x-ZWQOJ#Emc8jLSCR6+UQMP1|o`AC&cYdRiiVcA^gt73U_AHP#G$rw};v^AO zGt_R4*v0Lj2jD}Mxh}Jh0gv$Ev#rKAYN02YHDR^mINa?wpqQFCaG@tKk%)&3$HZ}} zn9T?k)2bnXR{+lxFUQ%r3L_kG=W_v1&t}lDacjkE;AQ)IIQYo)%S$zS+k{r}RL-kx z<|CN@%`Mg5V(;jH`-JTY>t-9JcNg92b22Tb|1LatD(lyk{JO5Uj3@bBv$vRh3p(># ztmS{zB5zCOkMY(*Ttik^V9I0ZJR0PbG#0dcKRX|?r`N-n5_G#Y*{9vNqKt~hQrjf+ zKw39UoZ@vS3WT2LS20tyj>$C&xkQn<$rY?6G$Gw5&PMPAex;3sfB-Nav*#t;H>U_? zEs+`YL|czG2&i!~kzNKo!r5Ra82uU&c*SLA^y%p~G_5&_)33xkNGF~HUmZO+{~8Dw z4DD!+%syB9%0WaS=(3#XD!YH2#iA6`w9{0n|1-IXaX=CkWY85OlGii0#N|g>7eO)9 zv_sQVjhGevZ6HUBV{jP7$0z}0$QK1gh?ILp7_5bL)|4b(7xcp@tr9k@z>0MJ>oh1D z=67fWPorTn4h&+{*w0-K4A~TAb0`NH=O1BeTQj~W;zb{mLsOl16$SWSRQu59;I!Fo zYFTN&yElF#U+Wc+{eS=E{HyZ*hdz4i%KH_&{y9|41|!C1J^#_iO=C?up!0Z3eLn8h zHT5?nG^eYocKuJQN|5x8eYebSwXwNY(n=x9fOe&RHN>hikSOeTAHRkXoCCcAh9t2j zEvb7|<|*bw>&bmndlzbt^!T%mxthe_eq66H>QPd@910tZk=w?uIKCl!g#??_!85m9 z>JB3+K3I!%B^x@0=?+LQoKxz<4;W%8WiY7?lL}^VgDxLVtKSx`@GFsGKGe_1*2$cO zsCT?I4)>^sg_fWQS5=SM(7qdON_l$?OA~DLWg+=eeUdZ7s#Dpe)WZ8a~ zaAtNIlb(hOa+v{JV&Oh|fzaZQ{bjg3&jDPC$RB`{=foF|%w>x4K>iT+isB*C2 zRKCPcNg}XScEJk;Pd7U6{Iie_Qjx!+3A7Y%KdAQU$|kJwetHQ%J!uOO>GWt^$uMPc zi#S%$6}Ku&V0qP^@@Yq*R)EBYJ}B54vB-Khp~qm=3@7xx0(LnXfm$hxmBlNzy@A#p z9*eEjwnEr7{hM3jN`1R$r$fWsYFT%>)vca+IzDT>s~l~^UC%t|+sYVYW{+`n<0i-@ zi9k^9w!j5JuHIY#{!MTTE7(i}3g=8ZrnR5E_z`?63BiSUOQNfw0$$=AhXT#{vi@F+~=k!)O ztnZRR&jXz^?jo{zfKNJxj!0S?p9|S1TdSZ(yP+$rtn~YyZauFuZ3qS*k;qqjW&)dz zJv4`z+X*xZ_eI;1BilyECRv$kCLjdTRUO5t)6mdZYz=R77nBqY@H$_gtZmPV0wP%-|?}HLBj!v%2difIIHkuz5MD<3%!7g3hM~vR6QWh^!PJxk6 zqhRC{-hQh6rs{{sB=gGdfbebW&PRH1k^7)QNf!agSN=B+c0!_@7>A~N(S)0@#h4$ z5UM9-UzLav#ce!F41ZchH}AnA%w@6-3R(0f z@<}$Me_yQ$E>sYm#fnKGeQX;e`*a9ZUuGhrzbex#~Ag`eS5FWs}vS1llnajNcd9kn?_N$JjC zk{5bZQ;jZTM4E9dTEZeFX5~lKEq^`x5uuN#t$fqXH2juk>f~ZzH|DRdh~K*9Fy>xj zEd$WmD0;a|=fV_e3LKoN6GgQBgXijQcRQSaS@c{e*QwUffUp;q!EDxzblV_1{e7V= zE9swc#xyK}$A8_>n-Blg`wU>kv0ER0^ctXL9DC1?x-xEl09FjI%k2P(maQ#ux@pas zd;GQ)2L-{4kAHPOtlQz1p=E%~dyWQ)e58-Pdl(Sw-dGYnu#O4GC>tgu$xt{jq zD)onBE$M5SCM{KMxxGfup890i(`Wzurb>4StkTWYgyqCHUuj*(2(imafLBJL6OOp# zsax-4e%s&xc%Iu$$r%{RzbOUHXg*l9$W2Mh)EGa$T1Nu?qc#dS*TQGdFJBSg=w^EL z^{{N18AtJ%TLOCQ)>GYSrQ^0vUR}Byfhnun@1}{=D8-V5T|Yz+iSo#ObFwKOGQdsq z>Zb8SlCnM})*^qKeOF8&OBtkM zLc?SsV>)?3t|t|L06>xzv-<)vok)lhD?K6=3#IMKueWvX;JAr7W0-PwBX? zYOXLpoDzl7>`ocZ=?!HN@y7Ms;r35gq^5sU_+#oBhYhOK*HCtPRraEp=UV(T{LR2* ztBs9MsfTazb6wc3(O@l+aO}QnfNp1SKK$CbsRf4eP}ZQo`7S9Tr!y{uz#ITH^v~QL z^!849=*}~`Qm+MW%c#uL=6Y+i_7A?+1NOPGMI1d7!k6#zXC@)Y27zpxk(-*tj{CIq z=WV#S9Om<2X6mz<@BOL-{;n=(L+e-wKFOaU;*w3sfOf(6MmAR7??rp>qZUvqz^I}q z6Onps%nGlVjJp$@p8A~5dihi(qQNYGo8IA(in-X_&J;xFdaZiO6%VC` z&fTn zi4eNn1lm+_J35X~g1`u&?5SZAfPL_N$;p!BiVGr=I2!%6vSy`|`1@)o(0=@qCNkI7 zydRMdVT?Bt$?dz4k-w>r-cIe_y)2 z`N6@{wdZ=Q3h1N3Vabwo`8xdeeYK+@lh8J~{@uAQF)UvT2axr#_jJvGRO$MSk z>R=sScY)%WUI+g9KfkVVr_og;Q8u;NKYyG-h`Q{U=8qhas2nabz4We9$L@K>t$M9h>#2b!X{igS;UI$TetOR->WrsdXA+$? zI;Jk45&{n`e>F?Y$^3OEet!3S5w>GI&C3(k*1|f>DCT7>_7^9x;GAGx3&-2jYwhhg z3Oi!eI%HM;QTO$DlQA%*xoCYNF&qD2`SSoqeL7TG2+xPr-%~o60a@`9( zZVyhQi%h>A#6wVHj#GpI*)(ac)ix;PhP3`n?W*AawD%@pO&w_8_d!}(TbD8@g0eWE zP>T^Gi!32=o}#z}8D+{65R@QW2o#Ye5SFQ}tR^bT5-_oXpt3|Hi?ReQU_=5D0znc1 zAwmccAtYf50l(9k)-F0T&h@OlX+91NBmV6m=X zG-!`r2W6|Zy%q0BX%z;`+5tff4U%`egeqp^B-@!^l5J32n|80^U7L2;qkUnFa%({dW3P zm+2FYir?<*-;Vt|3Pa>j_viy(8XHS*KUQFx*)gt|(t$33TGRi~*D+)s8`j0IP*!jf zFQq!YrWCe4`ggw27KOYSbaHrFuEV|bEWu&P$UO}*aK#{FNjcsbB{~-QBqgP6dGO>C zh4L?X`uYQ}xdO*i0iXj2RG^4^rL)MSL$YYu(mj}tqkkjLM7Wj)=)GUU&Ndi)azq? zlXYP<+DP`@*>+3_87+xW%!13SBJ)s?MSi({9?+?u040v|=}vj1SC+vx6i^2H07=!bUC z8hg1*IDEMRbiiEuc+Dpu!}G$t=k$YV>d@KLf2*hJZBctKAiWWlt=0n|nt+uV1-mz! zPzR*xxyI1Fp|YwidBP}^?gy2k(0EqpA4p|Y|vK-m+$Qa|{y*5RFA|C+M_ z_!D)Jd?6vXq0xh0=r&0R)c7)T$Ev61*>`8q1@nH43a~_Ov&N0TZ!VV?G80xUZaq-4 z@00l4ek%@JUGF@%F#Rs7@H>6r|MpBDUaq9VJj{3#&CcA&M;cy$qqY^y`4liL?3x#6 zdGvstwK|54J3rh5JgcHOT>~j#NVk#}2o&+PrYlx9Xdp`vfHk*JMEr{DPbK!2C>H}k zr?PckyA0Dzizz(gfa}Caz(is#Rrmjx~ z7hTczYx>omzfJpb@|Q=>+$Zgm3T6D|qNqCZz#V}2(}uL#e>~v)dh-8na^E`)@coH~ zauEMM%99ut3qhdM_x8U=KmnWYYb3u;TLPc_L7#R{p+0=jH6Ymu?4Lm1U*QxBz8z9& z0T%AxZXx8fHGj?a+LFF`cV$mFQ`!U7m9PxE{RO&XZTwkPb+LzopCtElLoP8oG z^Ssq`7I9dnGoOS-T*iTBZVw{AIG214M7Vt9fOqfR`U*G*|7v(}8?X;Er5+irv%u1e zXb%pgk6L)q-17-&CQL{;v2vubF%&^N@e1$-U@tmfoI9zwyU-Tj79ItQstt%2^W!I+ z7Oj@n8go??S0UXIu_0ybg@#7T7hNLCnq~Ai_ljw}lcN_Ra`L+D=4VgXj+(_qbFH}W z`o%r^+=Xvy;3pXHOY`y9?4~%<`Itb5D~(*iv4NuFOLfV_!TXx}lx+{rqK(RqWod)5En%IW%z0Y!G#%$CieK1oJF7{5lNJ?m1ePc*HsqWTbBb31j0 z!Wjps@WRm=?Y~%(rc}%2DX{rorKE0b*(YA~6e#ogzU^Aux<=hBXRzfR*1I*h3$s1X z$I8fkq$ZMCXWLP?JF&qIa8KFBlZiUStn7~ zVX7@RY1{sJwRi|v)u>f$Tk|+(5LG`?wXMZ2GI=&T$7vYiBpzBgv`M}?$;zV^YlC;% zu}!spX?yPci&j2wdJJgUqvd&RMi6E_*FDz0`}JsOl-|1v*AL2Wn9TFOGq)}~wo zbpHwF@eB2@6VE^uzIctVcVr2l=N@iTIB1-Pj};o#9OF6O^>D3L0}`5txEf$5nYzA+8g9Jp*b;1Y_6Z(2Hu z=+VlV%SQ0sL8p?z-=ici4ZJO%{6u5*)y#)X0}tUfvu8d8u$^&`aMCk(c`j?&Qt@cT zy`C`i7HL~<@M+nmN`%oBVXPc-EE4fJHiIk-?VV0F`W;{LMN6MowlZ392apLHeh))*X^=87KI6wT+`sPY!!prngN4Fn2MTT#aQQ?gtS$n^h zNNQ`mNnHW)l=)O6=o_-?_lH;iWw=7rV3B9QwerE6R|fO9Xn1%%sAa8skv~6`K8gqq z8ZBP)1y48J=URao8ry(@IwgbrncgM0iQS-|M%|daCTOt6Ac~vf>`6+V%Mmh{b^>4_ zIjHuwUKB!kG>(gv6{6T{yx>7j#5X~AK`>HYp@*#2#0=hRz<#9hy#rqVW@6}6POM$O zb*naq?-XR#U~n#}yyKZef?Vh4&%R!AJAef%06Q{#6VaK@(T*(4bn?h8wO3;|(8Yq; zM;;7Sdl$Ot=-aB)aBkBrkc>w2^vn#dV2f??iS-)Qc!%xo)-2HWC${jY#T|3`6zq@D zN4r}SgnS4r)vUh5WY>vwGWvd*j)W8UvhFtoqExLb7T;f~-p$n|+cVb*b*6@D-UcAL zv7!h}c!Zr^fFKNO4DXS_8&)#U_`~I2C8B|kH=+w)R3Wkj7IV{WzXuR~t-0>+j4myxCS(gdWB`rAC`gy{)nSo6|CVSeSrk zqcXso3kRGC&mVwJBf*f+JM|Bzx)w#Mq&3s?`_rG?(`<)`ZE>yXj$B{Mj`zsGj`MA(DoZg}eU)=3kmMP8*sJusNDW!yA z!1F;v>K`_qTd&kN-(i^JTjXaN6A%z&Pad?bdRy)q*!w%gCInw#nafM$2!e-!tG_wR zziYj2T<3i)|53ll98T2G^_%PZ2EcCHtfcB=auB6&Slmc0{|tzb)Fk=Cw?B)Ro;kXR z6J23sh{)!I_^~eF&hP# z(U!9gmC%mSG2pW6w1C4C^j`irEU7v(z+!zSXUULtWeN8uz^WX_TJ5IWGZrOo)7G}P zxHw-5;)vD}W*+R>B*^Y*Bb`FCeWAk4qrsUnZNt(;xv+p-`I%M}JTeO|3{!Z~^)cE; zI~LJ}Q4*Gg0m{p6MWlCCYh{~nfk`#W*&~0#;3%EgnLhVDpx=V$etBr$!G#FBRvnJp zxr?DXy-gxw>zUNuHu_Vy+VFkmg?>0PotXn<3QPa2h{=p&IbPk{#W2{LC|^n$`k=4%nE} zD(t?M4~Sbv)6-ic>27(U;)*!JRw7_tUMc*(wWM!WzVFy#gB`(EDw-Pksr%(kU40KI zzt{Pw2PqHxhLb++jgU5m*@?*z$qe`g4MZ)1zJYy>6MA5`P83tf*5S6@`9R~s7`|zo z)}CgD*t8~079NeQ_Zdp)5JrV8K<|IRO7q69DKkoVD9stDm(|u7xCX_8cbf+{YgkeF zhwF}n^Snx;P+b>bEf!wGVEcXl$H(7{-~ah39}o%=JyW*AIufcrnoVVQ9k~wxSE}yh zUdI%!jt(bSV0k`KT|rKN-m`M`Z-na~U>e(Qbni>#%Q7zswKt)=;wQ$k0JkN~uX<}b zLAM+(S(Vj3H6hV9x}ZHxL?bn-e=9(5e7@57_mDx;R2}VGnPK<@v~VCe7{<0Ta7+H< z8sHI}UMi;C@}&4JK%k3u=;z=^Dph5id* zZ2k%#+Zr=e_;CIN*UC3Wr~~ibuNkLRY)0l;|EQ#*^J%B^LjkkMs#Y5kKfii!oljF~ zU%teu|Cj0_K+wc&DP0(@yM~=1dfd?bqI=p$3Mh+k>YNO?x~jHz5?6h) z6x9-$5XCa3m%=;7h(v_0$?LuU)4;pt0)D^TzZ`#u$H9NOj{nPMc-MU8zx}n};iB?y zuk(NS0`CmPczheIe2$Niu^NY?6!i$Oe_`h36q}=w$;zwjT!5c(y8VK(v(Uo?SMof6 z%0P|LnFHV_j0XLuYg@C_X5>+uYk^v7IK!_%!k;^>ELat(%u?= z#P_K!Z!6vX0P8VI7#q#*%9wBZFR<&->lMJ@F0V`dW0_BR&v^9+WhB}~+Yide4F{pe zJnSfb8UBB4LxRQ~m|nv>$6JZIhhj5dFgFEL(%8=c?&*AW?^hS0xU+gJ%2uq7ZRzJl z(TpI-u_BZ?x&O(s=SYIotWg&)(Y)p>a>WRkM7qs*%zUb_@9*+yk z_(lU{bH_r3596hsXnwP`YW2Q4GjE=h)-_N#;p1lIG*OEt+l>Q_920f|PR@TyXw93#@DT*$HA zi?&SFBzzt?e*cXf^yY;3GVU8O`S~%6d9pOXR#>1Qm^ZfO#=(vk9-Rvl z8oh`=iM?rk2BLqfGUAO72~POeDitVJcw6~IIG|>iP<$-nj{T)l3Ma+^>Dh92*}$*U zmL6bg9-$W6JUl)Nqme*}18ZR;vLM z-axYPrBrypKXy^i*Ibvh0+yqTI*f(aX(6pCBasQhbs|Z7Z~-~Vlve=Ymx|mjooLH~ zC`f`Z?Ae+`(exBSh~{HzgF&vH;e~~iyrsm4g+WytPn0eW^b|%JJtF>D%dVsR#e57D zTHK57evw`2~MgHGsCIn}XCz=c8) zqwA_vCo7`#Z~Dl(c;i4uQ)YBvm4i$tbl6_8oI87FuEIZELqDt?VngvNAU@4K2NQGi zSdTsUg#$?z23#rmJdgpJK@C^ik}nu=pFOItOYim%=QN`mb=W#`QGsYS^^e%HcCXQ= zcbsELD8%>7tgp)ZH|;t}Q;cG{BQVhUX5i$ybI#KAjNzIP;(lhc#)@B}Z(EtRb(?RF z@jkVb9DoyzI5k>#5IRYDU4rDnF1FQleadGatlk!$o5{Ba#l=-;-pGyuZ0c_m!;A6O zU}MM0He9I#*MOaf;!RU&8lRCF6fNI47P-%!Z6E*}JA{0*hB{!gdpvEvc315zG?ys#U6jt6?Dx1;$1m?s(liFdpOkJ~juAHF-wUY9-^hrj>X%|oK5Ttz9R z1w^Jnb0VotlWK=llRm>Yp)){s_wi}73w5#Tn8q@L2r;vwME%=LCBW99{T4Tq`hm`7 zb}^M-=5~=M6GDXrqI_kB6bhP*N=d&Y9|3;Ecqr1FdJx%;fyIf)1FQ?ipK0YpYoUf| zf~a2e2;se9bE>iNdCk8``oB_GDZgQNY zvxOm{!a%Wp@C7kem7KTsP<%VKOObQ%vASGxxY$7Dv-+O8QAIL-VJ)bTt61xk7|iL( zd1JC<305z@3FpZX%PeNGglj>uEQv{gFV~-3UVEt2x87X%=aiqyXO})6`H^1n-+H|R z%yA`H`SV10>|y{`Plm2i-wW=#L?m`3e|G+#r0}kOQs3l{?5Qer^^P?VG1hVYF@&-sD zXrd<39StTdQNAcBM1qdg;C(W@W*4xyA!jUzjP{-7kJSk9-ZXT(ZEGk!xIh#bGwN?Q z(+`4uO7G3a7Y1}PV+Z0q(7jnPV?by3a~0!2Syt9=ZJoyTO!r1+`joLz`+w`8_+PTH z*26F2?`=CA-ycFn*bgCp2I!r%SLQy*Yr2lHZgRwIIL8x;ICB{i_UZ%tfL6yll8Hn$ zR+nzS&>VaIqjRwi&7wBH04q;?GuRfuuAdvaUU(#Y!DKd5=4WD|tq7=A!A=9-*Jz&Z znZe3oMC8eYA{oVOz634RZb_mT?+0wYB8Ba0UT$D5jKW{!_1;W|BzoFvOJM%g;*R0HVM`aR41E}SFyBEfuh(xF(FZ6aYm(2Yv$=v(6a*UigcRTywcKwY$ZSs5u`}ets!nW zjjiqD{IGqXNHsU?bpKOPyG;~gIC_%AQ<;3+6Iwn{dk_SJ*&ae60`F_qs*b`8=vA$4 zuKbDwXC;yHIcH(&25Uln&6MVRIvoi8We2T2V&$Dd;2944?a@d4229tz13wG)5rw4Z zS9mhv7vu#%px0Y8%scfa6q+Rb}u&1@UcaAeCH^5vzIKO%g{MjM?~lVv(EH+cOc z&5k$K%a(G+7va;>FVd$rNoo@paj74w-XPL79fFsc6@$52&q;oD3wi`txz>7V%A6Ug zIxz}s+97DUnJM=gh9pFX5Gk8i8tB`OnPG@Vt_3)Hh#IDt*Kq(&MLfKzyfp@Vei}DsLK3Y!VxD0%!0K$W=-@K?~R-kYP*}p>)0m2?$ zbNlr&SVYU&diCgum4!#ZYK_@pvuZH=O)zn?1CYV(S#B4f-Yjc@9uT$Seb#Oapyj_YKj9<%GF|o=JBe=%W{yEe6dF+h=+tnKDbj=leA@{^4tg zrfD%OIZ7gb*43UUQT*jWa&J9HCaZ$uhQX?qmdc-@b71wW)|WSzcrKuI8ffcdLM8V> zMQo@6x}R+zSCkcq?`pa>o;eb}>24+p1idA+T#E;gxRcWG8bVO8VL4WY2r?!5qYs(Y zK_u3dQNxuDXv0{!TnQX`gDtlY*afCww~frDE7`f0(8+ueCBaw=PfCm^QL#byF*64w zUXB6`}?{h)*n&_$#FBTBp=c ztUHl*mN)tuw?fvZGyBTmxgybEO~hNX&7WyObGFV1B5{RC^rzEz%X{ZD-jKwSI53z#hbBU@VpgNW6I`FKubAimD{7&j#a-s27WWP zdwM+jcohoG5R6uFF0=&4B*bOptMC4+qO;$u7`#iA{@2X&&Wgdmy}9?P&^JQ%b1HO~<~OM`z?NjN;Sp~!f8y%z zpV@*zcv_NqUI)``JxJM=@cShJ|IduB}*^p7v4H3a58FK<12v7-#JkQW>a%&ZP^u~#tNDA zCD5WkVK6p&w>=e7ZL2O8gYwuu7xRJyo8^H8fT4|MT+Iy$y$$n&5m>0U4b=d!kcXpu z=Phn_p(`!2w$c_%sk{qUSm{U;VT)HbRIA@}|*S(CJ76I9}?VM01j5s)V{ch!G zQUchg$nws-M;C;lOnQpwqh|IQ>Es|YbH4d-?#%XRBg{7pf*GTgbDGpO$OAsDmmkPl z6DH<0?=ukI%>?h1yJ83GT|%Y?XQ%r*g%k5Oq-|44{O-_KfMIeETIR`dV?7%C)(|9G2$RE3zV$UE| z>q+o5f`zs=N+9R?DCgVKUJcZ;6yl5ufFk+bxWdq z5v}kAqi;C)G#A?Dzrc2(E-h+3%Mni4!OyYc%KDg$lt92#!GCZ|?gW3f4y1iVH4lrE zk~gt>e+V8LyV&ymm5)f0ZLD2K=UVV_1mR6gEEx_K%))-4m)5wWnmy>;ToV(!^z?d} zwzj}T7e|>AG){okuT^_=9PnP!k&T?3gRT>K>pbLM{ayux0LX*^m6vj7BI(}k#pfE3 z5rLCg+2c{Gy`ec~rBTD}&Cx!S85O93x*#0E$?;msvL@)oI=K92%{5@{3T3M-uu9k! zpZ^LNY$~ZiHiGI<=lnozB_v>%vhA$Xu0*U`*DGKQ=#g`*+2z-vM`>VMGq$VRs~}&Z zJ+X~yMPe69%myxO^h@?BKL4S9Xvs`SspIp~@~>>N0%eZFE!^v|%a_#4A#SiNmx8dk zWSZLu!)`Hk)iV0bkxV5c&6OZG{?bgZC^49F4o$C%2^mxPqgrmYHb(Zy=8Lf6xEeB= z2)aIh{KoIPLE7@TJ#1I*Eb#0l5KNf}K z^2_rUHhJ_G^raLh9u2@e97q&4LJi`S`1=|SrQLyDqov^~VY&SVDz`gHj>uRF+F4_3 zc~2l;%nC1xXf!I48MtU`bGRYIFl&t>+}Sdha{loKYGS~2RPS~X!BRJ!9I(6DC8B>x z&rWlJ*BEA~4y}9ONP{;Og4q z;Ks|i%W8bVOvsFo_y@8f=rP2vOR}F`W_Rq-C)atGR*^^$qlK0YvR#3Vn7~GZ1iT62 z%qrj{#k)z(Qzi{QUcxjBTUhKgHcZCs9pY|BYCx{_4DP*A8a5|H9Blm5Be^G7{PB6M z6XJx5jqUy!-#p_rl_a>v=g&t$jVRS=sqiyczqIykZdIsxn{^eQ?CWv8ThY8EG;1nP zIuFMKD#)&d*fXx>rI4?bySZ6`QD>hH!xOK|uYRyhsMT+|p=!V5xy60q;|e#Oh~$kE}9Ah z%wa!R$AC;V7mQH%=~ObVUna`W5b=h_&%ANq0l+(e2yLOmtt4So*8-&LGT8K$UI0E} z+(M)*r4=i_A=SRRa%($?&jLIFNL|CWva zM<#ir1oD;uEWF+sIjc@xU#HCtc?pP8#%qb{qQ<2wpmWQgl4OA`4^<%OF`@$0rS_}? zRy^(kiD=$5MS20{F4!lwUz96%@zhdX>ZwYM5e~p())hetph<#ml{L?%i zQi>PAzpKEbwR`@>{njsQeaE49$&p{#LG?Zh$#TuviuWd9h*A_-#*nY_uikJ7Ed5r1 zC_*kYjMLn_Xy$ldQ*!TAnCata9>+{5#YbPy9u!6ED5MG&c>r!D{SIZg&&{f9;_LCU zmTWsnq0yl^mZRrTV{aB7HwG-62OcZ@d|daDRYd}lM0Rx9fYa4`g{KX_yigLAQXRb9 z>!#SV;?WG!I~3G~CiQoArCe^92zMuz*^q;93gY3D{+H%DaV2=zCtR^E8CcK#M`H4) zh&D_@V|KIkOd?TS9h?3ElRbI^kdGXE#%ltn zqn3(}Fk@=`ucKp0OnGT4D<3rc2OWWiY`v4Rzyk3k_OKu^UD(2a$dI}#@=Ca(M~i1Rix_Kz2h$a-#ObV;oiaxyV+Sr zPp2zw>1Bn207T$_S^OO^1)ycHxHLcqB+%BpsB#!>YcEqu1#qj0>9Y@W7PfE9eg)_e z*H9c2xFf(PHUi=%v!?|lw94j3BXL2n=<#QuH8L}Av}HIxzFO7}v27?TM1oM1A51BN z%o*+g;(6m;4I;2|wfU4@N=2FK>TAB?riGn8N6Op1q}qw?mGH|{_?^PHS$_k6q-lHc zMVZ3W&cduHHez3*jB{&da6p#QvRnXoI#eP9uiQJuc}X^nDHHMPA_h*DSBe)7cUlRt z4$Gfua_$ah2AS+S?9j2F$e&$2_J;NMea%fjgiM&bFlTNE+gmN}olP};lW`b^ej5`CFs&?mkY_km#yA|swk_RH zh*f4#BiL_%p+3`m2!VgzBnqMr*I?TwMWT@0Hev3wY)_tM{70{k_+I3u-n<0EfD1|0M_Pj!6z19pwe-|G&jG1pSf0_t!S`a z<;l+3;p1mP43FAR2##w{{wW2Xy3zAZtfK!2KXY7$ROYL}2FQWEok0{gz9kO*9|Ax6P9|zB*Q^nJo4^Kj(uAYW$M;?P4Ok`k9^uX%}at z($+J-U*(Taal5hN(7KM*Ymk?v;RU9~2)zmyCsxuk>I~UGp6H7UkQ=oU?B`OQaQF`Y z4IkF==cQ0Xh*8d3xQ`+-XZuo7ywnQbryz2GC!7w5Yv?kh{MeNsljKV-%rxujA&FdE z5q+@+#ZQWLRtd#YAmPc&7SZ9uXah9O&%6r@zdW3BAUVrM={arXmT_orSz#Or=_1HV zSOoM6um0REy)qlT+JsGA55OP{mCLXn&cRDnIIxZWf+KfxhE}PJ;-l zQ(Xe|zSfBwaxccGp$Y*$0g1Dd*$N^PWfGmlIMLIl`}+To2Z_E9nuElSoeBjB>z#SY zQ-@8@)x|F^{M2tU1ooTMP3R35y%a^J_}P(@kDcGVa_a54NAMk~8FTzD-D@^d_0V!H zsB+KBn&mT-e+2>)jCZOktxmKOA2yNdeGG(=y7PWDjAtk~@@oyt@A*Do-ei-j>jLMf zF!0t?$D3@vQZYpnK3(+{-kGVk?SM0gbIUyZ2q|3}O4?JxnvzNv!kd zfc=u_B_Fe;x;n5^&Zv6k$E9td{y(%Hl6ACsJWGgfmN)rom?{hRU5oA=;|yTg)Cv{e z{@brt1iXPc{NsK7kNC7_a^*q!#NgIaMDQxS#vgBV4<3Pn?kMc_mFP^q)A^q{31GY; z#9Zv&Yy9+#%$CGxiLrg|dCf$vGBa{eD7>K*^g~n+T?6vkegyl$VAdJRzQbnm|nUaob?^&%@6_E(LqqO~NQ0(=@OV)r+^{9RdLX-fdN0;yn0fQT`;wQscn;3?b7DxbrmX?$o*mNTV-5pdbtzE%ILY)A<`Ug?B+L1R9t~bNj*WV9|J zXZ7X}+Le7~H7K6$=S)oeX`z*&S>pGpuMhA|Ua|AEL73-hSJsj79^fSE_d;RzUo`SO z-wMxb-t&4be;?3ICXRxCshXRefZru#Vi_bA46udyC>Ou40-nkudtaWttBG!D^sn9c zd^J&bDR~T&`u^>I8Wu(`z2W4aH94RVM>((nnBMIYhyQOS$G=VT_E+BtxAjnaXj`4iZ<8;`Qa=14y(x|ab|A5$dciQ(&s*#c2SAe7H z@RA-9B$BsW!qFKM_Ee1X(O@E9DSq}MQC_RG_Z~?}S5YYoxW;{l#ElmbYjT=f3b!&{ z(!7KQ2^{&Pn~x{C_TyH2^Q4(59pNN*r2hBz>-!Y)8}a+) zJY{BG<1;S72rP7RVoG`1M)j)4TRX?bPZc)QC+o~b-%G-B-7bkBlAF|=n_DYeMPa7~ z%=1AEz$Bq_&U7O)c5qV|ED3HmErM%Lwo!c(uI%{4({_6_0aAMp!mPhN7rf10ROMb~ z7}MyoX+&1v0LM#*9*-n=+p01S?He%7Cz}$`^=`&`WTG)x?ig|)ZvF36WexIzA1nd8 zBV}jn!52jy+3}$fN?9=RI#ebEsr1R4R~@xRm3!ht;vzcuFQ*@$>H|pi&C{#Uh90(8hj49EV@8!EJ3Q= zBGU{(j?UR22cl)CLoAzOp!5Ku@?8YPuzwP;)4-HEfyjSO(RZKCfkv z-&WS-x);iGN9IeJMMA6>)oPH$O$L!tX~EVrSK!5@(3oye0tu`%XXsMBoF)5OUB+}f>RuTz%@!hoi-Y(3A9S7w?rDL?tvg`{awe64`sS@_+uF zzCUCA9EsQee>r2lacA|qDD3YuYbYQ#$u%!X*{7%Q`XXz-Q><%%y}FL0&L`w}7k-($ zYltCC0Q<~}uNRAej~UTw$u-&(GxvBAx&Gmy7W-u?0kK)D!|n>=!f4^_6_2oeb`$IJ z3z%)RCSCr795bcc3I&}ZFI7YxbUDag23?@SUIBgTM%g%m-`TZr85+7=d4t#Mo)xQV z9(XD}Eh2$oue!O~n zdyHO?6MC!p;uAhQWsRcIKni*j*1K(9TXiDSW#OV)Hi4rK7x}&dHgwP(7R7v_=l&v( z!o3sPjtq{o3ssw2w&#NeW%4l}0c3>HuLF&U!P$$+>ZLX1)KP)2d^ikNr;DtaNEl(+ z(Uc?)GjriHjR>c3Tv}0R%|681qWt0r=_??Z36oOqWe@i}gq)0lW>&9n)i+%qoXJj( zZI;1YUI60!&t zpdiS9cW0QXff!V}b)U+5jixFF8plTDf$&Dkou@eB)4#w%Mdi^LiVAWU9wnDN1r7` zX0mn9e<&E7rR{^&?)Pci*r*xmyJI58Yo`-hBxrIWbgT2bQ-hzB$>Iy`gbxd!Vtsn2;(cD%Xk8W$6qTf_00&^N1wKaK*=YJwT+AnXUk^59p?Hv($0$*w!fC#*9$ z9AQlGq`-2*dluN-XIt)F(oVpneTmeiHMgsM22zGH(lcj(N9wl7UOY3UPsPfKbK1>C zME{|8BDpTywy)-gCL{tA=V-6;yTFF2lAfZ5e8i@JK4C9-MHN`7E(P|-b; zYM3*~MfCX5f?>s$pU)}pp!fVd>;EhN|HGNMO?tAxcI;gVK38(Ey=>MQS#r9zAxMt_ zu|-~%1=&|2=%7>OFj|s)U+b)|{%tq?s25;oTLad6S@ESr|DULBsUNlN(YQQT&mf(1 zQrgpPc*ED#_666ej-@6^7IGkfc=ewU^fs@h)MU(L+ee*b<@mU|-sVT*lMQ9U@Pzg_ z@!IK&_o)_NJgtQ(^6x2kR_Gym;Z2cZQWlgD>b;2arZ>w_30_^!5lBd$)RU>s@iC2No7HTy51fx$MqtQ+l2LYB<`D z$-1>%@0Moe3q{PRgv4~sq38dJV{&_CTne&+QtU-&5UYUIo&KPoyNZ5)lMzI<)Tw@- z$9SK=d3*jQXqKMHWvkZzk{e5%`Fy4EcPXp>_SE;$`Typ@{0`KR6#~C`>4xi-hdgju z9$<-LA9O%R;1opBvS(9Vi0(Sr&d`SH0Y~Sk+WJ}P81NW$xF-i+=MVL`W$AtGV++fh z!pBmZQmDG~^3D(cp(X${_5DLl;JruG{|Jw$)!CHd`QxoB#908uYJ>e9P+q8veR*8D zy8^|#m>(rq=?M6=#CYi{JB|DWKE;R#hd5cRFN%+9k;)px^@5Zzi#F2_>32~#hbXMsA-81FJ&W8cC^vK3fNuv7f@81G23XtnlaQI3?e zzp2fp(a=qYq8JdED-{}=ex(|EUvrdN_q=PNw66KVsGWtK^iX)~Lp_E?bCNza4JMlg zU8Vm>#s$GpNoK|VIfkc%>&uC&Ee&|hObH6^7!Yt=5`u>G1Vk}!|!dl3a;H?Q;e!RBXL z?q;D&up~ZKXA{QI4!~blv|C;oue`-Rv;9PoZmXX3Ec7U7%gNvx-jhy`25QK6KT#}u z5-p2fbaC9iBW{&KWh76KMI0Fd;AL|kFXOX|_GPULy(bzMo?f&)Ee5ZOGI8&7U_MdO zc(etyQs3$Rfrq(qmUkvMez?1zo$@VG2COZBU&!Ms@5b0~vm(rgUhJ7J({-xxqOlFj zxn(qsze;3DX!v$tschHLA{lp6r8d~Z4vX$73Zp$$uyAMdi!%==IZYUv%vQ)|#AK(` zRlB{*RHel`425dP_Bc?Oz{iiqFI9-z@nU~hkMa+K^Vuwgu*SGgA-YneO2h%7=qD!_ zIe$`9W>l3(HcZIUyHX=0o7hir4P>$t(7Y|KL6#w;q=J5g%d|qB`iFsFq@MDgr0m?? zn@My+&|r`qdA6WH<*WYl9!>bnMt*NMHaoWap`|M9g(#zueXgGw^<(lQB_C7I-g0<# za0Sv$x8)$pPGVD;2tYsTn;xTsHQ*${s`U)t?(BQf9CV>)T!N?~klf#pn2-wJsp1w$ zV(js?F&jBHEjL;&(g(dt;_pg@lD+1HfcvX3K3Rr%YFi}gsAWn;B+bKwFEZDzO$ts| zh&!vPuYi!5MsoQp;4|>3;im1a>Ks$>pB%5eGg@-8jf^%B)YbJGqPNUSCxqP^vNS%P zd+27{2=4~}L=|M;_RCeO7Sk4X#NfeguxMQXU!b6FTF7%LWo&exaJbdsbCMpwJD7Yf zR7YSBnwTb~8E54g!Q%%LdMSRp-3+E%5k)o|mppQ#1q{;3GM#mmLAZvb*yd=-Q;oG% zyd*dGJT2+6Ap;aooob_D$$Z@Cax}H2qWgI%E;}l;!ZXNT3YBnplzNRBagUn1mtu5g z0_D4&d!y|l=8#X|!;PLafr(tZ7Hfb{HU2!Qd`F&td1qK-(bpc9X$B)Lll;AIyt)$c2` zxd!o~IXAoL7^u-h+o|Bs#=WYQT%d-5&(=u)rt6V@CXUX0Q=0m?#te9B(J#%?b%JNX znUHN4gYc`?H3am?AO-@tjv&^PH}n|f|17(;)z=s#xbAvE2^Vy}1dbE0Nu>T?%uJ$g zb>(%3MaHu+564;#y6M}d@ZD-L$2v*mvp@$*sQ z$wu?tpg$7R@sx{kSPef3F#aOYj*e>I$FS-x5@e7ETlvpq#~K4HnDiv_6D`~<@Jeh)utC5cJg z#Tjal1@@r^^2*|eG1|f#np$1sUq5HxsB(3(^4Z};qnEKP;#5Zj336e^ znAar(cBJOmabUs6Zl=gCH0!I%_Wf-e!+L`plL`at!A3_}xbpQ>2!FPgBaowT#^ zF|78(>JWcmGtbq)Tsp-=-5N7N{CLrh{e;280}DWS=;;Dw0Ssn0Iy*0uX!Cha*PQNj zRZh7Iv0*RIF@}Te2|4VFL_j}!jS!f{X&P8o$X1 zz1%y3fVeGh-J_=m6jqh33Yq%mw6ciNs++A|`l6TFsSpqK5|*>mUN`dtn@@$*JLwww zE#rOjO50h5$&UA?xR3R(#|W;h%?ZVpwGOq{oXogFXF@s4wEdbR)T;~FT{u1~@|GBp zmU#-*xQ}JlVM3E?8;+u906b`Z`u_Dk0`DX6J_7F}@IC_XBk(=~?<4R&0`DX6J_7F} O@IC_XBk+G~1pYrbmZmiT literal 0 HcmV?d00001 From 70263c6e4e01afe8628306d62142a075238033f3 Mon Sep 17 00:00:00 2001 From: Eli Mallon Date: Sun, 11 Aug 2024 16:33:25 -0700 Subject: [PATCH 14/42] tests: working signing and verification woo --- Makefile | 13 +++- go.mod | 12 +++- go.sum | 8 +++ pkg/c2pa/c2pa_test.go | 152 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 180 insertions(+), 5 deletions(-) create mode 100644 pkg/c2pa/c2pa_test.go diff --git a/Makefile b/Makefile index 5efd76bd..15042948 100644 --- a/Makefile +++ b/Makefile @@ -11,12 +11,19 @@ rust: types: go install github.com/atombender/go-jsonschema@latest cargo install --git https://github.com/aquareum-tv/c2pa-rs export_schema - go-jsonschema -p manifeststore ./target/schema/ManifestStore.schema.json -o pkg/c2pa/generated/manifeststore/manifeststore.go - go-jsonschema -p manifestdefinition ./target/schema/ManifestDefinition.schema.json -o pkg/c2pa/generated/manifestdefinition/manifestdefinition.go - go-jsonschema -p settings ./target/schema/Settings.schema.json -o pkg/c2pa/generated/settings/settings.go + export_schema + go-jsonschema --only-models -p manifeststore ./target/schema/ManifestStore.schema.json -o pkg/c2pa/generated/manifeststore/manifeststore.go + go-jsonschema --only-models -p manifestdefinition ./target/schema/ManifestDefinition.schema.json -o pkg/c2pa/generated/manifestdefinition/manifestdefinition.go + go-jsonschema --only-models -p settings ./target/schema/Settings.schema.json -o pkg/c2pa/generated/settings/settings.go .PHONY: go go: mkdir -p dist uniffi-bindgen-go src/c2pa.udl --out-dir pkg/c2pa/generated go build -a -o ./dist/go-demo ./pkg/c2pa/demo/... + +# need es256k-enabled c2patool +.PHONY: test +test: + cargo install --git https://git.aquareum.tv/aquareum-tv/c2patool.git + go test ./pkg/... \ No newline at end of file diff --git a/go.mod b/go.mod index 75990f7e..c7ad90d9 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,14 @@ module git.aquareum.tv/aquareum-tv/c2pa-go go 1.22.2 -require github.com/decred/dcrd/dcrec/secp256k1 v1.0.4 +require ( + github.com/decred/dcrd/dcrec/secp256k1 v1.0.4 + github.com/stretchr/testify v1.9.0 +) -require github.com/decred/dcrd/dcrec/secp256k1/v2 v2.0.0 // indirect +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v2 v2.0.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum index 51004540..4636228b 100644 --- a/go.sum +++ b/go.sum @@ -8,3 +8,11 @@ github.com/decred/dcrd/dcrec/secp256k1 v1.0.4 h1:0XErmfJBiVbl0NvyclGn4jr+1hIylDf github.com/decred/dcrd/dcrec/secp256k1 v1.0.4/go.mod h1:00z7mJdugt+GBAzPN1QrDRGCXxyKUiexEHu6ukxEw3k= github.com/decred/dcrd/dcrec/secp256k1/v2 v2.0.0 h1:3GIJYXQDAKpLEFriGFN8SbSffak10UXHGdIcFaMPykY= github.com/decred/dcrd/dcrec/secp256k1/v2 v2.0.0/go.mod h1:3s92l0paYkZoIHuj4X93Teg/HB7eGM9x/zokGw+u4mY= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/c2pa/c2pa_test.go b/pkg/c2pa/c2pa_test.go new file mode 100644 index 00000000..d8b5a0b1 --- /dev/null +++ b/pkg/c2pa/c2pa_test.go @@ -0,0 +1,152 @@ +package c2pa + +import ( + "encoding/json" + "fmt" + "os/exec" + "path/filepath" + "runtime" + "testing" + "time" + + "os" + + "github.com/stretchr/testify/require" +) + +func getFixtures() string { + _, filename, _, _ := runtime.Caller(0) + dir := filepath.Dir(filename) + return filepath.Join(dir, "..", "..", "tests", "fixtures") +} + +func getPair(name string) ([]byte, []byte, error) { + fixtures := getFixtures() + + certBytes, err := os.ReadFile(filepath.Join(fixtures, fmt.Sprintf("%s_certs.pem", name))) + if err != nil { + return nil, nil, err + } + keyBytes, err := os.ReadFile(filepath.Join(fixtures, fmt.Sprintf("%s_private.key", name))) + if err != nil { + return nil, nil, err + } + return certBytes, keyBytes, nil +} + +func TestSigning(t *testing.T) { + tests := []struct { + name string + }{ + {"es256"}, + {"es256k"}, + } + + dname, err := os.MkdirTemp("", "c2pa-go-test") + require.NoError(t, err) + defer os.RemoveAll(dname) + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + certBytes, keyBytes, err := getPair(test.name) + require.NoError(t, err) + manifestBs := []byte(` + { + "title": "Image File", + "assertions": [ + { + "label": "c2pa.actions", + "data": { "actions": [{ "action": "c2pa.published" }] } + } + ] + } + `) + var manifest ManifestDefinition + err = json.Unmarshal(manifestBs, &manifest) + require.NoError(t, err) + b, err := NewBuilder(&manifest, &BuilderParams{ + Cert: certBytes, + Key: keyBytes, + Algorithm: test.name, + TAURL: "http://timestamp.digicert.com", + }) + require.NoError(t, err) + + fixtures := getFixtures() + input := filepath.Join(fixtures, "A.jpg") + output := filepath.Join(dname, fmt.Sprintf("A-signed-%s.jpg", test.name)) + + err = b.SignFile(input, output) + require.NoError(t, err) + + err = c2patool(output) + require.NoError(t, err) + }) + } +} + +type C2PAToolOutput struct { + ActiveManifest string `json:"active_manifest"` + Manifests struct { + UrnUUID struct { + ClaimGenerator string `json:"claim_generator"` + ClaimGeneratorInfo []struct { + Name string `json:"name"` + Version string `json:"version"` + } `json:"claim_generator_info"` + Title string `json:"title"` + Format string `json:"format"` + InstanceID string `json:"instance_id"` + Ingredients []any `json:"ingredients"` + Assertions []struct { + Label string `json:"label"` + Data struct { + Actions []struct { + Action string `json:"action"` + } `json:"actions"` + } `json:"data"` + } `json:"assertions"` + SignatureInfo struct { + Alg string `json:"alg"` + Issuer string `json:"issuer"` + CertSerialNumber string `json:"cert_serial_number"` + Time time.Time `json:"time"` + } `json:"signature_info"` + Label string `json:"label"` + } `json:"urn:uuid"` + } `json:"manifests"` + ValidationStatus []struct { + Code string `json:"code"` + URL string `json:"url"` + Explanation string `json:"explanation"` + } `json:"validation_status"` +} + +// validate a file with c2patool +func c2patool(file string) error { + outbs, err := exec.Command("c2patool", file).Output() + if err != nil { + return err + } + var out C2PAToolOutput + err = json.Unmarshal(outbs, &out) + if err != nil { + return err + } + if len(out.ValidationStatus) > 0 { + errbs, err := json.Marshal(out.ValidationStatus) + if err != nil { + panic("validation status testing error") + } + return fmt.Errorf(string(errbs)) + } + return nil +} + +func TestC2PATool(t *testing.T) { + fixtures := getFixtures() + err := c2patool(filepath.Join(fixtures, "C.jpg")) + require.NoError(t, err) + err = c2patool(filepath.Join(fixtures, "screenshot-signed-badsig.jpg")) + require.Error(t, err) +} From 659e9e182d155a74e70399950742174efc760a68 Mon Sep 17 00:00:00 2001 From: Eli Mallon Date: Sun, 11 Aug 2024 16:36:30 -0700 Subject: [PATCH 15/42] tests: failing tests for every other signing type --- pkg/c2pa/c2pa_test.go | 7 +++ tests/fixtures/ed25519_certs.pem | 29 ++++++++++++ tests/fixtures/ed25519_private.key | 3 ++ tests/fixtures/es256_certs.pem | 37 +++++++-------- tests/fixtures/es256_private.key | 6 +-- tests/fixtures/es256k_certs.pem | 36 +++++++------- tests/fixtures/es256k_private.key | 6 +-- tests/fixtures/es384_certs.pem | 34 +++++++++++++ tests/fixtures/es384_private.key | 6 +++ tests/fixtures/es512_certs.pem | 37 +++++++++++++++ tests/fixtures/es512_private.key | 8 ++++ tests/fixtures/ps256_certs.pem | 76 ++++++++++++++++++++++++++++++ tests/fixtures/ps256_private.key | 53 +++++++++++++++++++++ tests/fixtures/ps384_certs.pem | 76 ++++++++++++++++++++++++++++++ tests/fixtures/ps384_private.key | 53 +++++++++++++++++++++ tests/fixtures/ps512_certs.pem | 76 ++++++++++++++++++++++++++++++ tests/fixtures/ps512_private.key | 53 +++++++++++++++++++++ tests/fixtures/rs256_certs.pem | 69 +++++++++++++++++++++++++++ tests/fixtures/rs256_private.key | 52 ++++++++++++++++++++ 19 files changed, 674 insertions(+), 43 deletions(-) create mode 100644 tests/fixtures/ed25519_certs.pem create mode 100644 tests/fixtures/ed25519_private.key create mode 100644 tests/fixtures/es384_certs.pem create mode 100644 tests/fixtures/es384_private.key create mode 100644 tests/fixtures/es512_certs.pem create mode 100644 tests/fixtures/es512_private.key create mode 100644 tests/fixtures/ps256_certs.pem create mode 100644 tests/fixtures/ps256_private.key create mode 100644 tests/fixtures/ps384_certs.pem create mode 100644 tests/fixtures/ps384_private.key create mode 100644 tests/fixtures/ps512_certs.pem create mode 100644 tests/fixtures/ps512_private.key create mode 100644 tests/fixtures/rs256_certs.pem create mode 100644 tests/fixtures/rs256_private.key diff --git a/pkg/c2pa/c2pa_test.go b/pkg/c2pa/c2pa_test.go index d8b5a0b1..02386b3e 100644 --- a/pkg/c2pa/c2pa_test.go +++ b/pkg/c2pa/c2pa_test.go @@ -40,6 +40,13 @@ func TestSigning(t *testing.T) { }{ {"es256"}, {"es256k"}, + {"es384"}, + {"es512"}, + {"ed25519"}, + {"ps256"}, + {"ps384"}, + {"ps512"}, + {"rs256"}, } dname, err := os.MkdirTemp("", "c2pa-go-test") diff --git a/tests/fixtures/ed25519_certs.pem b/tests/fixtures/ed25519_certs.pem new file mode 100644 index 00000000..1a70d086 --- /dev/null +++ b/tests/fixtures/ed25519_certs.pem @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIICSDCCAfqgAwIBAgIUDXaXYTVWskbzUnZ+o/7/DYqA0EEwBQYDK2VwMIGMMQsw +CQYDVQQGEwJVUzELMAkGA1UECAwCQ0ExEjAQBgNVBAcMCVNvbWV3aGVyZTEnMCUG +A1UECgweQzJQQSBUZXN0IEludGVybWVkaWF0ZSBSb290IENBMRkwFwYDVQQLDBBG +T1IgVEVTVElOR19PTkxZMRgwFgYDVQQDDA9JbnRlcm1lZGlhdGUgQ0EwHhcNMjQw +ODExMjMzNDU2WhcNMzQwODA5MjMzNDU2WjCBgDELMAkGA1UEBhMCVVMxCzAJBgNV +BAgMAkNBMRIwEAYDVQQHDAlTb21ld2hlcmUxHzAdBgNVBAoMFkMyUEEgVGVzdCBT +aWduaW5nIENlcnQxGTAXBgNVBAsMEEZPUiBURVNUSU5HX09OTFkxFDASBgNVBAMM +C0MyUEEgU2lnbmVyMCowBQYDK2VwAyEAZucl5DLFWQ4PDLdC763u76cAaFqglztY +Xg+BpbtBaQijeDB2MAwGA1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUH +AwQwDgYDVR0PAQH/BAQDAgbAMB0GA1UdDgQWBBSUvfJfsKISTaP2fbbFgfByEmtX +1zAfBgNVHSMEGDAWgBQyr6rA6G0yWduqQpnurZxS0u5LdzAFBgMrZXADQQCGsyg7 +Cq1/8JmeWVdC+Mxgloalrfu3qkU/2OwKD8YLnQQyE2ipzzfy0+Ib5oVm6pwvSdlL +GIkcdoyiMi5ynoYL +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICKTCCAdugAwIBAgIUOv0/dMK+3bRC1sUFH6NZAbdamqQwBQYDK2VwMHcxCzAJ +BgNVBAYTAlVTMQswCQYDVQQIDAJDQTESMBAGA1UEBwwJU29tZXdoZXJlMRowGAYD +VQQKDBFDMlBBIFRlc3QgUm9vdCBDQTEZMBcGA1UECwwQRk9SIFRFU1RJTkdfT05M +WTEQMA4GA1UEAwwHUm9vdCBDQTAeFw0yNDA4MTEyMzM0NTZaFw0zNDA4MDkyMzM0 +NTZaMIGMMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ0ExEjAQBgNVBAcMCVNvbWV3 +aGVyZTEnMCUGA1UECgweQzJQQSBUZXN0IEludGVybWVkaWF0ZSBSb290IENBMRkw +FwYDVQQLDBBGT1IgVEVTVElOR19PTkxZMRgwFgYDVQQDDA9JbnRlcm1lZGlhdGUg +Q0EwKjAFBgMrZXADIQCdb2IH9kGaMPM/oM0Gv7Eee601SUN6N8ibjxrrSVR0w6Nj +MGEwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFDKv +qsDobTJZ26pCme6tnFLS7kt3MB8GA1UdIwQYMBaAFEAEgXoUd3ybEHj+imNsl/sQ +pL3vMAUGAytlcANBAOWQOwGlJuyUl6psze8FpwR11VZb82pHD8IHUVpPyUKhd+2m +KeSlhwk54EM8yk86NKuWU2FKuqjQSJh8fiyPSAs= +-----END CERTIFICATE----- diff --git a/tests/fixtures/ed25519_private.key b/tests/fixtures/ed25519_private.key new file mode 100644 index 00000000..6ea35969 --- /dev/null +++ b/tests/fixtures/ed25519_private.key @@ -0,0 +1,3 @@ +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEICmROM+3UVzoh4m0NfqGHNkht3AVgcFMf7uVWb+Ih7It +-----END PRIVATE KEY----- diff --git a/tests/fixtures/es256_certs.pem b/tests/fixtures/es256_certs.pem index f24c51cd..db564a74 100644 --- a/tests/fixtures/es256_certs.pem +++ b/tests/fixtures/es256_certs.pem @@ -1,32 +1,31 @@ -----BEGIN CERTIFICATE----- -MIIChzCCAi6gAwIBAgIUcCTmJHYF8dZfG0d1UdT6/LXtkeYwCgYIKoZIzj0EAwIw +MIICiTCCAi6gAwIBAgIUEXLS6GFPdb9uvjrP441Zp/Sj70EwCgYIKoZIzj0EAwIw gYwxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTESMBAGA1UEBwwJU29tZXdoZXJl MScwJQYDVQQKDB5DMlBBIFRlc3QgSW50ZXJtZWRpYXRlIFJvb3QgQ0ExGTAXBgNV BAsMEEZPUiBURVNUSU5HX09OTFkxGDAWBgNVBAMMD0ludGVybWVkaWF0ZSBDQTAe -Fw0yMjA2MTAxODQ2NDBaFw0zMDA4MjYxODQ2NDBaMIGAMQswCQYDVQQGEwJVUzEL +Fw0yNDA4MTEyMzM0NTZaFw0zNDA4MDkyMzM0NTZaMIGAMQswCQYDVQQGEwJVUzEL MAkGA1UECAwCQ0ExEjAQBgNVBAcMCVNvbWV3aGVyZTEfMB0GA1UECgwWQzJQQSBU ZXN0IFNpZ25pbmcgQ2VydDEZMBcGA1UECwwQRk9SIFRFU1RJTkdfT05MWTEUMBIG -A1UEAwwLQzJQQSBTaWduZXIwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQPaL6R -kAkYkKU4+IryBSYxJM3h77sFiMrbvbI8fG7w2Bbl9otNG/cch3DAw5rGAPV7NWky -l3QGuV/wt0MrAPDoo3gwdjAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsG -AQUFBwMEMA4GA1UdDwEB/wQEAwIGwDAdBgNVHQ4EFgQUFznP0y83joiNOCedQkxT -tAMyNcowHwYDVR0jBBgwFoAUDnyNcma/osnlAJTvtW6A4rYOL2swCgYIKoZIzj0E -AwIDRwAwRAIgOY/2szXjslg/MyJFZ2y7OH8giPYTsvS7UPRP9GI9NgICIDQPMKrE -LQUJEtipZ0TqvI/4mieoyRCeIiQtyuS0LACz +A1UEAwwLQzJQQSBTaWduZXIwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATNcBHD +JH3h1HeAJ/cEkRGe+uYpqRgW8dpKt0NSCOmj3FjhiULvfksqI+hcf94Uf+WDj+Fe +tuUqDHs1qjr6Ep+Zo3gwdjAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsG +AQUFBwMEMA4GA1UdDwEB/wQEAwIGwDAdBgNVHQ4EFgQUQWf4eoYn/Ps/XOR7EKK6 +P1+tF5MwHwYDVR0jBBgwFoAULee8f++nkLoO+miS9Ma9xmH9b/gwCgYIKoZIzj0E +AwIDSQAwRgIhAL5cwp7d2g6EkCyL0iV/BtAD/bMTyTih8XYWCrcG5XfGAiEAoIKc +Vk7cDoZUKmmPKkCduY882sHpq+i4aOWeyD/Rpg0= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIICajCCAg+gAwIBAgIUfXDXHH+6GtA2QEBX2IvJ2YnGMnUwCgYIKoZIzj0EAwIw +MIICaDCCAg+gAwIBAgIUEDZr9NXIjPfbs7c6qQfuduaK9wYwCgYIKoZIzj0EAwIw dzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlTb21ld2hlcmUx GjAYBgNVBAoMEUMyUEEgVGVzdCBSb290IENBMRkwFwYDVQQLDBBGT1IgVEVTVElO -R19PTkxZMRAwDgYDVQQDDAdSb290IENBMB4XDTIyMDYxMDE4NDY0MFoXDTMwMDgy -NzE4NDY0MFowgYwxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTESMBAGA1UEBwwJ +R19PTkxZMRAwDgYDVQQDDAdSb290IENBMB4XDTI0MDgxMTIzMzQ1NloXDTM0MDgw +OTIzMzQ1NlowgYwxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTESMBAGA1UEBwwJ U29tZXdoZXJlMScwJQYDVQQKDB5DMlBBIFRlc3QgSW50ZXJtZWRpYXRlIFJvb3Qg Q0ExGTAXBgNVBAsMEEZPUiBURVNUSU5HX09OTFkxGDAWBgNVBAMMD0ludGVybWVk -aWF0ZSBDQTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABHllI4O7a0EkpTYAWfPM -D6Rnfk9iqhEmCQKMOR6J47Rvh2GGjUw4CS+aLT89ySukPTnzGsMQ4jK9d3V4Aq4Q -LsOjYzBhMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQW -BBQOfI1yZr+iyeUAlO+1boDitg4vazAfBgNVHSMEGDAWgBRembiG4Xgb2VcVWnUA -UrYpDsuojDAKBggqhkjOPQQDAgNJADBGAiEAtdZ3+05CzFo90fWeZ4woeJcNQC4B -84Ill3YeZVvR8ZECIQDVRdha1xEDKuNTAManY0zthSosfXcvLnZui1A/y/DYeg== +aWF0ZSBDQTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABAbMGkgqDWz48DABkqIj +k7JzHwtmILCc2oXBD/ua09U3QDc2LlWTtx5o1D2/07M/CGB+QSQ9mnSrXNfIW/UG +C+WjYzBhMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQW +BBQt57x/76eQug76aJL0xr3GYf1v+DAfBgNVHSMEGDAWgBSFn9J0fZVahqueaXWL +VR2ZF/FliDAKBggqhkjOPQQDAgNHADBEAiB6L2vZwCt8DdW9KJB3TNd8IZt4D7uC +H4+SLDkyyix/KAIgIUdz94JDfp6YCToagYXelMntPcRmwObgW1u31Cw+a64= -----END CERTIFICATE----- - diff --git a/tests/fixtures/es256_private.key b/tests/fixtures/es256_private.key index 5e59fcc5..1e494ac4 100644 --- a/tests/fixtures/es256_private.key +++ b/tests/fixtures/es256_private.key @@ -1,5 +1,5 @@ -----BEGIN PRIVATE KEY----- -MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgfNJBsaRLSeHizv0m -GL+gcn78QmtfLSm+n+qG9veC2W2hRANCAAQPaL6RkAkYkKU4+IryBSYxJM3h77sF -iMrbvbI8fG7w2Bbl9otNG/cch3DAw5rGAPV7NWkyl3QGuV/wt0MrAPDo +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgaQIFUE7T2LjT4v5K +fjZRBkVJt6AzwD+EuWxi4CEcfayhRANCAATNcBHDJH3h1HeAJ/cEkRGe+uYpqRgW +8dpKt0NSCOmj3FjhiULvfksqI+hcf94Uf+WDj+FetuUqDHs1qjr6Ep+Z -----END PRIVATE KEY----- diff --git a/tests/fixtures/es256k_certs.pem b/tests/fixtures/es256k_certs.pem index 591bd3a1..2528b273 100644 --- a/tests/fixtures/es256k_certs.pem +++ b/tests/fixtures/es256k_certs.pem @@ -1,31 +1,31 @@ -----BEGIN CERTIFICATE----- -MIIChjCCAiugAwIBAgIUCnNsaF5RjkLTBFOU9y2k24WKricwCgYIKoZIzj0EAwIw +MIIChDCCAiugAwIBAgIUBW/ByXEeQ0Qpgc6G1HYKjM2j6JcwCgYIKoZIzj0EAwIw gYwxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTESMBAGA1UEBwwJU29tZXdoZXJl MScwJQYDVQQKDB5DMlBBIFRlc3QgSW50ZXJtZWRpYXRlIFJvb3QgQ0ExGTAXBgNV BAsMEEZPUiBURVNUSU5HX09OTFkxGDAWBgNVBAMMD0ludGVybWVkaWF0ZSBDQTAe -Fw0yNDA4MTExOTU2MjhaFw0zNDA4MDkxOTU2MjhaMIGAMQswCQYDVQQGEwJVUzEL +Fw0yNDA4MTEyMzM0NTZaFw0zNDA4MDkyMzM0NTZaMIGAMQswCQYDVQQGEwJVUzEL MAkGA1UECAwCQ0ExEjAQBgNVBAcMCVNvbWV3aGVyZTEfMB0GA1UECgwWQzJQQSBU ZXN0IFNpZ25pbmcgQ2VydDEZMBcGA1UECwwQRk9SIFRFU1RJTkdfT05MWTEUMBIG -A1UEAwwLQzJQQSBTaWduZXIwVjAQBgcqhkjOPQIBBgUrgQQACgNCAAQ+nDJR8SnH -rM3Xyjr414/xvlmsJ9DpHDUEPupv6AANqY9Ovvd3joP/avrSRie91yjkC9gkRzZj -Kk9QA+nsW1ZHo3gwdjAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUF -BwMEMA4GA1UdDwEB/wQEAwIGwDAdBgNVHQ4EFgQUrym0Mg35E6+/SIj8E6BQuSgQ -vxQwHwYDVR0jBBgwFoAUTTYhdEZFi+hD9NstNz/kGDKos3UwCgYIKoZIzj0EAwID -SQAwRgIhAMueiVdiB+0s8nIPWtImWkTyeVFTKeDGait6qlxrkxK4AiEAjSADhYY0 -HYlugGMFQhVFICJvlqmH92pfByiQIZqYEJY= +A1UEAwwLQzJQQSBTaWduZXIwVjAQBgcqhkjOPQIBBgUrgQQACgNCAAR1RJfnhmsE +HUATmWV+p0fuOPl+G0TwZ5ZisGwWFA/J+fD6wjP6mW44Ob3TTMLMCCFfy5Gl5Cju +XJru19UH0wVLo3gwdjAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUF +BwMEMA4GA1UdDwEB/wQEAwIGwDAdBgNVHQ4EFgQUoEZwqyiVTYCOTjxn9MeCBDvk +hecwHwYDVR0jBBgwFoAUP9auno3ORuwY1JnRQHu3RCiWgi0wCgYIKoZIzj0EAwID +RwAwRAIgaOz0GFjrKWJMs2epuDqUOis7MsH0ivrPfonvwapYpfYCIBqOURwT+pYf +W0VshLAxI/iVw/5eVXtDPZzCX0b0xq3f -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIICZjCCAgygAwIBAgIUJXk6wN3IvkruFN8UcqPzifrlhbowCgYIKoZIzj0EAwIw +MIICZTCCAgygAwIBAgIUIiJUPMeqKEyhrHFdKsVYF6STAqAwCgYIKoZIzj0EAwIw dzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlTb21ld2hlcmUx GjAYBgNVBAoMEUMyUEEgVGVzdCBSb290IENBMRkwFwYDVQQLDBBGT1IgVEVTVElO -R19PTkxZMRAwDgYDVQQDDAdSb290IENBMB4XDTI0MDgxMTE5NTYyOFoXDTM0MDgw -OTE5NTYyOFowgYwxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTESMBAGA1UEBwwJ +R19PTkxZMRAwDgYDVQQDDAdSb290IENBMB4XDTI0MDgxMTIzMzQ1NloXDTM0MDgw +OTIzMzQ1NlowgYwxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTESMBAGA1UEBwwJ U29tZXdoZXJlMScwJQYDVQQKDB5DMlBBIFRlc3QgSW50ZXJtZWRpYXRlIFJvb3Qg Q0ExGTAXBgNVBAsMEEZPUiBURVNUSU5HX09OTFkxGDAWBgNVBAMMD0ludGVybWVk -aWF0ZSBDQTBWMBAGByqGSM49AgEGBSuBBAAKA0IABAqHVfoqCxkh3E4s8DwSHmf0 -iD95+F0djOPdddEmPsNDyioMFTiKqqVOGYgoOMM8PNPN/+pmszZDZ6wtgzfi8M2j -YzBhMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBRN -NiF0RkWL6EP02y03P+QYMqizdTAfBgNVHSMEGDAWgBSp8P3J1W2jalobJY0EOGjY -2yypUjAKBggqhkjOPQQDAgNIADBFAiEA+JGHcLQnfVK+aldiyz++fr3Vh5uUiNvt -wgBCY7vBUEwCIAz7npLJHSqCBYP7CnoS4CV4wwi/b2VAmJgmCTKzSnQ0 +aWF0ZSBDQTBWMBAGByqGSM49AgEGBSuBBAAKA0IABMi5X2ELOtZ2i19DplQKEgAf +Et6eCXpF+s4M57ak7Rd+1LzpQ+hlRXzvrpW6hLiO+ZaRTmQyqozgWwOBUm52rT2j +YzBhMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBQ/ +1q6ejc5G7BjUmdFAe7dEKJaCLTAfBgNVHSMEGDAWgBSloXNM8yfsV/w3xH7H3pfj +cfWj6jAKBggqhkjOPQQDAgNHADBEAiBievQIsuEy1I3p5XNtpHZ3MBifukoYwo/a +4ZXq8/VK7wIgMseui+Y0BFyDd+d3vd5Jy4d3uhpho6aNFln0qHbhFr8= -----END CERTIFICATE----- diff --git a/tests/fixtures/es256k_private.key b/tests/fixtures/es256k_private.key index 5e05d908..ece41fbe 100644 --- a/tests/fixtures/es256k_private.key +++ b/tests/fixtures/es256k_private.key @@ -1,5 +1,5 @@ -----BEGIN PRIVATE KEY----- -MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQg3LMRXAay/z8RuAge6879 -IMppovaQ2CjuI8f2PNPFPqOhRANCAAQ+nDJR8SnHrM3Xyjr414/xvlmsJ9DpHDUE -Pupv6AANqY9Ovvd3joP/avrSRie91yjkC9gkRzZjKk9QA+nsW1ZH +MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQgKJyB05ZmsgeVQ/291hKX +mLsopnxVDVAEYoL1vL1jglahRANCAAR1RJfnhmsEHUATmWV+p0fuOPl+G0TwZ5Zi +sGwWFA/J+fD6wjP6mW44Ob3TTMLMCCFfy5Gl5CjuXJru19UH0wVL -----END PRIVATE KEY----- diff --git a/tests/fixtures/es384_certs.pem b/tests/fixtures/es384_certs.pem new file mode 100644 index 00000000..da635ed8 --- /dev/null +++ b/tests/fixtures/es384_certs.pem @@ -0,0 +1,34 @@ +-----BEGIN CERTIFICATE----- +MIICxjCCAkugAwIBAgIUPxwNvZFavwPMmdn34oVn4EPQFQkwCgYIKoZIzj0EAwMw +gYwxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTESMBAGA1UEBwwJU29tZXdoZXJl +MScwJQYDVQQKDB5DMlBBIFRlc3QgSW50ZXJtZWRpYXRlIFJvb3QgQ0ExGTAXBgNV +BAsMEEZPUiBURVNUSU5HX09OTFkxGDAWBgNVBAMMD0ludGVybWVkaWF0ZSBDQTAe +Fw0yNDA4MTEyMzM0NTZaFw0zNDA4MDkyMzM0NTZaMIGAMQswCQYDVQQGEwJVUzEL +MAkGA1UECAwCQ0ExEjAQBgNVBAcMCVNvbWV3aGVyZTEfMB0GA1UECgwWQzJQQSBU +ZXN0IFNpZ25pbmcgQ2VydDEZMBcGA1UECwwQRk9SIFRFU1RJTkdfT05MWTEUMBIG +A1UEAwwLQzJQQSBTaWduZXIwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQ9GioplQEj +U21/IpnfY0uDH4uRyX2t8DMtbdSBHUlwP+OxBXGeP0iO21dGBspJnDIbOPy3sOsm +aXezhDICQwY0OHu2Snb1vra3oRgWRfVkgymdawpjWTAG2UEYImjNgh6jeDB2MAwG +A1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwQwDgYDVR0PAQH/BAQD +AgbAMB0GA1UdDgQWBBSVO7p+niNfGBeP9qzP1VI8Kw8vTDAfBgNVHSMEGDAWgBQM +viB42FsJTjGfC1IHfhxObxbmqzAKBggqhkjOPQQDAwNpADBmAjEA5CKls6B5cBba +O5CMA4pAUqvEq1lAOBsuJb7asOwvcOxsIv09XaILHDVi4uHpNuOvAjEAxfD6u/Vk +B4Z54ypIZmEwrllIoJ17ivFYrFefuPiRKObbuDhWdwcAGPKKSFkUjKG/ +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICpjCCAiygAwIBAgIUYqrRkwy5rIhBX4LsAcs0B0aZtvQwCgYIKoZIzj0EAwMw +dzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlTb21ld2hlcmUx +GjAYBgNVBAoMEUMyUEEgVGVzdCBSb290IENBMRkwFwYDVQQLDBBGT1IgVEVTVElO +R19PTkxZMRAwDgYDVQQDDAdSb290IENBMB4XDTI0MDgxMTIzMzQ1NloXDTM0MDgw +OTIzMzQ1NlowgYwxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTESMBAGA1UEBwwJ +U29tZXdoZXJlMScwJQYDVQQKDB5DMlBBIFRlc3QgSW50ZXJtZWRpYXRlIFJvb3Qg +Q0ExGTAXBgNVBAsMEEZPUiBURVNUSU5HX09OTFkxGDAWBgNVBAMMD0ludGVybWVk +aWF0ZSBDQTB2MBAGByqGSM49AgEGBSuBBAAiA2IABFQGuuszAH0UsUP56wf71McK +24RIJ9DtDFwNBRAdtnszrYyte/F/xvW3iytV0T+pciZwOsiSZIshakGIHAG8xE45 +S70sQ7Q8y9c59hhDXbEWIJv6t5PQox5FmtASIjqH1qNjMGEwDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFAy+IHjYWwlOMZ8LUgd+HE5v +FuarMB8GA1UdIwQYMBaAFAdr3i8zFPjnNPfQlrwlzz8g+/qtMAoGCCqGSM49BAMD +A2gAMGUCMQCvXtMCNmDGM4qL4c45IQYVBKfoq59SsTH8sUWErwcYaUKEiHly6MsT +U91DTNcWNgUCMGULrPzvqATaFwL/KlZ91BAyA8uDj6vIOkxSQTPTlQzP1fmAdL5M +7oIwnU3Ory5PAQ== +-----END CERTIFICATE----- diff --git a/tests/fixtures/es384_private.key b/tests/fixtures/es384_private.key new file mode 100644 index 00000000..17a029c3 --- /dev/null +++ b/tests/fixtures/es384_private.key @@ -0,0 +1,6 @@ +-----BEGIN PRIVATE KEY----- +MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDCnGeB0R890A18yQrE6 +cTWHV7/IpJ9docV6KYR8PiLPN+0FoOuP2fJBNd02jfoLGHWhZANiAAQ9GioplQEj +U21/IpnfY0uDH4uRyX2t8DMtbdSBHUlwP+OxBXGeP0iO21dGBspJnDIbOPy3sOsm +aXezhDICQwY0OHu2Snb1vra3oRgWRfVkgymdawpjWTAG2UEYImjNgh4= +-----END PRIVATE KEY----- diff --git a/tests/fixtures/es512_certs.pem b/tests/fixtures/es512_certs.pem new file mode 100644 index 00000000..dfcaeb0d --- /dev/null +++ b/tests/fixtures/es512_certs.pem @@ -0,0 +1,37 @@ +-----BEGIN CERTIFICATE----- +MIIDEDCCAnGgAwIBAgIURCnrZVKnXUhFXYLNW+j8euoU+YIwCgYIKoZIzj0EAwIw +gYwxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTESMBAGA1UEBwwJU29tZXdoZXJl +MScwJQYDVQQKDB5DMlBBIFRlc3QgSW50ZXJtZWRpYXRlIFJvb3QgQ0ExGTAXBgNV +BAsMEEZPUiBURVNUSU5HX09OTFkxGDAWBgNVBAMMD0ludGVybWVkaWF0ZSBDQTAe +Fw0yNDA4MTEyMzM0NTZaFw0zNDA4MDkyMzM0NTZaMIGAMQswCQYDVQQGEwJVUzEL +MAkGA1UECAwCQ0ExEjAQBgNVBAcMCVNvbWV3aGVyZTEfMB0GA1UECgwWQzJQQSBU +ZXN0IFNpZ25pbmcgQ2VydDEZMBcGA1UECwwQRk9SIFRFU1RJTkdfT05MWTEUMBIG +A1UEAwwLQzJQQSBTaWduZXIwgZswEAYHKoZIzj0CAQYFK4EEACMDgYYABAAptbys +TzJHzOX9BAYOjkTXS5vVJA3ruVv1JkDot6Z3GMSTwdXVulcyNGlW3qjPTMASyTew +GB2rek0ARv6nnshx2wGnLu8YAWL3JRmMFoOoR7e7FOEz6OOOCeMNR9yyUCYbMMxT +LQo8CN/aOr+FPlldN+ol6J83eL5LJGU7v++dbpFAGaN4MHYwDAYDVR0TAQH/BAIw +ADAWBgNVHSUBAf8EDDAKBggrBgEFBQcDBDAOBgNVHQ8BAf8EBAMCBsAwHQYDVR0O +BBYEFPdYlqBP9YyJZuIQghW0gTb6TI9uMB8GA1UdIwQYMBaAFDcn53MUrYZBqYUx +oqeJvj0rFGIeMAoGCCqGSM49BAMCA4GMADCBiAJCAbEqb2ZnIcX1sFMwFoxxUwn3 +hRQlihDF/GSIwuTYVkJ4ycoaiirRAPgGJ7FMHq9Qtbcanxsgn3II7742VdupmxGQ +AkIBSmjs1lhY1PKb7kTY6eRByqaFcRPzm+irCTYcYJFtc5v2XyahDHmwqj4LIN0U +9Fu2Ln66BVmhey5Nr6LpKr711HI= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIC8TCCAlKgAwIBAgIUDdNECy3zl5g1DaFszb1PNbB2vIowCgYIKoZIzj0EAwIw +dzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlTb21ld2hlcmUx +GjAYBgNVBAoMEUMyUEEgVGVzdCBSb290IENBMRkwFwYDVQQLDBBGT1IgVEVTVElO +R19PTkxZMRAwDgYDVQQDDAdSb290IENBMB4XDTI0MDgxMTIzMzQ1NloXDTM0MDgw +OTIzMzQ1NlowgYwxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTESMBAGA1UEBwwJ +U29tZXdoZXJlMScwJQYDVQQKDB5DMlBBIFRlc3QgSW50ZXJtZWRpYXRlIFJvb3Qg +Q0ExGTAXBgNVBAsMEEZPUiBURVNUSU5HX09OTFkxGDAWBgNVBAMMD0ludGVybWVk +aWF0ZSBDQTCBmzAQBgcqhkjOPQIBBgUrgQQAIwOBhgAEALHfLfnUzV9pGGbVakW4 +Ll1qEIEFGvit0Izg3TB7S3cZR3GclS8U8KnF2jpS+YayIKIm+v/FMttO4m3J2ImA +kQemAZXFP8U3Y6QsEWZXy9c9XO1KXDZ3HBckAEF+mooZOFnE+aAaUi/GCnryY5pT +Cm3jPT942Br6He4vmjipmAurJTGdo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud +DwEB/wQEAwIBhjAdBgNVHQ4EFgQUNyfncxSthkGphTGip4m+PSsUYh4wHwYDVR0j +BBgwFoAU2JkxamcaxDE0b1im5Se7NVvHW1IwCgYIKoZIzj0EAwIDgYwAMIGIAkIA +/C6yoJldk5drr/Kj4WAk6C2gRDweiYkJR6pXC7x/uBJVkQ/nQ/OvvEJqBxOtE47/ +n1NoKSWg/7XSSYTWLU+WjvYCQgHcS/7MrHaddhTKXPsbqvrduvoe5IkfQI/t1lv4 +i90/UXJqmSjryknReRCuC9A+wwWNx8tCnrM9wN4FRpPHgPfnPg== +-----END CERTIFICATE----- diff --git a/tests/fixtures/es512_private.key b/tests/fixtures/es512_private.key new file mode 100644 index 00000000..c780bdbd --- /dev/null +++ b/tests/fixtures/es512_private.key @@ -0,0 +1,8 @@ +-----BEGIN PRIVATE KEY----- +MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIALdmsWsvFcOrDKvpB +Kn4f9e5uLcQhNBpG5huD0RNvMfWudt1kEe3+rgyAEL4oUNOhvbEfrd5+njpeGvBu +BTWDA5ahgYkDgYYABAAptbysTzJHzOX9BAYOjkTXS5vVJA3ruVv1JkDot6Z3GMST +wdXVulcyNGlW3qjPTMASyTewGB2rek0ARv6nnshx2wGnLu8YAWL3JRmMFoOoR7e7 +FOEz6OOOCeMNR9yyUCYbMMxTLQo8CN/aOr+FPlldN+ol6J83eL5LJGU7v++dbpFA +GQ== +-----END PRIVATE KEY----- diff --git a/tests/fixtures/ps256_certs.pem b/tests/fixtures/ps256_certs.pem new file mode 100644 index 00000000..8e8302e4 --- /dev/null +++ b/tests/fixtures/ps256_certs.pem @@ -0,0 +1,76 @@ +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +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 +MFDu+94= +-----END CERTIFICATE----- diff --git a/tests/fixtures/ps256_private.key b/tests/fixtures/ps256_private.key new file mode 100644 index 00000000..716f141a --- /dev/null +++ b/tests/fixtures/ps256_private.key @@ -0,0 +1,53 @@ +-----BEGIN PRIVATE KEY----- +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 +zWHbAWccItOAAhBSePcOd1UYP77J1QRYnBs= +-----END PRIVATE KEY----- diff --git a/tests/fixtures/ps384_certs.pem b/tests/fixtures/ps384_certs.pem new file mode 100644 index 00000000..86cc8aba --- /dev/null +++ b/tests/fixtures/ps384_certs.pem @@ -0,0 +1,76 @@ +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +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 +Ar6Kg4A= +-----END CERTIFICATE----- diff --git a/tests/fixtures/ps384_private.key b/tests/fixtures/ps384_private.key new file mode 100644 index 00000000..48353557 --- /dev/null +++ b/tests/fixtures/ps384_private.key @@ -0,0 +1,53 @@ +-----BEGIN PRIVATE KEY----- +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 +J6S/r1T+mXY4UXqQZpqORjvP7S2jVGZXEME= +-----END PRIVATE KEY----- diff --git a/tests/fixtures/ps512_certs.pem b/tests/fixtures/ps512_certs.pem new file mode 100644 index 00000000..7dec5264 --- /dev/null +++ b/tests/fixtures/ps512_certs.pem @@ -0,0 +1,76 @@ +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +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 +y7J3R/w= +-----END CERTIFICATE----- diff --git a/tests/fixtures/ps512_private.key b/tests/fixtures/ps512_private.key new file mode 100644 index 00000000..6761a243 --- /dev/null +++ b/tests/fixtures/ps512_private.key @@ -0,0 +1,53 @@ +-----BEGIN PRIVATE KEY----- +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 +rjlndR12IH3mG/TIuDC9gTNXfl9q4TGxSg== +-----END PRIVATE KEY----- diff --git a/tests/fixtures/rs256_certs.pem b/tests/fixtures/rs256_certs.pem new file mode 100644 index 00000000..0a980cf5 --- /dev/null +++ b/tests/fixtures/rs256_certs.pem @@ -0,0 +1,69 @@ +-----BEGIN CERTIFICATE----- +MIIGFDCCA/ygAwIBAgIUBJkOL8SYAAWIqXkNtH5ht30iWmowDQYJKoZIhvcNAQEL +BQAwgYwxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTESMBAGA1UEBwwJU29tZXdo +ZXJlMScwJQYDVQQKDB5DMlBBIFRlc3QgSW50ZXJtZWRpYXRlIFJvb3QgQ0ExGTAX +BgNVBAsMEEZPUiBURVNUSU5HX09OTFkxGDAWBgNVBAMMD0ludGVybWVkaWF0ZSBD +QTAeFw0yNDA4MTEyMzM1MDdaFw0zNDA4MDkyMzM1MDdaMIGAMQswCQYDVQQGEwJV +UzELMAkGA1UECAwCQ0ExEjAQBgNVBAcMCVNvbWV3aGVyZTEfMB0GA1UECgwWQzJQ +QSBUZXN0IFNpZ25pbmcgQ2VydDEZMBcGA1UECwwQRk9SIFRFU1RJTkdfT05MWTEU +MBIGA1UEAwwLQzJQQSBTaWduZXIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQC8ccx4kMOrDw1/0pOPCbyz63GavGxZJfRdUnzuUkfwoVU20Bt7mceK8Ooq +lB6hXZK17qj5eMs8DG+9vnbSlkRf3BeZIocauoUP/HTKzMZ+ewwNAAAojd1Ojheq +voEv+Z2xA9EG9lQqEvPW6DjluiDdr1JW9d+VtKfcyTq9fYbVcewe6+VC43DbuPms +z8D+9uQ83mgG271lDtrB23w3tSvpLLr1nk+CN/ySrS9BgBRSHImrp74Yu9/pNE6C +Ng1+0TnZZwqtFHxgJgJ6GlKnp1DRG1dTFd6zvw4yBdwG0J3BEg7IWa6VSmX3YmOv +MTiJt/1WUamp2edns2FMzM4h3NZ6BVlEtYto+9CclN7JcwWkIjscyd8++Yot4cR/ +1nui4kxkSaoBjUfNx8F+NyyrfVyQdvwzG6Gr4Fj4sTiCgiA/7GQ4tbmpDTE9JBTA +ims6M3lwMiOoejyZm5JrvZJ6XZgJmDzEFu3a663HtAIRnb3DC6MWuK2gUzIMZbHY +/VMpdGwSUeTCiokPDfRzkM4qXZvUtVeXUIVxhxkzo5f2DLxyIc7UMY9mQWB1Mx+Q +Zd5UpHp6gNYpWhkDZF7fjxrU7nGwCJkiM9npIBrkM5//7YcSUZHfcm0HbqVvW1tM ++YUUC0LQ/wu4WGkdkXlKeqe06yl8qEYhGzNdl7uhDq19GPmT0QIDAQABo3gwdjAM +BgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMEMA4GA1UdDwEB/wQE +AwIGwDAdBgNVHQ4EFgQUOcMKvV2IJZXwWX+v80VL2WCKgi8wHwYDVR0jBBgwFoAU +yevganU6Ek6YUzrABur2qyzM15AwDQYJKoZIhvcNAQELBQADggIBAIcPT5ruJigN +IJ6FbBnPwAoIRnGIZmpNtjXmnOaW/FY9ZS+CCo6X3F8J41OH4M7dOW5w39Ex6U3b +aIK0CqHaQz5eTmMt59/VaKyaMqjxSzAY8/abtP1mc1RCum/CjKr3Or2jkj6fEOUM +WCTyVhQtuSNvXyzTuZtW32h8CvKiNWKImgBb39uJ0JxaUnlj9mHtWb0442n62Wdw +jcNaHA+AZbXVJX0wvm5PYDmkdHLRNhcx5CyUC4qFwV14In/NBX0y9AMthO5kWQ0p +KbUzAGuWaAIYczeB1n8jIH8+ZP5MRi9s44anSJlXudyrFPNke/dKcg1BzacpH83h +1W26eZhTlPVvYMG+rKrd1LT1frEV5985Hk/6+BurN/zqePBhv/3Q3it6+kFQ4dTV +lKbLOTXQvZ27J5nAaxN2DYbFEyVDZSh3TlSsX1nhOsDgL9M3OO1JXkUues/WUo5/ +E1LEH/noUmxnSbLQAMd5Pr1zYQkxj1HyJQ+jbqGuRRvTQSJPZ+hIKeXqKzycvibg +eX/BJRUTG2XpBRMRw6wfagbc4TUpSR9CTii0W3Ugp4RBWSd+0rOWCihm2rFMgiJE +MuogLE/DneT83t1lB9oZ2aW/IBjfQ6MHNAOy1WBanhA4YlDNNCYejDXxF8ScocA3 +BSLdzrSWVmfLrS66LnnZvGeASUQ6RW9h +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- diff --git a/tests/fixtures/rs256_private.key b/tests/fixtures/rs256_private.key new file mode 100644 index 00000000..fd84c783 --- /dev/null +++ b/tests/fixtures/rs256_private.key @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 +Wv6vUJW3wlvSidg8UfX1IgDnDswgmQ== +-----END PRIVATE KEY----- From 26c761b16da301a6a8278fe6f8e1751a2760d168 Mon Sep 17 00:00:00 2001 From: Eli Mallon Date: Sun, 11 Aug 2024 17:53:15 -0700 Subject: [PATCH 16/42] we support every algorithm now! --- pkg/c2pa/c2pa.go | 171 ++++++++++++++++++++++++------- pkg/c2pa/c2pa_test.go | 4 +- tests/fixtures/rs256_certs.pem | 69 ------------- tests/fixtures/rs256_private.key | 52 ---------- 4 files changed, 137 insertions(+), 159 deletions(-) delete mode 100644 tests/fixtures/rs256_certs.pem delete mode 100644 tests/fixtures/rs256_private.key diff --git a/pkg/c2pa/c2pa.go b/pkg/c2pa/c2pa.go index 4be1240f..188be432 100644 --- a/pkg/c2pa/c2pa.go +++ b/pkg/c2pa/c2pa.go @@ -5,9 +5,9 @@ package c2pa import ( "crypto" "crypto/ecdsa" + "crypto/ed25519" "crypto/rand" "crypto/rsa" - "crypto/sha256" "crypto/x509" "crypto/x509/pkix" "encoding/asn1" @@ -19,6 +19,7 @@ import ( "mime" "os" "path/filepath" + "reflect" rustC2PA "git.aquareum.tv/aquareum-tv/c2pa-go/pkg/c2pa/generated/c2pa" "git.aquareum.tv/aquareum-tv/c2pa-go/pkg/c2pa/generated/manifestdefinition" @@ -175,11 +176,37 @@ func NewBuilder(manifest *ManifestDefinition, params *BuilderParams) (Builder, e return &C2PABuilder{builder: b, manifest: manifest, params: params}, nil } +var algMap = map[string]rustC2PA.SigningAlg{ + "es256": rustC2PA.SigningAlgEs256, + "es256k": rustC2PA.SigningAlgEs256k, + "es384": rustC2PA.SigningAlgEs384, + "es512": rustC2PA.SigningAlgEs512, + "ed25519": rustC2PA.SigningAlgEd25519, + "ps256": rustC2PA.SigningAlgPs256, + "ps384": rustC2PA.SigningAlgPs384, + "ps512": rustC2PA.SigningAlgPs512, +} + +var hashMap = map[string]crypto.Hash{ + "es256": crypto.SHA256, + "es256k": crypto.SHA256, + "es384": crypto.SHA384, + "es512": crypto.SHA512, + "ed25519": crypto.Hash(0), + "ps256": crypto.SHA256, + "ps384": crypto.SHA384, + "ps512": crypto.SHA512, +} + func (b *C2PABuilder) Sign(input, output io.ReadWriteSeeker, mimeType string) error { mySigner := &C2PACallbackSigner{ params: b.params, } - signer := rustC2PA.NewCallbackSigner(mySigner, rustC2PA.SigningAlgEs256k, b.params.Cert, &b.params.TAURL) + alg, ok := algMap[b.params.Algorithm] + if !ok { + return fmt.Errorf("unknown algorithm: %s", b.params.Algorithm) + } + signer := rustC2PA.NewCallbackSigner(mySigner, alg, b.params.Cert, &b.params.TAURL) _, err := b.builder.Sign(mimeType, &C2PAStream{input}, &C2PAStream{output}, signer) if err != nil { return err @@ -225,46 +252,58 @@ type ecPrivateKey struct { } func (s *C2PACallbackSigner) Sign(data []byte) ([]byte, *rustC2PA.Error) { - h := sha256.New() + bs, err := s._sign(data) + if err != nil { + return []byte{}, rustC2PA.NewErrorOther(err.Error()) + } + return bs, nil +} - h.Write(data) +type C2PASignerOpts struct{} - hashbs := h.Sum(nil) +func (s *C2PACallbackSigner) _sign(data []byte) ([]byte, error) { block, _ := pem.Decode(s.params.Key) if block == nil { - return []byte{}, rustC2PA.NewErrorOther("failed to parse PEM block containing the private key") - } - if s.params.Algorithm == "es256k" { - var privKey pkcs8 - _, err := asn1.Unmarshal(block.Bytes, &privKey) - if err != nil { - return nil, rustC2PA.NewErrorOther(fmt.Sprintf("asn1.Unmarshal failed: %s", err.Error())) - } - namedCurveOID := new(asn1.ObjectIdentifier) - if _, err := asn1.Unmarshal(privKey.Algo.Parameters.FullBytes, namedCurveOID); err != nil { - return nil, rustC2PA.NewErrorOther(fmt.Sprintf("asn1.Unmarshal for oid failed: %s", err.Error())) - } - var curveKey ecPrivateKey - _, err = asn1.Unmarshal(privKey.PrivateKey, &curveKey) - if err != nil { - return nil, rustC2PA.NewErrorOther(fmt.Sprintf("asn1.Unmarshal for private key failed: %s", err.Error())) - } - priv, _ := secp256k1.PrivKeyFromBytes(curveKey.PrivateKey) - bs, err := ecdsa.SignASN1(rand.Reader, priv.ToECDSA(), hashbs) - if err != nil { - return []byte{}, rustC2PA.NewErrorOther(fmt.Sprintf("ecdsa.SignASN1 failed: %s", err.Error())) - } - return bs, nil + return []byte{}, fmt.Errorf("failed to parse PEM block containing the private key") } - key, err := x509.ParsePKCS8PrivateKey(block.Bytes) + key, err := parsePrivateKey(block.Bytes) if err != nil { - return []byte{}, rustC2PA.NewErrorOther(fmt.Sprintf("x509.ParsePKCS8PrivateKey failed: %s", err.Error())) + return []byte{}, fmt.Errorf("parsePrivateKey failed: %s", err.Error()) + } + + var opts crypto.SignerOpts + var digest []byte + + hash, ok := hashMap[s.params.Algorithm] + if !ok { + return []byte{}, fmt.Errorf("hash not found for %s", s.params.Algorithm) + } + + switch key.(type) { + case *ed25519.PrivateKey: + // ed25519 handles its own hashing + opts = hash + digest = data + case *rsa.PrivateKey: + h := hash.New() + h.Write(data) + digest = h.Sum(nil) + opts = &rsa.PSSOptions{ + Hash: hash, + SaltLength: rsa.PSSSaltLengthEqualsHash, + } + default: + h := hash.New() + h.Write(data) + digest = h.Sum(nil) + opts = hash } - bs, err := ecdsa.SignASN1(rand.Reader, key.(*ecdsa.PrivateKey), hashbs) + bs, err := key.Sign(rand.Reader, digest, opts) + if err != nil { - return []byte{}, rustC2PA.NewErrorOther(fmt.Sprintf("ecdsa.SignASN1 failed: %s", err.Error())) + return []byte{}, fmt.Errorf("ecdsa.SignASN1 failed: %s", err.Error()) } return bs, nil } @@ -273,19 +312,79 @@ func parsePrivateKey(der []byte) (crypto.Signer, error) { if key, err := x509.ParsePKCS1PrivateKey(der); err == nil { return key, nil } - if key, err := x509.ParsePKCS8PrivateKey(der); err == nil { + + if key, err := x509.ParseECPrivateKey(der); err == nil { + return key, nil + } + + key, err := x509.ParsePKCS8PrivateKey(der) + if err == nil { switch key := key.(type) { case *rsa.PrivateKey: return key, nil case *ecdsa.PrivateKey: return key, nil + case ed25519.PrivateKey: + return &key, nil default: return nil, errors.New("crypto/tls: found unknown private key type in PKCS#8 wrapping") } } - if key, err := x509.ParseECPrivateKey(der); err == nil { - return key, nil + + // Last resort... handle some key types Go doesn't know about. + return parsePKCS8PrivateKey(der) +} + +var OID_RSA_PSS asn1.ObjectIdentifier = []int{1, 2, 840, 113549, 1, 1, 10} +var OID_EC asn1.ObjectIdentifier = []int{1, 2, 840, 10045, 2, 1} +var OID_SECP256K1 asn1.ObjectIdentifier = []int{1, 3, 132, 0, 10} + +func parsePKCS8PrivateKey(der []byte) (crypto.Signer, error) { + var privKey pkcs8 + _, err := asn1.Unmarshal(der, &privKey) + if err != nil { + return nil, fmt.Errorf("asn1.Unmarshal failed: %s", err.Error()) } + if reflect.DeepEqual(privKey.Algo.Algorithm, OID_RSA_PSS) { + return x509.ParsePKCS1PrivateKey(privKey.PrivateKey) + } else if reflect.DeepEqual(privKey.Algo.Algorithm, OID_EC) { + return parseES256KPrivateKey(privKey) + } else { + return nil, fmt.Errorf("unknown pkcs8 OID: %s", privKey.Algo.Algorithm) + } +} - return nil, errors.New("crypto/tls: failed to parse private key") +func parseES256KPrivateKey(privKey pkcs8) (crypto.Signer, error) { + var namedCurveOID asn1.ObjectIdentifier + if _, err := asn1.Unmarshal(privKey.Algo.Parameters.FullBytes, &namedCurveOID); err != nil { + return nil, fmt.Errorf("asn1.Unmarshal for oid failed: %w", err) + } + if !reflect.DeepEqual(namedCurveOID, OID_SECP256K1) { + return nil, fmt.Errorf("unknown named curve OID: %s", namedCurveOID.String()) + } + var curveKey ecPrivateKey + _, err := asn1.Unmarshal(privKey.PrivateKey, &curveKey) + if err != nil { + return nil, fmt.Errorf("asn1.Unmarshal for private key failed: %w", err) + } + key, _ := secp256k1.PrivKeyFromBytes(curveKey.PrivateKey) + return key.ToECDSA(), nil } + +// func parseRSAPSSPrivateKey(privKey pkcs8) (crypto.Signer, error) { + +// var namedCurveOID asn1.ObjectIdentifier +// if _, err := asn1.Unmarshal(privKey.Algo.Parameters.FullBytes, &namedCurveOID); err != nil { +// return nil, fmt.Errorf("asn1.Unmarshal for oid failed: %w", err) +// } +// if !reflect.DeepEqual(namedCurveOID, OID_SECP256K1) { +// return nil, fmt.Errorf("unknown named curve OID: %s", namedCurveOID.String()) +// } +// var curveKey ecPrivateKey +// _, err := asn1.Unmarshal(privKey.PrivateKey, &curveKey) +// if err != nil { +// return nil, fmt.Errorf("asn1.Unmarshal for private key failed: %w", err) +// } +// key, _ := secp256k1.PrivKeyFromBytes(curveKey.PrivateKey) +// return key.ToECDSA(), nil +// } diff --git a/pkg/c2pa/c2pa_test.go b/pkg/c2pa/c2pa_test.go index 02386b3e..eb3614b0 100644 --- a/pkg/c2pa/c2pa_test.go +++ b/pkg/c2pa/c2pa_test.go @@ -46,12 +46,12 @@ func TestSigning(t *testing.T) { {"ps256"}, {"ps384"}, {"ps512"}, - {"rs256"}, } dname, err := os.MkdirTemp("", "c2pa-go-test") require.NoError(t, err) - defer os.RemoveAll(dname) + // defer os.RemoveAll(dname) + fmt.Printf("writing to %s\n", dname) for _, test := range tests { t.Run(test.name, func(t *testing.T) { diff --git a/tests/fixtures/rs256_certs.pem b/tests/fixtures/rs256_certs.pem deleted file mode 100644 index 0a980cf5..00000000 --- a/tests/fixtures/rs256_certs.pem +++ /dev/null @@ -1,69 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIGFDCCA/ygAwIBAgIUBJkOL8SYAAWIqXkNtH5ht30iWmowDQYJKoZIhvcNAQEL -BQAwgYwxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTESMBAGA1UEBwwJU29tZXdo -ZXJlMScwJQYDVQQKDB5DMlBBIFRlc3QgSW50ZXJtZWRpYXRlIFJvb3QgQ0ExGTAX -BgNVBAsMEEZPUiBURVNUSU5HX09OTFkxGDAWBgNVBAMMD0ludGVybWVkaWF0ZSBD -QTAeFw0yNDA4MTEyMzM1MDdaFw0zNDA4MDkyMzM1MDdaMIGAMQswCQYDVQQGEwJV -UzELMAkGA1UECAwCQ0ExEjAQBgNVBAcMCVNvbWV3aGVyZTEfMB0GA1UECgwWQzJQ -QSBUZXN0IFNpZ25pbmcgQ2VydDEZMBcGA1UECwwQRk9SIFRFU1RJTkdfT05MWTEU -MBIGA1UEAwwLQzJQQSBTaWduZXIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK -AoICAQC8ccx4kMOrDw1/0pOPCbyz63GavGxZJfRdUnzuUkfwoVU20Bt7mceK8Ooq -lB6hXZK17qj5eMs8DG+9vnbSlkRf3BeZIocauoUP/HTKzMZ+ewwNAAAojd1Ojheq -voEv+Z2xA9EG9lQqEvPW6DjluiDdr1JW9d+VtKfcyTq9fYbVcewe6+VC43DbuPms -z8D+9uQ83mgG271lDtrB23w3tSvpLLr1nk+CN/ySrS9BgBRSHImrp74Yu9/pNE6C -Ng1+0TnZZwqtFHxgJgJ6GlKnp1DRG1dTFd6zvw4yBdwG0J3BEg7IWa6VSmX3YmOv -MTiJt/1WUamp2edns2FMzM4h3NZ6BVlEtYto+9CclN7JcwWkIjscyd8++Yot4cR/ -1nui4kxkSaoBjUfNx8F+NyyrfVyQdvwzG6Gr4Fj4sTiCgiA/7GQ4tbmpDTE9JBTA -ims6M3lwMiOoejyZm5JrvZJ6XZgJmDzEFu3a663HtAIRnb3DC6MWuK2gUzIMZbHY -/VMpdGwSUeTCiokPDfRzkM4qXZvUtVeXUIVxhxkzo5f2DLxyIc7UMY9mQWB1Mx+Q -Zd5UpHp6gNYpWhkDZF7fjxrU7nGwCJkiM9npIBrkM5//7YcSUZHfcm0HbqVvW1tM -+YUUC0LQ/wu4WGkdkXlKeqe06yl8qEYhGzNdl7uhDq19GPmT0QIDAQABo3gwdjAM -BgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMEMA4GA1UdDwEB/wQE -AwIGwDAdBgNVHQ4EFgQUOcMKvV2IJZXwWX+v80VL2WCKgi8wHwYDVR0jBBgwFoAU -yevganU6Ek6YUzrABur2qyzM15AwDQYJKoZIhvcNAQELBQADggIBAIcPT5ruJigN -IJ6FbBnPwAoIRnGIZmpNtjXmnOaW/FY9ZS+CCo6X3F8J41OH4M7dOW5w39Ex6U3b -aIK0CqHaQz5eTmMt59/VaKyaMqjxSzAY8/abtP1mc1RCum/CjKr3Or2jkj6fEOUM -WCTyVhQtuSNvXyzTuZtW32h8CvKiNWKImgBb39uJ0JxaUnlj9mHtWb0442n62Wdw -jcNaHA+AZbXVJX0wvm5PYDmkdHLRNhcx5CyUC4qFwV14In/NBX0y9AMthO5kWQ0p -KbUzAGuWaAIYczeB1n8jIH8+ZP5MRi9s44anSJlXudyrFPNke/dKcg1BzacpH83h -1W26eZhTlPVvYMG+rKrd1LT1frEV5985Hk/6+BurN/zqePBhv/3Q3it6+kFQ4dTV -lKbLOTXQvZ27J5nAaxN2DYbFEyVDZSh3TlSsX1nhOsDgL9M3OO1JXkUues/WUo5/ -E1LEH/noUmxnSbLQAMd5Pr1zYQkxj1HyJQ+jbqGuRRvTQSJPZ+hIKeXqKzycvibg -eX/BJRUTG2XpBRMRw6wfagbc4TUpSR9CTii0W3Ugp4RBWSd+0rOWCihm2rFMgiJE -MuogLE/DneT83t1lB9oZ2aW/IBjfQ6MHNAOy1WBanhA4YlDNNCYejDXxF8ScocA3 -BSLdzrSWVmfLrS66LnnZvGeASUQ6RW9h ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -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 ------END CERTIFICATE----- diff --git a/tests/fixtures/rs256_private.key b/tests/fixtures/rs256_private.key deleted file mode 100644 index fd84c783..00000000 --- a/tests/fixtures/rs256_private.key +++ /dev/null @@ -1,52 +0,0 @@ ------BEGIN PRIVATE KEY----- -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 -Wv6vUJW3wlvSidg8UfX1IgDnDswgmQ== ------END PRIVATE KEY----- From dcac7f9e321576de46ef7496708839390f6e9cdf Mon Sep 17 00:00:00 2001 From: Eli Mallon Date: Sun, 11 Aug 2024 17:53:31 -0700 Subject: [PATCH 17/42] cleanup --- pkg/c2pa/c2pa.go | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/pkg/c2pa/c2pa.go b/pkg/c2pa/c2pa.go index 188be432..e4b93a2a 100644 --- a/pkg/c2pa/c2pa.go +++ b/pkg/c2pa/c2pa.go @@ -370,21 +370,3 @@ func parseES256KPrivateKey(privKey pkcs8) (crypto.Signer, error) { key, _ := secp256k1.PrivKeyFromBytes(curveKey.PrivateKey) return key.ToECDSA(), nil } - -// func parseRSAPSSPrivateKey(privKey pkcs8) (crypto.Signer, error) { - -// var namedCurveOID asn1.ObjectIdentifier -// if _, err := asn1.Unmarshal(privKey.Algo.Parameters.FullBytes, &namedCurveOID); err != nil { -// return nil, fmt.Errorf("asn1.Unmarshal for oid failed: %w", err) -// } -// if !reflect.DeepEqual(namedCurveOID, OID_SECP256K1) { -// return nil, fmt.Errorf("unknown named curve OID: %s", namedCurveOID.String()) -// } -// var curveKey ecPrivateKey -// _, err := asn1.Unmarshal(privKey.PrivateKey, &curveKey) -// if err != nil { -// return nil, fmt.Errorf("asn1.Unmarshal for private key failed: %w", err) -// } -// key, _ := secp256k1.PrivKeyFromBytes(curveKey.PrivateKey) -// return key.ToECDSA(), nil -// } From 8de0cec510ad6992ce7d36b5b93bd88b5f8c3882 Mon Sep 17 00:00:00 2001 From: Eli Mallon Date: Tue, 13 Aug 2024 18:16:28 -0700 Subject: [PATCH 18/42] add generated code --- pkg/c2pa/generated/c2pa/c2pa.c | 8 + pkg/c2pa/generated/c2pa/c2pa.go | 1972 +++++++++++++++++ pkg/c2pa/generated/c2pa/c2pa.h | 559 +++++ .../manifestdefinition/manifestdefinition.go | 446 ++++ .../generated/manifeststore/manifeststore.go | 520 +++++ pkg/c2pa/generated/settings/settings.go | 82 + 6 files changed, 3587 insertions(+) create mode 100644 pkg/c2pa/generated/c2pa/c2pa.c create mode 100644 pkg/c2pa/generated/c2pa/c2pa.go create mode 100644 pkg/c2pa/generated/c2pa/c2pa.h create mode 100644 pkg/c2pa/generated/manifestdefinition/manifestdefinition.go create mode 100644 pkg/c2pa/generated/manifeststore/manifeststore.go create mode 100644 pkg/c2pa/generated/settings/settings.go diff --git a/pkg/c2pa/generated/c2pa/c2pa.c b/pkg/c2pa/generated/c2pa/c2pa.c new file mode 100644 index 00000000..f9a0d0f9 --- /dev/null +++ b/pkg/c2pa/generated/c2pa/c2pa.c @@ -0,0 +1,8 @@ +#include + +// This file exists beacause of +// https://github.com/golang/go/issues/11263 + +void cgo_rust_task_callback_bridge_c2pa(RustTaskCallback cb, const void * taskData, int8_t status) { + cb(taskData, status); +} \ No newline at end of file diff --git a/pkg/c2pa/generated/c2pa/c2pa.go b/pkg/c2pa/generated/c2pa/c2pa.go new file mode 100644 index 00000000..c3f1931c --- /dev/null +++ b/pkg/c2pa/generated/c2pa/c2pa.go @@ -0,0 +1,1972 @@ +package c2pa + +// #include +import "C" + +import ( + "bytes" + "encoding/binary" + "fmt" + "io" + "math" + "runtime" + "sync" + "sync/atomic" + "unsafe" +) + +type RustBuffer = C.RustBuffer + +type RustBufferI interface { + AsReader() *bytes.Reader + Free() + ToGoBytes() []byte + Data() unsafe.Pointer + Len() int + Capacity() int +} + +func RustBufferFromExternal(b RustBufferI) RustBuffer { + return RustBuffer{ + capacity: C.int(b.Capacity()), + len: C.int(b.Len()), + data: (*C.uchar)(b.Data()), + } +} + +func (cb RustBuffer) Capacity() int { + return int(cb.capacity) +} + +func (cb RustBuffer) Len() int { + return int(cb.len) +} + +func (cb RustBuffer) Data() unsafe.Pointer { + return unsafe.Pointer(cb.data) +} + +func (cb RustBuffer) AsReader() *bytes.Reader { + b := unsafe.Slice((*byte)(cb.data), C.int(cb.len)) + return bytes.NewReader(b) +} + +func (cb RustBuffer) Free() { + rustCall(func(status *C.RustCallStatus) bool { + C.ffi_c2pa_rustbuffer_free(cb, status) + return false + }) +} + +func (cb RustBuffer) ToGoBytes() []byte { + return C.GoBytes(unsafe.Pointer(cb.data), C.int(cb.len)) +} + +func stringToRustBuffer(str string) RustBuffer { + return bytesToRustBuffer([]byte(str)) +} + +func bytesToRustBuffer(b []byte) RustBuffer { + if len(b) == 0 { + return RustBuffer{} + } + // We can pass the pointer along here, as it is pinned + // for the duration of this call + foreign := C.ForeignBytes{ + len: C.int(len(b)), + data: (*C.uchar)(unsafe.Pointer(&b[0])), + } + + return rustCall(func(status *C.RustCallStatus) RustBuffer { + return C.ffi_c2pa_rustbuffer_from_bytes(foreign, status) + }) +} + +type BufLifter[GoType any] interface { + Lift(value RustBufferI) GoType +} + +type BufLowerer[GoType any] interface { + Lower(value GoType) RustBuffer +} + +type FfiConverter[GoType any, FfiType any] interface { + Lift(value FfiType) GoType + Lower(value GoType) FfiType +} + +type BufReader[GoType any] interface { + Read(reader io.Reader) GoType +} + +type BufWriter[GoType any] interface { + Write(writer io.Writer, value GoType) +} + +type FfiRustBufConverter[GoType any, FfiType any] interface { + FfiConverter[GoType, FfiType] + BufReader[GoType] +} + +func LowerIntoRustBuffer[GoType any](bufWriter BufWriter[GoType], value GoType) RustBuffer { + // This might be not the most efficient way but it does not require knowing allocation size + // beforehand + var buffer bytes.Buffer + bufWriter.Write(&buffer, value) + + bytes, err := io.ReadAll(&buffer) + if err != nil { + panic(fmt.Errorf("reading written data: %w", err)) + } + return bytesToRustBuffer(bytes) +} + +func LiftFromRustBuffer[GoType any](bufReader BufReader[GoType], rbuf RustBufferI) GoType { + defer rbuf.Free() + reader := rbuf.AsReader() + item := bufReader.Read(reader) + if reader.Len() > 0 { + // TODO: Remove this + leftover, _ := io.ReadAll(reader) + panic(fmt.Errorf("Junk remaining in buffer after lifting: %s", string(leftover))) + } + return item +} + +func rustCallWithError[U any](converter BufLifter[error], callback func(*C.RustCallStatus) U) (U, error) { + var status C.RustCallStatus + returnValue := callback(&status) + err := checkCallStatus(converter, status) + + return returnValue, err +} + +func checkCallStatus(converter BufLifter[error], status C.RustCallStatus) error { + switch status.code { + case 0: + return nil + case 1: + return converter.Lift(status.errorBuf) + case 2: + // when the rust code sees a panic, it tries to construct a rustbuffer + // with the message. but if that code panics, then it just sends back + // an empty buffer. + if status.errorBuf.len > 0 { + panic(fmt.Errorf("%s", FfiConverterStringINSTANCE.Lift(status.errorBuf))) + } else { + panic(fmt.Errorf("Rust panicked while handling Rust panic")) + } + default: + return fmt.Errorf("unknown status code: %d", status.code) + } +} + +func checkCallStatusUnknown(status C.RustCallStatus) error { + switch status.code { + case 0: + return nil + case 1: + panic(fmt.Errorf("function not returning an error returned an error")) + case 2: + // when the rust code sees a panic, it tries to construct a rustbuffer + // with the message. but if that code panics, then it just sends back + // an empty buffer. + if status.errorBuf.len > 0 { + panic(fmt.Errorf("%s", FfiConverterStringINSTANCE.Lift(status.errorBuf))) + } else { + panic(fmt.Errorf("Rust panicked while handling Rust panic")) + } + default: + return fmt.Errorf("unknown status code: %d", status.code) + } +} + +func rustCall[U any](callback func(*C.RustCallStatus) U) U { + returnValue, err := rustCallWithError(nil, callback) + if err != nil { + panic(err) + } + return returnValue +} + +func writeInt8(writer io.Writer, value int8) { + if err := binary.Write(writer, binary.BigEndian, value); err != nil { + panic(err) + } +} + +func writeUint8(writer io.Writer, value uint8) { + if err := binary.Write(writer, binary.BigEndian, value); err != nil { + panic(err) + } +} + +func writeInt16(writer io.Writer, value int16) { + if err := binary.Write(writer, binary.BigEndian, value); err != nil { + panic(err) + } +} + +func writeUint16(writer io.Writer, value uint16) { + if err := binary.Write(writer, binary.BigEndian, value); err != nil { + panic(err) + } +} + +func writeInt32(writer io.Writer, value int32) { + if err := binary.Write(writer, binary.BigEndian, value); err != nil { + panic(err) + } +} + +func writeUint32(writer io.Writer, value uint32) { + if err := binary.Write(writer, binary.BigEndian, value); err != nil { + panic(err) + } +} + +func writeInt64(writer io.Writer, value int64) { + if err := binary.Write(writer, binary.BigEndian, value); err != nil { + panic(err) + } +} + +func writeUint64(writer io.Writer, value uint64) { + if err := binary.Write(writer, binary.BigEndian, value); err != nil { + panic(err) + } +} + +func writeFloat32(writer io.Writer, value float32) { + if err := binary.Write(writer, binary.BigEndian, value); err != nil { + panic(err) + } +} + +func writeFloat64(writer io.Writer, value float64) { + if err := binary.Write(writer, binary.BigEndian, value); err != nil { + panic(err) + } +} + +func readInt8(reader io.Reader) int8 { + var result int8 + if err := binary.Read(reader, binary.BigEndian, &result); err != nil { + panic(err) + } + return result +} + +func readUint8(reader io.Reader) uint8 { + var result uint8 + if err := binary.Read(reader, binary.BigEndian, &result); err != nil { + panic(err) + } + return result +} + +func readInt16(reader io.Reader) int16 { + var result int16 + if err := binary.Read(reader, binary.BigEndian, &result); err != nil { + panic(err) + } + return result +} + +func readUint16(reader io.Reader) uint16 { + var result uint16 + if err := binary.Read(reader, binary.BigEndian, &result); err != nil { + panic(err) + } + return result +} + +func readInt32(reader io.Reader) int32 { + var result int32 + if err := binary.Read(reader, binary.BigEndian, &result); err != nil { + panic(err) + } + return result +} + +func readUint32(reader io.Reader) uint32 { + var result uint32 + if err := binary.Read(reader, binary.BigEndian, &result); err != nil { + panic(err) + } + return result +} + +func readInt64(reader io.Reader) int64 { + var result int64 + if err := binary.Read(reader, binary.BigEndian, &result); err != nil { + panic(err) + } + return result +} + +func readUint64(reader io.Reader) uint64 { + var result uint64 + if err := binary.Read(reader, binary.BigEndian, &result); err != nil { + panic(err) + } + return result +} + +func readFloat32(reader io.Reader) float32 { + var result float32 + if err := binary.Read(reader, binary.BigEndian, &result); err != nil { + panic(err) + } + return result +} + +func readFloat64(reader io.Reader) float64 { + var result float64 + if err := binary.Read(reader, binary.BigEndian, &result); err != nil { + panic(err) + } + return result +} + +func init() { + + (&FfiConverterCallbackInterfaceSignerCallback{}).register() + (&FfiConverterCallbackInterfaceStream{}).register() + uniffiCheckChecksums() +} + +func uniffiCheckChecksums() { + // Get the bindings contract version from our ComponentInterface + bindingsContractVersion := 24 + // Get the scaffolding contract version by calling the into the dylib + scaffoldingContractVersion := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint32_t { + return C.ffi_c2pa_uniffi_contract_version(uniffiStatus) + }) + if bindingsContractVersion != int(scaffoldingContractVersion) { + // If this happens try cleaning and rebuilding your project + panic("c2pa: UniFFI contract version mismatch") + } + { + checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_c2pa_checksum_func_sdk_version(uniffiStatus) + }) + if checksum != 37245 { + // If this happens try cleaning and rebuilding your project + panic("c2pa: uniffi_c2pa_checksum_func_sdk_version: UniFFI API checksum mismatch") + } + } + { + checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_c2pa_checksum_func_version(uniffiStatus) + }) + if checksum != 61576 { + // If this happens try cleaning and rebuilding your project + panic("c2pa: uniffi_c2pa_checksum_func_version: UniFFI API checksum mismatch") + } + } + { + checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_c2pa_checksum_method_builder_add_ingredient(uniffiStatus) + }) + if checksum != 54967 { + // If this happens try cleaning and rebuilding your project + panic("c2pa: uniffi_c2pa_checksum_method_builder_add_ingredient: UniFFI API checksum mismatch") + } + } + { + checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_c2pa_checksum_method_builder_add_resource(uniffiStatus) + }) + if checksum != 12018 { + // If this happens try cleaning and rebuilding your project + panic("c2pa: uniffi_c2pa_checksum_method_builder_add_resource: UniFFI API checksum mismatch") + } + } + { + checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_c2pa_checksum_method_builder_from_archive(uniffiStatus) + }) + if checksum != 17341 { + // If this happens try cleaning and rebuilding your project + panic("c2pa: uniffi_c2pa_checksum_method_builder_from_archive: UniFFI API checksum mismatch") + } + } + { + checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_c2pa_checksum_method_builder_sign(uniffiStatus) + }) + if checksum != 8729 { + // If this happens try cleaning and rebuilding your project + panic("c2pa: uniffi_c2pa_checksum_method_builder_sign: UniFFI API checksum mismatch") + } + } + { + checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_c2pa_checksum_method_builder_to_archive(uniffiStatus) + }) + if checksum != 44718 { + // If this happens try cleaning and rebuilding your project + panic("c2pa: uniffi_c2pa_checksum_method_builder_to_archive: UniFFI API checksum mismatch") + } + } + { + checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_c2pa_checksum_method_builder_with_json(uniffiStatus) + }) + if checksum != 29392 { + // If this happens try cleaning and rebuilding your project + panic("c2pa: uniffi_c2pa_checksum_method_builder_with_json: UniFFI API checksum mismatch") + } + } + { + checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_c2pa_checksum_method_reader_from_stream(uniffiStatus) + }) + if checksum != 3255 { + // If this happens try cleaning and rebuilding your project + panic("c2pa: uniffi_c2pa_checksum_method_reader_from_stream: UniFFI API checksum mismatch") + } + } + { + checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_c2pa_checksum_method_reader_json(uniffiStatus) + }) + if checksum != 33242 { + // If this happens try cleaning and rebuilding your project + panic("c2pa: uniffi_c2pa_checksum_method_reader_json: UniFFI API checksum mismatch") + } + } + { + checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_c2pa_checksum_method_reader_resource_to_stream(uniffiStatus) + }) + if checksum != 44049 { + // If this happens try cleaning and rebuilding your project + panic("c2pa: uniffi_c2pa_checksum_method_reader_resource_to_stream: UniFFI API checksum mismatch") + } + } + { + checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_c2pa_checksum_constructor_builder_new(uniffiStatus) + }) + if checksum != 8924 { + // If this happens try cleaning and rebuilding your project + panic("c2pa: uniffi_c2pa_checksum_constructor_builder_new: UniFFI API checksum mismatch") + } + } + { + checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_c2pa_checksum_constructor_callbacksigner_new(uniffiStatus) + }) + if checksum != 51503 { + // If this happens try cleaning and rebuilding your project + panic("c2pa: uniffi_c2pa_checksum_constructor_callbacksigner_new: UniFFI API checksum mismatch") + } + } + { + checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_c2pa_checksum_constructor_reader_new(uniffiStatus) + }) + if checksum != 7340 { + // If this happens try cleaning and rebuilding your project + panic("c2pa: uniffi_c2pa_checksum_constructor_reader_new: UniFFI API checksum mismatch") + } + } + { + checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_c2pa_checksum_method_signercallback_sign(uniffiStatus) + }) + if checksum != 15928 { + // If this happens try cleaning and rebuilding your project + panic("c2pa: uniffi_c2pa_checksum_method_signercallback_sign: UniFFI API checksum mismatch") + } + } + { + checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_c2pa_checksum_method_stream_read_stream(uniffiStatus) + }) + if checksum != 4594 { + // If this happens try cleaning and rebuilding your project + panic("c2pa: uniffi_c2pa_checksum_method_stream_read_stream: UniFFI API checksum mismatch") + } + } + { + checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_c2pa_checksum_method_stream_seek_stream(uniffiStatus) + }) + if checksum != 32219 { + // If this happens try cleaning and rebuilding your project + panic("c2pa: uniffi_c2pa_checksum_method_stream_seek_stream: UniFFI API checksum mismatch") + } + } + { + checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_c2pa_checksum_method_stream_write_stream(uniffiStatus) + }) + if checksum != 37641 { + // If this happens try cleaning and rebuilding your project + panic("c2pa: uniffi_c2pa_checksum_method_stream_write_stream: UniFFI API checksum mismatch") + } + } +} + +type FfiConverterUint64 struct{} + +var FfiConverterUint64INSTANCE = FfiConverterUint64{} + +func (FfiConverterUint64) Lower(value uint64) C.uint64_t { + return C.uint64_t(value) +} + +func (FfiConverterUint64) Write(writer io.Writer, value uint64) { + writeUint64(writer, value) +} + +func (FfiConverterUint64) Lift(value C.uint64_t) uint64 { + return uint64(value) +} + +func (FfiConverterUint64) Read(reader io.Reader) uint64 { + return readUint64(reader) +} + +type FfiDestroyerUint64 struct{} + +func (FfiDestroyerUint64) Destroy(_ uint64) {} + +type FfiConverterInt64 struct{} + +var FfiConverterInt64INSTANCE = FfiConverterInt64{} + +func (FfiConverterInt64) Lower(value int64) C.int64_t { + return C.int64_t(value) +} + +func (FfiConverterInt64) Write(writer io.Writer, value int64) { + writeInt64(writer, value) +} + +func (FfiConverterInt64) Lift(value C.int64_t) int64 { + return int64(value) +} + +func (FfiConverterInt64) Read(reader io.Reader) int64 { + return readInt64(reader) +} + +type FfiDestroyerInt64 struct{} + +func (FfiDestroyerInt64) Destroy(_ int64) {} + +type FfiConverterString struct{} + +var FfiConverterStringINSTANCE = FfiConverterString{} + +func (FfiConverterString) Lift(rb RustBufferI) string { + defer rb.Free() + reader := rb.AsReader() + b, err := io.ReadAll(reader) + if err != nil { + panic(fmt.Errorf("reading reader: %w", err)) + } + return string(b) +} + +func (FfiConverterString) Read(reader io.Reader) string { + length := readInt32(reader) + buffer := make([]byte, length) + read_length, err := reader.Read(buffer) + if err != nil { + panic(err) + } + if read_length != int(length) { + panic(fmt.Errorf("bad read length when reading string, expected %d, read %d", length, read_length)) + } + return string(buffer) +} + +func (FfiConverterString) Lower(value string) RustBuffer { + return stringToRustBuffer(value) +} + +func (FfiConverterString) Write(writer io.Writer, value string) { + if len(value) > math.MaxInt32 { + panic("String is too large to fit into Int32") + } + + writeInt32(writer, int32(len(value))) + write_length, err := io.WriteString(writer, value) + if err != nil { + panic(err) + } + if write_length != len(value) { + panic(fmt.Errorf("bad write length when writing string, expected %d, written %d", len(value), write_length)) + } +} + +type FfiDestroyerString struct{} + +func (FfiDestroyerString) Destroy(_ string) {} + +type FfiConverterBytes struct{} + +var FfiConverterBytesINSTANCE = FfiConverterBytes{} + +func (c FfiConverterBytes) Lower(value []byte) RustBuffer { + return LowerIntoRustBuffer[[]byte](c, value) +} + +func (c FfiConverterBytes) Write(writer io.Writer, value []byte) { + if len(value) > math.MaxInt32 { + panic("[]byte is too large to fit into Int32") + } + + writeInt32(writer, int32(len(value))) + write_length, err := writer.Write(value) + if err != nil { + panic(err) + } + if write_length != len(value) { + panic(fmt.Errorf("bad write length when writing []byte, expected %d, written %d", len(value), write_length)) + } +} + +func (c FfiConverterBytes) Lift(rb RustBufferI) []byte { + return LiftFromRustBuffer[[]byte](c, rb) +} + +func (c FfiConverterBytes) Read(reader io.Reader) []byte { + length := readInt32(reader) + buffer := make([]byte, length) + read_length, err := reader.Read(buffer) + if err != nil { + panic(err) + } + if read_length != int(length) { + panic(fmt.Errorf("bad read length when reading []byte, expected %d, read %d", length, read_length)) + } + return buffer +} + +type FfiDestroyerBytes struct{} + +func (FfiDestroyerBytes) Destroy(_ []byte) {} + +// Below is an implementation of synchronization requirements outlined in the link. +// https://github.com/mozilla/uniffi-rs/blob/0dc031132d9493ca812c3af6e7dd60ad2ea95bf0/uniffi_bindgen/src/bindings/kotlin/templates/ObjectRuntime.kt#L31 + +type FfiObject struct { + pointer unsafe.Pointer + callCounter atomic.Int64 + freeFunction func(unsafe.Pointer, *C.RustCallStatus) + destroyed atomic.Bool +} + +func newFfiObject(pointer unsafe.Pointer, freeFunction func(unsafe.Pointer, *C.RustCallStatus)) FfiObject { + return FfiObject{ + pointer: pointer, + freeFunction: freeFunction, + } +} + +func (ffiObject *FfiObject) incrementPointer(debugName string) unsafe.Pointer { + for { + counter := ffiObject.callCounter.Load() + if counter <= -1 { + panic(fmt.Errorf("%v object has already been destroyed", debugName)) + } + if counter == math.MaxInt64 { + panic(fmt.Errorf("%v object call counter would overflow", debugName)) + } + if ffiObject.callCounter.CompareAndSwap(counter, counter+1) { + break + } + } + + return ffiObject.pointer +} + +func (ffiObject *FfiObject) decrementPointer() { + if ffiObject.callCounter.Add(-1) == -1 { + ffiObject.freeRustArcPtr() + } +} + +func (ffiObject *FfiObject) destroy() { + if ffiObject.destroyed.CompareAndSwap(false, true) { + if ffiObject.callCounter.Add(-1) == -1 { + ffiObject.freeRustArcPtr() + } + } +} + +func (ffiObject *FfiObject) freeRustArcPtr() { + rustCall(func(status *C.RustCallStatus) int32 { + ffiObject.freeFunction(ffiObject.pointer, status) + return 0 + }) +} + +type Builder struct { + ffiObject FfiObject +} + +func NewBuilder() *Builder { + return FfiConverterBuilderINSTANCE.Lift(rustCall(func(_uniffiStatus *C.RustCallStatus) unsafe.Pointer { + return C.uniffi_c2pa_fn_constructor_builder_new(_uniffiStatus) + })) +} + +func (_self *Builder) AddIngredient(ingredientJson string, format string, stream Stream) error { + _pointer := _self.ffiObject.incrementPointer("*Builder") + defer _self.ffiObject.decrementPointer() + _, _uniffiErr := rustCallWithError(FfiConverterTypeError{}, func(_uniffiStatus *C.RustCallStatus) bool { + C.uniffi_c2pa_fn_method_builder_add_ingredient( + _pointer, FfiConverterStringINSTANCE.Lower(ingredientJson), FfiConverterStringINSTANCE.Lower(format), FfiConverterCallbackInterfaceStreamINSTANCE.Lower(stream), _uniffiStatus) + return false + }) + return _uniffiErr +} + +func (_self *Builder) AddResource(uri string, stream Stream) error { + _pointer := _self.ffiObject.incrementPointer("*Builder") + defer _self.ffiObject.decrementPointer() + _, _uniffiErr := rustCallWithError(FfiConverterTypeError{}, func(_uniffiStatus *C.RustCallStatus) bool { + C.uniffi_c2pa_fn_method_builder_add_resource( + _pointer, FfiConverterStringINSTANCE.Lower(uri), FfiConverterCallbackInterfaceStreamINSTANCE.Lower(stream), _uniffiStatus) + return false + }) + return _uniffiErr +} + +func (_self *Builder) FromArchive(stream Stream) error { + _pointer := _self.ffiObject.incrementPointer("*Builder") + defer _self.ffiObject.decrementPointer() + _, _uniffiErr := rustCallWithError(FfiConverterTypeError{}, func(_uniffiStatus *C.RustCallStatus) bool { + C.uniffi_c2pa_fn_method_builder_from_archive( + _pointer, FfiConverterCallbackInterfaceStreamINSTANCE.Lower(stream), _uniffiStatus) + return false + }) + return _uniffiErr +} + +func (_self *Builder) Sign(format string, input Stream, output Stream, signer *CallbackSigner) ([]byte, error) { + _pointer := _self.ffiObject.incrementPointer("*Builder") + defer _self.ffiObject.decrementPointer() + _uniffiRV, _uniffiErr := rustCallWithError(FfiConverterTypeError{}, func(_uniffiStatus *C.RustCallStatus) RustBufferI { + return C.uniffi_c2pa_fn_method_builder_sign( + _pointer, FfiConverterStringINSTANCE.Lower(format), FfiConverterCallbackInterfaceStreamINSTANCE.Lower(input), FfiConverterCallbackInterfaceStreamINSTANCE.Lower(output), FfiConverterCallbackSignerINSTANCE.Lower(signer), _uniffiStatus) + }) + if _uniffiErr != nil { + var _uniffiDefaultValue []byte + return _uniffiDefaultValue, _uniffiErr + } else { + return FfiConverterBytesINSTANCE.Lift(_uniffiRV), _uniffiErr + } +} + +func (_self *Builder) ToArchive(stream Stream) error { + _pointer := _self.ffiObject.incrementPointer("*Builder") + defer _self.ffiObject.decrementPointer() + _, _uniffiErr := rustCallWithError(FfiConverterTypeError{}, func(_uniffiStatus *C.RustCallStatus) bool { + C.uniffi_c2pa_fn_method_builder_to_archive( + _pointer, FfiConverterCallbackInterfaceStreamINSTANCE.Lower(stream), _uniffiStatus) + return false + }) + return _uniffiErr +} + +func (_self *Builder) WithJson(json string) error { + _pointer := _self.ffiObject.incrementPointer("*Builder") + defer _self.ffiObject.decrementPointer() + _, _uniffiErr := rustCallWithError(FfiConverterTypeError{}, func(_uniffiStatus *C.RustCallStatus) bool { + C.uniffi_c2pa_fn_method_builder_with_json( + _pointer, FfiConverterStringINSTANCE.Lower(json), _uniffiStatus) + return false + }) + return _uniffiErr +} + +func (object *Builder) Destroy() { + runtime.SetFinalizer(object, nil) + object.ffiObject.destroy() +} + +type FfiConverterBuilder struct{} + +var FfiConverterBuilderINSTANCE = FfiConverterBuilder{} + +func (c FfiConverterBuilder) Lift(pointer unsafe.Pointer) *Builder { + result := &Builder{ + newFfiObject( + pointer, + func(pointer unsafe.Pointer, status *C.RustCallStatus) { + C.uniffi_c2pa_fn_free_builder(pointer, status) + }), + } + runtime.SetFinalizer(result, (*Builder).Destroy) + return result +} + +func (c FfiConverterBuilder) Read(reader io.Reader) *Builder { + return c.Lift(unsafe.Pointer(uintptr(readUint64(reader)))) +} + +func (c FfiConverterBuilder) Lower(value *Builder) unsafe.Pointer { + // TODO: this is bad - all synchronization from ObjectRuntime.go is discarded here, + // because the pointer will be decremented immediately after this function returns, + // and someone will be left holding onto a non-locked pointer. + pointer := value.ffiObject.incrementPointer("*Builder") + defer value.ffiObject.decrementPointer() + return pointer +} + +func (c FfiConverterBuilder) Write(writer io.Writer, value *Builder) { + writeUint64(writer, uint64(uintptr(c.Lower(value)))) +} + +type FfiDestroyerBuilder struct{} + +func (_ FfiDestroyerBuilder) Destroy(value *Builder) { + value.Destroy() +} + +type CallbackSigner struct { + ffiObject FfiObject +} + +func NewCallbackSigner(callback SignerCallback, alg SigningAlg, certs []byte, taUrl *string) *CallbackSigner { + return FfiConverterCallbackSignerINSTANCE.Lift(rustCall(func(_uniffiStatus *C.RustCallStatus) unsafe.Pointer { + return C.uniffi_c2pa_fn_constructor_callbacksigner_new(FfiConverterCallbackInterfaceSignerCallbackINSTANCE.Lower(callback), FfiConverterTypeSigningAlgINSTANCE.Lower(alg), FfiConverterBytesINSTANCE.Lower(certs), FfiConverterOptionalStringINSTANCE.Lower(taUrl), _uniffiStatus) + })) +} + +func (object *CallbackSigner) Destroy() { + runtime.SetFinalizer(object, nil) + object.ffiObject.destroy() +} + +type FfiConverterCallbackSigner struct{} + +var FfiConverterCallbackSignerINSTANCE = FfiConverterCallbackSigner{} + +func (c FfiConverterCallbackSigner) Lift(pointer unsafe.Pointer) *CallbackSigner { + result := &CallbackSigner{ + newFfiObject( + pointer, + func(pointer unsafe.Pointer, status *C.RustCallStatus) { + C.uniffi_c2pa_fn_free_callbacksigner(pointer, status) + }), + } + runtime.SetFinalizer(result, (*CallbackSigner).Destroy) + return result +} + +func (c FfiConverterCallbackSigner) Read(reader io.Reader) *CallbackSigner { + return c.Lift(unsafe.Pointer(uintptr(readUint64(reader)))) +} + +func (c FfiConverterCallbackSigner) Lower(value *CallbackSigner) unsafe.Pointer { + // TODO: this is bad - all synchronization from ObjectRuntime.go is discarded here, + // because the pointer will be decremented immediately after this function returns, + // and someone will be left holding onto a non-locked pointer. + pointer := value.ffiObject.incrementPointer("*CallbackSigner") + defer value.ffiObject.decrementPointer() + return pointer +} + +func (c FfiConverterCallbackSigner) Write(writer io.Writer, value *CallbackSigner) { + writeUint64(writer, uint64(uintptr(c.Lower(value)))) +} + +type FfiDestroyerCallbackSigner struct{} + +func (_ FfiDestroyerCallbackSigner) Destroy(value *CallbackSigner) { + value.Destroy() +} + +type Reader struct { + ffiObject FfiObject +} + +func NewReader() *Reader { + return FfiConverterReaderINSTANCE.Lift(rustCall(func(_uniffiStatus *C.RustCallStatus) unsafe.Pointer { + return C.uniffi_c2pa_fn_constructor_reader_new(_uniffiStatus) + })) +} + +func (_self *Reader) FromStream(format string, reader Stream) (string, error) { + _pointer := _self.ffiObject.incrementPointer("*Reader") + defer _self.ffiObject.decrementPointer() + _uniffiRV, _uniffiErr := rustCallWithError(FfiConverterTypeError{}, func(_uniffiStatus *C.RustCallStatus) RustBufferI { + return C.uniffi_c2pa_fn_method_reader_from_stream( + _pointer, FfiConverterStringINSTANCE.Lower(format), FfiConverterCallbackInterfaceStreamINSTANCE.Lower(reader), _uniffiStatus) + }) + if _uniffiErr != nil { + var _uniffiDefaultValue string + return _uniffiDefaultValue, _uniffiErr + } else { + return FfiConverterStringINSTANCE.Lift(_uniffiRV), _uniffiErr + } +} + +func (_self *Reader) Json() (string, error) { + _pointer := _self.ffiObject.incrementPointer("*Reader") + defer _self.ffiObject.decrementPointer() + _uniffiRV, _uniffiErr := rustCallWithError(FfiConverterTypeError{}, func(_uniffiStatus *C.RustCallStatus) RustBufferI { + return C.uniffi_c2pa_fn_method_reader_json( + _pointer, _uniffiStatus) + }) + if _uniffiErr != nil { + var _uniffiDefaultValue string + return _uniffiDefaultValue, _uniffiErr + } else { + return FfiConverterStringINSTANCE.Lift(_uniffiRV), _uniffiErr + } +} + +func (_self *Reader) ResourceToStream(uri string, stream Stream) (uint64, error) { + _pointer := _self.ffiObject.incrementPointer("*Reader") + defer _self.ffiObject.decrementPointer() + _uniffiRV, _uniffiErr := rustCallWithError(FfiConverterTypeError{}, func(_uniffiStatus *C.RustCallStatus) C.uint64_t { + return C.uniffi_c2pa_fn_method_reader_resource_to_stream( + _pointer, FfiConverterStringINSTANCE.Lower(uri), FfiConverterCallbackInterfaceStreamINSTANCE.Lower(stream), _uniffiStatus) + }) + if _uniffiErr != nil { + var _uniffiDefaultValue uint64 + return _uniffiDefaultValue, _uniffiErr + } else { + return FfiConverterUint64INSTANCE.Lift(_uniffiRV), _uniffiErr + } +} + +func (object *Reader) Destroy() { + runtime.SetFinalizer(object, nil) + object.ffiObject.destroy() +} + +type FfiConverterReader struct{} + +var FfiConverterReaderINSTANCE = FfiConverterReader{} + +func (c FfiConverterReader) Lift(pointer unsafe.Pointer) *Reader { + result := &Reader{ + newFfiObject( + pointer, + func(pointer unsafe.Pointer, status *C.RustCallStatus) { + C.uniffi_c2pa_fn_free_reader(pointer, status) + }), + } + runtime.SetFinalizer(result, (*Reader).Destroy) + return result +} + +func (c FfiConverterReader) Read(reader io.Reader) *Reader { + return c.Lift(unsafe.Pointer(uintptr(readUint64(reader)))) +} + +func (c FfiConverterReader) Lower(value *Reader) unsafe.Pointer { + // TODO: this is bad - all synchronization from ObjectRuntime.go is discarded here, + // because the pointer will be decremented immediately after this function returns, + // and someone will be left holding onto a non-locked pointer. + pointer := value.ffiObject.incrementPointer("*Reader") + defer value.ffiObject.decrementPointer() + return pointer +} + +func (c FfiConverterReader) Write(writer io.Writer, value *Reader) { + writeUint64(writer, uint64(uintptr(c.Lower(value)))) +} + +type FfiDestroyerReader struct{} + +func (_ FfiDestroyerReader) Destroy(value *Reader) { + value.Destroy() +} + +type Error struct { + err error +} + +func (err Error) Error() string { + return fmt.Sprintf("Error: %s", err.err.Error()) +} + +func (err Error) Unwrap() error { + return err.err +} + +// Err* are used for checking error type with `errors.Is` +var ErrErrorAssertion = fmt.Errorf("ErrorAssertion") +var ErrErrorAssertionNotFound = fmt.Errorf("ErrorAssertionNotFound") +var ErrErrorDecoding = fmt.Errorf("ErrorDecoding") +var ErrErrorEncoding = fmt.Errorf("ErrorEncoding") +var ErrErrorFileNotFound = fmt.Errorf("ErrorFileNotFound") +var ErrErrorIo = fmt.Errorf("ErrorIo") +var ErrErrorJson = fmt.Errorf("ErrorJson") +var ErrErrorManifest = fmt.Errorf("ErrorManifest") +var ErrErrorManifestNotFound = fmt.Errorf("ErrorManifestNotFound") +var ErrErrorNotSupported = fmt.Errorf("ErrorNotSupported") +var ErrErrorOther = fmt.Errorf("ErrorOther") +var ErrErrorRemoteManifest = fmt.Errorf("ErrorRemoteManifest") +var ErrErrorResourceNotFound = fmt.Errorf("ErrorResourceNotFound") +var ErrErrorRwLock = fmt.Errorf("ErrorRwLock") +var ErrErrorSignature = fmt.Errorf("ErrorSignature") +var ErrErrorVerify = fmt.Errorf("ErrorVerify") + +// Variant structs +type ErrorAssertion struct { + Reason string +} + +func NewErrorAssertion( + reason string, +) *Error { + return &Error{ + err: &ErrorAssertion{ + Reason: reason, + }, + } +} + +func (err ErrorAssertion) Error() string { + return fmt.Sprint("Assertion", + ": ", + + "Reason=", + err.Reason, + ) +} + +func (self ErrorAssertion) Is(target error) bool { + return target == ErrErrorAssertion +} + +type ErrorAssertionNotFound struct { + Reason string +} + +func NewErrorAssertionNotFound( + reason string, +) *Error { + return &Error{ + err: &ErrorAssertionNotFound{ + Reason: reason, + }, + } +} + +func (err ErrorAssertionNotFound) Error() string { + return fmt.Sprint("AssertionNotFound", + ": ", + + "Reason=", + err.Reason, + ) +} + +func (self ErrorAssertionNotFound) Is(target error) bool { + return target == ErrErrorAssertionNotFound +} + +type ErrorDecoding struct { + Reason string +} + +func NewErrorDecoding( + reason string, +) *Error { + return &Error{ + err: &ErrorDecoding{ + Reason: reason, + }, + } +} + +func (err ErrorDecoding) Error() string { + return fmt.Sprint("Decoding", + ": ", + + "Reason=", + err.Reason, + ) +} + +func (self ErrorDecoding) Is(target error) bool { + return target == ErrErrorDecoding +} + +type ErrorEncoding struct { + Reason string +} + +func NewErrorEncoding( + reason string, +) *Error { + return &Error{ + err: &ErrorEncoding{ + Reason: reason, + }, + } +} + +func (err ErrorEncoding) Error() string { + return fmt.Sprint("Encoding", + ": ", + + "Reason=", + err.Reason, + ) +} + +func (self ErrorEncoding) Is(target error) bool { + return target == ErrErrorEncoding +} + +type ErrorFileNotFound struct { + Reason string +} + +func NewErrorFileNotFound( + reason string, +) *Error { + return &Error{ + err: &ErrorFileNotFound{ + Reason: reason, + }, + } +} + +func (err ErrorFileNotFound) Error() string { + return fmt.Sprint("FileNotFound", + ": ", + + "Reason=", + err.Reason, + ) +} + +func (self ErrorFileNotFound) Is(target error) bool { + return target == ErrErrorFileNotFound +} + +type ErrorIo struct { + Reason string +} + +func NewErrorIo( + reason string, +) *Error { + return &Error{ + err: &ErrorIo{ + Reason: reason, + }, + } +} + +func (err ErrorIo) Error() string { + return fmt.Sprint("Io", + ": ", + + "Reason=", + err.Reason, + ) +} + +func (self ErrorIo) Is(target error) bool { + return target == ErrErrorIo +} + +type ErrorJson struct { + Reason string +} + +func NewErrorJson( + reason string, +) *Error { + return &Error{ + err: &ErrorJson{ + Reason: reason, + }, + } +} + +func (err ErrorJson) Error() string { + return fmt.Sprint("Json", + ": ", + + "Reason=", + err.Reason, + ) +} + +func (self ErrorJson) Is(target error) bool { + return target == ErrErrorJson +} + +type ErrorManifest struct { + Reason string +} + +func NewErrorManifest( + reason string, +) *Error { + return &Error{ + err: &ErrorManifest{ + Reason: reason, + }, + } +} + +func (err ErrorManifest) Error() string { + return fmt.Sprint("Manifest", + ": ", + + "Reason=", + err.Reason, + ) +} + +func (self ErrorManifest) Is(target error) bool { + return target == ErrErrorManifest +} + +type ErrorManifestNotFound struct { + Reason string +} + +func NewErrorManifestNotFound( + reason string, +) *Error { + return &Error{ + err: &ErrorManifestNotFound{ + Reason: reason, + }, + } +} + +func (err ErrorManifestNotFound) Error() string { + return fmt.Sprint("ManifestNotFound", + ": ", + + "Reason=", + err.Reason, + ) +} + +func (self ErrorManifestNotFound) Is(target error) bool { + return target == ErrErrorManifestNotFound +} + +type ErrorNotSupported struct { + Reason string +} + +func NewErrorNotSupported( + reason string, +) *Error { + return &Error{ + err: &ErrorNotSupported{ + Reason: reason, + }, + } +} + +func (err ErrorNotSupported) Error() string { + return fmt.Sprint("NotSupported", + ": ", + + "Reason=", + err.Reason, + ) +} + +func (self ErrorNotSupported) Is(target error) bool { + return target == ErrErrorNotSupported +} + +type ErrorOther struct { + Reason string +} + +func NewErrorOther( + reason string, +) *Error { + return &Error{ + err: &ErrorOther{ + Reason: reason, + }, + } +} + +func (err ErrorOther) Error() string { + return fmt.Sprint("Other", + ": ", + + "Reason=", + err.Reason, + ) +} + +func (self ErrorOther) Is(target error) bool { + return target == ErrErrorOther +} + +type ErrorRemoteManifest struct { + Reason string +} + +func NewErrorRemoteManifest( + reason string, +) *Error { + return &Error{ + err: &ErrorRemoteManifest{ + Reason: reason, + }, + } +} + +func (err ErrorRemoteManifest) Error() string { + return fmt.Sprint("RemoteManifest", + ": ", + + "Reason=", + err.Reason, + ) +} + +func (self ErrorRemoteManifest) Is(target error) bool { + return target == ErrErrorRemoteManifest +} + +type ErrorResourceNotFound struct { + Reason string +} + +func NewErrorResourceNotFound( + reason string, +) *Error { + return &Error{ + err: &ErrorResourceNotFound{ + Reason: reason, + }, + } +} + +func (err ErrorResourceNotFound) Error() string { + return fmt.Sprint("ResourceNotFound", + ": ", + + "Reason=", + err.Reason, + ) +} + +func (self ErrorResourceNotFound) Is(target error) bool { + return target == ErrErrorResourceNotFound +} + +type ErrorRwLock struct { +} + +func NewErrorRwLock() *Error { + return &Error{ + err: &ErrorRwLock{}, + } +} + +func (err ErrorRwLock) Error() string { + return fmt.Sprint("RwLock") +} + +func (self ErrorRwLock) Is(target error) bool { + return target == ErrErrorRwLock +} + +type ErrorSignature struct { + Reason string +} + +func NewErrorSignature( + reason string, +) *Error { + return &Error{ + err: &ErrorSignature{ + Reason: reason, + }, + } +} + +func (err ErrorSignature) Error() string { + return fmt.Sprint("Signature", + ": ", + + "Reason=", + err.Reason, + ) +} + +func (self ErrorSignature) Is(target error) bool { + return target == ErrErrorSignature +} + +type ErrorVerify struct { + Reason string +} + +func NewErrorVerify( + reason string, +) *Error { + return &Error{ + err: &ErrorVerify{ + Reason: reason, + }, + } +} + +func (err ErrorVerify) Error() string { + return fmt.Sprint("Verify", + ": ", + + "Reason=", + err.Reason, + ) +} + +func (self ErrorVerify) Is(target error) bool { + return target == ErrErrorVerify +} + +type FfiConverterTypeError struct{} + +var FfiConverterTypeErrorINSTANCE = FfiConverterTypeError{} + +func (c FfiConverterTypeError) Lift(eb RustBufferI) error { + return LiftFromRustBuffer[error](c, eb) +} + +func (c FfiConverterTypeError) Lower(value *Error) RustBuffer { + return LowerIntoRustBuffer[*Error](c, value) +} + +func (c FfiConverterTypeError) Read(reader io.Reader) error { + errorID := readUint32(reader) + + switch errorID { + case 1: + return &Error{&ErrorAssertion{ + Reason: FfiConverterStringINSTANCE.Read(reader), + }} + case 2: + return &Error{&ErrorAssertionNotFound{ + Reason: FfiConverterStringINSTANCE.Read(reader), + }} + case 3: + return &Error{&ErrorDecoding{ + Reason: FfiConverterStringINSTANCE.Read(reader), + }} + case 4: + return &Error{&ErrorEncoding{ + Reason: FfiConverterStringINSTANCE.Read(reader), + }} + case 5: + return &Error{&ErrorFileNotFound{ + Reason: FfiConverterStringINSTANCE.Read(reader), + }} + case 6: + return &Error{&ErrorIo{ + Reason: FfiConverterStringINSTANCE.Read(reader), + }} + case 7: + return &Error{&ErrorJson{ + Reason: FfiConverterStringINSTANCE.Read(reader), + }} + case 8: + return &Error{&ErrorManifest{ + Reason: FfiConverterStringINSTANCE.Read(reader), + }} + case 9: + return &Error{&ErrorManifestNotFound{ + Reason: FfiConverterStringINSTANCE.Read(reader), + }} + case 10: + return &Error{&ErrorNotSupported{ + Reason: FfiConverterStringINSTANCE.Read(reader), + }} + case 11: + return &Error{&ErrorOther{ + Reason: FfiConverterStringINSTANCE.Read(reader), + }} + case 12: + return &Error{&ErrorRemoteManifest{ + Reason: FfiConverterStringINSTANCE.Read(reader), + }} + case 13: + return &Error{&ErrorResourceNotFound{ + Reason: FfiConverterStringINSTANCE.Read(reader), + }} + case 14: + return &Error{&ErrorRwLock{}} + case 15: + return &Error{&ErrorSignature{ + Reason: FfiConverterStringINSTANCE.Read(reader), + }} + case 16: + return &Error{&ErrorVerify{ + Reason: FfiConverterStringINSTANCE.Read(reader), + }} + default: + panic(fmt.Sprintf("Unknown error code %d in FfiConverterTypeError.Read()", errorID)) + } +} + +func (c FfiConverterTypeError) Write(writer io.Writer, value *Error) { + switch variantValue := value.err.(type) { + case *ErrorAssertion: + writeInt32(writer, 1) + FfiConverterStringINSTANCE.Write(writer, variantValue.Reason) + case *ErrorAssertionNotFound: + writeInt32(writer, 2) + FfiConverterStringINSTANCE.Write(writer, variantValue.Reason) + case *ErrorDecoding: + writeInt32(writer, 3) + FfiConverterStringINSTANCE.Write(writer, variantValue.Reason) + case *ErrorEncoding: + writeInt32(writer, 4) + FfiConverterStringINSTANCE.Write(writer, variantValue.Reason) + case *ErrorFileNotFound: + writeInt32(writer, 5) + FfiConverterStringINSTANCE.Write(writer, variantValue.Reason) + case *ErrorIo: + writeInt32(writer, 6) + FfiConverterStringINSTANCE.Write(writer, variantValue.Reason) + case *ErrorJson: + writeInt32(writer, 7) + FfiConverterStringINSTANCE.Write(writer, variantValue.Reason) + case *ErrorManifest: + writeInt32(writer, 8) + FfiConverterStringINSTANCE.Write(writer, variantValue.Reason) + case *ErrorManifestNotFound: + writeInt32(writer, 9) + FfiConverterStringINSTANCE.Write(writer, variantValue.Reason) + case *ErrorNotSupported: + writeInt32(writer, 10) + FfiConverterStringINSTANCE.Write(writer, variantValue.Reason) + case *ErrorOther: + writeInt32(writer, 11) + FfiConverterStringINSTANCE.Write(writer, variantValue.Reason) + case *ErrorRemoteManifest: + writeInt32(writer, 12) + FfiConverterStringINSTANCE.Write(writer, variantValue.Reason) + case *ErrorResourceNotFound: + writeInt32(writer, 13) + FfiConverterStringINSTANCE.Write(writer, variantValue.Reason) + case *ErrorRwLock: + writeInt32(writer, 14) + case *ErrorSignature: + writeInt32(writer, 15) + FfiConverterStringINSTANCE.Write(writer, variantValue.Reason) + case *ErrorVerify: + writeInt32(writer, 16) + FfiConverterStringINSTANCE.Write(writer, variantValue.Reason) + default: + _ = variantValue + panic(fmt.Sprintf("invalid error value `%v` in FfiConverterTypeError.Write", value)) + } +} + +type SeekMode uint + +const ( + SeekModeStart SeekMode = 1 + SeekModeEnd SeekMode = 2 + SeekModeCurrent SeekMode = 3 +) + +type FfiConverterTypeSeekMode struct{} + +var FfiConverterTypeSeekModeINSTANCE = FfiConverterTypeSeekMode{} + +func (c FfiConverterTypeSeekMode) Lift(rb RustBufferI) SeekMode { + return LiftFromRustBuffer[SeekMode](c, rb) +} + +func (c FfiConverterTypeSeekMode) Lower(value SeekMode) RustBuffer { + return LowerIntoRustBuffer[SeekMode](c, value) +} +func (FfiConverterTypeSeekMode) Read(reader io.Reader) SeekMode { + id := readInt32(reader) + return SeekMode(id) +} + +func (FfiConverterTypeSeekMode) Write(writer io.Writer, value SeekMode) { + writeInt32(writer, int32(value)) +} + +type FfiDestroyerTypeSeekMode struct{} + +func (_ FfiDestroyerTypeSeekMode) Destroy(value SeekMode) { +} + +type SigningAlg uint + +const ( + SigningAlgEs256 SigningAlg = 1 + SigningAlgEs256k SigningAlg = 2 + SigningAlgEs384 SigningAlg = 3 + SigningAlgEs512 SigningAlg = 4 + SigningAlgPs256 SigningAlg = 5 + SigningAlgPs384 SigningAlg = 6 + SigningAlgPs512 SigningAlg = 7 + SigningAlgEd25519 SigningAlg = 8 +) + +type FfiConverterTypeSigningAlg struct{} + +var FfiConverterTypeSigningAlgINSTANCE = FfiConverterTypeSigningAlg{} + +func (c FfiConverterTypeSigningAlg) Lift(rb RustBufferI) SigningAlg { + return LiftFromRustBuffer[SigningAlg](c, rb) +} + +func (c FfiConverterTypeSigningAlg) Lower(value SigningAlg) RustBuffer { + return LowerIntoRustBuffer[SigningAlg](c, value) +} +func (FfiConverterTypeSigningAlg) Read(reader io.Reader) SigningAlg { + id := readInt32(reader) + return SigningAlg(id) +} + +func (FfiConverterTypeSigningAlg) Write(writer io.Writer, value SigningAlg) { + writeInt32(writer, int32(value)) +} + +type FfiDestroyerTypeSigningAlg struct{} + +func (_ FfiDestroyerTypeSigningAlg) Destroy(value SigningAlg) { +} + +type uniffiCallbackResult C.int32_t + +const ( + uniffiIdxCallbackFree uniffiCallbackResult = 0 + uniffiCallbackResultSuccess uniffiCallbackResult = 0 + uniffiCallbackResultError uniffiCallbackResult = 1 + uniffiCallbackUnexpectedResultError uniffiCallbackResult = 2 + uniffiCallbackCancelled uniffiCallbackResult = 3 +) + +type concurrentHandleMap[T any] struct { + leftMap map[uint64]*T + rightMap map[*T]uint64 + currentHandle uint64 + lock sync.RWMutex +} + +func newConcurrentHandleMap[T any]() *concurrentHandleMap[T] { + return &concurrentHandleMap[T]{ + leftMap: map[uint64]*T{}, + rightMap: map[*T]uint64{}, + } +} + +func (cm *concurrentHandleMap[T]) insert(obj *T) uint64 { + cm.lock.Lock() + defer cm.lock.Unlock() + + if existingHandle, ok := cm.rightMap[obj]; ok { + return existingHandle + } + cm.currentHandle = cm.currentHandle + 1 + cm.leftMap[cm.currentHandle] = obj + cm.rightMap[obj] = cm.currentHandle + return cm.currentHandle +} + +func (cm *concurrentHandleMap[T]) remove(handle uint64) bool { + cm.lock.Lock() + defer cm.lock.Unlock() + + if val, ok := cm.leftMap[handle]; ok { + delete(cm.leftMap, handle) + delete(cm.rightMap, val) + } + return false +} + +func (cm *concurrentHandleMap[T]) tryGet(handle uint64) (*T, bool) { + cm.lock.RLock() + defer cm.lock.RUnlock() + + val, ok := cm.leftMap[handle] + return val, ok +} + +type FfiConverterCallbackInterface[CallbackInterface any] struct { + handleMap *concurrentHandleMap[CallbackInterface] +} + +func (c *FfiConverterCallbackInterface[CallbackInterface]) drop(handle uint64) RustBuffer { + c.handleMap.remove(handle) + return RustBuffer{} +} + +func (c *FfiConverterCallbackInterface[CallbackInterface]) Lift(handle uint64) CallbackInterface { + val, ok := c.handleMap.tryGet(handle) + if !ok { + panic(fmt.Errorf("no callback in handle map: %d", handle)) + } + return *val +} + +func (c *FfiConverterCallbackInterface[CallbackInterface]) Read(reader io.Reader) CallbackInterface { + return c.Lift(readUint64(reader)) +} + +func (c *FfiConverterCallbackInterface[CallbackInterface]) Lower(value CallbackInterface) C.uint64_t { + return C.uint64_t(c.handleMap.insert(&value)) +} + +func (c *FfiConverterCallbackInterface[CallbackInterface]) Write(writer io.Writer, value CallbackInterface) { + writeUint64(writer, uint64(c.Lower(value))) +} + +type SignerCallback interface { + Sign(data []byte) ([]byte, *Error) +} + +// foreignCallbackCallbackInterfaceSignerCallback cannot be callable be a compiled function at a same time +type foreignCallbackCallbackInterfaceSignerCallback struct{} + +//export c2pa_cgo_SignerCallback +func c2pa_cgo_SignerCallback(handle C.uint64_t, method C.int32_t, argsPtr *C.uint8_t, argsLen C.int32_t, outBuf *C.RustBuffer) C.int32_t { + cb := FfiConverterCallbackInterfaceSignerCallbackINSTANCE.Lift(uint64(handle)) + switch method { + case 0: + // 0 means Rust is done with the callback, and the callback + // can be dropped by the foreign language. + *outBuf = FfiConverterCallbackInterfaceSignerCallbackINSTANCE.drop(uint64(handle)) + // See docs of ForeignCallback in `uniffi/src/ffi/foreigncallbacks.rs` + return C.int32_t(uniffiIdxCallbackFree) + + case 1: + var result uniffiCallbackResult + args := unsafe.Slice((*byte)(argsPtr), argsLen) + result = foreignCallbackCallbackInterfaceSignerCallback{}.InvokeSign(cb, args, outBuf) + return C.int32_t(result) + + default: + // This should never happen, because an out of bounds method index won't + // ever be used. Once we can catch errors, we should return an InternalException. + // https://github.com/mozilla/uniffi-rs/issues/351 + return C.int32_t(uniffiCallbackUnexpectedResultError) + } +} + +func (foreignCallbackCallbackInterfaceSignerCallback) InvokeSign(callback SignerCallback, args []byte, outBuf *C.RustBuffer) uniffiCallbackResult { + reader := bytes.NewReader(args) + result, err := callback.Sign(FfiConverterBytesINSTANCE.Read(reader)) + + if err != nil { + // The only way to bypass an unexpected error is to bypass pointer to an empty + // instance of the error + if err.err == nil { + return uniffiCallbackUnexpectedResultError + } + *outBuf = LowerIntoRustBuffer[*Error](FfiConverterTypeErrorINSTANCE, err) + return uniffiCallbackResultError + } + *outBuf = LowerIntoRustBuffer[[]byte](FfiConverterBytesINSTANCE, result) + return uniffiCallbackResultSuccess +} + +type FfiConverterCallbackInterfaceSignerCallback struct { + FfiConverterCallbackInterface[SignerCallback] +} + +var FfiConverterCallbackInterfaceSignerCallbackINSTANCE = &FfiConverterCallbackInterfaceSignerCallback{ + FfiConverterCallbackInterface: FfiConverterCallbackInterface[SignerCallback]{ + handleMap: newConcurrentHandleMap[SignerCallback](), + }, +} + +// This is a static function because only 1 instance is supported for registering +func (c *FfiConverterCallbackInterfaceSignerCallback) register() { + rustCall(func(status *C.RustCallStatus) int32 { + C.uniffi_c2pa_fn_init_callback_signercallback(C.ForeignCallback(C.c2pa_cgo_SignerCallback), status) + return 0 + }) +} + +type FfiDestroyerCallbackInterfaceSignerCallback struct{} + +func (FfiDestroyerCallbackInterfaceSignerCallback) Destroy(value SignerCallback) { +} + +type Stream interface { + ReadStream(length uint64) ([]byte, *Error) + + SeekStream(pos int64, mode SeekMode) (uint64, *Error) + + WriteStream(data []byte) (uint64, *Error) +} + +// foreignCallbackCallbackInterfaceStream cannot be callable be a compiled function at a same time +type foreignCallbackCallbackInterfaceStream struct{} + +//export c2pa_cgo_Stream +func c2pa_cgo_Stream(handle C.uint64_t, method C.int32_t, argsPtr *C.uint8_t, argsLen C.int32_t, outBuf *C.RustBuffer) C.int32_t { + cb := FfiConverterCallbackInterfaceStreamINSTANCE.Lift(uint64(handle)) + switch method { + case 0: + // 0 means Rust is done with the callback, and the callback + // can be dropped by the foreign language. + *outBuf = FfiConverterCallbackInterfaceStreamINSTANCE.drop(uint64(handle)) + // See docs of ForeignCallback in `uniffi/src/ffi/foreigncallbacks.rs` + return C.int32_t(uniffiIdxCallbackFree) + + case 1: + var result uniffiCallbackResult + args := unsafe.Slice((*byte)(argsPtr), argsLen) + result = foreignCallbackCallbackInterfaceStream{}.InvokeReadStream(cb, args, outBuf) + return C.int32_t(result) + case 2: + var result uniffiCallbackResult + args := unsafe.Slice((*byte)(argsPtr), argsLen) + result = foreignCallbackCallbackInterfaceStream{}.InvokeSeekStream(cb, args, outBuf) + return C.int32_t(result) + case 3: + var result uniffiCallbackResult + args := unsafe.Slice((*byte)(argsPtr), argsLen) + result = foreignCallbackCallbackInterfaceStream{}.InvokeWriteStream(cb, args, outBuf) + return C.int32_t(result) + + default: + // This should never happen, because an out of bounds method index won't + // ever be used. Once we can catch errors, we should return an InternalException. + // https://github.com/mozilla/uniffi-rs/issues/351 + return C.int32_t(uniffiCallbackUnexpectedResultError) + } +} + +func (foreignCallbackCallbackInterfaceStream) InvokeReadStream(callback Stream, args []byte, outBuf *C.RustBuffer) uniffiCallbackResult { + reader := bytes.NewReader(args) + result, err := callback.ReadStream(FfiConverterUint64INSTANCE.Read(reader)) + + if err != nil { + // The only way to bypass an unexpected error is to bypass pointer to an empty + // instance of the error + if err.err == nil { + return uniffiCallbackUnexpectedResultError + } + *outBuf = LowerIntoRustBuffer[*Error](FfiConverterTypeErrorINSTANCE, err) + return uniffiCallbackResultError + } + *outBuf = LowerIntoRustBuffer[[]byte](FfiConverterBytesINSTANCE, result) + return uniffiCallbackResultSuccess +} +func (foreignCallbackCallbackInterfaceStream) InvokeSeekStream(callback Stream, args []byte, outBuf *C.RustBuffer) uniffiCallbackResult { + reader := bytes.NewReader(args) + result, err := callback.SeekStream(FfiConverterInt64INSTANCE.Read(reader), FfiConverterTypeSeekModeINSTANCE.Read(reader)) + + if err != nil { + // The only way to bypass an unexpected error is to bypass pointer to an empty + // instance of the error + if err.err == nil { + return uniffiCallbackUnexpectedResultError + } + *outBuf = LowerIntoRustBuffer[*Error](FfiConverterTypeErrorINSTANCE, err) + return uniffiCallbackResultError + } + *outBuf = LowerIntoRustBuffer[uint64](FfiConverterUint64INSTANCE, result) + return uniffiCallbackResultSuccess +} +func (foreignCallbackCallbackInterfaceStream) InvokeWriteStream(callback Stream, args []byte, outBuf *C.RustBuffer) uniffiCallbackResult { + reader := bytes.NewReader(args) + result, err := callback.WriteStream(FfiConverterBytesINSTANCE.Read(reader)) + + if err != nil { + // The only way to bypass an unexpected error is to bypass pointer to an empty + // instance of the error + if err.err == nil { + return uniffiCallbackUnexpectedResultError + } + *outBuf = LowerIntoRustBuffer[*Error](FfiConverterTypeErrorINSTANCE, err) + return uniffiCallbackResultError + } + *outBuf = LowerIntoRustBuffer[uint64](FfiConverterUint64INSTANCE, result) + return uniffiCallbackResultSuccess +} + +type FfiConverterCallbackInterfaceStream struct { + FfiConverterCallbackInterface[Stream] +} + +var FfiConverterCallbackInterfaceStreamINSTANCE = &FfiConverterCallbackInterfaceStream{ + FfiConverterCallbackInterface: FfiConverterCallbackInterface[Stream]{ + handleMap: newConcurrentHandleMap[Stream](), + }, +} + +// This is a static function because only 1 instance is supported for registering +func (c *FfiConverterCallbackInterfaceStream) register() { + rustCall(func(status *C.RustCallStatus) int32 { + C.uniffi_c2pa_fn_init_callback_stream(C.ForeignCallback(C.c2pa_cgo_Stream), status) + return 0 + }) +} + +type FfiDestroyerCallbackInterfaceStream struct{} + +func (FfiDestroyerCallbackInterfaceStream) Destroy(value Stream) { +} + +type FfiConverterOptionalString struct{} + +var FfiConverterOptionalStringINSTANCE = FfiConverterOptionalString{} + +func (c FfiConverterOptionalString) Lift(rb RustBufferI) *string { + return LiftFromRustBuffer[*string](c, rb) +} + +func (_ FfiConverterOptionalString) Read(reader io.Reader) *string { + if readInt8(reader) == 0 { + return nil + } + temp := FfiConverterStringINSTANCE.Read(reader) + return &temp +} + +func (c FfiConverterOptionalString) Lower(value *string) RustBuffer { + return LowerIntoRustBuffer[*string](c, value) +} + +func (_ FfiConverterOptionalString) Write(writer io.Writer, value *string) { + if value == nil { + writeInt8(writer, 0) + } else { + writeInt8(writer, 1) + FfiConverterStringINSTANCE.Write(writer, *value) + } +} + +type FfiDestroyerOptionalString struct{} + +func (_ FfiDestroyerOptionalString) Destroy(value *string) { + if value != nil { + FfiDestroyerString{}.Destroy(*value) + } +} + +func SdkVersion() string { + return FfiConverterStringINSTANCE.Lift(rustCall(func(_uniffiStatus *C.RustCallStatus) RustBufferI { + return C.uniffi_c2pa_fn_func_sdk_version(_uniffiStatus) + })) +} + +func Version() string { + return FfiConverterStringINSTANCE.Lift(rustCall(func(_uniffiStatus *C.RustCallStatus) RustBufferI { + return C.uniffi_c2pa_fn_func_version(_uniffiStatus) + })) +} diff --git a/pkg/c2pa/generated/c2pa/c2pa.h b/pkg/c2pa/generated/c2pa/c2pa.h new file mode 100644 index 00000000..39ea6801 --- /dev/null +++ b/pkg/c2pa/generated/c2pa/c2pa.h @@ -0,0 +1,559 @@ + + +// This file was autogenerated by some hot garbage in the `uniffi` crate. +// Trust me, you don't want to mess with it! + + + +#include +#include + +// The following structs are used to implement the lowest level +// of the FFI, and thus useful to multiple uniffied crates. +// We ensure they are declared exactly once, with a header guard, UNIFFI_SHARED_H. +#ifdef UNIFFI_SHARED_H + // We also try to prevent mixing versions of shared uniffi header structs. + // If you add anything to the #else block, you must increment the version suffix in UNIFFI_SHARED_HEADER_V6 + #ifndef UNIFFI_SHARED_HEADER_V6 + #error Combining helper code from multiple versions of uniffi is not supported + #endif // ndef UNIFFI_SHARED_HEADER_V6 +#else +#define UNIFFI_SHARED_H +#define UNIFFI_SHARED_HEADER_V6 +// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️ +// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V6 in this file. ⚠️ + +typedef struct RustBuffer { + int32_t capacity; + int32_t len; + uint8_t *data; +} RustBuffer; + +typedef int32_t (*ForeignCallback)(uint64_t, int32_t, uint8_t *, int32_t, RustBuffer *); + +// Task defined in Rust that Go executes +typedef void (*RustTaskCallback)(const void *, int8_t); + +// Callback to execute Rust tasks using a Go routine +// +// Args: +// executor: ForeignExecutor lowered into a uint64_t value +// delay: Delay in MS +// task: RustTaskCallback to call +// task_data: data to pass the task callback +typedef int8_t (*ForeignExecutorCallback)(uint64_t, uint32_t, RustTaskCallback, void *); + +typedef struct ForeignBytes { + int32_t len; + const uint8_t *data; +} ForeignBytes; + +// Error definitions +typedef struct RustCallStatus { + int8_t code; + RustBuffer errorBuf; +} RustCallStatus; + +// Continuation callback for UniFFI Futures +typedef void (*RustFutureContinuation)(void * , int8_t); + +// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️ +// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V6 in this file. ⚠️ +#endif // def UNIFFI_SHARED_H + +// Needed because we can't execute the callback directly from go. +void cgo_rust_task_callback_bridge_c2pa(RustTaskCallback, const void *, int8_t); + +int8_t uniffiForeignExecutorCallbackc2pa(uint64_t, uint32_t, RustTaskCallback, void*); + +void uniffiFutureContinuationCallbackc2pa(void*, int8_t); + +void uniffi_c2pa_fn_free_builder( + void* ptr, + RustCallStatus* out_status +); + +void* uniffi_c2pa_fn_constructor_builder_new( + RustCallStatus* out_status +); + +void uniffi_c2pa_fn_method_builder_add_ingredient( + void* ptr, + RustBuffer ingredient_json, + RustBuffer format, + uint64_t stream, + RustCallStatus* out_status +); + +void uniffi_c2pa_fn_method_builder_add_resource( + void* ptr, + RustBuffer uri, + uint64_t stream, + RustCallStatus* out_status +); + +void uniffi_c2pa_fn_method_builder_from_archive( + void* ptr, + uint64_t stream, + RustCallStatus* out_status +); + +RustBuffer uniffi_c2pa_fn_method_builder_sign( + void* ptr, + RustBuffer format, + uint64_t input, + uint64_t output, + void* signer, + RustCallStatus* out_status +); + +void uniffi_c2pa_fn_method_builder_to_archive( + void* ptr, + uint64_t stream, + RustCallStatus* out_status +); + +void uniffi_c2pa_fn_method_builder_with_json( + void* ptr, + RustBuffer json, + RustCallStatus* out_status +); + +void uniffi_c2pa_fn_free_callbacksigner( + void* ptr, + RustCallStatus* out_status +); + +void* uniffi_c2pa_fn_constructor_callbacksigner_new( + uint64_t callback, + RustBuffer alg, + RustBuffer certs, + RustBuffer ta_url, + RustCallStatus* out_status +); + +void uniffi_c2pa_fn_free_reader( + void* ptr, + RustCallStatus* out_status +); + +void* uniffi_c2pa_fn_constructor_reader_new( + RustCallStatus* out_status +); + +RustBuffer uniffi_c2pa_fn_method_reader_from_stream( + void* ptr, + RustBuffer format, + uint64_t reader, + RustCallStatus* out_status +); + +RustBuffer uniffi_c2pa_fn_method_reader_json( + void* ptr, + RustCallStatus* out_status +); + +uint64_t uniffi_c2pa_fn_method_reader_resource_to_stream( + void* ptr, + RustBuffer uri, + uint64_t stream, + RustCallStatus* out_status +); + +void uniffi_c2pa_fn_init_callback_signercallback( + ForeignCallback callback_stub, + RustCallStatus* out_status +); + +void uniffi_c2pa_fn_init_callback_stream( + ForeignCallback callback_stub, + RustCallStatus* out_status +); + +RustBuffer uniffi_c2pa_fn_func_sdk_version( + RustCallStatus* out_status +); + +RustBuffer uniffi_c2pa_fn_func_version( + RustCallStatus* out_status +); + +RustBuffer ffi_c2pa_rustbuffer_alloc( + int32_t size, + RustCallStatus* out_status +); + +RustBuffer ffi_c2pa_rustbuffer_from_bytes( + ForeignBytes bytes, + RustCallStatus* out_status +); + +void ffi_c2pa_rustbuffer_free( + RustBuffer buf, + RustCallStatus* out_status +); + +RustBuffer ffi_c2pa_rustbuffer_reserve( + RustBuffer buf, + int32_t additional, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_continuation_callback_set( + RustFutureContinuation callback, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_poll_u8( + void* handle, + void* uniffi_callback, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_cancel_u8( + void* handle, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_free_u8( + void* handle, + RustCallStatus* out_status +); + +uint8_t ffi_c2pa_rust_future_complete_u8( + void* handle, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_poll_i8( + void* handle, + void* uniffi_callback, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_cancel_i8( + void* handle, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_free_i8( + void* handle, + RustCallStatus* out_status +); + +int8_t ffi_c2pa_rust_future_complete_i8( + void* handle, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_poll_u16( + void* handle, + void* uniffi_callback, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_cancel_u16( + void* handle, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_free_u16( + void* handle, + RustCallStatus* out_status +); + +uint16_t ffi_c2pa_rust_future_complete_u16( + void* handle, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_poll_i16( + void* handle, + void* uniffi_callback, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_cancel_i16( + void* handle, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_free_i16( + void* handle, + RustCallStatus* out_status +); + +int16_t ffi_c2pa_rust_future_complete_i16( + void* handle, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_poll_u32( + void* handle, + void* uniffi_callback, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_cancel_u32( + void* handle, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_free_u32( + void* handle, + RustCallStatus* out_status +); + +uint32_t ffi_c2pa_rust_future_complete_u32( + void* handle, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_poll_i32( + void* handle, + void* uniffi_callback, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_cancel_i32( + void* handle, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_free_i32( + void* handle, + RustCallStatus* out_status +); + +int32_t ffi_c2pa_rust_future_complete_i32( + void* handle, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_poll_u64( + void* handle, + void* uniffi_callback, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_cancel_u64( + void* handle, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_free_u64( + void* handle, + RustCallStatus* out_status +); + +uint64_t ffi_c2pa_rust_future_complete_u64( + void* handle, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_poll_i64( + void* handle, + void* uniffi_callback, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_cancel_i64( + void* handle, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_free_i64( + void* handle, + RustCallStatus* out_status +); + +int64_t ffi_c2pa_rust_future_complete_i64( + void* handle, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_poll_f32( + void* handle, + void* uniffi_callback, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_cancel_f32( + void* handle, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_free_f32( + void* handle, + RustCallStatus* out_status +); + +float ffi_c2pa_rust_future_complete_f32( + void* handle, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_poll_f64( + void* handle, + void* uniffi_callback, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_cancel_f64( + void* handle, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_free_f64( + void* handle, + RustCallStatus* out_status +); + +double ffi_c2pa_rust_future_complete_f64( + void* handle, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_poll_pointer( + void* handle, + void* uniffi_callback, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_cancel_pointer( + void* handle, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_free_pointer( + void* handle, + RustCallStatus* out_status +); + +void* ffi_c2pa_rust_future_complete_pointer( + void* handle, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_poll_rust_buffer( + void* handle, + void* uniffi_callback, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_cancel_rust_buffer( + void* handle, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_free_rust_buffer( + void* handle, + RustCallStatus* out_status +); + +RustBuffer ffi_c2pa_rust_future_complete_rust_buffer( + void* handle, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_poll_void( + void* handle, + void* uniffi_callback, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_cancel_void( + void* handle, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_free_void( + void* handle, + RustCallStatus* out_status +); + +void ffi_c2pa_rust_future_complete_void( + void* handle, + RustCallStatus* out_status +); + +uint16_t uniffi_c2pa_checksum_func_sdk_version( + RustCallStatus* out_status +); + +uint16_t uniffi_c2pa_checksum_func_version( + RustCallStatus* out_status +); + +uint16_t uniffi_c2pa_checksum_method_builder_add_ingredient( + RustCallStatus* out_status +); + +uint16_t uniffi_c2pa_checksum_method_builder_add_resource( + RustCallStatus* out_status +); + +uint16_t uniffi_c2pa_checksum_method_builder_from_archive( + RustCallStatus* out_status +); + +uint16_t uniffi_c2pa_checksum_method_builder_sign( + RustCallStatus* out_status +); + +uint16_t uniffi_c2pa_checksum_method_builder_to_archive( + RustCallStatus* out_status +); + +uint16_t uniffi_c2pa_checksum_method_builder_with_json( + RustCallStatus* out_status +); + +uint16_t uniffi_c2pa_checksum_method_reader_from_stream( + RustCallStatus* out_status +); + +uint16_t uniffi_c2pa_checksum_method_reader_json( + RustCallStatus* out_status +); + +uint16_t uniffi_c2pa_checksum_method_reader_resource_to_stream( + RustCallStatus* out_status +); + +uint16_t uniffi_c2pa_checksum_constructor_builder_new( + RustCallStatus* out_status +); + +uint16_t uniffi_c2pa_checksum_constructor_callbacksigner_new( + RustCallStatus* out_status +); + +uint16_t uniffi_c2pa_checksum_constructor_reader_new( + RustCallStatus* out_status +); + +uint16_t uniffi_c2pa_checksum_method_signercallback_sign( + RustCallStatus* out_status +); + +uint16_t uniffi_c2pa_checksum_method_stream_read_stream( + RustCallStatus* out_status +); + +uint16_t uniffi_c2pa_checksum_method_stream_seek_stream( + RustCallStatus* out_status +); + +uint16_t uniffi_c2pa_checksum_method_stream_write_stream( + RustCallStatus* out_status +); + +uint32_t ffi_c2pa_uniffi_contract_version( + RustCallStatus* out_status +); + + +int32_t c2pa_cgo_SignerCallback(uint64_t, int32_t, uint8_t *, int32_t, RustBuffer *); +int32_t c2pa_cgo_Stream(uint64_t, int32_t, uint8_t *, int32_t, RustBuffer *); + diff --git a/pkg/c2pa/generated/manifestdefinition/manifestdefinition.go b/pkg/c2pa/generated/manifestdefinition/manifestdefinition.go new file mode 100644 index 00000000..3d1a457f --- /dev/null +++ b/pkg/c2pa/generated/manifestdefinition/manifestdefinition.go @@ -0,0 +1,446 @@ +// Code generated by github.com/atombender/go-jsonschema, DO NOT EDIT. + +package manifestdefinition + +// Identifies a person responsible for an action. +type Actor struct { + // List of references to W3C Verifiable Credentials. + Credentials []HashedUri `json:"credentials,omitempty" yaml:"credentials,omitempty" mapstructure:"credentials,omitempty"` + + // An identifier for a human actor, used when the "type" is + // `humanEntry.identified`. + Identifier *string `json:"identifier,omitempty" yaml:"identifier,omitempty" mapstructure:"identifier,omitempty"` +} + +type AssertionData interface{} + +type AssertionDefinition struct { + // Data corresponds to the JSON schema field "data". + Data AssertionDefinitionData `json:"data" yaml:"data" mapstructure:"data"` + + // Label corresponds to the JSON schema field "label". + Label string `json:"label" yaml:"label" mapstructure:"label"` +} + +type AssertionDefinitionData interface{} + +type AssetType struct { + // Type corresponds to the JSON schema field "type". + Type string `json:"type" yaml:"type" mapstructure:"type"` + + // Version corresponds to the JSON schema field "version". + Version *string `json:"version,omitempty" yaml:"version,omitempty" mapstructure:"version,omitempty"` +} + +// Description of the claim generator, or the software used in generating the +// claim. +// +// This structure is also used for actions softwareAgent +type ClaimGeneratorInfo struct { + // hashed URI to the icon (either embedded or remote) + Icon interface{} `json:"icon,omitempty" yaml:"icon,omitempty" mapstructure:"icon,omitempty"` + + // A human readable string naming the claim_generator + Name string `json:"name" yaml:"name" mapstructure:"name"` + + // A human readable string of the product's version + Version *string `json:"version,omitempty" yaml:"version,omitempty" mapstructure:"version,omitempty"` + + AdditionalProperties interface{} +} + +// An x, y coordinate used for specifying vertices in polygons. +type Coordinate struct { + // The coordinate along the x-axis. + X float64 `json:"x" yaml:"x" mapstructure:"x"` + + // The coordinate along the y-axis. + Y float64 `json:"y" yaml:"y" mapstructure:"y"` +} + +// A description of the source for assertion data +type DataSource struct { + // A list of [`Actor`]s associated with this source. + Actors []Actor `json:"actors,omitempty" yaml:"actors,omitempty" mapstructure:"actors,omitempty"` + + // A human-readable string giving details about the source of the assertion data. + Details *string `json:"details,omitempty" yaml:"details,omitempty" mapstructure:"details,omitempty"` + + // A value from among the enumerated list indicating the source of the assertion. + Type string `json:"type" yaml:"type" mapstructure:"type"` +} + +type DateT string + +// A frame range representing starting and ending frames or pages. +// +// If both `start` and `end` are missing, the frame will span the entire asset. +type Frame struct { + // The end of the frame inclusive or the end of the asset if not present. + End *int `json:"end,omitempty" yaml:"end,omitempty" mapstructure:"end,omitempty"` + + // The start of the frame or the end of the asset if not present. + // + // The first frame/page starts at 0. + Start *int `json:"start,omitempty" yaml:"start,omitempty" mapstructure:"start,omitempty"` +} + +// Hashed Uri structure as defined by C2PA spec It is annotated to produce the +// correctly tagged cbor serialization +type HashedUri struct { + // Alg corresponds to the JSON schema field "alg". + Alg *string `json:"alg,omitempty" yaml:"alg,omitempty" mapstructure:"alg,omitempty"` + + // Hash corresponds to the JSON schema field "hash". + Hash []int `json:"hash" yaml:"hash" mapstructure:"hash"` + + // Url corresponds to the JSON schema field "url". + Url string `json:"url" yaml:"url" mapstructure:"url"` +} + +// An `Ingredient` is any external asset that has been used in the creation of an +// image. +type Ingredient struct { + // The active manifest label (if one exists). + // + // If this ingredient has a [`ManifestStore`], this will hold the label of the + // active [`Manifest`]. + // + // [`Manifest`]: crate::Manifest [`ManifestStore`]: crate::ManifestStore + ActiveManifest *string `json:"active_manifest,omitempty" yaml:"active_manifest,omitempty" mapstructure:"active_manifest,omitempty"` + + // A reference to the actual data of the ingredient. + Data interface{} `json:"data,omitempty" yaml:"data,omitempty" mapstructure:"data,omitempty"` + + // Additional information about the data's type to the ingredient V2 structure. + DataTypes []AssetType `json:"data_types,omitempty" yaml:"data_types,omitempty" mapstructure:"data_types,omitempty"` + + // Additional description of the ingredient. + Description *string `json:"description,omitempty" yaml:"description,omitempty" mapstructure:"description,omitempty"` + + // Document ID from `xmpMM:DocumentID` in XMP metadata. + DocumentId *string `json:"document_id,omitempty" yaml:"document_id,omitempty" mapstructure:"document_id,omitempty"` + + // The format of the source file as a MIME type. + Format string `json:"format,omitempty" yaml:"format,omitempty" mapstructure:"format,omitempty"` + + // An optional hash of the asset to prevent duplicates. + Hash *string `json:"hash,omitempty" yaml:"hash,omitempty" mapstructure:"hash,omitempty"` + + // URI to an informational page about the ingredient or its data. + InformationalURI *string `json:"informational_URI,omitempty" yaml:"informational_URI,omitempty" mapstructure:"informational_URI,omitempty"` + + // Instance ID from `xmpMM:InstanceID` in XMP metadata. + InstanceId *string `json:"instance_id,omitempty" yaml:"instance_id,omitempty" mapstructure:"instance_id,omitempty"` + + // A [`ManifestStore`] from the source asset extracted as a binary C2PA blob. + // + // [`ManifestStore`]: crate::ManifestStore + ManifestData interface{} `json:"manifest_data,omitempty" yaml:"manifest_data,omitempty" mapstructure:"manifest_data,omitempty"` + + // Any additional [`Metadata`] as defined in the C2PA spec. + // + // [`Manifest`]: crate::Manifest + Metadata interface{} `json:"metadata,omitempty" yaml:"metadata,omitempty" mapstructure:"metadata,omitempty"` + + // URI from `dcterms:provenance` in XMP metadata. + Provenance *string `json:"provenance,omitempty" yaml:"provenance,omitempty" mapstructure:"provenance,omitempty"` + + // Set to `ParentOf` if this is the parent ingredient. + // + // There can only be one parent ingredient in the ingredients. + Relationship interface{} `json:"relationship,omitempty" yaml:"relationship,omitempty" mapstructure:"relationship,omitempty"` + + // Resources corresponds to the JSON schema field "resources". + Resources interface{} `json:"resources,omitempty" yaml:"resources,omitempty" mapstructure:"resources,omitempty"` + + // A thumbnail image capturing the visual state at the time of import. + // + // A tuple of thumbnail MIME format (i.e. `image/jpeg`) and binary bits of the + // image. + Thumbnail interface{} `json:"thumbnail,omitempty" yaml:"thumbnail,omitempty" mapstructure:"thumbnail,omitempty"` + + // A human-readable title, generally source filename. + Title string `json:"title" yaml:"title" mapstructure:"title"` + + // Validation results. + ValidationStatus []ValidationStatus `json:"validation_status,omitempty" yaml:"validation_status,omitempty" mapstructure:"validation_status,omitempty"` +} + +// A Manifest Definition This is used to define a manifest and is used to build a +// ManifestStore A Manifest is a collection of ingredients and assertions It is +// used to define a claim that can be signed and embedded into a file +type ManifestDefinitionSchemaJson struct { + // A list of assertions + Assertions []AssertionDefinition `json:"assertions,omitempty" yaml:"assertions,omitempty" mapstructure:"assertions,omitempty"` + + // Clam Generator Info is always required with at least one entry + ClaimGeneratorInfo []ClaimGeneratorInfo `json:"claim_generator_info,omitempty" yaml:"claim_generator_info,omitempty" mapstructure:"claim_generator_info,omitempty"` + + // The format of the source file as a MIME type. + Format string `json:"format,omitempty" yaml:"format,omitempty" mapstructure:"format,omitempty"` + + // A List of ingredients + Ingredients []Ingredient `json:"ingredients,omitempty" yaml:"ingredients,omitempty" mapstructure:"ingredients,omitempty"` + + // Instance ID from `xmpMM:InstanceID` in XMP metadata. + InstanceId string `json:"instance_id,omitempty" yaml:"instance_id,omitempty" mapstructure:"instance_id,omitempty"` + + // Label corresponds to the JSON schema field "label". + Label *string `json:"label,omitempty" yaml:"label,omitempty" mapstructure:"label,omitempty"` + + // Optional manifest metadata + Metadata []Metadata `json:"metadata,omitempty" yaml:"metadata,omitempty" mapstructure:"metadata,omitempty"` + + // A list of redactions - URIs to a redacted assertions + Redactions []string `json:"redactions,omitempty" yaml:"redactions,omitempty" mapstructure:"redactions,omitempty"` + + // Thumbnail corresponds to the JSON schema field "thumbnail". + Thumbnail interface{} `json:"thumbnail,omitempty" yaml:"thumbnail,omitempty" mapstructure:"thumbnail,omitempty"` + + // A human-readable title, generally source filename. + Title *string `json:"title,omitempty" yaml:"title,omitempty" mapstructure:"title,omitempty"` + + // Optional prefix added to the generated Manifest Label This is typically + // Internet domain name for the vendor (i.e. `adobe`) + Vendor *string `json:"vendor,omitempty" yaml:"vendor,omitempty" mapstructure:"vendor,omitempty"` +} + +// The Metadata structure can be used as part of other assertions or on its own to +// reference others +type Metadata struct { + // DataSource corresponds to the JSON schema field "dataSource". + DataSource interface{} `json:"dataSource,omitempty" yaml:"dataSource,omitempty" mapstructure:"dataSource,omitempty"` + + // DateTime corresponds to the JSON schema field "dateTime". + DateTime interface{} `json:"dateTime,omitempty" yaml:"dateTime,omitempty" mapstructure:"dateTime,omitempty"` + + // Reference corresponds to the JSON schema field "reference". + Reference interface{} `json:"reference,omitempty" yaml:"reference,omitempty" mapstructure:"reference,omitempty"` + + // RegionOfInterest corresponds to the JSON schema field "regionOfInterest". + RegionOfInterest interface{} `json:"regionOfInterest,omitempty" yaml:"regionOfInterest,omitempty" mapstructure:"regionOfInterest,omitempty"` + + // ReviewRatings corresponds to the JSON schema field "reviewRatings". + ReviewRatings []ReviewRating `json:"reviewRatings,omitempty" yaml:"reviewRatings,omitempty" mapstructure:"reviewRatings,omitempty"` + + AdditionalProperties interface{} +} + +// A spatial, temporal, frame, or textual range describing the region of interest. +type Range struct { + // A frame range. + Frame interface{} `json:"frame,omitempty" yaml:"frame,omitempty" mapstructure:"frame,omitempty"` + + // A spatial range. + Shape interface{} `json:"shape,omitempty" yaml:"shape,omitempty" mapstructure:"shape,omitempty"` + + // A textual range. + Text interface{} `json:"text,omitempty" yaml:"text,omitempty" mapstructure:"text,omitempty"` + + // A temporal range. + Time interface{} `json:"time,omitempty" yaml:"time,omitempty" mapstructure:"time,omitempty"` + + // The type of range of interest. + Type interface{} `json:"type" yaml:"type" mapstructure:"type"` +} + +// The type of range for the region of interest. +type RangeType interface{} + +// A region of interest within an asset describing the change. +// +// This struct can be used from +// [`Action::changes`][crate::assertions::Action::changes] or +// [`Metadata::region_of_interest`][crate::assertions::Metadata::region_of_interest]. +type RegionOfInterest struct { + // A free-text string. + Description *string `json:"description,omitempty" yaml:"description,omitempty" mapstructure:"description,omitempty"` + + // A free-text string representing a machine-readable, unique to this assertion, + // identifier for the region. + Identifier *string `json:"identifier,omitempty" yaml:"identifier,omitempty" mapstructure:"identifier,omitempty"` + + // Additional information about the asset. + Metadata interface{} `json:"metadata,omitempty" yaml:"metadata,omitempty" mapstructure:"metadata,omitempty"` + + // A free-text string representing a human-readable name for the region which + // might be used in a user interface. + Name *string `json:"name,omitempty" yaml:"name,omitempty" mapstructure:"name,omitempty"` + + // A range describing the region of interest for the specific asset. + Region []Range `json:"region" yaml:"region" mapstructure:"region"` + + // A value from our controlled vocabulary or an entity-specific value (e.g., + // com.litware.coolArea) that represents the role of a region among other regions. + Role interface{} `json:"role,omitempty" yaml:"role,omitempty" mapstructure:"role,omitempty"` + + // A value from a controlled vocabulary such as + // or an entity-specific value + // (e.g., com.litware.newType) that represents the type of thing(s) depicted by a + // region. + // + // Note this field serializes/deserializes into the name `type`. + Type *string `json:"type,omitempty" yaml:"type,omitempty" mapstructure:"type,omitempty"` +} + +type Relationship string + +const RelationshipComponentOf Relationship = "componentOf" +const RelationshipInputTo Relationship = "inputTo" +const RelationshipParentOf Relationship = "parentOf" + +// A reference to a resource to be used in JSON serialization. +// +// The underlying data can be read as a stream via +// [`Reader::resource_to_stream`][crate::Reader::resource_to_stream]. +type ResourceRef struct { + // The algorithm used to hash the resource (if applicable). + Alg *string `json:"alg,omitempty" yaml:"alg,omitempty" mapstructure:"alg,omitempty"` + + // More detailed data types as defined in the C2PA spec. + DataTypes []AssetType `json:"data_types,omitempty" yaml:"data_types,omitempty" mapstructure:"data_types,omitempty"` + + // The mime type of the referenced resource. + Format string `json:"format" yaml:"format" mapstructure:"format"` + + // The hash of the resource (if applicable). + Hash *string `json:"hash,omitempty" yaml:"hash,omitempty" mapstructure:"hash,omitempty"` + + // A URI that identifies the resource as referenced from the manifest. + // + // This may be a JUMBF URI, a file path, a URL or any other string. Relative JUMBF + // URIs will be resolved with the manifest label. Relative file paths will be + // resolved with the base path if provided. + Identifier string `json:"identifier" yaml:"identifier" mapstructure:"identifier"` +} + +// Resource store to contain binary objects referenced from JSON serializable +// structures +type ResourceStore struct { + // Label corresponds to the JSON schema field "label". + Label *string `json:"label,omitempty" yaml:"label,omitempty" mapstructure:"label,omitempty"` + + // Resources corresponds to the JSON schema field "resources". + Resources ResourceStoreResources `json:"resources" yaml:"resources" mapstructure:"resources"` +} + +type ResourceStoreResources map[string][]int + +// A rating on an Assertion. +// +// See +// . +type ReviewRating struct { + // Code corresponds to the JSON schema field "code". + Code *string `json:"code,omitempty" yaml:"code,omitempty" mapstructure:"code,omitempty"` + + // Explanation corresponds to the JSON schema field "explanation". + Explanation string `json:"explanation" yaml:"explanation" mapstructure:"explanation"` + + // Value corresponds to the JSON schema field "value". + Value int `json:"value" yaml:"value" mapstructure:"value"` +} + +// A role describing the region. +type Role interface{} + +// A spatial range representing rectangle, circle, or a polygon. +type Shape struct { + // The height of a rectnagle. + // + // This field can be ignored for circles and polygons. + Height *float64 `json:"height,omitempty" yaml:"height,omitempty" mapstructure:"height,omitempty"` + + // If the range is inside the shape. + // + // The default value is true. + Inside *bool `json:"inside,omitempty" yaml:"inside,omitempty" mapstructure:"inside,omitempty"` + + // THe origin of the coordinate in the shape. + Origin interface{} `json:"origin" yaml:"origin" mapstructure:"origin"` + + // The type of shape. + Type interface{} `json:"type" yaml:"type" mapstructure:"type"` + + // The type of unit for the shape range. + Unit interface{} `json:"unit" yaml:"unit" mapstructure:"unit"` + + // The vertices of the polygon. + // + // This field can be ignored for rectangles and circles. + Vertices []Coordinate `json:"vertices,omitempty" yaml:"vertices,omitempty" mapstructure:"vertices,omitempty"` + + // The width for rectangles or diameter for circles. + // + // This field can be ignored for polygons. + Width *float64 `json:"width,omitempty" yaml:"width,omitempty" mapstructure:"width,omitempty"` +} + +// The type of shape for the range. +type ShapeType interface{} + +// A textual range representing multiple (possibly discontinuous) ranges of text. +type Text struct { + // The ranges of text to select. + Selectors []TextSelectorRange `json:"selectors" yaml:"selectors" mapstructure:"selectors"` +} + +// Selects a range of text via a fragment identifier. +// +// This is modeled after the W3C Web Annotation selector model. +type TextSelector struct { + // The end character offset or the end of the fragment if not present. + End *int `json:"end,omitempty" yaml:"end,omitempty" mapstructure:"end,omitempty"` + + // Fragment identifier as per RFC3023 (XML) or ISO 32000-2 (PDF), Annex O. + Fragment string `json:"fragment" yaml:"fragment" mapstructure:"fragment"` + + // The start character offset or the start of the fragment if not present. + Start *int `json:"start,omitempty" yaml:"start,omitempty" mapstructure:"start,omitempty"` +} + +// One or two [`TextSelector`][TextSelector] identifiying the range to select. +type TextSelectorRange struct { + // The end of the text range. + End interface{} `json:"end,omitempty" yaml:"end,omitempty" mapstructure:"end,omitempty"` + + // The start (or entire) text range. + Selector interface{} `json:"selector" yaml:"selector" mapstructure:"selector"` +} + +// A temporal range representing a starting time to an ending time. +type Time struct { + // The end time or the end of the asset if not present. + End *string `json:"end,omitempty" yaml:"end,omitempty" mapstructure:"end,omitempty"` + + // The start time or the start of the asset if not present. + Start *string `json:"start,omitempty" yaml:"start,omitempty" mapstructure:"start,omitempty"` + + // The type of time. + Type interface{} `json:"type,omitempty" yaml:"type,omitempty" mapstructure:"type,omitempty"` +} + +// The type of time. +type TimeType interface{} + +// The type of unit for the range. +type UnitType interface{} + +type UriOrResource interface{} + +// A `ValidationStatus` struct describes the validation status of a specific part +// of a manifest. +// +// See +// . +type ValidationStatus struct { + // Code corresponds to the JSON schema field "code". + Code string `json:"code" yaml:"code" mapstructure:"code"` + + // Explanation corresponds to the JSON schema field "explanation". + Explanation *string `json:"explanation,omitempty" yaml:"explanation,omitempty" mapstructure:"explanation,omitempty"` + + // Url corresponds to the JSON schema field "url". + Url *string `json:"url,omitempty" yaml:"url,omitempty" mapstructure:"url,omitempty"` +} diff --git a/pkg/c2pa/generated/manifeststore/manifeststore.go b/pkg/c2pa/generated/manifeststore/manifeststore.go new file mode 100644 index 00000000..653252cb --- /dev/null +++ b/pkg/c2pa/generated/manifeststore/manifeststore.go @@ -0,0 +1,520 @@ +// Code generated by github.com/atombender/go-jsonschema, DO NOT EDIT. + +package manifeststore + +// Identifies a person responsible for an action. +type Actor struct { + // List of references to W3C Verifiable Credentials. + Credentials []HashedUri `json:"credentials,omitempty" yaml:"credentials,omitempty" mapstructure:"credentials,omitempty"` + + // An identifier for a human actor, used when the "type" is + // `humanEntry.identified`. + Identifier *string `json:"identifier,omitempty" yaml:"identifier,omitempty" mapstructure:"identifier,omitempty"` +} + +type AssetType struct { + // Type corresponds to the JSON schema field "type". + Type string `json:"type" yaml:"type" mapstructure:"type"` + + // Version corresponds to the JSON schema field "version". + Version *string `json:"version,omitempty" yaml:"version,omitempty" mapstructure:"version,omitempty"` +} + +// Description of the claim generator, or the software used in generating the +// claim. +// +// This structure is also used for actions softwareAgent +type ClaimGeneratorInfo struct { + // hashed URI to the icon (either embedded or remote) + Icon interface{} `json:"icon,omitempty" yaml:"icon,omitempty" mapstructure:"icon,omitempty"` + + // A human readable string naming the claim_generator + Name string `json:"name" yaml:"name" mapstructure:"name"` + + // A human readable string of the product's version + Version *string `json:"version,omitempty" yaml:"version,omitempty" mapstructure:"version,omitempty"` + + AdditionalProperties interface{} +} + +// An x, y coordinate used for specifying vertices in polygons. +type Coordinate struct { + // The coordinate along the x-axis. + X float64 `json:"x" yaml:"x" mapstructure:"x"` + + // The coordinate along the y-axis. + Y float64 `json:"y" yaml:"y" mapstructure:"y"` +} + +// A description of the source for assertion data +type DataSource struct { + // A list of [`Actor`]s associated with this source. + Actors []Actor `json:"actors,omitempty" yaml:"actors,omitempty" mapstructure:"actors,omitempty"` + + // A human-readable string giving details about the source of the assertion data. + Details *string `json:"details,omitempty" yaml:"details,omitempty" mapstructure:"details,omitempty"` + + // A value from among the enumerated list indicating the source of the assertion. + Type string `json:"type" yaml:"type" mapstructure:"type"` +} + +type DateT string + +// A frame range representing starting and ending frames or pages. +// +// If both `start` and `end` are missing, the frame will span the entire asset. +type Frame struct { + // The end of the frame inclusive or the end of the asset if not present. + End *int `json:"end,omitempty" yaml:"end,omitempty" mapstructure:"end,omitempty"` + + // The start of the frame or the end of the asset if not present. + // + // The first frame/page starts at 0. + Start *int `json:"start,omitempty" yaml:"start,omitempty" mapstructure:"start,omitempty"` +} + +// Hashed Uri structure as defined by C2PA spec It is annotated to produce the +// correctly tagged cbor serialization +type HashedUri struct { + // Alg corresponds to the JSON schema field "alg". + Alg *string `json:"alg,omitempty" yaml:"alg,omitempty" mapstructure:"alg,omitempty"` + + // Hash corresponds to the JSON schema field "hash". + Hash []int `json:"hash" yaml:"hash" mapstructure:"hash"` + + // Url corresponds to the JSON schema field "url". + Url string `json:"url" yaml:"url" mapstructure:"url"` +} + +// An `Ingredient` is any external asset that has been used in the creation of an +// image. +type Ingredient struct { + // The active manifest label (if one exists). + // + // If this ingredient has a [`ManifestStore`], this will hold the label of the + // active [`Manifest`]. + // + // [`Manifest`]: crate::Manifest [`ManifestStore`]: crate::ManifestStore + ActiveManifest *string `json:"active_manifest,omitempty" yaml:"active_manifest,omitempty" mapstructure:"active_manifest,omitempty"` + + // A reference to the actual data of the ingredient. + Data interface{} `json:"data,omitempty" yaml:"data,omitempty" mapstructure:"data,omitempty"` + + // Additional information about the data's type to the ingredient V2 structure. + DataTypes []AssetType `json:"data_types,omitempty" yaml:"data_types,omitempty" mapstructure:"data_types,omitempty"` + + // Additional description of the ingredient. + Description *string `json:"description,omitempty" yaml:"description,omitempty" mapstructure:"description,omitempty"` + + // Document ID from `xmpMM:DocumentID` in XMP metadata. + DocumentId *string `json:"document_id,omitempty" yaml:"document_id,omitempty" mapstructure:"document_id,omitempty"` + + // The format of the source file as a MIME type. + Format string `json:"format,omitempty" yaml:"format,omitempty" mapstructure:"format,omitempty"` + + // An optional hash of the asset to prevent duplicates. + Hash *string `json:"hash,omitempty" yaml:"hash,omitempty" mapstructure:"hash,omitempty"` + + // URI to an informational page about the ingredient or its data. + InformationalURI *string `json:"informational_URI,omitempty" yaml:"informational_URI,omitempty" mapstructure:"informational_URI,omitempty"` + + // Instance ID from `xmpMM:InstanceID` in XMP metadata. + InstanceId *string `json:"instance_id,omitempty" yaml:"instance_id,omitempty" mapstructure:"instance_id,omitempty"` + + // A [`ManifestStore`] from the source asset extracted as a binary C2PA blob. + // + // [`ManifestStore`]: crate::ManifestStore + ManifestData interface{} `json:"manifest_data,omitempty" yaml:"manifest_data,omitempty" mapstructure:"manifest_data,omitempty"` + + // Any additional [`Metadata`] as defined in the C2PA spec. + // + // [`Manifest`]: crate::Manifest + Metadata interface{} `json:"metadata,omitempty" yaml:"metadata,omitempty" mapstructure:"metadata,omitempty"` + + // URI from `dcterms:provenance` in XMP metadata. + Provenance *string `json:"provenance,omitempty" yaml:"provenance,omitempty" mapstructure:"provenance,omitempty"` + + // Set to `ParentOf` if this is the parent ingredient. + // + // There can only be one parent ingredient in the ingredients. + Relationship interface{} `json:"relationship,omitempty" yaml:"relationship,omitempty" mapstructure:"relationship,omitempty"` + + // Resources corresponds to the JSON schema field "resources". + Resources interface{} `json:"resources,omitempty" yaml:"resources,omitempty" mapstructure:"resources,omitempty"` + + // A thumbnail image capturing the visual state at the time of import. + // + // A tuple of thumbnail MIME format (i.e. `image/jpeg`) and binary bits of the + // image. + Thumbnail interface{} `json:"thumbnail,omitempty" yaml:"thumbnail,omitempty" mapstructure:"thumbnail,omitempty"` + + // A human-readable title, generally source filename. + Title string `json:"title" yaml:"title" mapstructure:"title"` + + // Validation results. + ValidationStatus []ValidationStatus `json:"validation_status,omitempty" yaml:"validation_status,omitempty" mapstructure:"validation_status,omitempty"` +} + +// A Manifest represents all the information in a c2pa manifest +type Manifest struct { + // A list of assertions + Assertions []ManifestAssertion `json:"assertions,omitempty" yaml:"assertions,omitempty" mapstructure:"assertions,omitempty"` + + // A User Agent formatted string identifying the software/hardware/system produced + // this claim Spaces are not allowed in names, versions can be specified with + // product/1.0 syntax + ClaimGenerator string `json:"claim_generator,omitempty" yaml:"claim_generator,omitempty" mapstructure:"claim_generator,omitempty"` + + // ClaimGeneratorHints corresponds to the JSON schema field + // "claim_generator_hints". + ClaimGeneratorHints ManifestClaimGeneratorHints `json:"claim_generator_hints,omitempty" yaml:"claim_generator_hints,omitempty" mapstructure:"claim_generator_hints,omitempty"` + + // A list of claim generator info data identifying the software/hardware/system + // produced this claim + ClaimGeneratorInfo []ClaimGeneratorInfo `json:"claim_generator_info,omitempty" yaml:"claim_generator_info,omitempty" mapstructure:"claim_generator_info,omitempty"` + + // A List of verified credentials + Credentials []interface{} `json:"credentials,omitempty" yaml:"credentials,omitempty" mapstructure:"credentials,omitempty"` + + // The format of the source file as a MIME type. + Format string `json:"format,omitempty" yaml:"format,omitempty" mapstructure:"format,omitempty"` + + // A List of ingredients + Ingredients []Ingredient `json:"ingredients,omitempty" yaml:"ingredients,omitempty" mapstructure:"ingredients,omitempty"` + + // Instance ID from `xmpMM:InstanceID` in XMP metadata. + InstanceId string `json:"instance_id,omitempty" yaml:"instance_id,omitempty" mapstructure:"instance_id,omitempty"` + + // Label corresponds to the JSON schema field "label". + Label *string `json:"label,omitempty" yaml:"label,omitempty" mapstructure:"label,omitempty"` + + // A list of user metadata for this claim + Metadata []Metadata `json:"metadata,omitempty" yaml:"metadata,omitempty" mapstructure:"metadata,omitempty"` + + // A list of redactions - URIs to a redacted assertions + Redactions []string `json:"redactions,omitempty" yaml:"redactions,omitempty" mapstructure:"redactions,omitempty"` + + // container for binary assets (like thumbnails) + Resources interface{} `json:"resources,omitempty" yaml:"resources,omitempty" mapstructure:"resources,omitempty"` + + // Signature data (only used for reporting) + SignatureInfo interface{} `json:"signature_info,omitempty" yaml:"signature_info,omitempty" mapstructure:"signature_info,omitempty"` + + // Thumbnail corresponds to the JSON schema field "thumbnail". + Thumbnail interface{} `json:"thumbnail,omitempty" yaml:"thumbnail,omitempty" mapstructure:"thumbnail,omitempty"` + + // A human-readable title, generally source filename. + Title *string `json:"title,omitempty" yaml:"title,omitempty" mapstructure:"title,omitempty"` + + // Optional prefix added to the generated Manifest Label This is typically + // Internet domain name for the vendor (i.e. `adobe`) + Vendor *string `json:"vendor,omitempty" yaml:"vendor,omitempty" mapstructure:"vendor,omitempty"` +} + +// A labeled container for an Assertion value in a Manifest +type ManifestAssertion struct { + // The data of the assertion as Value + Data interface{} `json:"data" yaml:"data" mapstructure:"data"` + + // There can be more than one assertion for any label + Instance *int `json:"instance,omitempty" yaml:"instance,omitempty" mapstructure:"instance,omitempty"` + + // The [ManifestAssertionKind] for this assertion (as stored in c2pa content) + Kind interface{} `json:"kind,omitempty" yaml:"kind,omitempty" mapstructure:"kind,omitempty"` + + // An assertion label in reverse domain format + Label string `json:"label" yaml:"label" mapstructure:"label"` +} + +type ManifestAssertionKind string + +const ManifestAssertionKindBinary ManifestAssertionKind = "Binary" +const ManifestAssertionKindCbor ManifestAssertionKind = "Cbor" +const ManifestAssertionKindJson ManifestAssertionKind = "Json" +const ManifestAssertionKindUri ManifestAssertionKind = "Uri" + +type ManifestClaimGeneratorHints map[string]interface{} + +type ManifestData interface{} + +// A Container for a set of Manifests and a ValidationStatus list +type ManifestStoreSchemaJson struct { + // A label for the active (most recent) manifest in the store + ActiveManifest *string `json:"active_manifest,omitempty" yaml:"active_manifest,omitempty" mapstructure:"active_manifest,omitempty"` + + // A HashMap of Manifests + Manifests ManifestStoreSchemaJsonManifests `json:"manifests" yaml:"manifests" mapstructure:"manifests"` + + // ValidationStatus generated when loading the ManifestStore from an asset + ValidationStatus []ValidationStatus `json:"validation_status,omitempty" yaml:"validation_status,omitempty" mapstructure:"validation_status,omitempty"` +} + +// A HashMap of Manifests +type ManifestStoreSchemaJsonManifests map[string]Manifest + +// The Metadata structure can be used as part of other assertions or on its own to +// reference others +type Metadata struct { + // DataSource corresponds to the JSON schema field "dataSource". + DataSource interface{} `json:"dataSource,omitempty" yaml:"dataSource,omitempty" mapstructure:"dataSource,omitempty"` + + // DateTime corresponds to the JSON schema field "dateTime". + DateTime interface{} `json:"dateTime,omitempty" yaml:"dateTime,omitempty" mapstructure:"dateTime,omitempty"` + + // Reference corresponds to the JSON schema field "reference". + Reference interface{} `json:"reference,omitempty" yaml:"reference,omitempty" mapstructure:"reference,omitempty"` + + // RegionOfInterest corresponds to the JSON schema field "regionOfInterest". + RegionOfInterest interface{} `json:"regionOfInterest,omitempty" yaml:"regionOfInterest,omitempty" mapstructure:"regionOfInterest,omitempty"` + + // ReviewRatings corresponds to the JSON schema field "reviewRatings". + ReviewRatings []ReviewRating `json:"reviewRatings,omitempty" yaml:"reviewRatings,omitempty" mapstructure:"reviewRatings,omitempty"` + + AdditionalProperties interface{} +} + +// A spatial, temporal, frame, or textual range describing the region of interest. +type Range struct { + // A frame range. + Frame interface{} `json:"frame,omitempty" yaml:"frame,omitempty" mapstructure:"frame,omitempty"` + + // A spatial range. + Shape interface{} `json:"shape,omitempty" yaml:"shape,omitempty" mapstructure:"shape,omitempty"` + + // A textual range. + Text interface{} `json:"text,omitempty" yaml:"text,omitempty" mapstructure:"text,omitempty"` + + // A temporal range. + Time interface{} `json:"time,omitempty" yaml:"time,omitempty" mapstructure:"time,omitempty"` + + // The type of range of interest. + Type interface{} `json:"type" yaml:"type" mapstructure:"type"` +} + +// The type of range for the region of interest. +type RangeType interface{} + +// A region of interest within an asset describing the change. +// +// This struct can be used from +// [`Action::changes`][crate::assertions::Action::changes] or +// [`Metadata::region_of_interest`][crate::assertions::Metadata::region_of_interest]. +type RegionOfInterest struct { + // A free-text string. + Description *string `json:"description,omitempty" yaml:"description,omitempty" mapstructure:"description,omitempty"` + + // A free-text string representing a machine-readable, unique to this assertion, + // identifier for the region. + Identifier *string `json:"identifier,omitempty" yaml:"identifier,omitempty" mapstructure:"identifier,omitempty"` + + // Additional information about the asset. + Metadata interface{} `json:"metadata,omitempty" yaml:"metadata,omitempty" mapstructure:"metadata,omitempty"` + + // A free-text string representing a human-readable name for the region which + // might be used in a user interface. + Name *string `json:"name,omitempty" yaml:"name,omitempty" mapstructure:"name,omitempty"` + + // A range describing the region of interest for the specific asset. + Region []Range `json:"region" yaml:"region" mapstructure:"region"` + + // A value from our controlled vocabulary or an entity-specific value (e.g., + // com.litware.coolArea) that represents the role of a region among other regions. + Role interface{} `json:"role,omitempty" yaml:"role,omitempty" mapstructure:"role,omitempty"` + + // A value from a controlled vocabulary such as + // or an entity-specific value + // (e.g., com.litware.newType) that represents the type of thing(s) depicted by a + // region. + // + // Note this field serializes/deserializes into the name `type`. + Type *string `json:"type,omitempty" yaml:"type,omitempty" mapstructure:"type,omitempty"` +} + +type Relationship string + +const RelationshipComponentOf Relationship = "componentOf" +const RelationshipInputTo Relationship = "inputTo" +const RelationshipParentOf Relationship = "parentOf" + +// A reference to a resource to be used in JSON serialization. +// +// The underlying data can be read as a stream via +// [`Reader::resource_to_stream`][crate::Reader::resource_to_stream]. +type ResourceRef struct { + // The algorithm used to hash the resource (if applicable). + Alg *string `json:"alg,omitempty" yaml:"alg,omitempty" mapstructure:"alg,omitempty"` + + // More detailed data types as defined in the C2PA spec. + DataTypes []AssetType `json:"data_types,omitempty" yaml:"data_types,omitempty" mapstructure:"data_types,omitempty"` + + // The mime type of the referenced resource. + Format string `json:"format" yaml:"format" mapstructure:"format"` + + // The hash of the resource (if applicable). + Hash *string `json:"hash,omitempty" yaml:"hash,omitempty" mapstructure:"hash,omitempty"` + + // A URI that identifies the resource as referenced from the manifest. + // + // This may be a JUMBF URI, a file path, a URL or any other string. Relative JUMBF + // URIs will be resolved with the manifest label. Relative file paths will be + // resolved with the base path if provided. + Identifier string `json:"identifier" yaml:"identifier" mapstructure:"identifier"` +} + +// Resource store to contain binary objects referenced from JSON serializable +// structures +type ResourceStore struct { + // Label corresponds to the JSON schema field "label". + Label *string `json:"label,omitempty" yaml:"label,omitempty" mapstructure:"label,omitempty"` + + // Resources corresponds to the JSON schema field "resources". + Resources ResourceStoreResources `json:"resources" yaml:"resources" mapstructure:"resources"` +} + +type ResourceStoreResources map[string][]int + +// A rating on an Assertion. +// +// See +// . +type ReviewRating struct { + // Code corresponds to the JSON schema field "code". + Code *string `json:"code,omitempty" yaml:"code,omitempty" mapstructure:"code,omitempty"` + + // Explanation corresponds to the JSON schema field "explanation". + Explanation string `json:"explanation" yaml:"explanation" mapstructure:"explanation"` + + // Value corresponds to the JSON schema field "value". + Value int `json:"value" yaml:"value" mapstructure:"value"` +} + +// A role describing the region. +type Role interface{} + +// A spatial range representing rectangle, circle, or a polygon. +type Shape struct { + // The height of a rectnagle. + // + // This field can be ignored for circles and polygons. + Height *float64 `json:"height,omitempty" yaml:"height,omitempty" mapstructure:"height,omitempty"` + + // If the range is inside the shape. + // + // The default value is true. + Inside *bool `json:"inside,omitempty" yaml:"inside,omitempty" mapstructure:"inside,omitempty"` + + // THe origin of the coordinate in the shape. + Origin interface{} `json:"origin" yaml:"origin" mapstructure:"origin"` + + // The type of shape. + Type interface{} `json:"type" yaml:"type" mapstructure:"type"` + + // The type of unit for the shape range. + Unit interface{} `json:"unit" yaml:"unit" mapstructure:"unit"` + + // The vertices of the polygon. + // + // This field can be ignored for rectangles and circles. + Vertices []Coordinate `json:"vertices,omitempty" yaml:"vertices,omitempty" mapstructure:"vertices,omitempty"` + + // The width for rectangles or diameter for circles. + // + // This field can be ignored for polygons. + Width *float64 `json:"width,omitempty" yaml:"width,omitempty" mapstructure:"width,omitempty"` +} + +// The type of shape for the range. +type ShapeType interface{} + +// Holds information about a signature +type SignatureInfo struct { + // human readable issuing authority for this signature + Alg interface{} `json:"alg,omitempty" yaml:"alg,omitempty" mapstructure:"alg,omitempty"` + + // The serial number of the certificate + CertSerialNumber *string `json:"cert_serial_number,omitempty" yaml:"cert_serial_number,omitempty" mapstructure:"cert_serial_number,omitempty"` + + // human readable issuing authority for this signature + Issuer *string `json:"issuer,omitempty" yaml:"issuer,omitempty" mapstructure:"issuer,omitempty"` + + // revocation status of the certificate + RevocationStatus *bool `json:"revocation_status,omitempty" yaml:"revocation_status,omitempty" mapstructure:"revocation_status,omitempty"` + + // the time the signature was created + Time *string `json:"time,omitempty" yaml:"time,omitempty" mapstructure:"time,omitempty"` +} + +// Describes the digital signature algorithms allowed by the C2PA spec. +// +// Per +// : +// +// > All digital signatures that are stored in a C2PA Manifest shall > be generated +// using one of the digital signature algorithms and > key types listed as +// described in this section. +type SigningAlg interface{} + +// A textual range representing multiple (possibly discontinuous) ranges of text. +type Text struct { + // The ranges of text to select. + Selectors []TextSelectorRange `json:"selectors" yaml:"selectors" mapstructure:"selectors"` +} + +// Selects a range of text via a fragment identifier. +// +// This is modeled after the W3C Web Annotation selector model. +type TextSelector struct { + // The end character offset or the end of the fragment if not present. + End *int `json:"end,omitempty" yaml:"end,omitempty" mapstructure:"end,omitempty"` + + // Fragment identifier as per RFC3023 (XML) or ISO 32000-2 (PDF), Annex O. + Fragment string `json:"fragment" yaml:"fragment" mapstructure:"fragment"` + + // The start character offset or the start of the fragment if not present. + Start *int `json:"start,omitempty" yaml:"start,omitempty" mapstructure:"start,omitempty"` +} + +// One or two [`TextSelector`][TextSelector] identifiying the range to select. +type TextSelectorRange struct { + // The end of the text range. + End interface{} `json:"end,omitempty" yaml:"end,omitempty" mapstructure:"end,omitempty"` + + // The start (or entire) text range. + Selector interface{} `json:"selector" yaml:"selector" mapstructure:"selector"` +} + +// A temporal range representing a starting time to an ending time. +type Time struct { + // The end time or the end of the asset if not present. + End *string `json:"end,omitempty" yaml:"end,omitempty" mapstructure:"end,omitempty"` + + // The start time or the start of the asset if not present. + Start *string `json:"start,omitempty" yaml:"start,omitempty" mapstructure:"start,omitempty"` + + // The type of time. + Type interface{} `json:"type,omitempty" yaml:"type,omitempty" mapstructure:"type,omitempty"` +} + +// The type of time. +type TimeType interface{} + +// The type of unit for the range. +type UnitType interface{} + +type UriOrResource interface{} + +// A `ValidationStatus` struct describes the validation status of a specific part +// of a manifest. +// +// See +// . +type ValidationStatus struct { + // Code corresponds to the JSON schema field "code". + Code string `json:"code" yaml:"code" mapstructure:"code"` + + // Explanation corresponds to the JSON schema field "explanation". + Explanation *string `json:"explanation,omitempty" yaml:"explanation,omitempty" mapstructure:"explanation,omitempty"` + + // Url corresponds to the JSON schema field "url". + Url *string `json:"url,omitempty" yaml:"url,omitempty" mapstructure:"url,omitempty"` +} diff --git a/pkg/c2pa/generated/settings/settings.go b/pkg/c2pa/generated/settings/settings.go new file mode 100644 index 00000000..ad07c6dc --- /dev/null +++ b/pkg/c2pa/generated/settings/settings.go @@ -0,0 +1,82 @@ +// Code generated by github.com/atombender/go-jsonschema, DO NOT EDIT. + +package settings + +type Builder struct { + // AutoThumbnail corresponds to the JSON schema field "auto_thumbnail". + AutoThumbnail bool `json:"auto_thumbnail" yaml:"auto_thumbnail" mapstructure:"auto_thumbnail"` +} + +type Core struct { + // CompressManifests corresponds to the JSON schema field "compress_manifests". + CompressManifests bool `json:"compress_manifests" yaml:"compress_manifests" mapstructure:"compress_manifests"` + + // Debug corresponds to the JSON schema field "debug". + Debug bool `json:"debug" yaml:"debug" mapstructure:"debug"` + + // HashAlg corresponds to the JSON schema field "hash_alg". + HashAlg string `json:"hash_alg" yaml:"hash_alg" mapstructure:"hash_alg"` + + // MaxMemoryUsage corresponds to the JSON schema field "max_memory_usage". + MaxMemoryUsage *int `json:"max_memory_usage,omitempty" yaml:"max_memory_usage,omitempty" mapstructure:"max_memory_usage,omitempty"` + + // PreferBmffMerkleTree corresponds to the JSON schema field + // "prefer_bmff_merkle_tree". + PreferBmffMerkleTree bool `json:"prefer_bmff_merkle_tree" yaml:"prefer_bmff_merkle_tree" mapstructure:"prefer_bmff_merkle_tree"` + + // PreferBoxHash corresponds to the JSON schema field "prefer_box_hash". + PreferBoxHash bool `json:"prefer_box_hash" yaml:"prefer_box_hash" mapstructure:"prefer_box_hash"` + + // SaltJumbfBoxes corresponds to the JSON schema field "salt_jumbf_boxes". + SaltJumbfBoxes bool `json:"salt_jumbf_boxes" yaml:"salt_jumbf_boxes" mapstructure:"salt_jumbf_boxes"` +} + +type SettingsSchemaJson struct { + // Builder corresponds to the JSON schema field "builder". + Builder Builder `json:"builder" yaml:"builder" mapstructure:"builder"` + + // Core corresponds to the JSON schema field "core". + Core Core `json:"core" yaml:"core" mapstructure:"core"` + + // Trust corresponds to the JSON schema field "trust". + Trust Trust `json:"trust" yaml:"trust" mapstructure:"trust"` + + // Verify corresponds to the JSON schema field "verify". + Verify Verify `json:"verify" yaml:"verify" mapstructure:"verify"` +} + +type Trust struct { + // AllowedList corresponds to the JSON schema field "allowed_list". + AllowedList *string `json:"allowed_list,omitempty" yaml:"allowed_list,omitempty" mapstructure:"allowed_list,omitempty"` + + // PrivateAnchors corresponds to the JSON schema field "private_anchors". + PrivateAnchors *string `json:"private_anchors,omitempty" yaml:"private_anchors,omitempty" mapstructure:"private_anchors,omitempty"` + + // TrustAnchors corresponds to the JSON schema field "trust_anchors". + TrustAnchors *string `json:"trust_anchors,omitempty" yaml:"trust_anchors,omitempty" mapstructure:"trust_anchors,omitempty"` + + // TrustConfig corresponds to the JSON schema field "trust_config". + TrustConfig *string `json:"trust_config,omitempty" yaml:"trust_config,omitempty" mapstructure:"trust_config,omitempty"` +} + +type Verify struct { + // CheckIngredientTrust corresponds to the JSON schema field + // "check_ingredient_trust". + CheckIngredientTrust bool `json:"check_ingredient_trust" yaml:"check_ingredient_trust" mapstructure:"check_ingredient_trust"` + + // OcspFetch corresponds to the JSON schema field "ocsp_fetch". + OcspFetch bool `json:"ocsp_fetch" yaml:"ocsp_fetch" mapstructure:"ocsp_fetch"` + + // RemoteManifestFetch corresponds to the JSON schema field + // "remote_manifest_fetch". + RemoteManifestFetch bool `json:"remote_manifest_fetch" yaml:"remote_manifest_fetch" mapstructure:"remote_manifest_fetch"` + + // VerifyAfterReading corresponds to the JSON schema field "verify_after_reading". + VerifyAfterReading bool `json:"verify_after_reading" yaml:"verify_after_reading" mapstructure:"verify_after_reading"` + + // VerifyAfterSign corresponds to the JSON schema field "verify_after_sign". + VerifyAfterSign bool `json:"verify_after_sign" yaml:"verify_after_sign" mapstructure:"verify_after_sign"` + + // VerifyTrust corresponds to the JSON schema field "verify_trust". + VerifyTrust bool `json:"verify_trust" yaml:"verify_trust" mapstructure:"verify_trust"` +} From b6093cad18506a7691f0fbbfcfa08b734bbe133e Mon Sep 17 00:00:00 2001 From: Eli Mallon Date: Tue, 13 Aug 2024 18:16:43 -0700 Subject: [PATCH 19/42] remove gitignore --- pkg/c2pa/.gitignore | 1 - 1 file changed, 1 deletion(-) delete mode 100644 pkg/c2pa/.gitignore diff --git a/pkg/c2pa/.gitignore b/pkg/c2pa/.gitignore deleted file mode 100644 index 86d4c2dd..00000000 --- a/pkg/c2pa/.gitignore +++ /dev/null @@ -1 +0,0 @@ -generated From d84609622dfecdf0355a31de9b4b811e5d86f059 Mon Sep 17 00:00:00 2001 From: Eli Mallon Date: Tue, 13 Aug 2024 19:05:55 -0700 Subject: [PATCH 20/42] add meson.build --- meson.build | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 meson.build diff --git a/meson.build b/meson.build new file mode 100644 index 00000000..f997638c --- /dev/null +++ b/meson.build @@ -0,0 +1,19 @@ +project( + 'c2pa-go', + default_options: ['default_library=static'], +) + +prog_cargo = find_program('cargo') + +lib = custom_target( + 'libc2pa', + input: [], + output: ['libc2pa.a'], + command: [prog_cargo, 'build', '--release', '--target-dir', meson.current_build_dir()], + command: [ + 'sh', + '-c', + '@0@ build --release --target-dir @1@ && cp @1@/release/libc2pa.a @1@/libc2pa.a'.format(prog_cargo.full_path(), meson.current_build_dir()), + ], + build_by_default: true, +) From a55eee81068a7b9fd8795418143e17c214632815 Mon Sep 17 00:00:00 2001 From: Eli Mallon Date: Tue, 13 Aug 2024 19:36:06 -0700 Subject: [PATCH 21/42] working meson dependency --- meson.build | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/meson.build b/meson.build index f997638c..2bbc5256 100644 --- a/meson.build +++ b/meson.build @@ -5,7 +5,7 @@ project( prog_cargo = find_program('cargo') -lib = custom_target( +c2pa_go_dep = custom_target( 'libc2pa', input: [], output: ['libc2pa.a'], @@ -13,7 +13,7 @@ lib = custom_target( command: [ 'sh', '-c', - '@0@ build --release --target-dir @1@ && cp @1@/release/libc2pa.a @1@/libc2pa.a'.format(prog_cargo.full_path(), meson.current_build_dir()), + 'cd @2@ && @0@ build --release --target-dir @1@ && cp @1@/release/libc2pa.a @1@/libc2pa.a'.format(prog_cargo.full_path(), meson.current_build_dir(), meson.current_source_dir()), ], build_by_default: true, ) From 0c6bf82557c42f26f6d015ac55dd9c81780fc733 Mon Sep 17 00:00:00 2001 From: Eli Mallon Date: Wed, 14 Aug 2024 10:51:53 -0700 Subject: [PATCH 22/42] rename --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 6c5e2f12..4463e3b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "c2pa-python" +name = "c2pa-go" version = "0.5.0" edition = "2021" authors = ["Gavin Peacock Date: Wed, 14 Aug 2024 12:04:48 -0700 Subject: [PATCH 23/42] add RUST_TRIPLE option --- meson.build | 12 ++++++++++-- meson.options | 1 + 2 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 meson.options diff --git a/meson.build b/meson.build index 2bbc5256..0681fda5 100644 --- a/meson.build +++ b/meson.build @@ -5,15 +5,23 @@ project( prog_cargo = find_program('cargo') +cargo_cmd = '@0@ build --release --target-dir @1@'.format(prog_cargo.full_path(), meson.current_build_dir()) +archive_dir = meson.current_build_dir() + +triple = get_option('RUST_TRIPLE') +if triple != '' + cargo_cmd += ' --target @0@'.format(triple) + archive_dir += '/' + triple +endif + c2pa_go_dep = custom_target( 'libc2pa', input: [], output: ['libc2pa.a'], - command: [prog_cargo, 'build', '--release', '--target-dir', meson.current_build_dir()], command: [ 'sh', '-c', - 'cd @2@ && @0@ build --release --target-dir @1@ && cp @1@/release/libc2pa.a @1@/libc2pa.a'.format(prog_cargo.full_path(), meson.current_build_dir(), meson.current_source_dir()), + 'cd @2@ && @0@ && cp @3@/release/libc2pa.a @1@/libc2pa.a'.format(cargo_cmd, meson.current_build_dir(), meson.current_source_dir(), archive_dir), ], build_by_default: true, ) diff --git a/meson.options b/meson.options new file mode 100644 index 00000000..b3093e04 --- /dev/null +++ b/meson.options @@ -0,0 +1 @@ +option('RUST_TRIPLE', type : 'string', description : 'Rust triple for cross-compilation') \ No newline at end of file From 09d77c1e2b38107c89079f8cc58f3fe4b703977e Mon Sep 17 00:00:00 2001 From: Eli Mallon Date: Wed, 14 Aug 2024 16:08:02 -0700 Subject: [PATCH 24/42] migrate to two IO classes --- pkg/c2pa/c2pa.go | 65 +++---------------------------------- pkg/c2pa/io.go | 84 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 61 deletions(-) create mode 100644 pkg/c2pa/io.go diff --git a/pkg/c2pa/c2pa.go b/pkg/c2pa/c2pa.go index e4b93a2a..ba47d7a9 100644 --- a/pkg/c2pa/c2pa.go +++ b/pkg/c2pa/c2pa.go @@ -37,7 +37,7 @@ type Reader interface { } func FromStream(target io.ReadWriteSeeker, mType string) (Reader, error) { - stream := C2PAStream{target} + stream := C2PAStreamReader{target} r := rustC2PA.NewReader() r.FromStream(mType, &stream) ret, err := r.Json() @@ -85,65 +85,8 @@ func (r *C2PAReader) GetActiveManifest() *manifeststore.Manifest { return r.GetManifest(*r.store.ActiveManifest) } -type C2PAStream struct { - io.ReadWriteSeeker -} - -func (s *C2PAStream) ReadStream(length uint64) ([]byte, *rustC2PA.Error) { - // fmt.Printf("read length=%d\n", length) - bs := make([]byte, length) - read, err := s.ReadWriteSeeker.Read(bs) - if err != nil { - if errors.Is(err, io.EOF) { - if read == 0 { - // fmt.Printf("read EOF read=%d returning empty?", read) - return []byte{}, nil - } - // partial := bs[read:] - // return partial, nil - } - // fmt.Printf("io error=%s\n", err) - return []byte{}, rustC2PA.NewErrorIo(err.Error()) - } - if uint64(read) < length { - partial := bs[read:] - // fmt.Printf("read returning partial read=%d len=%d\n", read, len(partial)) - return partial, nil - } - // fmt.Printf("read returning full read=%d len=%d\n", read, len(bs)) - return bs, nil -} - -func (s *C2PAStream) SeekStream(pos int64, mode rustC2PA.SeekMode) (uint64, *rustC2PA.Error) { - // fmt.Printf("seek pos=%d\n", pos) - var seekMode int - if mode == rustC2PA.SeekModeCurrent { - seekMode = io.SeekCurrent - } else if mode == rustC2PA.SeekModeStart { - seekMode = io.SeekStart - } else if mode == rustC2PA.SeekModeEnd { - seekMode = io.SeekEnd - } else { - // fmt.Printf("seek mode unsupported mode=%d\n", mode) - return 0, rustC2PA.NewErrorNotSupported(fmt.Sprintf("unknown seek mode: %d", mode)) - } - newPos, err := s.ReadWriteSeeker.Seek(pos, seekMode) - if err != nil { - return 0, rustC2PA.NewErrorIo(err.Error()) - } - return uint64(newPos), nil -} - -func (s *C2PAStream) WriteStream(data []byte) (uint64, *rustC2PA.Error) { - wrote, err := s.ReadWriteSeeker.Write(data) - if err != nil { - return uint64(wrote), rustC2PA.NewErrorIo(err.Error()) - } - return uint64(wrote), nil -} - type Builder interface { - Sign(input, output io.ReadWriteSeeker, mimeType string) error + Sign(input io.ReadSeeker, output io.Writer, mimeType string) error SignFile(infile, outfile string) error } @@ -198,7 +141,7 @@ var hashMap = map[string]crypto.Hash{ "ps512": crypto.SHA512, } -func (b *C2PABuilder) Sign(input, output io.ReadWriteSeeker, mimeType string) error { +func (b *C2PABuilder) Sign(input io.ReadSeeker, output io.Writer, mimeType string) error { mySigner := &C2PACallbackSigner{ params: b.params, } @@ -207,7 +150,7 @@ func (b *C2PABuilder) Sign(input, output io.ReadWriteSeeker, mimeType string) er return fmt.Errorf("unknown algorithm: %s", b.params.Algorithm) } signer := rustC2PA.NewCallbackSigner(mySigner, alg, b.params.Cert, &b.params.TAURL) - _, err := b.builder.Sign(mimeType, &C2PAStream{input}, &C2PAStream{output}, signer) + _, err := b.builder.Sign(mimeType, &C2PAStreamReader{input}, &C2PAStreamWriter{output}, signer) if err != nil { return err } diff --git a/pkg/c2pa/io.go b/pkg/c2pa/io.go new file mode 100644 index 00000000..e8515d75 --- /dev/null +++ b/pkg/c2pa/io.go @@ -0,0 +1,84 @@ +package c2pa + +import ( + "errors" + "fmt" + "io" + + rustC2PA "git.aquareum.tv/aquareum-tv/c2pa-go/pkg/c2pa/generated/c2pa" +) + +// Wrapped io.ReadSeeker for passing to Rust. Doesn't write. +type C2PAStreamReader struct { + io.ReadSeeker +} + +func (s *C2PAStreamReader) ReadStream(length uint64) ([]byte, *rustC2PA.Error) { + // fmt.Printf("read length=%d\n", length) + bs := make([]byte, length) + read, err := s.ReadSeeker.Read(bs) + if err != nil { + if errors.Is(err, io.EOF) { + if read == 0 { + // fmt.Printf("read EOF read=%d returning empty?", read) + return []byte{}, nil + } + // partial := bs[read:] + // return partial, nil + } + // fmt.Printf("io error=%s\n", err) + return []byte{}, rustC2PA.NewErrorIo(err.Error()) + } + if uint64(read) < length { + partial := bs[read:] + // fmt.Printf("read returning partial read=%d len=%d\n", read, len(partial)) + return partial, nil + } + // fmt.Printf("read returning full read=%d len=%d\n", read, len(bs)) + return bs, nil +} + +func (s *C2PAStreamReader) SeekStream(pos int64, mode rustC2PA.SeekMode) (uint64, *rustC2PA.Error) { + // fmt.Printf("seek pos=%d\n", pos) + var seekMode int + if mode == rustC2PA.SeekModeCurrent { + seekMode = io.SeekCurrent + } else if mode == rustC2PA.SeekModeStart { + seekMode = io.SeekStart + } else if mode == rustC2PA.SeekModeEnd { + seekMode = io.SeekEnd + } else { + // fmt.Printf("seek mode unsupported mode=%d\n", mode) + return 0, rustC2PA.NewErrorNotSupported(fmt.Sprintf("unknown seek mode: %d", mode)) + } + newPos, err := s.ReadSeeker.Seek(pos, seekMode) + if err != nil { + return 0, rustC2PA.NewErrorIo(err.Error()) + } + return uint64(newPos), nil +} + +func (s *C2PAStreamReader) WriteStream(data []byte) (uint64, *rustC2PA.Error) { + return 0, rustC2PA.NewErrorIo("Writing is not implemented for C2PAStreamReader") +} + +// Wrapped io.Writer for passing to Rust. Doesn't read or seek. +type C2PAStreamWriter struct { + io.Writer +} + +func (s *C2PAStreamWriter) ReadStream(length uint64) ([]byte, *rustC2PA.Error) { + return nil, rustC2PA.NewErrorIo("Reading is not implemented for C2PAStreamWriter") +} + +func (s *C2PAStreamWriter) SeekStream(pos int64, mode rustC2PA.SeekMode) (uint64, *rustC2PA.Error) { + return 0, rustC2PA.NewErrorIo("Seeking is not implemented for C2PAStreamWriter") +} + +func (s *C2PAStreamWriter) WriteStream(data []byte) (uint64, *rustC2PA.Error) { + wrote, err := s.Writer.Write(data) + if err != nil { + return uint64(wrote), rustC2PA.NewErrorIo(err.Error()) + } + return uint64(wrote), nil +} From 66ef0cf24bc8a89aed7dde32969ca1b92131b280 Mon Sep 17 00:00:00 2001 From: Eli Mallon Date: Sat, 17 Aug 2024 18:07:23 -0700 Subject: [PATCH 25/42] fix mp4 signing bug, add tests thereof --- Cargo.toml | 5 ++-- pkg/c2pa/c2pa.go | 4 +-- pkg/c2pa/c2pa_test.go | 14 ++++++---- pkg/c2pa/io.go | 58 +++++++++++++++++++++++---------------- tests/fixtures/video.mp4 | Bin 0 -> 331029 bytes 5 files changed, 48 insertions(+), 33 deletions(-) create mode 100644 tests/fixtures/video.mp4 diff --git a/Cargo.toml b/Cargo.toml index 4463e3b6..ba6a69f5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "c2pa-go" version = "0.5.0" edition = "2021" -authors = ["Gavin Peacock "] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib] @@ -10,11 +10,12 @@ name = "c2pa" crate-type = ["staticlib"] [dependencies] -c2pa = { git = "https://git.aquareum.tv/aquareum-tv/c2pa-rs.git", rev = "ac3a37eb1f51b9498b6b2974bd26ed2a362bf517", features = [ +c2pa = { git = "https://git.aquareum.tv/aquareum-tv/c2pa-rs.git", rev = "a0b0d3298ae14c4fd71413232eff159cda8d7bcb", features = [ "unstable_api", "openssl", ] } # c2pa = { path = "../c2pa-rs/sdk", features = ["unstable_api", "openssl"] } +# c2pa = { version = "0.33.3", features = ["unstable_api", "openssl"] } pem = "3.0.3" serde = { version = "1.0.197", features = ["derive"] } serde_derive = "1.0" diff --git a/pkg/c2pa/c2pa.go b/pkg/c2pa/c2pa.go index ba47d7a9..96dac7f8 100644 --- a/pkg/c2pa/c2pa.go +++ b/pkg/c2pa/c2pa.go @@ -86,7 +86,7 @@ func (r *C2PAReader) GetActiveManifest() *manifeststore.Manifest { } type Builder interface { - Sign(input io.ReadSeeker, output io.Writer, mimeType string) error + Sign(input io.ReadSeeker, output io.ReadWriteSeeker, mimeType string) error SignFile(infile, outfile string) error } @@ -141,7 +141,7 @@ var hashMap = map[string]crypto.Hash{ "ps512": crypto.SHA512, } -func (b *C2PABuilder) Sign(input io.ReadSeeker, output io.Writer, mimeType string) error { +func (b *C2PABuilder) Sign(input io.ReadSeeker, output io.ReadWriteSeeker, mimeType string) error { mySigner := &C2PACallbackSigner{ params: b.params, } diff --git a/pkg/c2pa/c2pa_test.go b/pkg/c2pa/c2pa_test.go index eb3614b0..2ee9da76 100644 --- a/pkg/c2pa/c2pa_test.go +++ b/pkg/c2pa/c2pa_test.go @@ -80,14 +80,16 @@ func TestSigning(t *testing.T) { require.NoError(t, err) fixtures := getFixtures() - input := filepath.Join(fixtures, "A.jpg") - output := filepath.Join(dname, fmt.Sprintf("A-signed-%s.jpg", test.name)) + for _, fname := range []string{"A.jpg", "video.mp4"} { + input := filepath.Join(fixtures, fname) + output := filepath.Join(dname, fmt.Sprintf("signed-%s-%s", test.name, fname)) - err = b.SignFile(input, output) - require.NoError(t, err) + err = b.SignFile(input, output) + require.NoError(t, err) - err = c2patool(output) - require.NoError(t, err) + err = c2patool(output) + require.NoError(t, err) + } }) } } diff --git a/pkg/c2pa/io.go b/pkg/c2pa/io.go index e8515d75..6f4c2ccd 100644 --- a/pkg/c2pa/io.go +++ b/pkg/c2pa/io.go @@ -14,9 +14,38 @@ type C2PAStreamReader struct { } func (s *C2PAStreamReader) ReadStream(length uint64) ([]byte, *rustC2PA.Error) { + return readStream(s.ReadSeeker, length) +} + +func (s *C2PAStreamReader) SeekStream(pos int64, mode rustC2PA.SeekMode) (uint64, *rustC2PA.Error) { + return seekStream(s.ReadSeeker, pos, mode) +} + +func (s *C2PAStreamReader) WriteStream(data []byte) (uint64, *rustC2PA.Error) { + return 0, rustC2PA.NewErrorIo("Writing is not implemented for C2PAStreamReader") +} + +// Wrapped io.Writer for passing to Rust. +type C2PAStreamWriter struct { + io.ReadWriteSeeker +} + +func (s *C2PAStreamWriter) ReadStream(length uint64) ([]byte, *rustC2PA.Error) { + return readStream(s.ReadWriteSeeker, length) +} + +func (s *C2PAStreamWriter) SeekStream(pos int64, mode rustC2PA.SeekMode) (uint64, *rustC2PA.Error) { + return seekStream(s.ReadWriteSeeker, pos, mode) +} + +func (s *C2PAStreamWriter) WriteStream(data []byte) (uint64, *rustC2PA.Error) { + return writeStream(s.ReadWriteSeeker, data) +} + +func readStream(r io.ReadSeeker, length uint64) ([]byte, *rustC2PA.Error) { // fmt.Printf("read length=%d\n", length) bs := make([]byte, length) - read, err := s.ReadSeeker.Read(bs) + read, err := r.Read(bs) if err != nil { if errors.Is(err, io.EOF) { if read == 0 { @@ -30,7 +59,7 @@ func (s *C2PAStreamReader) ReadStream(length uint64) ([]byte, *rustC2PA.Error) { return []byte{}, rustC2PA.NewErrorIo(err.Error()) } if uint64(read) < length { - partial := bs[read:] + partial := bs[:read] // fmt.Printf("read returning partial read=%d len=%d\n", read, len(partial)) return partial, nil } @@ -38,7 +67,7 @@ func (s *C2PAStreamReader) ReadStream(length uint64) ([]byte, *rustC2PA.Error) { return bs, nil } -func (s *C2PAStreamReader) SeekStream(pos int64, mode rustC2PA.SeekMode) (uint64, *rustC2PA.Error) { +func seekStream(r io.ReadSeeker, pos int64, mode rustC2PA.SeekMode) (uint64, *rustC2PA.Error) { // fmt.Printf("seek pos=%d\n", pos) var seekMode int if mode == rustC2PA.SeekModeCurrent { @@ -51,32 +80,15 @@ func (s *C2PAStreamReader) SeekStream(pos int64, mode rustC2PA.SeekMode) (uint64 // fmt.Printf("seek mode unsupported mode=%d\n", mode) return 0, rustC2PA.NewErrorNotSupported(fmt.Sprintf("unknown seek mode: %d", mode)) } - newPos, err := s.ReadSeeker.Seek(pos, seekMode) + newPos, err := r.Seek(pos, seekMode) if err != nil { return 0, rustC2PA.NewErrorIo(err.Error()) } return uint64(newPos), nil } -func (s *C2PAStreamReader) WriteStream(data []byte) (uint64, *rustC2PA.Error) { - return 0, rustC2PA.NewErrorIo("Writing is not implemented for C2PAStreamReader") -} - -// Wrapped io.Writer for passing to Rust. Doesn't read or seek. -type C2PAStreamWriter struct { - io.Writer -} - -func (s *C2PAStreamWriter) ReadStream(length uint64) ([]byte, *rustC2PA.Error) { - return nil, rustC2PA.NewErrorIo("Reading is not implemented for C2PAStreamWriter") -} - -func (s *C2PAStreamWriter) SeekStream(pos int64, mode rustC2PA.SeekMode) (uint64, *rustC2PA.Error) { - return 0, rustC2PA.NewErrorIo("Seeking is not implemented for C2PAStreamWriter") -} - -func (s *C2PAStreamWriter) WriteStream(data []byte) (uint64, *rustC2PA.Error) { - wrote, err := s.Writer.Write(data) +func writeStream(w io.ReadWriteSeeker, data []byte) (uint64, *rustC2PA.Error) { + wrote, err := w.Write(data) if err != nil { return uint64(wrote), rustC2PA.NewErrorIo(err.Error()) } diff --git a/tests/fixtures/video.mp4 b/tests/fixtures/video.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..62b4c60acab951de47e66657498977821b0a4490 GIT binary patch literal 331029 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

hB-Qy{`WvCZwsgIOXS$<8%`w_x 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

N%j z>(^OG6|QiR)?3v_@@pYNo$9g+*FSq1(Vp!(URI?zl8SFbx=LibJ{V&z8TiH)E|85tBVKra5MwEoK&*k{`> z|D|NZ8GYr8TBQ^QGVFSoi8N~?c1`RmA!g!6(w5Crjcr|VE&o+$!3afDkM&H*b@;L+ z4%&*JICzAKU-7ujxDOn009ye}p&-RSJ1}s~vO;S{Iv^x?FdjB*0_EdoQQGU?)Zk#) zpv3kU@9Lzc=@qXzzb=hVGjs-j0(z>$sSC3)XbR*2>dbQ}ghzPNz?m$pJt44@h&3~p z4j{&Z1{Vv`ajGb1^XeEMB*d>EK;4M#sQbf%#v^mqX;vYv%hfsFAmsm$DmJ_DD}k{U zJSCSlzY~E(h?!20sztsQn;)dHwbyB5=kLd>q`!R?#ZLjdUa^H_8@Tj{Y;(-~&27n? zh3dSFb$@hdtL1aguX>on!Ga3pGC3AnY7mZ%>Ql?*h5FiZB2;Y6{x$?bROhno zCQw|lkkH&4%HpB`mZdRcWH?Y_A`1oT*C#x+<{eDALz;u35S7Wr(dAL|F`XPqVN>Bo z!V+1wwg9OHL9 zUw2=>-**dqWoMqtG_EhXG8Remj3t{hc(%V1VkibK+ENt!zDmDNy_SwG!jxrr(q?XW zJ-h37u5q~f2pGxl^KGI&Y=)(!Z|stwxZnCs!xoWz(8HP8bfsz_)Av&BxyYe5av)%zB@hKt8Ei^oEw|- zWY^$F-zV&@hv!CI`grF4b5MF_q^nGjVcz-1l&@04sV1X{XOrBr2TG~O2r@sa13SRR zJl~p0JY$Y497)&Yu%HiOhiIe*)il~Q?)Cw{VxLnkThKNhON1G6{1fO9fI4E}-Xq4; zJfSlsG8UAOi4fF!7CxJTd};HURV-N80tJzvKZY#c_^wMw0Q0iA?BP>(mJk=W zky6?Ck;s7a@W+%q{F~YI>Ru!{KJ=;wg;hnj^iH#Nw@%q&s2$%}`azny35|l4j*NGz z@UU{yGdFWWM!3?6NCq-0tWdr|UgmxDOnOb13!flAzxv_HZQ}Kh%yU2e(!4hBMbxFt zZtlQPf!khwpUOi(&%~DM(EJ|zm8`~ve-hfT1$N@oJXKfVO0KWHTWgy4yY zCs0*VqQJpJQk86MYhqeg6p`qu)|TsBUvT7!o^^YW3HESCO1ttjjdVPIcaCTIx~apk zVnK7hVq|=Q&4g=`hDUAzx}oK53Uc95>FiNc6@|fdscj>H+TLq94W;9F?$0l-NtM~_ zP(4vh#`p_>b>?xT33Iq*N))>(XIcK~fw8Cf( zTt$_bq=t{1^UlOIR0eUyIQ*!T4TeIjJ$@>I14y_G8{ z3V)!mD__t{{&wPWe4w`=SA{L0EG_=oS<*|U%+Ya+T<5no^%wd3m}Gx+PAt__YgOjW z%RRbm+FEDpPu*KDh;ECiB<;SCi6tc4XXROd-Um%XzO+=cMp0gP>Zv0^b!BgR(>8+8}$eB_k=(v1o1$Hb}vE z8kZ|^|MsiOz{Mpx77|jr*MV8g1L3WwR z?gI1?Ovt)YXnnWWd3kTzq3$pfw9;6+x@01UUJw$&2E!sUNIOskaE=sw@;~SJW=(o0 zS3y+$i3v8_d8LMN+M9F9&zwW#{V5O4u89RI16hAGIU>hsxfV(Wz)HOG2ZuE1YF5(p zIhW~742eD<_~Re;q$`HcWl!=oMD#ZVzS!~__gFgj9Nx*!d&|{34qV3=*wr?eWudc> z7Iye$*dL}A@kGv5db99kmIC6+6!l4KXDN)vR9KQ1+-}EbZ^{x!zgaS8!p?XM>-)HR zDhAMFnEzO{MG4>}!jYy$2mvNuggHSAQBqkLh{|4^m61x_ldd`s1*>8Qx7w$_2pHQt zO2F^vOM>LV_X9uvf{YV5HOXf>#1hZW4;cr9yA%)|N~}cLD)VeQNR5Zx`$gbf$x;D} z#*#+pr-y-aoVZrgUs`8Pmv+i3%@OQVmcR zyx?ArxC)zHU~QL5kU9h6Sl)N$I9Bp)xbvn<+#wZwV^(blt z`SeY8T_%;`$9DB_eFO#olw-Zx)ZyD&2$Icb^z>hX-B&OuyI#C++56l80RwS%H}4;! z5O|39hcBPnpChSM%23Ttbd<}bKLb0$1IZv3y3|f6L7}x--62tR{$)lW@W%dM=ldGx zB;_@Qs`YB*ra?@o$>Ez;Q?`5=8rpzsk2f0yL8Pnq79#9k2KLRaRT-im6O#?X0AA%O z8dqXP(&kQOr=xgQT1OVW*HA}El`ya~9gkONv^G_fKbji@H49fd&G_+H_N$gD^kO$2 zid(IS!RoXIo7wv(gLxT*3DE#OgF-!}szfO+>k*bMg+v7rjZbOZbGoQH^APm^Wm!NU zXimzQe?2RuAV#>9&|G&3jk4RYou;f6MyYo`|NXS|zQ;Rsf;;hgI><~ly`sP`?aCaX z#?!R^CEo9%i94Gd%=2ur*et~o7;C~2$i|e)bHX@itc)Z)J?Sf@kKRIuAAqDPhn@pfN7>$SR?tBxL1MWv^{$@$}xr~wFZ zQMpIqr{-Dt>T;snK&FJy_T(7-rE=6Zg28zbLbnoOX>kzHVF4hr=+$Wn^r!)zDUMol z?gZFQe-v>48C(z~w-yM6i_L(=VYHmTw&?nHN~;j4>`f6MB6p|agG{&kt2$*H=UT{! zYP`j|^2OHk^(;q0l(ZT@-9>`L;&StWl~9NXL?5VYI*br9D=`vu0`zrh9PirjMYDiP z-ktZ)Y%f^=78UlrQn##=Z&#n)r6XNFlEa)yzex_W*II-h`V2q{!jMTScHC{|EutR6 zD=rXJWg)E$O#Z=6sLg`3l=O5QTTdIOH}oi3a#nFVx=Ae#CB4W#q9AU*sM41AENMzX zTH~9N{ZOL-JZL3-h#%+HMwN{u-_H^=f5VAy7)L9SkT2Wp*@ai#n$8OAv{^}CPo$YC z*AI20nnj?3b7f7mbTD3g2<^|u+PBA5PtQ-!p3mEn6L%N)+dVGxV_Pk5pv?dvYeftb>8>N2?^Y; zJTG+h6aWCfi|ePHW53PAqow|mk>fdYBEEq!Lxe$%?OGk(?3g6V?~)cR+EQ1FArP!8 z{^Q2nK`bJ2z*9?LkLN!M;h;b|ho=7l;Y9EqVa};3ZF{hGC&_hVLJnw({NEsjn@!!- zJzQ9b!K(We77mAZ>;4VmNM(T&P>|z&uGW>E{f8OGm!|jrKWAs5Z+N64oB5eoTfuhA zx@07RgP+jXzBKZPY$`9--_JSgyp0slU;O!{oi#I-AHuA1oslkg|C)-1H|{&RKvKP7 zhcU7RFk8BiVKC~ic%K0e`IQ8OQo?bDgz-YUbRoXiHX2QOd>`kM8P2lrAiB#LB3t|n zeaP;y`+}h)h=w{AYT&7jwQ-k-`WmK%|5A9}0S`gl@hu9k!nJ)Nk3f)CeH?J{2{Gj? zs4DyAXPISt8I1?=Hk;DmP--wCgQ_D9@t|svydthL;7%jKAnI0#edo5}ceF7WMf#Kyh?Z+M7%Z)Fir^f~#X2{(|cRAsubF+`6Pxx1WU zabc+1%6f+-OLRW`PC^6!y4=Ej&j=tDaErS$+O;n_TaukIRJnHxx5#gl4lQKLmd~mw z-0-*NSNCJYxJi^nw4MdM;$bWn{73))`XJy)Fn8hq;9)^(;x=q9I#BAcS}PqOV4)oP z=z)-5@>~BgcgD13?MrYHx=Z+oz*9H3eM~1izuS{&!c|#0q zgh76Y(>G1o)r0?_7MZ7;1y#x}=c%b(W>2Z0xnZEaHlQo#lDEEdlBRB4)0^M!dc9Kzrov0<2Av}1bw+&hHICmyZ#vxy9Nb)K`bG~E-d(abdH{%*ZZ>Bc0r+iT?8h-QCCok}5splN zKk!1=7SS-2@qt=V>02_#Ftwy(!-??eDBJJQEX%)tU;9Wq&3dY?eGcwuT#*-VwZbt| z`CE6v)Vg7pe5tn)Piwo{c&T~h5!-SNoDvl7?c;?HcTgr((vU-1(5Ly4`fQ~^g7k{r z1tc&Lv|Xq@aKG?zpS}I$f231|wVjIN0+?XNKrCSgD(lI< zZ>Z-`QffU`L^xDm4>*2(<9KLcS@|qm-25EblnXGxF4?0UG>ZTw7VO@3%At0_Ml710>2e|!sKztgTG!|~%B5CfY@(*3YW1n@|_BpF zf!};-oNR{7>Gus6o=n*XQfY3@IDX*}FhN2c$`;flq??38>lRqxRsoA6xFxhp7w!)P zKB%i(6M;DrdcEQy_8&bh;w&(>x-)+7hWp>L4o5lVb-pvUO`Z09p1XN9Zo&r{$V66% z&Vp*W0IP@;djCAoP|9I+i}=WPS+`}$VFM{L1)XmD!x}bk2+(@E0Q8~J(lp(;DBVws zjU5qz!Izc<7j3&t;mw8&nSjoRQHUqHGDFP}ABG$N(Uf&jUJ_%Z<^ z1uCk^7UW?VkV_+xz)im18vX1BPL$xTO_qy5G(i-@U~~|q1i^0OXxrZ%XAQ+x0W00Y zrmy#R8*7?FvbGm_)=*Dj9;M$GFvm885!A5b*U?bgDy(+)s~LWaS=PD?IgRqol%fy0 z9Ru}TF0nmFXnECt)EfJ*5ju*yh|0c3Mm^(s!Jm7SFK?-;JKm~#`%-fD;7TXCd1gJ^ zKX<+B`64pi@Y_-_aytFl<+^o5(D873_HO@_e@E1Qm%jnL_kL`RI&}eHEEBW}_lnn!`@LNobM%bO;KwR?4FvBd@T&$dn= zx%a>lV}F^1jHSWK>N~880i%(*u@B!Tijgs~=?&RpRdo2w{3}|(I0k-$c1=Yal`bF7I5RTh+OE0sZ=W0(4=4TR znhFvhjkanNVpFt<&T{@|6aC1Y-}~fPuS>3x7(Holbol6uSD({zR}Y z*ZuS3NNU8zlj8XA!3yaG$1}ahgs_Drl?ylEQl_7MUAcb6kiR&H1;UdQBLehkrStW% zchuKwKa#YoJl@oyv~Ufu?B#xmpz4#BXSr8x9O8G=uRxH+GBTSspUxAs?SveHZYsJb zU_y-mz@Ed_4#|%J8O&uo0vU@D{Z^3pj?$AR<_|OqU^>5LAc)s^pYQnTI4=90@36pE zoW>)cO0AWug?v?z0}~VXIu_uAMoe@Po#xUF!S+W^$}LEzu6iIFK#8~FY%{T z3SS01BBn*q1=&m>XMi%bhn3tk7Y43`N}u((``4XLOLbqd9;t&d7TI{K)ZSfpsL;Nc zMez4>7G`|CT?J6 zdvXf5BzmPMrg|ti9q7SF1SN7+v&(ovW&t!1zgl<1*3@pEpHI$qMR;7L!paLCz?Z^)i_|%9xeQ>pA7NLjCj3++`Sk{PWbHfKqj^qg@B6vlT4~T}D zB#;ez-$ECUJ{4ITLc@<}&%}LVr8n>}+=nuVt%w!3z1oGjz3A_Q3em6I*&<54<4-r> zR*;5w4kFaen{6*lBb*)$3!l0HW_xLI`oBN?35TOaARoqo2g1x9}B zhcqZHOTK+`+L`hyqhTjEJlHR1DKx%U<R$X0xy{d(+dKzLZpB?pWjZx z`53_k$z&R@7xHL6oSmxJ-wZ|_AYEO$-MtW}UVfBgp;oDcX++1SVBo}1{SKXOyiL59 zLy!wU&}k3d)}43VG;ja4fmAEwS>51l5@5FCXx$GI@i{X!!#F$~!c~FZjq}`u%ADZ8 zarW9o5zf@&VKSk!1SAYr4F!-OP*J6rpwH$YH~tOX=svjXbF9-{s~IIBlF?P&c0*uC zBEq;%qUr)bvEGCIuEPp5OcYhXBImX1e%}v=A2R=&hWq=J{@EXhcm~Qlz@3GRA>se< za7vTWcmADBmZa=<`^|~Zr=NAMq5=&?SOOnmceGLoej?2H!9M|0hnb--&mK@5`W<@M zrJ<5cvuVS(}$0^UK!NOy2w>_u{K)>u~h?t8&v%Z9bHngS3xqRf!ma6c*wf-Ez~8C+x-%NNi9- zU=RXtf-F2&J}V_1(y2$c)1}Ax1nlE)ojs>+%jd)2m-~5&e?)_??fRg9tKF!nZ*5j@ zC5ypCR&ksW(ewe|k^)oy zcLhBwn8Pm0Dudy-!5BV(*HN3s!{Qs^Yzpa~B;}e(DI~mSVED&*Z&P`@l1WB87?eAw z1q-kK`?`D5sSHni2S~e772)-nI3C5ishu|ma3WT9+_cN9s_hKG$xdQ(4fjwGbbC3ut>e8wxVhi*oP1BA|NiBMIf zIS?QHHx`yRJ7H4F`9eqrw^k*xLmp>dF}KiDSVNsPT1f`&;u;x^S$I3~4DU__%Q5`m zSFw3w6EU-~%7%8v`C#SC%H^d}obqVWv`Tww-aXoRPK3ZD*J0z<(qLv##l#!V_HPXV zaJ|oW^jw|Ob6yHJm)j`%3R|ZG!#+Wx-7En>R1D-0vkqhIk9Yck=fr5fpD+8y&m0tq zJ&$RA2d{jD>pfoA;1K@Kqq&(2Hu^2zU2lHrznVkCX>}&zgix!D z|KTIR8ze^^SmdOf|2be$RzF9kK$%Gu3&1{PE+2C92YZeJwg?~h%@)k9@?==UD& zNANTi?ARBOL+7PZHkC8O(3d(5!)wuU<-pj623Z15C*h4khzy7oup(gd1+uo*RQpu& zJZ|vKbEt7Yg%%%cB|Qf#XBi6h#a+KdJ<_l>RsCkeJX@lN-JD*O&ZyYy1J278(45oT z(k#FuGM_f?Eeq|IO1c_9!q}7gP!Stpop4rp2UY*7A5ShkunSkNY!1H`7yjkgqOM}^5R^%1+JV$7Ix0N>K?zI{1I|$n1n%H z1yulCOtNi1>-?>kB~cbXSHtpSxs0U4Hv*L5HNW^uByoo0fP>>Iefjj$ zPL1U8VGhNtRoy`los%nB_i_anjX;{9h+H`nQ7;|a9G1?4WyftsFxqdBA{ zG*QZ6{nQxzWin9kX(s7{FT3!Od8D3@s{yzf6z@Y$ZRZcxdSb8?O)8xj?Mti_ z__|3NDE+(G(r2Z+Mmz?Ci_+1wqYTQgO&k7GxBe0V+ny#A^Dlr4gcH@cn;(X0sG29I z0f9&}%5sy<;K@ngpwO7POMrpBvW@4b4XnaP+f~A}X18av`Ol`V0IMLG8nPiBRyBt_ zkI7uPzW8<%g*N#8McN863yEsn(**^v1Nf#4TRMA(UkFdunAC2Hq7HSENSz;a2bc9P zDsHYz&dWOQ?yVRVf3bX!jigC5T~Xr3U|8!8vXYBhMdWYgo6l&OYtGLr>7KX78>b|< z_v{@cLbU$Iv($9HQS#;)P*}_9ckx_+ZGZ9P$V!dt$ea5nuM_eu5T5&{<)I*^g86H zQ?-c1P7~4?W+F}M#VJAH(G@gdjwZGC-eJW_c!Y?hqoA`^fa(%b zYcg)ET5}ICH-^gRd$0(FU$5!|GT@I)Az`Pg`P@Dr5|*OQPI8nm3I*{tdTksK{56~) zk?CJB;cGT#XRRA%KQP2y+C1#Rz86k&yPKNfh^ z$f-ZnnXh5KmGZS34p{KhnxlTV>}|5rS39ZD`It^8I<<~fqWFwX&Mf@aHOBzD`#m%k z62{k5f)n+P6NWg9`jO(*)^~0G|Hw> zx&MjB`=%}cqf?%=5FD%>0;-fjy7g2xfXz?6{H=lySMkiJq?mNPZV;hAzOkncy(YE5 zN%!wzHky_ReNZ!Q9fIrA&SV**5&%^lhN+DC4AySOzcyq+q*CMJEadxC>b3}gT9*h& z^g#vp&;Up;soj)%e=&H{18vvquKB^|D;d4yefSF=O~DM13Tnv%@h6uwS<&q0Pa~K@ zE+~-@zDh2BJ})_QxyRHRbKL23{4aFdLm)Z4d(!KLcr~X9E^@DqT=7Bo--@%I#k?`H zPrBn>n=n$we09$cCBtS0dU{;`?^`RSgD-wy&yCB5k*VH}J< zp8P&_XYRj%lM9_E-PT>j92`4CJki!FgRW$JaAN!Gz|{{Tu9|7Vb1=#0sxie zU-bWvv3CrwWZS-ncg&8{v2EM7Z95&?wr$(#*tXf}?$~z6`0sPhJ@@|JZ*P65{ZyWM zviGXF<{Gos9CHp3ks)UEGR}Y3Z2&JwUMFy4Z<_EbsoEBX&Z)Gheg6IAne;U%y(Ak6 zU(oY@&zG6YyvpaMvrK#WH9tTCi)@Vw?}%b}eqI!asFKZQ^PovtqX7x`#I-12LX!Qg z&vI>as#@f`RHk8;`;O9AW}p)~Hq85_05xXjfD z2PlD@Z~05vUs@8Pj5qK@rIT&N;HZ-=y(~5HdD`Q#-G9=sm6yX*k`w53S5>E|X~Eos z3SJ*a9Jb9KtaQEUVVlqLFss=x>RGY7#V3%YUIZxh-M1)4&ZpfcTOr4pi>+~>sEH|e zcAk`lQH__CP{AB_-0iw<`Zzv6lpa{fzqe5y74zruP6vv8q`gub*@BA)#&QXd<#AXv zLAeL|x*O!$YpcsE_AAkv5y`h{aTv7RuFZ)_NvCg!gwf2fyec0p`+$RbEAebk zc;FSAXX3u;a$z_si#NKWL-Kth`io<7BEg6dWNgH%G3?s15o#${`;kr82hPHe$yZL3 z(r+^>+&?vNhu&|nV=K5uiHNY}mDU6mL4$sciDX8?X5GCwM%}5nQN!B!s$QMBA9+6| zgD9)wQ3tEKmocyhMq365moo7W7=?-Z@40|iPiv0Zx0r{H(EpyfOrW}M&A?W)`8fzN z4Day$QfXMfoRJti-;!y9z71Mm+B$=s%?2$tnQF=;;DIz)%vvD%v$)f}$Cbpgb%ji5 zxjmGWDl|UD0anz_NFmG)W9L_!e;cmCi_xL~DKx{>jl?@X;{%s^46w%=ip z@-T5wR*ex>L?<#zx&PdeKJSeh<=(?X>y>_Q^;piZx4XVRAs^4O`q+LoX-o8=imhQi zztkmtHLRK10+a#hAKRqN-Rs*lma^Ix|Ia6Iu8tg^IYe~ZN(2jjdEGdGPd>0MlTgdO zk)=h1nVQytha$R8nvt5KWvw7o$Ht$o`uA;w(qSqhSz4ds0A-RUSRWRLJ_I0J0f|kg zbsC2W^V-AJ^V)wuqFC%%g?|8&{$IBRO%}>3{7t^no^!_#m{7#1+My9XBE#8K|Anaji24+p2qQi+f;FVpnQg zS$u*_uTd5vEgBw~Di;ZWSa)t8ZC;nm4S(k6S*#KS$pz)aMGV>=3_R8JIbBt8?)i3i zmPn>u0E1w+y#U3$<^t8LH7!!zPo+VAg3H++7i6>XDYziLiJ@o@M;8l9YnoF7AoImV z-k>o3YRsB!16~|uT(hE5fG-=uXsbPS8Bi8gk67?X6z67;K1H<5)RKM}^{9{1Vcuz_~;Ouj^c-({yq)%rp$$$=r^PCcU zoDWRHE|v)Hi|)4mweC{XL~4(pN!nEipA=UXa*tLDOo8W$2S{?8h%71RW5Ic-q*Es( z-)vEZ#hf3Byj}_nqqaF%*q&M4;JznlUBs%UwDsNf5;}~lN1$Aef?a$RU^48bSR%Pj z9k1+CL>WUI&8a?jUZ(~>Cbm9In!c)TYDg{u@G-e^FFQzyW{CW-qa$Jp9}lJsoqSAr zkCcxHwCnfEB%}yU;M~NJKXTdQKOfynP_m32?;!i=NE>3~skEqn%Pd{*-SJ=_{>iDlDQr5q@3b6`LiPInphAI)% zV<_~(h5ntKfJ>pgEnrUw;NA-YWczJ=_dw;X{mri)43uTI#4-ssoFo@%&-Az$%GZ+3q$xcs@zHy& zkM4gXw?=Lobf-d*@8KcEybG&PP-f2FA8khV%~bA>UjO(B6DB~%P7yLRKnO?B5fj`% zRt4D2O6i%Ml>)^hg5VT=5$8@LQ+kpDHPUtTOq z;ysC1^^sIw1nxCQVeEBK$X^?U7^S3X=kbDPO6xdEZ=^psUj z;!Q$v)|8*)pd`i9%5Nrjos6YgTw^4i{2f&G5{G8F#0Xs5hOjzc6ECA9RVAaXOnv8^ zKP0+8<}PK+R`4A}Q|Z5B($b@ut@=pR7Ur&J`{+RBHSY2>4|Vb|ftN>L>=8KD=%Y1~ zLQr~)33w%0o@kvK8Ka5@d3x}5?C0|Vj6e>}a>Ul| zvHNNY1_ljISHw-ri~vW}uia8@Nb2&^8|!6SvDrS0@tM)zdvR^WZxE`_fZ!uln6C2- znucf;%xp-FER7^qs9&XKz;@wLm8k1<)==qdpq?o&*ycw%R?4eXsxTp9hS)sPeUKe@ zL4M}nC$Jw|HmTVfiwuuEEH^@07{`wzM*>S zc2w`1B*A{@St&U+oMg-)KrV+FfhXu=)}Gfv*yisJl;s)RyI^s|jDx$|?C7O~p}nbi zK$8X7-n3Kjqvqe{0BC%*MVn2j-J7fj2Yd~ZZ(1BPJ`+y%n@oCc{^LJk*Zi47auhcQ zy!Y?)5x^BBZyOkD^rhKy{ah21cqC+WMPo2mo-%7z^-JazPLEWUx{S^JK}Rgi&Ispz z)olb50;&5;HKvDTRTBGk)V9G1kQ*_cA?O+N*s?Mhk%lqu$tJF2Y{P8he?EBfo7O40 ze#QsEVx}EZi+AaHb@tm06OJA*k1tu?>;DAoA2CJ}c~8m*0~5Vjs&B1m<1UC|6YX+e z%Y{I0a~1J@RmD`y)Rj%La)wzysLdjc76&fDU3h;5k8|?85tV=kj?6HKByZ2^>(hw9 zT)z8RIKz}u*`wS639SKm*z|>>bYM2C1zBC4zJVL8;uCeV!x7aLBaSxFy;Cba@4H9C zZz7@hlUIzrgSkF-32)jbNh|jDP)^XHfXnB{BJ(!qC6{p1z&HtP2MvIpYSwN$$#%Y|E#PzYdwTM=I4=0Oe~-SoJ=)49=}GhX=j*%U?Dggx z{xuV@mL}`qKUqHV&q=opdeNC;Yr5hs%IHR_n3 za62L{9=B%-2|2oHi;dC@Bwd5BjbTV@$P8guMp#+Zi$V;0Vu})WOqF(a;7Yr*5K6|v zs`Fp(#P2C!zQ()qz z(A>m5@FrmzECTEXTpvT2Oi#-IO35l8)r0O{WFVn#a2GjQ#09N%n1x$E1h$4#FxWP> zv*%oj4*M>~padVX+XxM?n3NM0K_Qf>V~W@Mih72MeZY?$38ziwtPu0`TJI9um&Cmn z__E~0&V?_0ZPBsO3>aId$H(GU?T%+C7|3-KA%aCK_Bw|yI*p57({_cteEH*Swnoe4} zCNtVA$DP3@V{406xjCof?#On^nog^qi22%FHT~hC=fUbY5;O;4_mlnD&^kOvnXI^-e z5=?)CAGocD6LB>7=It+(q}<1_GM7GDipIE?7YbL1)KSMoqoUu_<1lBBuUD;=Wv{<^ zrEo-F))%mMK^O3vKw1cs6?T_XT&w&F#wm-Z$+(v0T>eXK>IFmLbLq2`2XF3E>ar_e z=F_73oja2mKwTq1tZDT-91Pt_AuFm0-<69ZFnlPof%#=_ z1%CHas>V=yNQU%owDuyd$)x#QlDh{Lj*$%%`Px`Jm}F>C#jxha8}PaZ*ASO`DTM>p zvZ6=a(gHkk7f6 zt_wI2W$=CjHy0jlBmW&GkY7R!1o)peW*rF%bl_MI%)KGnp1`(+z30!_yQ6{7l*o%6 zFR;-+H97Q7gi{PDm$+RZb+;K4C#JEs-MW}+I>$P+$wDmyeT|$Sc06#KFN>aMec`L4 zY-&})(-`nEBA~rNOApt7l*-u?!?YkDNGo~(=kTA_yHn7xtXNd!X;}@OR6WX?c75r9 ztljRTFLNNMC`Is#@Y|Z%tW%PK&+g=cf+BXS0|6ZwuK=k9C~L&kUWTv=Texj<7H;w} z2?_m=>mpW746pz(8gotN1@@l>ExUxgHlSDHb5kEix!)ux3t`lAlwqaeWIWL;OWoQW zmi9m2G<;4mvV;V?u*xg2n*)2yoZ1`OYKbb#j6$+i%ABlNBfR+H@U4A;Ax%bnxMT0e- z-2oI7*~}AnSSyqogMZ|K1`RKc?srKhrqs0i-lFEjE@=E;OG7 z5|!NB+rZXQ7WQpb)O4;@L@5Ec~9J(6PM@yyGA0v8Yfs)qlf+%T)OuC8X9Y^{_ve0I^4=9l8xlvq;X!jim1qPs86uN)GJD)`9Y~Q_&FnxXC(c+ACd5rNx z9NSbuongoY#ErT&L(wpzQ19nSR>?6BCN4UmglW_TNn2tgS3$W&%0ubpB=&H&^^V&? z5IE+OhmGl7`-qzW)vKRi;cqNOJU^>jv<7qAa(MlIV#{G0prYNV*VoeS8*e%8quQ8k zX@HTXd%~X1D5<=0_phN@y0_Xw5vAB*&eijf)SiO+dxY0o-n?F_`h-xuTqy-^idb|} zXd2m)z7`xkSK_*Z=uKY-0yI$2V;Pox60-HrHU^>M9~mI6G7s<_K)uerSGWeh%oY#} z{qu`lf26CM7+P=e=ABXa2!t{L$WO8vD3Ga)7v!**ZEIO*jIh{OBZDi@XX z`Fr|Jvae~|&^pm{MA!;f1|J`wL0J>RRhSWY zuq|D~)!n<_OSNhvt!B%y?-+c$f*6~UHk)ntV<4Cyu10OTWNuBBl%*2_Lg^^3Hyyfc z@n0#|IS1z;BUbdyfPmFM-e{#_z8ngu)PcdF?SB?wI|Z2mrehNjC1 z#&vTv&KZtC06086my5>nId#b$17q4``l#Z!z$y;^W-U2O&Tzu!y=R4~&@?rlEvO-x$*b!4VnjSpNRE$o0|7Q|I;7YUS3~B$@ZNoPRI-0n;=*9O% zTK$2;3P^F!4S2oAttun=fppBq2%FW8po`1eG)|KHO!v8NJAc`Wp9xHiNRf zTc*y_PovoLw!n&V?O38uMY@PFS-yF?kip%y{H|!|>T5 z4^R5C_PJBs_>tVDWkTmb8*<6@HhZk|JXs~V%zc0dMzjf}{c~OFVyB97$PEIT-F##- zu?6jln%y`s>oGE^wXY4Pw>VJ1bU^2dA@&tMZIH?=H9SxN6AjW{jO=Cfa3N^ zbyfT3{bqW#ZZ0ZGD0_p4gCz8hG)NLq5wj!(ua>`xML6|vN z$3AquoKefeEySWgTJ!n-RU?Q08V<%c9B+xieOCsOGR{dn5Rq-}+)`7q(J4UW0njkb zjAx%8Kshw=M%>koRbQWVvy}xhDH-U!n(cA;?~G}!PQ;!dQk}~eUaq#!IjWsaprF|k zSkTNwxS1&QcV+#_@g!LJ_%+*EKN7TkxCpptLIJ(BmKP^$Nt{g;-O0j133wc)f7p}S z4c$WUv9yVGeXgspG?6X0<-RhZQ>*Ky_`=IrU^DV2>AKu<-!%rKhUNC2X_pMo6s?n2 zJ5zX<)KuAWf|Z?r8f|Y@jznn{sAW@*U0bkT5MJ{T_b+{cZrH|z(HFl!)j${4K{$e! zQZr{!<4j;Xzfj?e=ueVoaabaO4s&+aRyVtf)X-d-lJH^Ozz!2*rKCULrG#Z%1r zk+2g$A9N6@{?iPN7tdnu-Nq)JB2h#4JN@Jt&&Kwrg~5WB_vaS@KWQU%Zm4M8Cx+@@ zgO1u*5kPYR4-jaZwM--}N9|!I*%lJQUE2Y+72u}Czg*x4p=_If!T~f{RR49%RAXiB z{;x8*&<5f#crd~D2R}lW_Il0sC+flJ$62zr3(xCp^npq(=LNj{TNGcg5NYXlP`4VF zCkU9_)`5wAk3kqa30;+RG=sT;cE5nR-R)N40`~-AKrgB6%VefLQNGW zBwH#kwVAC)#5R=sp!Bbsi{xiEThZLhUP)^OFY4FT+QrvlTfHTOY%MB znIYoJpTMp}Nt@-8(~f~)X_@qJ?Huq*ci7Q>xH%|!j3m0379NKJ<_{gLP~6pzMxsm) z_}4~;Ha9t->6_l;-J=WH+?pc_Y^)F+#7TZb3WQ$CY;uTNkvF~+3t4BG^_WceAp=P6 zVV}qnopr;bQ(iz?Hw=c)BCIjX1}PWYE*tWg_&Sd}IzEUq@#miSvhPBF6+TJmA``e5 z;4wc(Ip7Ip>NR)u-D%CC{9VD(ELZL|n>#k>NOUmJ8w-V-M4a7wa;7jQveH0i11Akj zC=9HmRRsJO@!(_G{@EP)OKPsQy0W|nMfjvG*`cRNFwBl;iBDI0xu335(pPqmalka9 zFni@S#+ndW)bj3B6FMGYX{Nm4H>SN=+gz#W?0wbuvz6|-w^hF9t(vU>V+nnk8WnY@ z67klD!}}9m{--9tClDZj%LHyd^nIjdO^8@3MXwMi&;AI#T=Jnl*>WVW7DN(ZM*gT+ z)K*OnlTFTl#P~5dZHbi*x(>~pQKivpd9RV~%3AlcpO=+EXGJ@=;mDR#)<|;lltw=% zHOtBTkKk#xf}_fADIxL%XQzrUZc2{Ff`>;tqi^9pA{2aXNYC_J&Y{={_F`}I!>u1f z*W0JUj^ogE#@SC1m(+9X3)hS`V$irTBsRS)0o>BANLS5usP%(@Z3J9EE$A#x{= ztc3tXp9TOZJJ%_^<6B)e0mLL+X;`rf@xf`7Tz;IX_$DQ~RxZ-o#;6zBuO`+$9k_9}I0y|?t#soD5r&LQW3A%Y`{m2Q|IWSdY7KSt<%`ezE9&I>?!ev1i5w93ED^{ zGUB2{JNiqXV^9M!Dfd{9Oo2Lp>}6%&lY}Dvloa`l-=6&rUq@=O_MQfp4OPO6It4`I~~mN$@ujzwv8TBSwm`U4FPgZnn(`fmWX&s*6;@b$2iz1M)&*THD}3T8kxZN=cDVI#qqZ-F1JJP1#Jd0{vaW-vGo zJxi)-{IO zu^c?w^2DU**yg*{0mUjLY5k2Kbjpcvo`63Xu(oU@;;=+>=z(tk;t;Az)n{qG!|_sXCI~NQUYSp$!{PV!ae02qwA5ckv_k+ORjzL z;v!s`m{l28<-McOtTD--c{~=+Szs=CUL-LjyPn~>Ccb{A-$=Fk61RAFb6!04489Vy zQm%Cql4~sWMEfT4yFke;HroaLu2N~Q4-E$|BwS;qOz0|y8iGWDl-R`x6U?ioFHiNg zAQzu&{C65>chtNM#?+m5h z-j|;N9(6Tss{P2;vfI8&LYaDn&S<4a)uKj`){H_>r-T}O#{xvzj3)*=O{<)FAUgcRccZ?@-e=+d(od%= zeV+1kn@s*0Bnvn8)tb7mT~V1q zMd(q}aDGXlz>9(bHD=X$2zx)xhE^fIO@ww>s^$`dS%H)}-d7CQ zyirJGH$CWtEDN??b>nausyA3vB~M-CT1D-k%UEW?9w(Ex`c*S$)>GseC%a^Apj=nq zztaZLA(UVHH~;(z{*87*mm>!l+wm_#5Xf7q)|cEb?MbC?XFWDZxlnF-zSnOKI@n7k0s!X z$cU5Jd22=QLN6^iWt~O`9qb6FLwwi!Y`ourGN07Q<2Om-)7NY8BFcm{2LX_1R$q^^ zLj3Vi(GTtzu|9I7uzLIEc0cf1LsIiH&5?J=4{xt@U{>w4_w(~3EN4ubc`?=OC~t>M zOl&xkK8FMJ3n{qjKDaKDDT%6^4aOH}*h&e>N%UyRShYRO^0<)sA@n-ZEWhlZM?4jl z0o8J;5^5a5T=F;qNIaw)k8KO4WGry^Q7ZW3r-d{OV8Xj&lB?|xRZ2@Yltw?J`fP=j}A zDm2wvEL0(6D(gH~nHS0`I|zGW;$09uX%drenug9hAE77;UMr{orhrnD&ld0I9Q6J( z8UPaqG-YBxCY-Z7g>VymJ4;qjLxnt#`@I{m7&HQEt65HvHUZJ1KvhVkrAUzQI4IL4 z$Pj>ZnDwQ3{$~82RphU8Qa|8-#dLWf`O|-8Ged*V@PmUZEVL2xl7DzX_<4b1!w1dY zG^N&=Qe+^j=pISz-_a%W=5~{1gBeo1HQAAkk#Q~lVS<$y_&RMFKzCKZ>Op9vB^K{; z!wrEoGPaKdzfY9WUKDHm^5HgbjlI5o6;Z9uLaa$R7?E4+`}x`EI_drI^9y8MSP zDO9x~hPF}o_YU=K>B`E^=*uE@ofq;q&CaQ)ELrIcJw2IloRHdlPfn(MPeZ z^IunDTOb6{O+oxTxAHvi0nH3^ABa!>e?EI)4*9U6Lb$$<*y}zkPo~n>)gy4MHi>r$ zVpk#hpY#3E@F3RWI5^wo3NBX>iC+M7Ji4?)`POg<1r)j@1aaVhT?G{zrlRu>{>~lJ zqQ!EAv3WE5MLZC@jJA)B@vkX?(93v&8yo#sE*7k1DPOJBLLGzP+JES?{{8sB**pLX zNd7EP@s@d~$TL(K#vLBTF#(|#wf5q~w%3ir6=$+gh7UH;Dx?F_=0>pgcO2HmMcgfL zZ}h7E^YbO@SB{Kb_g1*>O{Zkls-BJ8PKA*A7p?+|lHF;?HKRn2!vjzd~J#R^AfT_}cd_gR!Mp0P}SmAt@I zBk#CGfm40A^MlQkRLzFIrORQNumV&dr*E^V79ds%H%IIg^ZU`M%0C8=3pJ zJgSB}+@x=~yuh3w07Gas;IBvi?}4`K{kA2<0lfTYZ`4yLdkOe5^jA>;kSUc?hq)C> z@s&s}(jc$+QeoiTlU#3*YDXNBjj3{&IEM|F>A|y!Jm50R;Sj#yc)5_*+l9h8^hQiD z{OipQRY4Yb;(%6GUhXjUR>8gT@Y!_X8Oo(?!PTQc1p-{(_YD>KO#i(hmS*#BnY;#e zcWwLn4(Q1*0#Yqd1?QICcMw(~T%=We+QjaTP&Serx-4VLOWQiqRTJS5j9%?;+ur+g z^1Tu|Rix_Tw=e0&h|Hf#4I?l}1$ZsN`9o`=Rxf+ohPDY>O`H5FqD!xc8`XmGC7GL} zFGuH2eC+70o6k?P3B)P1mdFX8PBNo{(Rnt5^zG)zG8Yop_HbT-GX_G=?$Hj z-^Pc5KRF%YEH64e7+iiDX-Q6&;XmOZ8@n~Hz_!2|1%16u+lVu`_HhgLOMVMq6A>YH z;{PG~(}^CGu0Fjs1ohttF5iHPz z3%w$B8H@0VK=E% z+b!}K8RVf|{Hyv!79++hP1a2+8=ur+J=+yn7h22lTvDLVD>UZ~6x?Q?adKz3VTF6x zeguzr#yWhPINrbHKTy>VGyk9=`ka4R(HWRaHPwbb;|svaNR{Zju$&nLs+-8ZqGx73SSR!ooxJ{vb;cq{*mg38~ z^n*pe!nCEX*n~TfA^VzmW>*vEpBK#(nxm*ju|>*5yRt_pIT>jy=hPk%x#qFh`HV)t z1SO3WiHreGpes;kkhlN@2rxrvPHyC*h!iFC0^SfWCzEMKA6{OE>}`Cp-AR5YiM`5`K`b2my{j^AnY-Mlx!Pv0%>|YJoCNS)i!`)6cA1qsNXNsW@ zr}tx5?)iHQi0qq_KoE5Jys4RJ4!LXEi7A?Bt4S-OI>%-AcjL@KDtrjT_mY%}S= zF&@%e`RlVwUrn(LYejTYMj`acBhH~!wB(qOt5$DamLPtbCQB~d1lWD_BJr9oq%_FW zOmjbQ2>&o@tU&URfYSB-Rh}(KmMZq>8pa?G0w?e%+QrF*cSSj~Sp_va+Le z!|yf6rnHgKDDJY*!Eht#hsB!mzjd@le^8`XW(RZdtcHQd5>GP`no#DwuEWK@w>4Gb zlv164wo1!!69G#DWjgXdKkLFuro_2+dhAXeqq4SeQfpeIsAsI(YYj=Dn1@RAJwn0R zwgrM=?EFQ5%_7EK8!w!!e#vxJ07OzkFlCjsWHhWCNQs3s)f>L2x1F9=>ni<}Lw`gu zZR#rH^4|41l>(>`>Fg(`SjwfNmay>7UpW_4F$u!f@aQ==Q4jps7=#_xCbY)^^(o`- z&NGr(!rLy zQ*FM0s2(KhO@gYNQ|Kp=cR-1qhZx*d zVlvr!IcT)3lhm?SECci1D~5l^2Y0y|6^GAUT}A91Qp+~W3mHQ%6)Sgipf_WTXzi-|pMqAvh|!)hvtSu%nj<$B1ubHLwNpB7^H4CqU!) zegYSPpan^@!VGD1!ML}iLKg0;+8eID3?DAn89{T(Y+<#FqjmHjX6w-~Cto^x)dvVQ z;EHN(yD$ANUVKdf@(BeJm2b9odUzZj~73iZWc3sN_N)K+P~oFiw^)o zkm0TF*NT0kjEika<)hgazO8jXqg{N%AAjeh6#iXoqrYGKUJBdx`3GqY;q>ciI7tR? zM1*9TW9WG*TIC#+WaFd4D-t4NUV(v_mVvsONp6RZZ$(21ykeHr7wOIeWsY0=T*dC) zCYr@PtGPJG(j&(nORW zf&Cpy{bL~D`M+_~UovwT|4DZOYr3k%b_{6x9cZKLUx}(>PSdq-PPmewVw+i6#5dkR zzY}YIgHm|0CvY=f^?yD6-6$e^rK3p=7$ZaC2}s|C#4us-DK2(5SziK}#(1CNlM^BC ztofSfcVP26#H7F)hwqflFnNPNeHbMo>>dOT;KBFW^zQ=&>RPQLZUQQge@ zPO+9|ne9Sy;d)bJCQUvBCm-FK|F+UgPpEguqg6tx{@r_H*5cQ|_qA@OsOHP?1Vw=Z z;XbW09H!GScOrrrM_UN^V#>mc7Hv!BmR3r3%2h|qk)&>n$)lC z^@dO(0*^y~Y;cYUes+#nTtsK7SRne|TI~UqJk|hyLf9b}JuE#&%6k1om5rfY8a{@V zOe;cA)86)G`pzbN?9Nmdbjf*wa#lJm1H!R*b@Btibirre=jUP2h@u6NW?a_<6)AHI zg>%H!R9+%i2YI_Db~OcjI~ZNqS|Nv_(q<1r82XiwOmnuljD~^lmW4hi_f)uOC2~7y zmm?h1m)M-qmaIQM-#{y7rIbJ9F>Q+RS8~r@{ejyjR*Fe$INae8KuQ0Qu)udeVo4w%bjHWu*a*SQR@k*r2X6nd}4 zJYJVc%%#~z@WzD*RL*uMLOT}J*&smF-B!fzjZ*KGB8;)Zhypyjfvr5V^YMKIc?ZTO zgy=}%*Mu?j_EVBLXq4SKP_vV)@5ZftIhLkn@eoD2+#`-$rZIGBwp!NT`^?MI zdHI@4`pWK~r)#cfz)xu)h~X+TdCf?kYH3CvuUEL0eK8vTaW)uD02rKeQH?b$Xw5}c z^zjhQaWT>`3%m#bunoN9AAGYD%47SRyOd{->NdrO#A5snGU}K8j+G@kTRIMWan72e z%>>JWB6W9v8J|e9NW9gcLDnQ6kk+B zws4yK^`lNz&`6vKA|NUN|{*EdTSC7;Sd?-mE>!GF+J?7>^W@~l) zZlG#-u%pP$46ed+bA=5ITeYk63dp^j81z#bfXlFb&u5rh(g=*@36eG`E(B=zRCUIR ztXSE)s#-7crD)r$zj=n4pCscN0DxYm0w3rUkS|`hnrmJf&nQs>v(Q#Eto31#|EuIZihR0U?y?1`3@s7D-PO@;^cE7++Mdh^^m8AsPze|-4m|WUuE?m4jK9jR z*O9ozCgJkd{6~0&&xOLuXRrH*aZBS%pLr1C+wn_3`odU0J?Q=Po)eO25K8-WXB|^j z5siu+HTCm~wQRbjRhY(p@RtB6s9R@HT1zm9^SbBt0Q`jpLV|p^m&8GCSCVQ`1Y- zkSq!^d|sRN;MU+B!VTq!b7!nhv}07b6iNIE#xP$rG-5SAEr zH?-x*Sshq1$}yDk-8dPhdn^AFr{h50kjHOL;+`$|54uweA9@xM0oxA#g#S@F2MDGi za2+`6`@DKcLt33L!j;?5Q_)&AW}_~s7P|Qw?fVSJ|6)dI$6T&}{%hWn0LPxIt^$aYOAryVyyoTvmHI|7s_?J@(tyELo)cON`=RU`(OiVX4J5 ztZ6frzUQ$dNoTfO8=tfeqGEh*jX^Mn%?$hj(9-9MY!X@zR;j*}x|V z!o5sb-)fxrwtsGK_glIavp^0wC^Oewa}wn$*ke zk_k3dL>@~~;KK$UinO7^MW$%~TmV2mpFaPTyKnp-l1%8o-rpWdxJE zfxf_mQ3_X4(G4oPS#yX3RTT#ux$}KqG#ffjzyb9_)d?oY%L)ue6QK!X3Q?uKbnKWn zdc5)1mLn5FIR<#r$)d#v|8?V5VY?wRR8gHLTSG1v16CQnr?pJqj(YN#Sh>emu2J}w zO3E2N^XQhh0fU?4^fR(JBMDqCD3$F&gAnqGL8I!|R_wL&!f#!4xcU0$_pLBnPVef) zsxt$5uUUFHfSjhoa|PAIn+f`e;_=kQ#vewF3$9vUIQIkxAGk*CsMog_v5%VOhpI^6 z;MFk-jX2JPc)KBVdQz9pj{^+6Co`lwU}s~>vo0?9$j4AeM`0GLr_1(}#M=~`9?B+M zY*&B0zdWlt-{vRuJR*L_0WBTR6e5A6d-WaEWfpvir%@g)i?0x>88J`DNh10wu{79m zXg)IRCR$e_sMF45hgdyE0%yPF_T!i8CkRPYWix8Sw8c_WB^K*VuT@-pY1{k>S@>ORMJt@1 zq`-#ly+HmXBm%rbFWoYuK(=r4Ynp%Q8$dwCmWJ}KG$4v+Nr za{vm+#rtu3XbM`%q%Ju7hNuO?qqd{zf89w{v$!q*qd8t)CZ)XTis~5&sbtpE&`f1M z`YN*x=j(&5n>IJww_6+-74uR2tpI!vi9Y}U=>N}z|LX(MqWt~SzYP!g_x}a!K0{uBPQ5q~&Sa)kffOreZN|FT*OK?`xKrl~ zCxHuREpPT@rjuu0rPTNQ+E=v1?P}O=Qyx9HQZ`FRbenmI*H0IDMS-u<#R47$;6e~v z3W)YF;hN*5kaKi2KWQ_7>(t6$(Y{l`SnzjSS_~ieRoOlQbRvxn>t7YA)T(<7as0gl zq#aj{sx518nbWMX0eIGXh;m5U+nAO_X4tI?7k~uZUr{YeD=c-(16`5VU5FdDmlu&U z39>}+g5|0`*U^+(w2}IwE3)I1K9+ScY1N^`s#0uyNkyWmm_H>_0+%BhW=%lkGUn#u zIN_t!*@fnL=T#F;AI4Dzjo^qtL)8;FYHcxR0tK<6nI)0^4O~-OmD1$&*cyihkNtoG z0(cx@UjQh~!o@sY!r0uD%%Xtl?IW)UQpMZv!z90UTR1yIEum1!#QXqL*KnN>=->GSjR@Ja-38>+VV2Kn0)0`aSeAO1; zCxBpikvoVjizs$8N16*j!1?Ox*Kold`xJ!=o};4_)S$2Qa`HrY0tTE77r^NC!7XDt zpy60joGA9{x{AVY)>>-J*Ln3|=6DOsNA1s_t4DwlwWF~zp7xTV3)<1EECxX#`nnce z+n!>C^=N64Najr5bpkaeCym z-)l1a!_MAM(`oFFg;jA06_$&+Cu_UG>XE_rY>yJA4Le`N@6)n ztU4PPCd{e{MM1)+95^l^!4^SGV4(W-x7gF~1gMV7&rifpsk6t=p^sxwmsn9EK?!D# zzQKwljwE@Wv%EHXd-12K-TM0a{C=VL$Fn8nO?82k3!Q^yeZVdYxcU_x;}}G-$l1x{#F0~LB3C+to;8VAN}VY%5)f7 znU$u=Wnf=^800}_%15*gq{xfQN$93+KC!l-cIKk5yoVyQMf(od#_7`25_~C9IJv&OhA{OV8zP0sR%Xuon_8P?? z#MeIuLbk2!wTJqGj1HKILP>b)1bRT~f0Sh=`=M=$n1g(g@>9Ca~FuEh>&&V8VVfl(1{GZa!paJl+fSJsS`ALFN~0V zV`45w8rGljf-;&9L@&|b;Vj+!W#(2c!g;p+L5{qF4Z_4A`JE5hhlGXaeR9h6A>x!0@G z=TCPpgdYA6kE-s6?0kPj!o`EDFEwx{%Sl&1MRc2y4r`zerA*V{D^UGWj%k8u#oTJp zmwNX4f0?;*O9rfUbsEpdoJI`Sg2_|}3&CWOhFrDcoxgv+HB^k>GHGmt2f&#y@(4E` znr!B%BvG-C5~&|?s7aklItA#8_lex}K&xWs!l$e>FPxsalH>2_5_>e4mF(B4=V$@2 zHZ(Kgh=W`$dGcUki9L#HdR={QtFfD=ncH|3$(Ul2sct6?$#=L^zICvF=z}PJ!)sUX zwa=?E;In54)R5i1QS-du;QzoEB#8N=@bR)R=nK~b06=^N*lxtv{Np(Po)LTR0zmqK zSO2VcE<$-de*@-uE>@5N+{)Fc^#a__rV@lAp8d~viEoLz3UYVs${3HTG;9t9Ekz z9Xm0>R~HWl=T}KOaM*imW_frSaEi}QYJ?A$vDF)E)&7?m74VU0u6( zch#=m-Osz$v);wzMT2icEx&6)BLuDjf!ok+#-BCs0P+f92@h+jvlqISWF;V3FterF z`A1hbxNG*UN&?MFX99)?hb5eCxf>s}|0HFG77im5mozQR)#%Mn)diT%af76v{fVmqW*6TxBn8Mp_M)U!bEDW}?sk>46$=5~a zRP5LnF!0!@HW z$m#O~89|v*CNP?0m@Mo`v1)~F8ZP=$m5W6W@9!=EO2&PkWoTt;_%$--ImeSr*(%1U zgwis0wDz^}gO*l0ey%6mwQzj6x}OfcaSOhumK;)Us1b4C8ISy}#;~hSwUmsLkTj+} zYn3hW=8fKou{JgSk~vg%3L$4PgbZpD9_=!J447W~Mjjf_uF)MBK3!tut^FgpU(SRF zCchHS(Xl?aR@$R3zDBtx<<0;wW$l)I-&^O3XQX&LX*~SV}OC5S(yb`BE9*`rpPJscuS4zq)$n zczKE{x?^H&&B{y@*dYihdj&5a>Mc!r)5+cH02?^0S1IyB#W()a(`hS1oogbfe~B&S z9IDcx9=$>mz|)==HmfH3AWhsn+0V{&wkI;f`=oA>`q!x$>4p4>_lP*gkpS*CP%xIzrLV4|mT-Ec zt>?F`jdfq6eDI|lVMhQGQ8f)()-gYzTY>!RX;#Lpgp}I&rJ_RZYoo&52+SXQ>?brV zq}ll`vz+A;i=D{ffPnmG4P}PcSm@7IPoaU_6}B43qT;7KE^~B_Tq?ahQJb<(uf{RSqz@ zXSY>+WURjmWV~gobDFBRv9gRt>fDSFN(=9*6CWS}MyCo;2?sT{$f71z$xnpx-nhO& znwhh-yQqdiviNKbY^nO{ZlUFN`}$dnBVE*CVskZVKC zf;|Dzwb^f>h_e*}P$vO`a^#UE8}+=eE{8p5>g$^CG~UZnLVIB0cP(o^j#xQzvX-t+ zC33o#5?CFZHZ$?Jb@-XxZ+L@X^_-o~uhUMDYm%tT5^WoGZgls%_ z;C{=h(-|UE?9EbzNkl&cUoZa_H1wh2evNBnnI0Ay1xC?|Nk|Bz{qQtpvT{U5X?3AA z1^W1*C^znK0WKn=W%+e4p*y)m%}lfy_Hb>etv6?27MQSLx?*_wv||v&;?uT|dZo;! zjw}7GK3_)-J6zDg4vm=iiV2RlMy&xx4>j(0fuD`IQtZ z1M>MNalp5pzB7;P`Lwv8Tz0g_hveuk=|h6w@U8q<;#k017@bxVNfSBt5N7~!#`Ktk zH6!){r}qMvg(n6(so(8t}KlyqfIO*(Pbr2LNr6T_L!XvTYKqwkSqg>i~Wvc*ct8U zGTZ+Ag={eALE$830utlOfp)8);1of0KmqnQ^)I9>(H$BcR1_K*>WOCb6nAF@m!y9@ z+yaZrh`Y#NxI!u?vCsQRlqU*)D%>znxF; z23k>t^xuc-6a`wRVokcQ4Fdvguei>3=DH_HQ+S0G2Q_35o^I)#fP{gcugEMM&q(<2 zQdzzFNv`b>E1IT=jYExJpPOA7=I5R!1imHhy_OW%4}H0Xzp0|VfWOW+A{mSO%ECF7 z;+>di>ZoQ(VZ3KA-wF63fGY95v6LIShdKknC|~0O-(T<_gdxC!S-Xw_u>NQe7Nh`$ zF=PM$Idk|Fm4=1x|BxR8V}Jh6kJRX88; z?+=v#2phXONmD^B3Ul|aJp9`nWqDoY;*3!}$;(OA8by24-nw`p|R7M^QSj_iA**({zU=VIY^FM zZ-8PXq$1lvu{Wd?OJtJ6s={FWMN>f|AI8Q0p*KWMr|&A0eoR|5Z$dD6M&m?`<>A7)9-z)$lr}ndgsITY5iyqF!hA=lY>JpyvCv^ zspIZ(jzQm)lAcOgcAvi3{eW7%+*bTAJ$n0{n@4T{$zUR_nO^0pVzlPLMFQ>j7`jAN zB~MduV)zyi%1BEAk2}zB z|J2C-F#JD`Y&u{b7|1TV*H}aBw%)?4ejB|hH3)?(QwwVeuTI5@w9)i(RIfIt;(<#u zW7!2={V#J0cqx1l$6tG z_e#nJ`BH1WEeJfRTGQfKk}yZ>A5Vx#}j0`EX;iTH1P57L%m^fE| zY$+PHdA_%A?@oM{7Qu8aQE4&kR9>=18u|iu!c*d)HuD35m9 zwH=%l;p*PSiG*gJ`l6SxMlXhqE34GAoq_f`m?vA6psggb129GUPE%VX;q=-~XlacP z1J3I*HWerarwxT4WCJ$9WSc*b3l{Ze{ZWgRDcI~39Ofj59E70~o>0vf0gM7m_hxg zDNO0`sT7}UwQNM3iyv<;kf9HFwO0)mgc_vdOzpFv8EP6jz?KzS|6bv?-$e|yr!U+s ze1hSy!==ZGJ$yQXn<9Yk`oDur;PHPqpk_h93EtS5l>Xw_M^2&J``$o-EXlwTlh(`_ zPUOb51`U6P@0SyH^b2JVq%0N7?8M?U!tnooQ`YFnzlcbFGyYn(s*)}&f$ao0Sh?V0 z7OW}7$Dui50JnqVN;O$aU1)MhD_yyYD0v8(lIES&TICGII69dSjc=}z^i5s9_nq-_Z2*4 zVu(6cE|cqT)!pFfYsc0itV`M;sEkUb(#kQ38C;pg!ikwu8@FF}PzKkg4g}vd zJ`_So-u0t;>A#H{)}|0wvOr! z#VV6}v9#Kcj#DkUYX{A4+dL|9HHfkr|+2^fw^yIY2%|&>H{YAJlI$mVh+%-jv#Z8G& zv1oV^%4R*iu4&X5HmNX;_uFe+W9R^)qWbn(nt*&8qd@SnBRI5Mt&kp;k3HTL4Dh@p zc#b!!w&y7$q&ar}Lw_jbqF|T9MbyHbe=vo*0`MXu5rNL2xBdUulLxQ_=83uprM(Bw z?Iq}EXD{p3P1Xy^wmnzwyoay2&!-lOg(|tnzU%DFoqe%3{Zgh@n5hZ z8QBm`Td~mQZc#H3ldG3kq10^nE(VtSJs|p599u82MMrpU2xQ%-W~Du>k!ohB*D}bh z9i`~W@rK`}hDmS@#^D~64eZuN_6rg@+1@+X;YIbXB=sMD?{63i+b#!v5R1GITNjabIuT7n;lU!A0;;!Mz zMfE2_>&l45w*NSWPsb0Sl!8f2MQ3>3h>eHZHadK%4XAqb@#nm8aM~X_7~46R^9O(* z%=^jwBnUfTD^EY38fbC87e2Y~~EXVc%x--EaRpL5{O)BklpPy_SCt%TB^!8UtZ z8jTiW<)4+Oke4}JZ>s2rdVUXuSPuTVJQSwh_iFg(>VMHCSfWst%zr(#_wgH4h96UU zf-RnJr85P;AUE_bIFQ*Sn!hYlCvhEqi8@2sJNemV zfHn-E45!wkn3^3->@T8-j-%v{uaWR5i@G$j1I6ud?R|Ma8c6YzBrHdit;{ zUXLF52XJ;p!YM;F{t{~9G@OeHkzd$0j{9EKnqcfQP|o9WeiDqg*cq=pp|lF_XKkdB zm@K!JfstKJpK{Q;ONtL+PmGA(re@CXJtvW+h!Ca?^k7 z*^)&rD)4#|u$|i^u&2yTE#MH>!!I$7e$v;w z2&wGHUtgBpK{JG8RB0o$_33F1#3bPBDK~nRZXU@i5XR>6vY99w`;_}IQKy>eItvBKL$|HEH%Qhs_UV_QKRWyJSp7AZ`YGJX}p%ngad%rB`65}urwBYWvx%^|XOZ;r(KQ=iOZlvG^KcGPB}35Hj<41RXy zl!u7ecNJo-j7=;x;_J6lZ?fLicXF9^7hAMhJU2JtRnp&tCG}2@XFwu|yrEwdpA&)) zRf5*)CWXVEbV@z$cKKm3#JFk*Y$-rvWJ5`zFYv+xN*yE2_d2pGuC1C zQU0`5rBxqj&P?(&=aI42CO%`->>htIxOR2V7mF4#yfChthq$QPa}L_z&Jz|${M!uV z_`a`*^zR*!PoA!1^usAm$EAOBCkkj%n_qdT?GoFCY9K#Q=is3E(isV%64nF@QC>%$J+x-E+C z2rI($snfwq)YiIDW<&%5J>O0QXS9NTIWvCgs0PIq&amzuk5aJ~#M!sSv{Zq)7)p2V zkR9(qjKj=yrdeNfsx*bYd#0fhh5#{g~BSN{F9@OTDrfS z3x@2lf5@t%LD58CM9+jKK_*E4X}x&MTM_Cx?qgZ=kmM&~_+SF|sI94dG=CRFS3c}R2WLv@45zHV zRowYgk&KB)?@$aeX-8~B9rftCo!cVT_`i|8$i7yEZ?E}v?LVMSrK;C{Bx%pi@m4tY zwZV;L{^ZbbNwL-+UR#x&7iz3IVdoY35nvxcB3?M7-!E#`Ldz?O1{{VYDqdoQ|E``n zJi=?BefKm$#yev9M)s)lz0*BslO{}6dXM0CUd2H4s~mF-E2feyB#w$hk1(+kIV;!K zTQ#L6A|Pvji4_rit@AKWL`D^DyeX$?&eL{IhZ*KU&b=AXF>}jg>j%A5_2>e-ExjHd zF+7`M_=b36OOrD5b#>3xR_UE|^I0$NR=}Sah*(G|>Z6CPg``D;)`VY}`EVu!r{`^MBwbboi!uNU*-B@JMK(FM}U zQ`<3#hD1%~RB&t+?|>-|M<=>NEQA7Ikc)`1VNWbt>B_Xf)>hAlw5#X`UE6s;+Ls1m z1QMqQSXf%)RyU0F6lX2Au?dj9@VN!5!^uRR%Ws%JM@L1GY%Gz=oba0NtV$V8)EJ8m zs`hMH8e%6dliVghUA$Ntowl-OrG2COm7qBk4{#llpYxc!z;2l>rU%fgF_o)MQsvCg zNWi&hECCc{ps-^jZ%v!{zyHCTwQL#XUQqY?3Y3)ALb6q5asPW}?nwYM+m@aWAqt5M zsDw@^*i_m9<}Q~FICSndFUpiA&?iXu#dW2!KsYhp672*Kea8OwfBmqMc{yqEJ0qIA z_6s92n7l5MlKxUxLjG)-J2r-`L|+0?_63on=kv+P_DRjQ1rjslIr7y)CGhu?uS&JT zttxHczT$v*Zaf`|WPJrG`5E(Rtp=`?0ReB&W6%QroxPtubUFocM1WOLEh_)O1Cqa> zu=-FSYpXj8rc`)IbSK0uFc|_)=D+@J0PtSEL10F)@k<;C=YQ)4rhnC3r@c))LQkmA z!DYEI11=1D|75VdOF+#-?|BBOB}*}x`~GD)g#p(vO)cd^0~y>+U4*Yzp(NwPOvqOp zF|wT#HFnL#c5F6j8fkNbEBAf*2kYOhGuP#-D}+EUPs>6vBdP{=HJBC57scJ~G7>q; z6C;Q}iZS%TUiO)$0rAu#0M%+cjx-Dn2}7mQ11pb(ZZp)Kt`FW@U17cOScc`L#_$wzbZVV8+$@H${wmo3nS>2J zw?(1z9Vwr)q|e_xy{|+ckti;-t3wIc1;`jLIPyJ!ON0Dx0=q|kU(hlJlss41+^>wB za{{(YVRX8DAF^-fx#on|KQHYcJ+l7_JoY_5X8)z9r^ZA3iaCfuBwNd&e^Y{0vzi=p zdismLj{ag)<7@|@rKz8?R#2p1Nks9R+tRgDyf^7eqk%t36!y(vc`yV=rSr7O0q#r( zqP`Tt$M5KTQiqkCFl`t>(>D1|d-*&SObco-4?f41lItw)}ZG)qWxf_(}lH zGH~_$!Yv_k;Q|ubIdRx^uRBqR|3LYVFC_MVj6259`#6oZ)p{tP$ELqaU#YiJLVFMF zByvc}W@v!*XPK{%`~prxWijMHSsax8O_-WcV?xEJjx^(y(vMazV=s3c4k_MK$uPVM z2IVpHl8!iUxpf&DJe}o@0Zk41@aDKIY|4FP)Sf?K%KB{jKB*)~_`DwzXDR({LpGdEO;wH zxmUwnb?(a_bDQcb$mz!;W&S%`t1E!b-rTk;<}<1zWRlXrAt|2&-^P!6^4U>7JD86hm5Ue$Jri7)8pT#$(5M z_zQ3tO3$~$Vk9Dp)2Uzy>`^JNIIUPN8ePA*Tz1Iszwk)14S!F{RXUD?`L`aVwKSQA zio-mb>V`6lOTlt2T+sx(Ukea(8z#9nDO+%%G|gfU|7S{%!_)9Hv%r9P!|VSof-rN0o?uhscCkMC{@W0oU~rZzAyF#iqyD*;U+%DTl_|oFA(rsb|xzhoFd<=HK6&lRZ;Y^u^_#SHbci zEzYIV$-vFiaBox*^SkCM_j#QYBN!Ym?nw3&D3`6Bn)>#87NU#4Q&0@~l#LIC_w_*8 zkX^laE^%Kt7=Gg8EU8iu7FhkFW^70sFaK}r?f#hXwPU z5W%;MS9x%nADfly1tUAnzcrQKT3ElSl-p|>dnviQ#>&9wcGlN2P@HAS3i$jwvC4XA zNv2eBu|)f{xrch~XwX-`LHkR6{ZHZ2Ym;|fZ!C6{agmyfNyU-xbLF7IkbetLT}~lbd=d>MxTU6{;RMoGQ5~+#<=hw7?+0Gj5zqwFZA}*v%W_IM z2CLNQTY^_=SBC3!UKY6CJz!an%lzv$F43)G zf0X=0?}u8Tk*N&a-S6=9DwqV{Ucdt>S5bb*ri%8DCCZ{<(Tefm>4k_XY#15M z;$k?3x|3{B2N>+xo*!X z9G?#5gGyo}jHY^42$huW!>N-8qrZ~Pet{1P- z;G9^>M%Fl!qPzH#LR-=p#$_cZ?v`Hmj)L6Do^PnNH&vZ))PVMUiwrHOnVQFs*WwG+GP|qh>f(f#*c^ z#aj=d8AstWqs-gKHqt!~z_D{dqwO3Q4;6LizjP-ily~(n7xZ%$h0K|{!r2yV;DW2{ zZGw^pd3@;00Rt%CuiO|(Q5K*@Ev_2HuG(Dx% zc_`zr6B_5W{)unqa#;VILmm*Rh~rv>JsfcWjK zjgs5;KwHexAJ)zZ<`tqnGhl}>Wk&A%nXQFKPX&YBD7+q9Em$FQm<%Y zf|uLGxoOpYG-s9dl(eS%>T%Y`eH59(*4O9uwQ-EpKn2OogtJC910A`XiXK4A7P^x7f99iM+)3oyDRLVj`chC3B>v9HT9Ha+| z!7#ouZDB826Ww?to|x)%T1g=0C{c(ckFg8sGJLzr)HJldFt!7 zvRW8oDam+=Ry#h0FuMmi`b=7_VkU4yaHmER@;aPzK@LfF0D8+;^3@K`Nj~8TIl%4X zccTjzP2379r`4$ydlo;psg~+=F*!N5wzYiVnfu)P<|batT|eXSJS2pZLd`?-2pj8e2mX zjqH?iRO^v?ZNQ8@>3g>})+xS4i8o7vPrs$P67Sd_ia*D9t}EXdrf`| zpa=I5FS|u(H9Rh|fk7z^N+{pvJN2f8&uMQ(lE$u#?nvL|@FY(UwVwuXwq7^PIxEJA zNT#?w<;#iawsEx|KWzsSQIFOfw&x*#UK z@Ocb5Gyr%0%6K^hyyp)$YsiQ|M3ZdQBfJGMVWOgK(&^5<+$Lw5Jisp60afuJ0F|-y z_7X2d(pksEu|>alppDZ*Z(L>f9pS4QMv*|9hJ1EepM+_4{JV^3Rv`LYQJt+bsc}#*`rc>Fb+~Dearj@1=#;1C;?`!Sszb`jCEDW84 zg}PVq#*dTxC0}Yb3Mmq+#D~|C$h;R2NkP$DWuD*G<0;R{L;^=b3=ltL$JYFd*+HWU zZu0~6w^mPj7A6jM*yKf5^1=I;X+|{s_XiwbC6^Xs)g6Jnrn9A%odk`$R_HlLH|9m< zROAd^8htGcD1L5=wa|*NLrJBn^{|DT72c}C+^%k~+LsHS*pG#nyp z=jk(FGv~t+;|0_LEH%EiLz7O#!fh3fjduon6f&%Z(~48-_;S=SoVb> z`mw5Lw)n4Dtp^_Mg%_jBG!sbrb6$<0Y>5*41J&Apd^^4xl5H z%la>`?H!;t?dNT<{P$lkNdbM~3nEt!3K{}?+~7sq5TQE^%Wk?zD}3~SKQ?0_!G(oq zpAXrRZOVFo;a=u+zB1%Xb}um1g*8Q_4lw(;nP^U~Rq3mBfXFY2qCcEF7Ig}wSqD{7 za-4eYm8!_FE-8;q?VcS|_&t?V<6j{#N_O z$$_d!wSR;xqqCd&HvHTfB?W%>qayQ)$5SDFl7bHkF=jqLm`sSe*-kY{XH}Yk1WkcQ zwS%j+)BmJUtI6!*Y?1`^DUe~(0Uq$Dx!N&~_i~bR#fRp-bInn+x1_S)7+-Hge3s0h zrI`4VMA1pHIis4~)H9NWsyM1uVle0)&gF<$P*9Vu+owVC=_2?T*tf+M_cvte4xx&2 zp`%;(SN2-jT+4PDyUjdVJ^(<+U4@xbgLdG@@Lwc7fVnCl(!94C#XWQ5KzvGbdd->a zPD!~t2;nQFsBL|XDu0)F;WS(+uvh#XhS!xYphCsb+xiyN^4=SK!`AF6FYaY)r{6t~ z?2+EH@WPz>ZMmi4x%=GpH;L6S><#3noP}d5%MxefFei3@PE6{TgOR@pvq`c8|Gfid zV6NIfu|ig_Se=0EG#`h-J--s8#=s^-stOI|+&~Ps#{)0{(l*stoS$U}ogrwq^PnYS zd|CnkOC^Y9LTM(W`x1Lm>;A(yg+1U?KZhUR9VH+Hh~B+eH66B! zjy8LKrA5XP`t#XJ`^rP|huC2{W-PLqM*=|tmGzt2)~#BSlx$~X8NvybuhdfJo3|<= z*m|xk>H-gmU=LPZ6xVq7J?2$5Fp&7l=01E?qdFK$X>GT5a_xv-JKR$>z*Kl=o zbxA!Y(aeeVKYpFaABTRFT%;?&?-mF#3H2kQKN-TU2}iuwoj&Ouqtbg(^h>0Sp%oN9=Os%da zee3#)RaiP{FiQ(^2f75e(j$xgox$kkHtLRMTiKV84)y4pwjuGv!$k126S|f|AaN<1&{C{R!ME4=%_#u)CB} zWbJwYb)qw|luKV04`(4|wroJDixtpINl1^4oNjfnM_=Mmzi_Si_vao5Wl<&VvV5Wi3q87UY<4Fu?3`1AnlI&9eUxsk zhzKi~k2e|qO&DXh0@M@gPikgRx}k8YKLE@Ldt9_4z8ce`u1>k_y0^o18?@9T_wuD` zCbOmdg!JqK`2Si_Q>G>v6@7Q+#@7pUMNJxwD}>Z>o}J;adVV?~%_>%-pp#uk*KiTs zm(WRN+ni;+r1mL=H z3b>k6IH}O!oF7Y z#r*90)QP_c|5tBmd=s^5lGO4qf&MX54*QzDGI=a&q9dKGi8cJg()lVNY4Vv?CD%-6 zKp-Wo=C_lHm)EN=ok)y%qV69tKIKws;K=NGkb_PW>L!+3()B4!Nd8n)S2K^Bd z6aN^7{{P4$KvyW& zF&rdw=q|a(mp}pI?@S$Y^XG&Y=fFz5XX{YA$=HlUv=bdOIH*9qxU#ci^sjS`>60U`XSw?ki~Oh6nIaX{51J6B^*sRdsl86}hUfFPwinYCZcs z&EF~Oz7&qyX%3bm=;P+&b%^_b4>%#JLG(sdF6B!e-d!yZFP3F4(m{g9NE^h9Hy~Di z%sFEEnlGb{q2?Z92z&g&AJ204HGQ#%pWhq5-E27&rIyCE#vHJdF^+_!!#|OqhWvvX znkwi|_d?kh4CIV{&)LJT=A*)Z?5!;tBsM>m{ah@x05T%1yab9`qT*Z zSWByhzb1A(CPx7nj+6ZB3=xA}Zi&Rttx$<0VQU$)yC8oZ#tN+EsXmg^4y|8!d2t6g zJ?l-C2(+bckJ8iP&+z2mPsgor<%$8vz_sQFmXSbUxN{HEt>UAslis^SXkf)o8ld>C{<54x!EYOzj z{k5jSc5Mh>PdcGsEVv?qeWgXMkBZQj4n+aKw7z{dkjGYdt$ z{d!$bg`5T<=nXFeTM0+-E^*P+jgo4*d8uokx6};#BGyZIx=g*l9Nu59|F5ia0&_D! zX~WlpxiZD^8=lw5M|0?8=IA@W4hI-ry9mZhy0rQO+fpYBYSb9$l+vCuRFxtKvOM>C z|FcZMh=nq9|94+oteD09@nVuwDF562=7bgIBFvY_VBFR+O->TO{XAtb>wdcf8P{c8 zXugP?xT)MO+9HGRl_9+!t~aVCSa*YFMGtc@W=}Vrf%|j<4VjJaTsC4}bm+(M24kqn z2l4O7m=>)R$ga}azL1wxm|({;xusv)&Qj76n%r8kCv{)NHeb5G-1pX2$^pD@7^X{E zQU{SD(}s#7&XB@k)K5?`^pL~z^=tF+IdURV5k%>8I_HO-+ABgDslV+`TD6-r-1m_F z1S>&2vcj|G4t`T>Owv~wD8oU-wb5{?ovd7nrqk`%^C|2Pg3ev~DdGaQPl?VYo=OaR z@7%t~n#QFbZ_G^)l92z%qtLh{YO4d$rO5E1elScNg zk)35{o^Fg_^e@YeAWtzP>rsf#X z>cG}Z|6>JlZ+8$IC)ull#Ng#y(?E`xLBQ(nrFJ|q2-pbdPL&xRLK4tOXm@r9@OV0w zxA-GGxK|rkZFpNTAWR#+t^yxIhiQ8(*c~9kc8RSDrMw{o(vq3bml!6R`LmE!-73Uy zjrd!naZS~a8{ygVS$oCFjLnxJ-=S!br%)0A0g9K0CQk_0uV`Z;h6HjrwYjH3Uw^pvi;j8j_y`A@tQy*;96}{$*_vxkL;uq8C5P?v>vsO-MTyA#`=F`Lh~A#4 zyEPM9dEqscVitPWt0T+3D>%O*M&pZB5jB9Rb-oW!I1ewpQQ7)>Q8?d*&+qfZ4~@(f zbTn}7Cu;IBlKFz)1tACxPdH9fBg9yS&Sz?T?{N5s@y1w@J~E+!>0_m)SV zQn{HKu0V2%)Dp?*hPr`wr_bo_Ksst9X$kxi$a zYFXuV9&Lhq=C0utiWyIBRBE}SdUe*k^lm;fG^Eu2Xr6X7nF?0?(-8&DLkxn~JA_NU z@B0Kh1AH!*XT{HVPMKEw2;m6`d=pA{Cr=0^TZ;i~YS_RMI<)3~&v6NKvfBSG{b?4; z?fw@xzfTg$o*GrnJdok-BE2|jcTaO@C;vL{Mdyg}sewUC8Q?m1_Fk7-<>STvwgzh( z+!aHQqtbmS-qxih|KY6w8BCfckan^~0C_r((QzrHSu@jjuPNwr;Z0q2BLCa3vl&bQ zc6$T^(o(#AJsC45T+_7J&Af+D@x(`hpE9X8nJhdwngVR*Y%h3MWL~dBm(8SQuzp>2 zL4S(5N7bD_+oN7i1Am#dm+7kU zVj+5JqSRCiWy~{9Np&CqEqP9=@YAw+&f|AOkvD=8=1S?DX)i)(hy51IuI=~wd*;)o zX$a4FsplVw*4zEGnd#CBHI=1bZ?xKwDeR9P3^}b05@S1f&UJCOd~- zE@6;HVOmQz9-ZpBt?x^gmiCmmV$InG6v#y*v~Qj>@x%8N{ed72sU| zR7D2WB(I5I#>-;D&Z~AL% zO~Z#qbGA*RH79Lxs;^}|Iq;)mwRRf`K-)xg5MpZ-9ob-WN>jO&5sLtJnz-?`3Pa93 z>)-cF$1lpe0VBLp)6W9ViM8!s+tT>kwo_Wtoc%(*HVJ$Hl4!)P-U0P&nq;oqYN+ zYOl9FPq|4WQIK)gV7I;vtLaQ%y@FbZN)~S*gOI`>7qSR1HAV~x?tXfFy3DJ?y3X3u za5cy8e$MB8>h1oFtAVBj)#v;=s(q+DLKJH6-v0fKluZCqzA7Xq11%+sdy^!yV|zNA zQ5FD2ti%9UVSdGHSJAA^nGhNpDT9S!t2Rmb<_fP6reUMZl$ZP>nCz|HJl~Y3{jzo! znOjU*OOB|Ig;5GYiuE-6=(uSp>xz;aD+}zxi0-7=po{zDn0_eNV4d*I$=Fy(nq!eE z)##_JC2o@X=}ZhqNo*nMg>F(=)xn}geXLrtLT}|6o5LWexNybc+B9jB{axvzjUa$p zx=PHzIt7^c64Vf(L9hiE2<6t$-(=66V8phT>ZNz zT;#VDb~*R+-Cv`j_&8hf<6sAhI8QNfSp=+>w{}tF1xoWKaPhZt47`W*OG%!uW-xm8 z5I=HT2h;xZ4Zu#J%>I9~JGv~6--9OcdA?9Y`A?Zi*G}cC<|$w43#^9X#hEX3WOx!+ z%TNGQvs7Xw``q!qQNsBRl=v$*Xio=$lDvgqkvWpJnNnd$h%$#wwVb%kn{RDqzjTo5 z7Ijx)$8LTQPDF;AyCB|CPZT-!Z;bLy373|-JRg0{Ep12!qDxfVql;2%OlI3{S-b_e z9h<1m(|t)Q#~p%zK7N!=ck}=RtfZO@sH>KUP#*g}QIsbnU_Z+blf}cz?F;@fHm;?< zP!h|a@2|bJAG31Q>n}`!{&N31H;UIOh`hW5nAES`XTu2GCs@SO=1*idm#6Qr+5L!k5B z?20N>zWy_>zwJ+6Ur>>KwpQ&g$N;1I>5YjL`)lM0Ooil@dfaP|wr9C+cB><^tgV9m)t-{Cv$vyVei1xx~Ctuaoc z87bJWWhi;7fq13tF}vuHC~+~w%*<$!O{m!zm)e4yU9rlBuWaVI?TxlN6Jlhc*YWg+ z)Vx1h|G^ca9;QtUQxHK_D)#r+Ogl6d7J(nXJPwPdbci|4cT{pF>*AC^p(|%`Nr|u1 zCiKtZGFVr9<%Y$NL$k^0!EtyuRty)EDHF{3WsP!@dS4(1OQff6Hz&Izzq;2ftKME@ z>%V9F!X(i1H|Kt(s^&XwW5C-m0Z?SYF>4EYG{rz{qzT0&6jY`*fup(>FsxIqjDCppd z-{;u}DQ;#gk0IcS(~;}VMK?`-!9{28yrqKrT4 z|1tHA(UG=G*B#rojfrjBb~3ST+qNgk#I~)8&51e5#P-+cyyrRR`_rpe|LDF~*R^Zc zRkf=s6cNe2&(;BAg0T>mg64R)ptK=zp9wKaOl+>umtv2*>#TGnOyno;_QFT@yI~MA zqPc*KH?9(M1u?-<zfNlhA`zUdC=9bn;u4L295nq!K++^ZLBqjhfMZJoMJ)ckSNb_N2+~mc* z;^UXWklu1hA*fJx*zJFBy&!*uvk(8XKBepH!6g?CzQyev8qFaf-xVn%KLN+ALfZrx ztks3OzENa0&V&BXlN8^JOP{YhGny^u#U%nMI zKcxkkddz8`Yw$1WSyTloUY>?HKKx7w=k|$~m7@tB<9QHMrYWwnSxf0{ zEfS>2Rljo~RP`3UxKc{!?BAT3OyX(cZoa_wY?`5~C7Nk+k1o_bC7 zcpF@R4uQ4Rk=&xc``+UtW3bCOJ`X8v)YR~Vm))1=zf8iBE+4Cp^R0COWdapl{F<2d z_PYk24Nzy|{DR{Q^5F!!ZV-}@Ej!s5feHFar0IL}5Mv{|vMCqg3rr%0F4Y4>fKZzI z!A9za3%y;H=TIEM=Td3oEzl7h6fhfo@Boc?wj2tLcZZ>(S65eCArsBf8F+t&wv(2? zK&coy8ErkbOXB96zHwuXX-3(Z4_tzoFpOvWaiz|%@;8cq3NxAv!#>Q$=k-n{!xs6b zyP1)XpH#pq(J%kbu$Ab%mv`s*Ty{b{G22B}P2ex6)^A~7S* zXRx^{8xpa^TCg)bSQWG?&80J4?tGG~g)_dgrkm66_Hnf^5XL%|_nNWs^JwQJZ-Eps zN2g)?NIBDBR(a#Q`^0z=raBER0ca^9q{$HvQEF|U;j!6mF{(O!JO&*CR`)KDXu=r$ zzPovqBHVgj?*8_|K~VaJxZzOn^VwhTzi!VhA*n z;kXl`Q2-tkRD{~s6UOB8Sr45Zt)*j=JkwSC^Mlzokm>9Fc?b1iJK+LFsy$lKm@;c> zMv5!Lumtyoje)|4X|=c7n%19fE6Oy>8o(rS81cHidp6f=s>H9bS*zlHuZ+ zx+%p~)U|S-Td_^Se<}KEXpKh*?urQ z4V9m+ZV+c+uZwy*n(v>6S6&G4!WchDaD*t$E5*$l*|p6MRI>y0c%sBIR~w6p@rp5-Wcf(XCV3hgg`K_1f4&@Z_(Q2R{l zC)Z)+?bh=#;1~U$P+ktCfDFif_YyW2tWvJ?$;{~lG4`9Qj6id3ag^Vd^QmjD_h9C5wevQr^rIz;F^z|ZXbah`25B5bsL{MDq^X` zgN6#N5W@J)`=;_hU0vnQe>1@+awGZj{moJg4!Wx~Zs|PcM^(7WV*<`R2>kOg>4-8o zTh8*qk5xV7lhQgzK0*aCCX*rG)}>05+UE1VtAU~}A$=up6p+dRRyO|tqOr~X$G777 zPr(7#91un$J;MmVU|zbMlTA{#I~xmKj?hetD-jc93;IBp(ml z!Hx(27un~s6~jtAVVE)U^f1NNH}A^U%F3TtQ5`xJcq;lkSzzf%&&pPvu$i}^_&L1q zGnu8LJX0W3o?EAL1*-8ivv1_C=A={&*XRC>C-x zP*iTW7gazi8hq(Ie<4K7(Vg ztSAG}ec?G-VvswCspHk3$FG-_&lQXPk~hQ})-~c~%$cNkmePDD&E((k@__d6omCJn z!Gf8pGzFwBwnj3fZ8Gdg7X@_~ENH3QByqS>lO`1oZLk*F0CH^y#p@<5C;Wi20NMMG znIi+1z!-{gBKKdMmth#9#@Qvb&xvxm0W;;cQv~=p^oo>W`prl$T4Riw0-Qbfnuqv7#n)W&8}P0tGsU zHl-eQjdER_`_TU&A0(c_xHA5Nw2zvQ-zB?FCEYVm?!}X*&r9&3@n(11`RV0Zj}Ncx zeSXxI_;7vlBb?s``GWZS?xpM~CG)|>>8BC6`AR<4pMoKlK%mqs>1)}3KNxpR*^47!m<`Q$U2yA+BHkF|9<4?eNW&V{`P&F+VAyZ(AaLU z;iHIIZby#TRvU4(_)^0!;9ds|}s<9f<^O2yh!}N9$LIkG9lIr>$iRH}3o&a?_ z068(`W#G6Q@=l&+pqaQv1U_>cpXD%9GvNJOuG?C6BLHN_C&Ckm3Bh|0pY2oN8%njQ ztQN_v_-x~G;l=BJ&;oE4E|383_P`0acd85^Tv^WdpV7nFbBmJHc`4>&o|=S1x!Llel_)?xV-6q!7_Q z?{`Yk&u()Ng{)I;6*%cFXXX`(192kreraH#i#_1$g)|vI0rdx)JA9Jq-6}`{-jV0wA`~BHrHz1f0iyb zSjT)nHZ1e^&o@G*YoDser6V(fXgX_G@!g!go9mG2AI|FK?A*Jn`7kI_oeQD9PwVBgb`Y&V_vVq0Z+ zVSv~AzPsci1|Cf9qSCXLX{!umyMw--Hk{LSkJ!tGm{@lzOp~JX6Vl8$sSIviFKujJ zCSLQ}$htl1RK^TaHntb(_@Q6W1Ooqh066d&MA%L6jflv;;*E!)J%R$S@5o`ezAx#} z2ivCrtVa0+trSM^m{%+Rx5!Qmq`(YFRv*xC{<~bCv64*Y2KqTrY&W2=VJy%Lo^H(g zL2=(P!tf9peI(ekcBeitbMis7sJ1|{6`bHXtl4fGoE!hEKnKW!6j%URtK8uJFgUc` ztg7k9aWK+dmVh~lB?cPaz$vOl<*T^h`^+7@Z}^s2CzWRFB#u{jwtC}Hgn zXD}DK0py;N5;}w+piRa1oUcLMvnS{3b4|kW*gI6@x=JdOt8{N)pb~hxRz3n6gDD$4 zhiz`^K>9iPNp)RQ6-3Q>KW6{`=;)tUpR~OXBXij#=6u8--Gp%f}takWllh;EWF5`9XMzAoV^Wx%LzvipT+Me zTsyjW-8dahQi?jASDa1@@N~6LJJfPm2LdEiCYv7vzr_Kr+zn5y)>qnBMUuCzFjf~w z#zgqnioV!|u>p~JM~<*yRe8yer$ePYNO8$d+P1sEGxcxw14S0jvHf4sDjUg8DK5$j zjC%G$m>gcF8}rCem;B9{Ep$1|+c4ml+={c&%KVk;AfLtXt0PyNF6AFyV%%=yE zjqid%Dba>6Mn3^%zlkSCXrt5RzCfCcsu^4adbMHqW{2@f)kPh*C}+wvwgRRRk$L~Lm(5J`vOKnt97Cqj*^1)gsU1g&8 z5t7iu<9oZ4zzI8)(;1o?Gq<4WjJ*-w!*t}NgmOhm1>IS8mfO{O#-oClkx0pq_eLbda51OGeN7~cbvD3cuGb}ay(|AVufdP>jt|tq&G~SgweEIbz3uZL2)LQL^m=~?oYBXk{u7;+ zFZ^}Q4f?P{$MU2!k#f%0L$4*g>a^u5wQ_!=Nq8-yaca^#XF{8H)M_mBZW3YQX&*^F zxh@ya(TN^R3H~U|wWOGEthh;j#h{tkrL2ewZQQ1Uk(FbcAEs34+_GNfV;{su(Ph!p z6WamMVc#(n>Cf6m-Q^iHhSn?a};0mV6dFj>IltM~FRpWNVw0=`0mR9z9;DsvJJCu8fU9e0ur3titU zY#%)fm48Z6izw4`TT9Y1WofQ}YzbO6Na$>CKjp+n zMOFQvDo=-zgk)zy#;|}Q81fw-ehEP47E5h?^I%@7s5`?hO&+B+hG_2!sYfZL#;hT9FFeV{g7M8-6L2} zf0c$CLEHd?C4jE6|J>{ zCbcGpp0uyId;_J{?)UbG(9-$UdswP8n~sK%c7C+86LCGMHhq#xFljys>Qjia=68p^ z)bVrw=I@EYdA!*jY5bX6FqI<{OLlUG>?qDhaRc<4na1HM7`4hyOb`rfsU%4dNgunl zgMnCdq%+yKqdTf1ymlB^X&z_0-B=Ust2B7 z-Wn}cT^&zT_zf2HCa8BAa5OYlfEZu|fUWBe#_*_x4N1wE9?BZped*x3I|cv`%C`^z5ko&G+kyIE)bvYT*>c&~|FuCsk+L;NYpyFTG z##cHcV7unRC6eATv+7m3MAPL+oeh;$#&djM(k`4aU%k<3B4rw1rxWm2=ZLy@Z4!?E zYwm|Q4?6M=26K&Cr3H$HFEy5*zF$w@$nPZjJB5{rW zPkk?!3T601Fp)A^l$jB;<`K5qC_Ap8V@t{+B+)<%eFyqvZF=2lP~I;Ww5DU?U`;^U zF)XZA#w7GlqR`6k@@9B4T~yeVL+vfeuuCX=ZeiTKKLZvOETS7?A;!eW=!*DIA+OoJbbL*W4$Cc??U2>9h_fF7A8loYN7@i-4$ zs+0XvVdsw-`7hM*=FRGUY1X{Q@A-tab`sfs)8qE!R_OCvtit!hndd14z#b2ZZt|zg z2#EIjZ~xZ<+8_m0!009OWSk`qT4;{oM7Fkeye3672B~C6N6{Ds`H;c2A9?T%uB!=d z5L4oc-)w^YR)X`3F%>I16S+^j;i=>cK6Jq7YWj#-;5QD-1b;SIawvd>$fNXY z(mwNQk!ZUdIHPq{hKCa(Gen37{dER&>mzbN?hrHM<04mDRnj%O{L1PfAFsHF`r!9V z(KJ53S9IevPyr%LJOr7|{-Y{3nEXMn-w4~Wi*#);aUnn4M21tI-&!E$0S7I zQzeliG>9lx2A$ZY?VM$(Wf>|(zF(_^LiCm}!z&D_ELJ7E$J|G{Rh;}!PQ@;iVvY7A zb=!$;69xL<{`BuzshRW&CCl8``ExzEnn&31LJjKWm`gKBQIWdGc-9e4&W6r=tp*Qv z^GS)u&RkCc^Fj64ch`n}vMVan(>iLIVu4(=nXF*LW5%KKHQ5R$4La%e$A8XR>1*KUDAai z%6mYTG#6@KdRHMas=?vm*?2aaU7OoB64BlM&yfbW2p3HMo1b12>%>=m-5Bp^IJq${DMD883!5=Dg+l#g~>IS&Gw*=i3moFav`fsZi-zylp@jfBC;Eyq&qXglmAA{1mx`2ON=>B6m@wJr zYg>YfjD^@sz53%z=ZRXgb8K`m7il%aL0YI1?6iY>tX50><`2R0M`v<+TEwj;Nb3QG zB#X(3oH0HLQ>Q2cY7I8?SMz8Fgg5f8gzR!1u_*~Z~V-uM2 zu`uk)F0^4r=?>3)eBIrktFq!Wn4}ok(VISl_pQAQ8qMOC07L~UMvI}u=Ro2n0c;vX zue(8qRip%dPLBdULL2i8cp1*Kl9b3WGFW9$XOdUXpN>L#?!NSVrYjpOs2jjR6aOS8 zxj_mpfll%R(KXYczsof90|KeHm1TSjio zK^LiHIP2UmR~5`Fi7^y;xVChDWSFkP>j-D_HWOxf(?;ZjuSELs=7 zWL9leSgyrP-M#Kju+9a|B~%3kxXgq-MI`v3%$6qS+6ShNq9R!au@+sd4fmF}esWKP!aGq0f~UQTb0`X z#*Lt^!Z~;Up&H_^`@HMki#`C6#ReCE&E(BO!`jPT{FB+Ke|YuS9^x#;RkinNwUa+- zW9K=e@Fg}98HE+bh>5(BE`VQ<%$;HE0c$V_6^D(QLv}|rd5)#*HZaE?R2PD1I8Uoq zQfNNnE4onL<(nv%AQs}5{dVOeYDu1~WzQ`_hNn-LSI|x)P9L>J^W0+8yfng`bKehF1QI)#qkx&Mg>$HT0no!o6fTZH%!`{n^2J?b%aVt0I2! ztI>+rI!%yriSMCcOX{FBrZbaIp3x8%92$RnqZ2X$PU8FCC~>jfC#;@)gq+2UWo~!< zA~!FsH*kW-Z;C+;3M7r%t^4T@W<6Q4ol`qt+bpbXI-;BQ`GdEcF){k7p-A`wpy%ghD5eT+ z3MGJ$+h`7erdQi@z zc2L}e@-XBvxY?5$hZoHKiSIGOS{w|yo*xeu3tcTX0jtR};~G%5)Jc%l37qP0617&X zVTl`7s8v}gXv&TbM$aVM%?S?)fV2thXUGr49pe)Rr+J?SGbl*2LEBAbVB4IxJwtaa z6~Pmuo{My)mvHpHUAdP@+EK3_Ym`az$lp>dhoUuWI{lpwXYreC(T5M`hz;~NTlb(c z?~oQNbuywYS>vP*1#3=+;0O|`PV6Ub@Jg>WeMxIOfez9v7Xa$f-OgVKfMPyfzy*La zFg+yX^0Y~An8FMVTx0k*M*>`Vf$aJR*7&v3XnU|1D;%Y z1y}eMvDK9?wAN{^bybr-v+Vk#f6xIHEdemgR_Kj&L84=q##B7zzxoZJjr4jMBw^Qm zpPQ!&c^nA8v{Cai1JaT>Cg-b_8<0;!GMJm$iVX4NqdD&Um5Dk#SZ+D=nRfsA%mTOZiqOaP+jY`JF7DD!!^&-$9Zp7y!-$il2Kh5qaM z?LhLGT!lTdtHtVYzjcN>I9r_zj&k_lnEHByv<*zz?qw< ztGkDLS?ZjZT5xC83JoDH-`&o)`U9OuMJeRht%!H(xC9P#CkAYz+SI`}?eq>2VQOFR$Gd}BL?hlWbzaBRcy=-_ z@N?XF1RccB``MNnLrn`sbZ_sM1fH4`UoL(gMrM-O!3)#X6>rRnnbZRTnqcDl%swuJ z){h#ro=cv;-Ya*SJVHx{zF2k#R|ml4JKi8A*9(6da$f}nfm<@s6}?G_&kn4V&1q<1MJW*J<{~q#L%hw>=%Qw#z>7y7 z2tM`Y0sIFekHYCu6!0X8@I;}dtGU`SACED7&T;FjpAs~uohBV13KM};SU zyfhnsci!PuP1xA^?2KgW9#O!0p`vw8jn~$q7RpG;lE9nIMM3MJVMc?V`#y-JmuD_! z)!evx6Mi^8-!b}w)?H1olsM^H!?SHnSOovBOx>ht(Hi}Z6TDh8G9_72m*Ok9eh|9q zU$V9^VIuuVbG~|%+7sXcdH=_GFYm^uz@jCE6gVQ$5yc?0Lu|IfHu1;FTMWww$RXgR z5A1u=RTf)yO@c*gU$Vo>)ng{DaB?MJgZ(kKDWS0&{e16MGXDu{C1kd@IA}#AyL~GC2DgWM{q0jOVXD4{l?)y zGneLZWUn>yP@h9ez}rF0_P0G*M#*_C;?a|68)LmYe1~f8d*9-r;{}opQ>gM334Vyt*Cf!^Vx!hhPUqL zu!@&fM)2NjT9D<#?@3n%$?USutlg6_k7xleBCkVpa(2Pun5XY2epV6DE+?^p5*twD z-5P9x@64E9-;9!fd02K^Rp{t9!1P-2Uy@O-rzehezBYH1q+=?l&`c#VpT-qPd)9#k z4{fW;s^Sqs+u-<#`J?fd2pnSsn~#4t)E>l_mdQ+Ed2yAuxw66OI>ZsOzd3c@SI2^f5u3+uaEw3!bmZ8<{bIA7p0~J82aK1W_ zJy8Opk1+*Z`+FlvhIfCH#_6S$<*B=r zU)`zJ#;2wJypzuw)f+zpRvx3^)}j#>MGnop_2e%}`5nd>H5CQbasW9oZ>L|Bsl6+~ z-J_K9t&FoH7K@R6l}EkkZr;xMnUb;6ev4HdijfcB(DT_zgK<|+2!Q|6{_gLPk^x2N z&WZ}Tj|~IAH63aC;t+~ZJ@jS%JRJTysp!JvSNzg7#0fPC2}rE!RebH(d|f`-p0gJV zx&E((3_UEMws>qpn&k5 zm#oi2z?TUyBp^WJNw&P*@n)TWAda6#`}7OYxT}Zp_4{k_(tAiCCw#TUV&UF~|E1p6#EtO5`QaAHf$e*q>aEyGjxvWTUbj+~OoD~Ir>zFuBJHet;QXF2W#+WVmbDl0I;sFwwHj6PeG4&r02_B=0jsTUa4U_LFu4L z_3PS)Kr~85Q;XnFy32@mINLGRKzHp~1i4UV&j#5{LI4DRo2HuMAmE%ZA>=%sgrL^X=B${ zYkj^ZMw+NPB=_~;J`T7Szj&)zPE>ZITKEJvp*j4xsNW%A$sBok@`zqjOd{<$i$0otcwzq$8^015wRNs_iVs zixZ{-wIp?SB6o86dtcjopi!b911CQMpN|YWv!_xnXQ==S=E=iuH-ep*$T+d*3GjVn z0RG3T6~@PL>&0Q*I=COLHH}P=rZwwf={>Cizu!lZh-EpbhPcg+=U9O}mRon6J#p{EI^tMB9@_W`AsqrcXQT}x z7yCio$)iwD#Y#AqecGLVFXY-;r$U`w=FRdwjD|8~G+@y8@e=q-|Hg3Y^0?bCD+i8m zH;ZCbObI7QhB1q)BB3ru#c>tJz~pYZojFTbeitRaX1549@TQ&EWMpW|UM^+)nxJUvXx(a>q@zEDbhWj`Dc>QINbW;`SZuutmqahfC)quQo zl+M)3W&aAbcG&xq->3WZ2mMd#G7;gqLRWfr8}_bn;hJ2-Isrn-2wcytmp<$I4p06T zx)eQ0TAIKzA_0`%PH7>0pAm*T)mZ5#{xXb(E4(Ty-5O0)5=}Mp?7uG61Yqu}KbZNV zq<5+pF@Q%s6ctdaGrJ0(Wqadx&b|Gb@F%~2mf+|kV8h|}gMXsir`$@30Su+3KVk=G0)2KHRb1re&X3|{EWcR-(GE*5*(=q;r zgeG^QKAtlq&KN}3-B7(??JxPFyHS^*Z>b*iKaBs-Eglm7bu8HLrb5sIi^+OkhCplP zOODd1zL;4H7y(?(M1>T|)fGU1&F*|#=FsGA*Fh=a2bZ}$Eqkpr3GWhTux2_LJI9=> z+}RFTvYVk=H4tzTN9?Dq zGuM;x=yHQX{-YA4i)D|bL{^ub7`f^NC7FOulNER+_v3!FQJzs-v&NaFaSL(+u?6w_ z-47Hh25*ZWk>kl6-ohOlI!8Cn_Dc=RkH%9B1qVnEF^ETh!^`wAxI z0W-I@Yc=dP1ll9&E!=>&a&<}f+Gwdpjj)RQm^wM0!lEUPG1=l@X2UIPyldGe zFW3{%9%;p2;77?|Zkv~C8D_(tF~!hMk>bhbEeO9VT2cU9R0>XpM9X&({#mzl;0>*} z@dllFV0LuKM$c&P&L)SO<#aTWGPg>GCRH;r2XIU`eV1(+1-mh13abaAt|=3&A2ggg z9OX3B)Ky*HzCBqT_g8u*;-8BZAD1CnM2VG9Q!m`5o3){l7J1%LTrp6)e(V}>Z+M0B zrac5^`h}QKEC@wK3=UicN7Fb_Qt)r%;yCYDeeUedqc1n=!B+8W+W}U8kcmM?&KFJg*~hwp43tfFiP78;Zno{IRmF=3)8a@ZWFOjuz!fRm@%d@%TF^hckHb5cnf<$W ztOZ2O3nx-l?I2HxIhbtEIBPX($aH&TR8}MTjp}<{48s)x{5F;O_f@TDcO&21x){yq zbr3mK0=E+auHlDXNVOME1n*MG)oj)OF9c~4&hG^3zp(>8l5c{EM~>l#zyrj}Mrebq z*IMqqv?DRr2N;I>#vAf8j1ZL+>fMcIC5^%&t(4oaGl&;%=?CxB9rhhy9~@tg&)^`` zGRT>BL-h#u4w-?s_p?OLbY)ukb?5nZm1^9jihP-tKj1tVN(fDouEZM)`I(Wa39n3! z0o4;v&*O%%qsZ1MSQ|~)@R0iFE&Ub4kXY`cX^2LwGfgbq?vV8-!6~il2M7iiDJcKA zj(_#+tpBV1QIpkZZqcCkxRJ++p&~4g0GwasCFZTLZ zd+>3xD^O2UV<~Q$GiLEtCVOAgG%8g2)c))kdf@y;4E3h@_0h-Z$UA`ROV#RN-Grpb zBer)4YoYOWw6iP^D(Tng-p$I?L@l)7YtFPn2kQy{ZpeugK^C#UckB4v(cyHs%N4;n zmcXcI3`^nca)__@I}o&=SPTVunw{9)!S-Yq3UbPRd=011RLmDL@?~~X$Gu6LtV!H+ zo}mgqL@y5QPqoBFv5|b1vc@Kd_J6w<0j9v_RbXjnh)RA6Hp94egiICH{`49?0{z$f zcv&5x{E3%O;VQpfYmuXKyNGW3_QQ#QlCC zLoJPvZUvb-okSuw!2dAHsT(8ieWiwHYTdl;L+*{Pgau>Mn8fG4J?TH(-OaNQhX!3y z7eW@<70qnpk}{C1`vLE?qJwNz4%Q~=A3GC7d^KJBg=CPxa!fu`2Tt#YfsqBGiW8ZQ ziWmm=!ItkTHtwrpHa7o-nN?mRJWH%VkXxk^j?yBpz=$8sjFB zgKMmcwI?|nfn*uAf$If0km2oVQ)h-cg>wSE1J&v1r5C9HHRpRmI4}XxL!Jci-fmj8 zr)-W5`U>P(WTBR5m@9t(nRlqXme!{Yz$+6>vD*r(@i|5M0X@zr!!E7yO=#8cb9&bK z;rq`E?-Q-Dx+c>2f6xd@`hVT1{}QbOC$xJybELbbsX-pBGMJ+a`+C2BiO4zDf-Rg7`s`g8H)08Sqw^T6EQ8YrFdGD(JE=|=x zNVsr{wV8@gJEYPjFp%YrzBE97LDV10vgo+Bh=OW&ECi=h*rk|(jJ5Vwdp~IN3C3c(kraqtht@XWzky%< zdX)+I<3aB9l1#L0AZ0dr-cmeYKFpL*`Vhf}bK$l|RZM7cptGGH7@j(ZAaL7YBGe!_MYT7xYgyL#MV^Qk~GvrvPDNOW&pXM*d>aJ`g zabOFws56NVK#2wwm$+e=NNBmsC6?ZoA@b3OLfqDepTR~azmm0IOP%mm z1)QMAXOY0hikBYn_Fxuy4MM~B%chwj@AZ`EIk!@gP$J&t8`@Ais%)~{cUaS zrmjIHZHUef?;*?*OReo)ssxhchzUNW6gM%syb3iD+fjEv;(drKFoPY+-Iv>^Chjw9 zSjMrTjX6w-6XB3g)0Y_NG|ee0VM!x5%!U{ieiA2tQ>)wE%kAS$*kZjE*1;tdqkpl7Ei|A%F&o~I{={68YAqRgwEm-@i z&1W1Er){=#c;(PG{=vanwxS z5qsU#Z1D6GTylqO^SBgJ@un}xlQnbcpB_}dP(UqXaTI0k`ny!uLk~htiy4Z9XE4PX z5FiYCfsIc8rvL`1f)o${iG4hO-QdRXxvgnbN~{h95oy@LJ5NRWUHiVJKL1zBo)@>k zWrzA1rMUOXxj}V+EJy726+M*Yx&CTUNl2p*LawUjc$97Yr9qOBl8qMd?5QGy13=w# z$4TnNsr6y)*UVnF+f?)^luw2$6s_v$(vP5h}seoBVr}6}fqGcC$%T zge-IGjf;hAbR@Y)&sVU@($^2Fj|dRhv1S=OY$&}jWo()fMfUEV=c6IHtj4g@ZNd0N z-aO6G!I>3oPN=%JOc}GD^FWw1tb;sUSb$Jf{F*BUk4LZ>Npd$lZz@Qy)C zOWU#+j&uss;V1Bpr{Ap4<;SgKL&^LTxzmB7h=w>4%_!SE>wiKW4UhsNAibx3CN0_; zf@WVZ2_z)1&z-gEx17ZOrC&$NQ7kp@`|~v5keb4RLhVsgF?x||+$-=pTb0Y~@@@2FJ1cEwePN7+fg|gw)q^XzT;d@-4Boqjs z6ZU#4q)ZiFesJUD`PbZ*Yu6@g#hMgUE}xv41`((ID7y&v_4#ua!HBWCsePfxG(?<< zdV4ZEHbKbuo^K#v?*{3$S3vYFry!LZG!g`xSUF8w2Lqx{keQEug~qtU30h+r)JO>y zX4#C%(V@ZTEbhBhJDG4Q!rdvNwR_M~g@}jlY$SJXI5g=&?>>_vFA%ZL&v-Ea` zA1`b~Ah&~q61jP}%KyD(lc#mF>Mzo@-&JFyJ-poQA2hc8dq=}#xAfyANA8=QGGvFk zdWKCG%Tnq4-NuFcAA|V_i^8ZvS}=fN&ljFkZ|+v>2mogeN8PV2&=m74;)Q?pR&Y09 z1gLAoY!b|rh_UsccZ!B`s&Vd;Kfhx#9=U>UwO*)7%KC9U9MW0lpAD`DdEw+ zO;L`jn!jo-_SO>ALJ_BwenHER`m9UMk7#J+z#P0l$!1^{wB=>xD?J{GNFR%_e8iRn0rr3|?inut< z`>Zec$i5}@f@_Z7LHl=K_5)H=>lMD1;}M*ypkF&oiAwJ<6zfm&SgF{-%htz~MW zQW`VuCHbux$7RZIMum`me7W1W3W$$e3u|6Y#q!XVwc<1Dqs!x%Q9g9-3{FO0U_th- z>_=5umzs&H0y^4Nbu1#3Q<9zYLTYl37o!6?b{qv&*r6v*+^b0hS8hsX9*F!sF?upt zS_m@?L}Y-58GMIRl9FRTc^YLd)xd(98R?a{W!j5yL*Dtz%U_m*6ps>0a=U8-KONK@ zAD^BV$8mUTtU5=Pn&6%ezU0-XM^Y^M+S5|J3JY}ttRXj-aTkHON*@>$0u9dnH5VNz zMF0zwGCMW0@Hpsg{PFZzps79&@BT_XCB2+aP>k>!Yt&f4G&%pNk+%O$O4eg618=@* zFvzf2?GfP($4<;JhRm@hN0|wpCSA6G#(Y7(n+tsXlxP3oc#Vo=zlkShy7a=f0 zz2==ESWGH!C*Nl`RV$T}5piOARhpPAvf;mBAfCg_3a@-`?lyzQ>(|bIA*)|^(pt8! z@_KjaVS`&T4$E_~H2GM?Cd(}T&iB2026SQz-km404)cw*rE1%IOu)BGmmwq-3tw#- zXdy4H?3^9B`Tm0-K)G-M>A&@gLm0l-7(o}5;4i~RYHBq+zL6jLF?ZKf%xxBO$cK>S zwisT4QVx{iQ^=kB2lvS(sy>g<?!r>^C`Bcju{~Ea&*D`e`2Vr>j?r~+Te$F!ZQDj;r;V+~wr$&18=H-7n~iPTPQ$z3bMD)7 z$N0Yd%*ftZV@*7BO+1sv`jQB)lzldj@P_GvVt#uWl!DjXuW&y}%mJ%nzyB~+<73SM ztRnq;?uzwz*F$!PPnc~Pssfp16Xhv`N6Czwuhd-oN+jw} znSDM~JgyQc4ao&s9MKouUwiF1+}`kpcE*z| z;WMnn&(~k|NXq&vmQ)?qA*4)MwjY8Ah}_|sisS-@rH*YZ0D42p4h{B z#^p@1sJbCys5(7=)j<;^M-Uj<^tn#G^s(OThqHQ}v(N0&yCqcEKM#yn1EH4@$taV5xjE9 z`};q>f#e7Sk#_)koTx_LNH)WhPmQOOKnlsq%e2zi(zG{Eu?N7BCr>jwM%%P?V$YY- z{B*{h=|E@8gTsNH%XJ2fl=iQ1dO%~dM_GStFvKP&e#fmMYzOnsHNYPcbJQS zK_byb+hQ>d>p70CS0qGU%cL(NM`bJ4)-4S9rtgQ@(qy3Tc>P$yXc%l@=O}S@9T_5L zX_+S2bp)&?u4rv&-+IS_%A`ln1VqK6?(_l)6Q|xG_BkHmbOA7Epo$a81#FHfwF#7` z4U^R$T~)3-w*_^jX*=5meh)h3??+4T#5D%!+dYYY!u&3-mMXT=s7b6Bkc;5*O!Lbl z$97Gv*bX~R){`iCUAoAV7`lyye{az5EKjNLHctz-8y<53x+Jotgi>| zfZL-=ZHWlA1hr5K19ovju)S^I7noe?dG>QUwn9ZT`!q`Y~faL$n6z}(Py##DEfB= zEqq3%Oai?)iJCu6`9=v6o;50B86m8ZeG^NAX)N)Y*Str87MkV&V}0hF47FtoAsY~e z7Dsn#-XH4CmU`AB*(2U}b6(fZ+TCvH3z~zZjpN7HZEnql%b5gK?)Ee7Z9mh|}82<69Z z_#|Dr(e0^{(SnGI8`JqcFNl2Ld31KWMem%mnmiyT#5MX<37db?qYCk8#o5-;_|Bx^ zz8L9V2ux8imUz1j_ZwiC{`9Q+zIOBzLLJeV1vE=mr0_zS%MoJ!U1wI6F~)0)aqr2* zgaPun1l|{8AGx*OyE76Ts_igd!>6Q9=N&Wq!(GnUO&UGIo)_I3l-vNuf7~ld*YJciX z>im#YPqDkNKj04Iu}qH@d_ta28tvLy&6PkC7((W%OT;PG=H43V+|dj=rqiDlHkhQZ2nd#{cOaejOtx%9{ zKXKYmEX3T|9*NuO(|${e5+i_<&@zEE5 zcfbdht@0bbD7sm)?v2sxAxC5OdKmrBirkJS_fU+~?tR&)mcrh&uL0XCy}**a6}2!4 zsAm?0VaXsg!#`n|R0(hi=<4BJ*-`Y)&XU!yjH4U5ZVON}qGy_sB}K_lFJwLr)lnf! zs_|fx{vrDju*c7u%)CD1A4AefBDdgg&e0KD-Tco?th-(yh-f84m6R91aaB6}=PJHT zRp$`JJk5ylU6g>9snud~81r=yZ6obEUt+XZfNq2L?a1RS8GkA2AL$PZ9g+wdjGam5 z^M}1G_3D#1iv^*6W2F1l+gOk%pO{NazZXBj51iAwmqDK}@^%^~Sjx$NUmb<79iZEfOdlHHhR|$oR`S2BNAO1UsQWs(!k&k9$UDLYAj!lcXG0B$M63Wbi>W^9K(Z~Bz;`FRR`4a;k~KZ19H;T#+~Q!nA;?gNfZ-~_?zYdjCjdZ!7Ii(7 zSD`9H*m*EY*c|M_Y086gSYY!+^LJzNoo|!2*8ASybN){))IoAW{sH7pXRlK!IAa7q z*f&yKZ8uwlRO~8S$}0uw-t5MM&&H->i>Xp|gf9r14a^ya1mc&&6i)edi6tgoLK?>X9=OB2#qO3^BWNw31 zzj$wasp)c=W>nWByGH{iR^Ar=~;C{GtverDz)uF*27^qs%|&HIf3q@EZw7gJQ-yybKl z=xH@cY~zvAidTDCJ}gv*%l!nyi?4^W;+8%6@-1!G^22veUs95F{2h4RJB4V_HT!PB zhMx*P!j`-|_Nk30I&wJd0URO=3tDxR8yJmBMu=YWP|(#f<4y^(|FEqFgXdP;I6sLv zF3DgGZ>~w7COoobY8gnna_$fHX-N|J5a!g-z!|3WqihQ4VVqW3?a^VjhuNp6r`@iG z%klu;u5VfQEF>D2z*EbK$L$uw|nu@F~V59l=?RbqeB{^lfmV4YtT%2Dwh4$ zv22{o#YY;f94L{U0Dz@TjP`{9uN);l%g3k<0uFr`1oFd`s4F?lW=Cjwsq(d=eiq$< zDJqUAD$~(#g?NEk8H}w|kg~Sq^7lCi&#Z{5+`j??sT0hI`oGZ3@_E-j7we4kDoJ;< zsNJz6W=AC5no1Ihb?Bdn>-B^u$5|g;;Oo25P4NmwRQjxt%rlq|1+g|0`#~mJN}%jx z%*Yn zglP2rcCzS6SzlY&$Ts;-&3j*T0!f7oxYQcDy=OMj=t4ZQoGR$&@^*=cV`C}Wl~q}H z)ns2CpIi`HJJy@0z*wRSoeq)My21~2iCUS9W2|91(%6U8 z`Uf!kAdTK5SUnwkrcLdZbG9U5Mn9>{WsONEr-{fjJbWh-cQ+T_d1=dWjD=Q?B**JS z@iYHfaQ1SG!1>Uf zrSF`nn>+gtG2(g6*;#ebM>q+%XGioOdONgL4?e9$ew_Dei;x|*;YEWJXDy*F<=K|p z#sPF{`{3ve(VX0;vRPtA4fX-w_OmX~sH%ap1)9K-PVce4Fz8GDkZSQNU0OaSAej`$ z{P{MJc-|nCL{t+UFFvQ1YpnZa#KABWr{Yt<^g}~~xi+Y?yE?qyfJGgd z{FnQA4~IP{bl*x})L-d>`@mKx>%xW*!<($=3ni|TuuDEkq%ObmN41AXgh0= zdf(dlaZe{O{G|7SCqT9oHdm+7mx@t<{PK6(P0h9&E zqlUv?@I%hYP`Hy*Jfj(>Y{@1yt#vFOwg{$-H%Dhss|qv$TjpDO&7V zp5vCd_N&rxy3$7w>-mr#(mY*lp3-`f0m=!$#zd&NW>y~SBNe#G{Vlv??y!J3Ws zWuz+?L|!U)DyiO5%%27E1HQtn;vbUMFQ;U76z-}#6rvf(Yd>Y`1_OiG;Y_R9;dPkK zc<8wGnlJqBJ&m8zr(W|9<7zpKeJK zwe+8~KQ_S#lbG3-QtrGCikdQ^cyRX8a{L_2##3hd4ZM2rA)4etKUi$PQlh+7q7k*i0D0cvj#^Ls)1bMBfQ4jDh|I6*wE0{6$f6;~UbHOvM`eXUNbK(s_8N(>g zi(QH0J~ciA0wIR2!C|L8B-&8D^~}=-1SvTgKNP9Yf(tQO zlt7z}cJigIYHrL{vIXMmf>eA>2ntUE96a@i$eM~M1}mcK^yiCL)H&8Ar_gurXT2}I zq5%dPR7l^ga`L4J1}(>Y!f-*#lJCio3RY#GC#fJhf6KA;{CiniIErp_NT|r304zVl_Y?r88`C` zuuf67q_)P?cvea)Z8A{JiTO}n>|5^l>^!*5=;UP3wq16U$Z;j!%=}o^vWX{*?cy5v z^}aCJ@PDZPQ;D|Mnlf|eLd7YzKV*qsViC~T#Gn^e`+wMx=+VP(`boc zCXK`3y6OL6+W3zs0Y3$EF8<>fM@@hJVO|BYa&1;%@}?k}e%4^121MGb#LZzRfe(IM z7m|Gd(AbJ0ys4wdPE{OtpMROlpD?e!?zXw23yQVCj2ui_rFJQ#fUJvO^O|P8pw8|8v-8@ze+vm(&&B}Pl zF06O~si~yWs{OV>1M!E1B9TROU^EYltaw!ROUl})^`!5H)bL{cS*P!9MeE1F#u+xY zQb&e4sD3z!^U6ID9eiy0$~`ewz#B=rU;VUcVKM2qR-~1t)6qeK8i-tGkX?tTZR7mvMQLj(--7?ik15F@rHiNiqY z1&Y5~(z(6fHqV`j`YAXBz?KTsP!77y3?SzZFh&F}I_x&8`0(B(=9eAkS!X$6fKSk%C z?L8a6ubs_fC458rVuWCn>6`kN?pZ#-PH8rR=gGzSU@Hki2uD*5LTnp&&1QD`oJ(DtyVr3BLm(uMx}xf z9XuV`K#M{c0z9#iR=VVbgS&VScgLg0`})I8u3nn*!HZ4>-|}r2b4Nbayg|(G6_iTd z_0yVYA5+uSaej4!H|B?{b27* zv9o1-3U}u$TKW>gf0{lXS(Xj$G=*P(P~pNqYR~_#jVXd;BLeYig!+U(W7n1mPv%?T z9Nowg|ASEjnU|T?1aS4qq)VR~d1?4kZU?^@<{@>@BCL;Ur_wh7aaQY*|r&69AHCW zuWyL&& zU{ij1Qt0E0D z*wAppb76>M09^Q?vBAFGYlgeV1@`g4s|L|^qowCq1h5w@4+n2p`z%IZU`3H5gBLVi zVs5Q2Bugm%?IQr05==+^o4Cxg`XE@{S0-uE@Yu|mO_+sbgOv{>LpnUk(>Bq!eav1Y z>@ju?aIyM2GOG{ws8yX@K3cu_Zb`+lUGHTi%N4a+UrgrCxB4Vf)y`0)uiZS*brT3C zNL7q7w(ZbczVy6jo*r@Oibhi6e3QF=o%W&lx+giBgZ7Eu6P)9LO)soE;74V9C~qG@ zshLQl1y@Zssx@amAL+kON{j;rA@n21o1o*^iLqS9u9BfIgtx%=Sq0VDtCx0DrC2JecR>UxY~a* z$j{^)k2zsnjG3^_`Jl7j;@*H|TzX0TU*_=g^I&rHJHpv3N1df|%5I0w+(`k;BB2~ArT9NHZ|n=;}% z0fRcJ`p<`bq0sbUGMwpjRGi9U(O%VhVz?Ya{?h*Bo|q4m)x0Swn-TxtL4Sr{XI@-I zXEbf2FuE4Zs;QIIjZ9&NmeYM;*v>-SF^)!$+SdBKIpnv-qlBNg?2cRO`tK#;E~Zx& zXyu{8f5F0YrpGoVCN4ISjwqAU{C>-EzIp<85Gy6k4A?=}Y_OuE$uy$kT1Pm-`?ajy zjIz4CDCqymv5C&5|D7hTLzMk12US~O>WEM}@+yVHn6Wm76$@wOanoB|x9cfjE(g9c z?Z;qgp&-cTZlhqmo-!!tc|s6j?e)i?sun_kU?8$k^RC$K7ZyqX+!43U9JnA<=cd(A zv<>(}#Uo|dvgHc8qLCV)JrL|%`gM#e+5`T7&ZX5E<)y>32a4L5g}h>z%LeAS{nuF; z31&0>H^&HWcf-q9+aiA)D0M~8xyQ@DbPq*)z1~$5ipL{CT1GfEtjUU_dl8gtJa^dSZ!t{<&0ex7Kd^_Kd*{j zU?NAf^?{5vSVs@Jpgu8%1uWskLc*T;Ho6vG3sQ@^I>^~xcbc;jaJNUM5cS_Ew9_;SEGUrZh8p8Eb4`m5#a0omZz=0bDAsA%T%BTT6 zi2C`L*RwXY3=ebIyZBE*GYB*>Ai|BgV;AIocuc67vY73yTbHlDs=mXvQ(P16p8-cc zaLz9b%GkcCGXME1e|$cAR9_~NolAlo1I+{mc>v$Sy#K`KT9|}(ea{jl6o`)8K#Gai zcc*!%mafM=Uum*;3f!A;m-3~0$mgNQF-aGhOn2XI@cc>dwcP}wS~csZQVwFEM!GW@ zpd!lYpfa@J`e5vKR`2S#TZP-(xb%3)V+XH|I8(S>dvm?S8WsR$IrJ$Z0s5V}J&$_5 zl#TI;YBpR&{{KCsltl&z+}d|pf!-bxG51c*OnU3y*#5faHks-J^d;JZV$lr$fAs^z zMlfCFZ)Pb|kh+EwStTJPkw-4{g_ON(|Dq@yKhkg=j-}7T$UbEerR-$(*@HF_zA}2% z+_N&U#KK{41v_$5l%3%yu2!td`^6wl2y$H3@us6?f$Hi5e9dR9j+|VQ>&@b8Tc&j< z!^%{nj+Vmq?>m!QhBX8;kZjn+0!n&K$od4{?}9uLbKBm2FFbdusRt59Ue&FQJIQMJ zJ|?{iNP!*7x5mG|rAa?MYwyOj!Xvm($KRYa=}~^H2;1JVTQay7I?M{a%=mkTLG~sm z?|=Aof;n(8L4^{1;LwB3uUlTNsw);%^IfZ);Eimktpw2F$*M>ep%5#1FO@@o?dC1* zW{n}v%V5IZB$`x$q9ej@ff70Q*dZA}3Djf0W)pk2r3~aH;M{YQZ_kIJ;W)J=C;P;$ zC{zv#Smu54o84Okjg)}Y_up#Y^2A+Dm48eM=h`S@?OSgq8k{_Y^Mrbn@)eQj1wL1= zicYp6J!u0;BcMIefwa2@Rr~D&U3Qg8)G*!-{nZ+t0p{u#k^FOXgHmgfD_(7OhcV@< zFXg!Yxjg00mh=|L4V(@I&WPz!*o(g`m#eNTCCosC!ZmVy98Po1Qz#_cj@`+;SF`WM z?gMbtAL1TCzp6p$oyR>@izSzU)FSPj zS%6?@P;xAvZVQw47fDBsCtaUDj&;gq5yk^dVzH$T}GV)El&*ifw{3A zjtBN3)*aekw0*Kp7Rku#wfvrr5PE=0NdjAm(U0L^aDx4U;~Nu@!tw5j0h=$S&f{)@ zGc@0kSR9bit@z!`MR(Mo%;TV&4(ne<21p2IJN?Je4e!`{t6llZkoo?p;M`iG_Bi2D zdOfmU!L$`&k%~_BWUPG+?DOkYL+^>muuPzvQ{cTp0#jAUN83gKmL%4JBZ?9VYm(cb zq4@(O#}cNFEIQA-earsOBbVc8f4&c0Wi?RMx60UoY<4Jj! zYE;REClDz7H#|e3^53G9oJ7uC zlYZx6*;EOpAJ#Tj&NG0FWUkiR&e=m6)_1Z{(KG|Ms}BYI`*~0#!SuAh5#MR8+chM-c9@CilY7yR z2)b%iBA{t_bECqK`t{s4=|X}4mpb;K_=@^LOX(mU-fwYjI8T~8w0K#9Nw!c8q0KnM zarc;uRIor`A5%H@>*D}E+J=Q^L+vQk4~ntG7C+>7LwAlZv=W>?XUXb?=AU3BR*WB&?szdln5k@iHG^Jf64>johqU|V z8oIYsFuF$dfIXP%(8kxiCW>X|fmuWQGhl4rZ&x83E!6r^Fz+BqF9he~qZaRm(HbD| zkc@467fQYLHZasr8{=??mJ+*`-3h1j-hp*SC|+n9h}e#eaoCAIC-5GT`rU%nn^Tw) zW`Gy0rY#gqMZvg;<;~@lVOZLNx3=wrL9MZ%`&i|-0;;4Ooy$#MqlOUD(3mK%f|Ip+ zMXo96*E2UspShSoTo42EVnPhV!^*L1FXeu^oZ5X0!Mlndk{nv*z+}60i`$StXk_LH z*|CUmNTwre6i}<^Gys?U^%ENb$mHsyro9$XGTie&zxr34Cb1(!^9cLkgb3={g<7%)TW&9f{}~N`;jeW0s3D+dm#5 zrP09{&y@H>ChdYh;}J>^LT97e-h2{$CE5cSdi%5KzTeqB@w+qr^3BCP<<6M07mk7% z0a9f6RNHmZK92#N`DY-CxyUg;bVzW0HoS|b>4OtZdRTk=p~qxnb^mUEf=h0!yNZXY zT~5VgUA!$BN5l0{xtS>L5_U?@iD{ZZy`x*{krq@9v^llEJv%cfa&iAGn4F@<{G{El!*pC3 zM+`JlH0OTB4*+9DS-<9-a8TFWLBRy&0aKCHJ4GdSMFlLyY_}!2wi=Kfn&M~;2u$_w z_R2P@rgVzv2I|j^ubZyjf&-Fxa*c0eh6q1<3!QaT%I7%XytKdI(eYoUv0+#@5F~Ip8nUBg@Ap4{%2UuMo-)N#~Fz z!xhw8D_b#18DFX=C#j{_+IruWZv0ljx$UZ zE^^qG-`Gy*5+&OvV z+^B$++Q^|m984Prlv`X-!6np)3t0hzT1pC@sYLt04xc7A7Nw~~4toR#mu2cG%tN$x-Fg@O?hFO>CPv`35fO~yzR$afEu9i|26 z7!H*qnn?yVgG%?Ua2=+a<}_fW>C3;8GxTd|Bw^bqw9SQF@OJk5AoVibi=Wo*T7}YC)jt882cdq4SvSN? zyV5>Tg9RL|K+t-ItsS=mLu+C|?4}Su1qoYOh%!+Ow&nD?2B35pLkj2y1Z}{;bSYHw z{c)UZA!}Cm(`#aA4XJ<|P8F%}GB1QKPbACY8s(PG>cuYXq-(a_n38)n9klty_*Y`| zAlb`6bnWHX5a}P$C`I|=Eqpe&NB+YXJNZj9aj;T}IKIVM25LDR=Z2;+u(b~BAuJcv zlW;_+6hJBqhv3nL(%a>}F z9+vZ_ZQa@uTHd?Oyb(VXe0S}+hvNY5HUx;XKuJCh1BEKX*{f0#FAFRB#9%h&y7ut? zug00#R<_*pFbSaZsr_KriXdT;SjC$Y<>$o;@kmac>kr) z2tJNcKor1gM>8Y&p=1gomBxRqy2_Cv#P)xm8z|PWzz=Es$!O;PuW&%71=BbFhAwww zQnyOUMe!^Ef0d9_y5@|uvu*UF20#UQBxb#Y`0YvQa_RKT?{Xl=oLR&a$XM2K$@_w% zjj%%wEt-~mFthH%+4n!!P&R}10=>IUeN;o<#T1krnmoBi0mgl0}+6J=O=Nt-s zAfz~;clTELDnt)R-2v^_^O^(OBjwab|J;|T;ibZqP46`Af#o1V${?&05f=YLtu%b(&(07WU%?%~E1!26@f?}50PGvyoG6wMLixHP<(A$(TU>7Q|J=%I%eVU=Vn6FtP|6xmq9E!!#0a(7@wh~mmrn@NY7+M@j{Y5l7 z8B+kA!0_@0%_vywNHho`2z9so!9sDCo@`- z@Fha$r>FRTFLOQJesj$YyR8|a2MgHDk`SaDu!wBDm3e-UgaCXaso5~<2(kbtu*fNs zkO%rxS!^{UV6Q>us1U5PHF;y1lkSQWFyS}2AP~za&OWsifLy}pCujIo+XeCK)Ul`W zb;&rN_yOGPaBXIlBW4u5k|NlkmvkL}n|RDk&EfT2jWaLHgz9R=*T!5+D`+WkW6Sa_ z1Y#w=6*KJpF9FG?&~TG3`QC+v%ZX2sl89w6M|3leH_&L;<@A;BMY(MQG_}NsMxf4Bp9w4gI;Ek2 z>VFP40Qi3nW!nbt>^~@c|JYw+cIO7%niGO(0hY&6u92@x}$3zdetehuJ1Qk)z49p|T_gD*%qyx=@ zH4$AC3$+x7ixM{JsH=6a6?eho@+C>m0F@nnldWSJz!aJ}MrON6k`llkv>#4d>HwyO zHROEIlCRu6d3#X{$77TJoF;@K!7Nq6rC_XdS&lr8e;TZ6A9U&0{RveeEQaN#bM=&c zDY{)7`dAmbLE1#h3K0$8sGwPb(T90w@N=GMpx~4HB4IpdP_?a{eCUH^(pwdTezu^~ zN|Gy%*dasJa6SSbbzD)TMg^ZHxfm6B;F3ZW^<-e(xK!3wwr#V#yuPgGNYX&qk_<`B zKp?sdb9_Zut?~YYyp|zDSs}2Cho(9~6J9ks<+};38!UB`;+j`}Pafsoz8(Trc0{sg zo5?qFJ(_U(5(bU*uVi7w`}&m?X=4rm-ifbjCziQt&{`9rN(z3cac@UW;*5UC0BB9# z@{`6Qc$YWeGE*q|t++X1$iHOqAT5F!Ab)|Z{s03BmzJ_QPxGcJ&H7Z=%O2zcGi|1n zm~wxV-9Xo2MpuTt7|CLZ>9(uYTUN|m;rAopB$A3Y2 zBN#Hi()uw!5eG~4uGfLY3#)7J%|*D;J>b-lu;pRHsfo|w^H0nHt}!|>+k zhhAcJSME)d1|dP*JC6LjuR8r?Dws&lKZ~m5k?RI`&V7zwl6U@euJ~Z{<7VjQ3KCHr zHbXuhDSm6x%U1w#B`1U4e;Je2`XLm$$=g~H|A*=Zp2`)e27J@m?L(V61JbDO#t~(u zkoWg?pS46Plwu+4uIznV#4hLt`q>|(-D74L^ZPa@$lbXVsd1T8ufJv$B$)HK{0rgQVvUOZ>KEBV>j zZIs&^iw{peeK}{o-nnXTtYVYXO17+Ep`bY}Z@|d3#7LIJE zxA9;z;)Dv!oxMoq^(`$(!>fx6I%)v{@>vxj@IE(kbDh6zf2R~`aM9`5Zw=-h3$`3p z<1*DG3gaELxmUs;Re-}bV9*p-l$(#_J?a%lQ0`aH97@ukUyow^*e-%l?8qJTZOgmG zZrFG@|C}V+HtWt2^=dMF_1Yr({FL0z5%yWF8lWVMP%-bAf7asV9#!-imJv;FAZ36K z*^;}eY<(_pZeQO?EPl{jva{iRYXs@;(|I-oRCr`-cUhnziK$aa0a!+2sy3>W)j`Jf ziy67orLvkKu1@UQ%wM#=sK!>dY6uKh`m_3wE)>%;eoXG!@8WyYp8Pq(Rcz zdh&Yj0prNh2}3Jr;BlV>fEbhegAo5MfdZ&Oa&-UG5U?(n{=25mrQ4+wH}UM*$Nl}_ zs6G*X9`PPhY)e7B_T-}Sf9J~4vNvZWs5NZ4F~*Hqo~z_zsOpryKNCy` z)`qm0tuQVM;=M!gN3CXmu9RlvLwYIvTzqmQ7Hi1gFgbzc94ZYz{(2)iR=8?AgxiV7 zG|8FQDb*sEf`*=%-gx0PCYA<)<7E6XC)CA|^9I0`yP3$E_NmD-grCkaQO<9ud1b*o17p(uvo-ReH5j zx+i%xB}d2iE+4(@TEN~h&0qC8fPw8Z5mtyTC#n~Ej~}Bz$H~b9a@EveY+RZ=@sy@u zwQ4Kv-kyVPFZFPjPL&-KYQQha{>myq%rC?|p_$U);h_HDcX2$9L>4>R?Jy=F(EGgbFhrC| z;T%S{k12@a#J)bDxwnU;hdC1e;?WCo{LJKKwUAirWFwrgf<})zWd*h@+eycr_sj@& z>pe>P4z4R!BYRP%c)g=OZ#N%zmmFA{g$Q39SSNV1lUs?8diYUTM{RF%DpU1Y7`>|$ z$&%${1xi@f=gpoMfvm}?vE&3l1o8qu!_SVIa(Yun}4?&A$4oReUKP5D)is;< z8V_1S8wMUYOnj+N{gf@%y5|NH{l?8XH5%f@`l;l-9y@Kvz;gVqJi7J(4boa!0(KaN z{}kD3Vg<(uyC86W>7y@gsbPlGxWH^1BoAIvsg_{0?)g6RhkN>*2O~<8(HQkA=BFsX zi!*l_EradIFL-z9760XAf%7$8Z=#p$XWw~W`t`3m%0FFWL%=PC$YVKOvV@v-?AZ2F z8gn8A%9g2A(twNq~ zLlDpc0-bD}M_B8qsL&})sLH}rU@)%%YWE-=oO_=4xSZ2>GPTG+{Zt-o1Hg^cUh?}M zzs1A-bI_&;`$BRM`h{06*OOWND&FtGHxv5iBSgX-e%-U}VP*;0|Ee`WOEAaizc?+s z!^Pb_sY>!YaaBDR`hla1&`pH)Z%N4BF%jDgE=L|Une1xzP!*dL z+$ST4cOAIShKIS0WOsCi_hm;KaYs!1Qb)(>nj3__q~v>|Ys&O7_XQdRAe&qm4u#OT-n^liw! zI@ac~F16pbVKC3a+>&_$k!<9cu%Vz=k(E-L9%|_2WgVSoM6ytog30+$x0acdJ{L2n(4Ki*plVzkOPA)N7eS*P23!&?m1 zIAb@=GvY+NI9Dp|XgFL32XM$b5bbpQfieYX0#pDm8}dmq<5yl8&%aa~PX1YftmFFQ zdZ^L@ZUOF?`iE9*0pbQiJ3Eq+Y>NF~J)i^02?pZK0#^zkc`@=Fg8SR0-p}3$_y|>G zKLY%c!QGTc%v4B6X@YkV;A>O@sci9C&Nq8VC;}L&z`+TFDkrIw>EVrkaZpP%&b@v1 z^5u#Nmfe9xY)vecf4)VE8H$nFuaV*F4Ii!&I1jkHsnlmo%icwiY9wG>MLG z713BFzmO`+jshQ>a>j6LdNQ}g-XR-Ss;hg1$uD+BctwK$!f>&c64MzG62SCxQ9w0` z2CrdWTBgg2Ykt>I0Q^^~d9Y-wQNTs7!o0dprg{-FC3KfI3Cbq2YFZfVrXaD<13Y_d z=yW6-B_>LQSZ$de{-VQFwoUpd;e$}4pWdFGO&I05JF6X|`?c?}gh9nHEYHqEf1axkQQv%U_chondDJztKxXdS*3Gd5Eo8b|%gg6jl!T)q(HvD+o|WZC+`G#h?73>2~KQ_4;5tEi4EQS+2$P_jz zj|02uUgx8}74cbOx){c{Zcx;*_qY@YhHlp_Zb&PlXsZaNewQk|$c&U5{~0O6OnGqk zYrkum7S*b_t2`J595q2ZV2iT$P8DraN;JpcbezWF$Yy_qD8$cYTTU{6LGm5 zE0FCF778H&=D$d3dq0z7HX(!c3j0PT+YP*KbyT+v7vR}??D$uyoyn*{Qa09EyC{4f z^jSl=$jH}I`oBmW9gA3C9p}pWAm_me%HH@=+Mm`^KI4=PH%t>cMebegbKz$zNRkx! z;y041(SJfsw5PTI^6FqEtb|>jXQqQa8XTO!O?E_P!%UDuh5lIu8@md~oB+tudoXNQ zToX$uVf#b>qv@!^4Zahy@>wzn}JUa1~u;tRuaOeXOAn4Y)0xn?{$^xdT3&DYs$76zw6YBbdP*6Xc5me*qB&XYPH z^wvmBvBis5GxF$hYC|wx`EAG4z0Dbz>~dONVvvUgB@ZbO!yv2Y1q>DG2mcRK?*Lp` zx4aLZU}8I&*tV02ZQB#uwr$(CHQ~fICbl!NlkdFu{_gw#c2%lUIeS<3S-pBat*844 zBrwg0?NT%5ozDSJx02Gn^r;NshH}Ff;)_7!qx~di*caLKHyIg|3g+jLmYVYY_}lbzl4@% z<$-&Do}*KlY{Y3JpS~Q{5%96M?0#*r%uF-wkmy1p`i-x>D!7}vA-2bbuCuu+fx*f! zau!v8po0X1%?9akr<%B4ytW%7*43sZRy)0YPp511$4KqhA5t_T!a7CWE=7g09vv5k zo}Bl`tlY1I5HJZcX?UjB;{wZ7z8!KR_uB6T5Q1<3>Ux~>HOoc_rht;YWH1B~b6G7k zj9~E*{7-L>3Wi)7Z?)%%gwq+2Kb6mqcMSgYYjyZMIiAcX(#FiG@A-vYzYSueS#fYv z=oaZJf-vyws%*&YHSKtG&MvMNeh6>0x{Yk-ZTz6e$WA>oOrPNH10e)aifT0u3*!Ss>l!HkzoYm zr5u(z)mqBogba_#?O`4kTqSc>_#AV^b%H%)_~ng`h(- zGp5t%Q;4W1-Iq2XaUDFDg9Xd0YOF%ek1T13K~u?a8by3YT}*uJ4**+S$z(!8(&$P zW(GP$Bt%-|ROeemj_4nn1@4@IzR7iut)1wH9gFuGR+>tC7a?eD4NwW%|16(DkQbGkR+=`fR#B5Ubb_R>gc^L4{ZT{u} z00RA=ygMsO0EAGpjPlB_$OD?!CZWF$;2%`_mp0Bc6HXN;^~;SVLu)YPZof*CSbkTe z<_IrLblSc5qyOE_-n#ntw4(yaI{_NRD~V#=6j(GCD=*H(m*}~t?hbN%@0zMLx4C`c z{!D1rXv7ad@13X`=6)}fFp4;Q){_Y4w#bKL0p*N>*R(L7jniU^6^};!91&itdZDLs z|B<+|l@H&+1*rt$rJwT~1rqqMWRPh0^Yd5U7^{_qvY(3V?JYUMj}m|}R_7{aRWCkz zB$+x?heM$QEC51!=rSODyS2?-3Oi&7qJ$*jERCd?Q9_tN#Zow&o@p{h@mH`r8krf< z#re8V#Ep3VF8`?`&tJ3mHuD&jwJd(1h{{c7GF4CEb!lx%+hAbn65)o94vXV>!PB*_ci zuE8n8D0`VH?zj^&RvQQdM_vOfQEN4#n%{ zE2fS-h8mMiI^IuG#en@SFPRMR^5NdHS&bRo>=L5by=qebap z4-NwvV{bsn_(yFotpOEY&{nnwEzF>(U1QCibFHGlSpPL=#N&!Hve8b6JlyY#{zm!u zVTlo?ku*Y~!tjkA;xouoXwr9OY)DZ}F4(h|@uKf&_%7rW+F5Xu!^&H=BEB`}@8jppyzD2z>Py^5UYSQ6G2YI+)8&_$F z%^03{!pyglL6M|~zU9pbVU0Ek7bi8VbzFpO0ZQ(T6o&SLDcU>~a+()ewn<|{mkUi( z&HeKMz#D@G(9dqa{xO2ACVL_>hxm24#d;_0u?TcBKwD^R8zx*{yoJ^8zWh4|H=R?A z-~T5jkPyMFYoN*d-&U&2+TLrBD<1Igxj_epQix(8*VZ-^-NsF%h7mW|Vhj9$P6e7F2L7K`; z+YtlnrV&s5=`Yk9wxOe@q!AlKzF_oDA>P)VLeWH; z#^rYZ#Gixy7SEYLU{R!xsma>^6h50HJI z22LiIT1mE&b$f_}xtSPv|IKo!fYxg49H1tSpL3U`^vLrHg9DJU*>9>_f z+a+rlQx_jPSFN`fyNde2(wyvu9Zm?({4rogkW`QvCeV=itlf&<31_Rgth*B_)0gIi z7+${Zcdvy}%QdJEOsiPAc4*J3u~&%gVs>LrE|RkJT9gDgO)_?3e8A5L@VoZZsoTI>HDwosQbBhYC zmWV=vtohmW)RCQ0RXvw%zZ?<8g*JI2JU!qrX4Y_r+#bXnWA;a`D6N;BK)W0Z_ zGqH`IG2yH=mq)VT!a(;7g^5V(xI5tiW>h2~K@mjXYM0q5im_B|daGTZZ|lDEw%9eQ znGaKDUAtxiWATfRSo_MYo`b)(uf2MX+aE##jq*LOo&88Q+`P8-JJlx(Q58dH|M$iv zx*_L_cDe2x6u@)w|MJdMAi18VLYQqE8{wx_cRAIHyazRtE$HQ2nr?ouBc}v^#JKRF42f+E_54TRQS%~T%4lI z>PMZY;1cb-il*I`WQia9NT3E6TI(%Go$OOxDjB3jbhw^$POGJWM{SQLc;z#rE6#w8 z`Y*EO$PTA)O|kvV6}&N@1LUgBeb`+**qHQav=b3ebUW^CP4()o>qvc$Z**GMb+*2E z?=Q{>GbgN06c%8dlGkhY_;Q9vcT$qb$pxzGRftKz_UmDb4>N-&=)c9ZXdXQZDe}E^lUQ!-&u$du=RSh}lfO_XrtumbX|_A^4b2Ugk%u(gC(m^o zX|kfb5+b^dEFAa@G>6%&Um?m-@PIqd)H9xt9o5%ow=FOA+g{w7Tz$HZ`DI#eTfb{H z+y*m9Yuy6|Xxu3)FI7N8BqpL%xPO||a@fKf-4E?OqD83raIpUo!^gp6n0`5>5z=C+ z%(u@7OTY^M0aXE3K*Vp$bkm!+egC-kwpS_B(Sz4#HDj4$>)Z9|yR*~JUaKtM`58S2 zIN>A;8LUKtK&29(#Djvk&BMd^@%|c{Y`foFyuNBsM8#Na0Y(ZdB!vDW_s%5`lpV6O zdK}RA`fLNN@3;ZA+I+uE{9;||S$l5zc?hMK+HwLw??zK~tpq?hG;S8T?ktl4DXJ?$ zJ~33u4vahhlMqOlU?$7I;ryfV@tecY3@L`wo&8D#aURSO1kJn}sOHkU0d_hH|+x`n*oz}K~63j~Am z#8}H__bXvrwyOD68+`mRq<#Piq7?zo)K9jdxtorZkUP%o%JIMCn43v{tazgLDBc- zH-K$$7}9V>{5m~f)p*m~*lw0|-+sJTBnwndg0b>t6q*qhvFJ# zJmUo5fG5rm&-$bM1k7qFE2$xqQ8{^eU$fp~>Z{1V4)dRdYzgd-q0z|A7X@gXDhwbg zMdHQ)Sy;xHzE0J>>b!@l4qN4So0Hk@cQbe&<}QEokLh199t?kA?U5)4jA_Bo*%B~q zAd|~~N4F(o+G3OPv8Cc-Fr1Khfy8EOL- zM^y1(-v)dG{Xq&yHZlePNc^)t)*qzRMUxpaUo2EFxKIAw$^d7GV*kYk6F&1?J^OC{_rtoQ}Iasv0>5vh&t{#-WSR{T+OXO zCugS564b=ohRR=i437t`zPB)1TNm{F*5s=wdkH!(x1CPhnDS9I#tib8R5}O%f%QK{ z0#JkG+5_RGfR^bGeea}k=(C~!+dU_Y%4i6kDI(E>Ba+`m^-FflEbQ_<(6O9I0klK- zW{xVF17icwNHPQYg{ovv^Eci%ZP;zS+C3n!b$2@8N;W+a@M3S42AKvA5qDVZi50AyroO~dg6(MU?DCq7|YaD&2?g)Tcx!>WzXJG73h5EzA_cI1=ajG z01w&SRQ&Q4C+mgs5f?@Tj8mg{G(9n9WObc<#_-)p9EnIB#wYRFgUkc@(ked_O=2TF zhkk!=Gtih~;{`{;q84clE|R13F0D)}VHN6lW|0=Nm<`vk*kA9M;cGvga=AXLZJfJH zs>7GZ-&h%ZS3kpbZZ^4|Z|A+GO7pVb+FQiL5W(S)+b9LIdR!M~LCr`inISs^03caU z17LFmWK@~VO-uKp_AKNvrz+dGhQdfxcfsLKE@{MQR5I@Adg-VhWjRx{zB+O+p zO^S5{i?eCPewKrP1TK0!R%u+E&A|5kD+>mYTt^`6EHT0J`tQbB+88VmugZx=jr|P} z5t3UDs}s&LWeR|0C8fjGtLlpuhXlGi^h!j+prN$l%=yifc(Bkt@Scll)l!vvwdGKH zWoc?H$Aa_uVxL;A)+!#>S@828>MDhD82Wx0`(qaEt>)1#)kg@Ur%*&1DpAppTv$b7 zk$sI50F_s`6X@{6(hX4ni|22zhpzL-;-s_UtSotmP9|UUYoP$z5*88}6XfQ^>J@*j z1`4kG@Wr@mhY4i~t%BBWBSO*QGPWyLHCrNdxo1bjVG}3m_3IFozidc_wdxO(_=6MN zTNILdAtS&rjVSQ7k$afCpGb9XE+?^%UGSf_b&}51NAUDPyzQt0H_$?q(vzvhC~vII z&NBM@DSy%$ceiPP-&l|`Cccx(2HjKwD>&~i*$kPARAb3d?0bV7aZiu7AGw0i`r3xu zg!-V1EU7$b04;k`N?#5gz@0DhjjKA7hG`Yf2rUU_&lF=xYQciK+A@h^`Y5es8>V8% zl1Nmi?mYcRSgK36Tw?G4Cv1G!*$zZ_S6Kd8)|9|jl?e4Io(Z?>!Pj`j_N_@XfF4@IkPp7aSAry8PRM%ad; zPPbA{w1RwLY!k}K(24E_*)CR2^5*josWDJ144J_JA{Z*2zpK>G4EFqRRK$m9=>kPE zU=H4Ad{}F}bNzm_2w7o?cdVPQIZZsgn){DCN=@Ri9LpQ|<}%Q%JGi?5`G$CLiE!|a z8hkpH(!CzR(|t3m+#&yR*V8OUf~rp+CMkEaYE^hy_ZIkHW_;@Dp2`-&R6{6h`hfui zj%ldmlA^Y%EhyE8V_{#V9R!J{<>1=>Bhnzy#~4HBCdik8N!EAdQ#6cXFC973cVz*E zdO;XYDNnV3bMJL)+BIp~X5!x1={Xv5}Xyg+s$1byz^_ z%f9x+iFcv`XbuX1y6{vJgbo^t1FUnE7_CrRbG%cPOK7U`}E=SUBrNHMJPd_maeQ2?|nHq3dwp@gm96byuCcAfXF;Ssq}RsYuQ0{ zg-t25RzA*eq<5I7#T(@MYf-ET;T+$pLv35#B36ljFZS7D&0oZmERCt8n)VH0Y;;t^ zT*ZXEXH1A`&xz>)MvVbPSVtO#Qcn_{wo0oV$^NaXD4&3>+WM(+Rc2+m50nd^(jQH5 z<_z%P_v>NAI4_XMSI1bG`+vufx?pa~e*kHIGH?R16^iZU$O8jCcJlsDavA*A3(;YL z9>nsm&zTZ3UA-+$x?NJmC6fMc=q%cHvRonM_?#tW+zUvo(a6i32mX{v3M8BO*mRC2 z48s4@BLI+u&LD$0xv(FcW1fIJ4>l?QpQldhk4+^^P@y*sgwrlSX4)UW$-{eC^Bk|`7&49H zonfn-F*Ei*a1EA6)LvS?Or2W5nZc(?{u~2E!!#mdfHkX`ia~-`6(J4D-T~dKh>vFY zPUJ8b-Jngakip?c7N+yAfrs~9hX(eXh7iC#`cF_z0#pDTbjW8tAwppkmIfw8+W+6x z20Dt-y)uh-oQ&gGTAgxfyj1MaGQi zX$oEy*G_%;JXm>)=RBpeQ^!b`S?N@kcT?S`8QF4LeXSKviv@yWeA0hSYrJfoIYRTQ zk)Yn3Py#fIG)-F$O*U1`Jg6iNF>%V;AM!>$r}P@FP?ZxYX*!H5n_<_i9j`d+Z(HaY zOmXk>y*e3G66e8Y)LdLT)^;Mq)jpbzjdYtME&oK?b=7FjaAt-Jdv(1&mDk8Vw5zqL zb&9Ay&f|*_aBFP=W&i?*<5Cjyz zC9W+yfnnSB7-v_GL~9rEJ&|E!?CWD8?cO^yhn3L~kLeSIX;h4E$Q?=JeXfieZFw)M zZ>sw*Dzqe(Zz($Yn9Q&e0koL}wE}i1dT*A0;NE1s7!sSq4@Q}Lb3(nOZuc(KNq;Cs z2u~^aBN$MxRZ`aJ&_(OL6PiEb<14@iKN$R>{#*?a8-0k*+FwRl=MdK!+$)OoWel$W zQ7v-fU_`z`;eFEj=RnFs^~;$K<64QDcU;G}A_Qci``5{(qketG@NL9nyE1ms(H6e| zyz>zG^I#7YJ(p8f?Zo)AH63DHMhBw3D@t=R2#_%_gRvmq>!FRIh5@q-Uw|*26xQ(j zL-jkbCzI6=Rjorp0J4e25HwKUi46Cj> z3M>kV0w3R7MQKm95R|1A=fhinqKR^&Op>ns%;d1_v6E;=p2$V3oi;j+UR_djQMfn5 za(t=wGdSJn^?>i^4pCSa-M}zbQj8{jL|=FJ`zMYJqgHtY6PN+@6KS#&6%|=Wsj1eb zBAIh@l3J15o#$8gL$*CKuT}{BZm@ZrX*R6`JkH@LDJ7>UOK0nob`?Ck9mSTkgdkHW z3L5KN{~8iw(-;zP#t{W5gK0FypO@KhtVwUwnY)>TB{rQ}3u-5*T6#pFa5({TrUiS- z3HseZ%6#;YsSiuwodXSHCF_&~U;XD^L0wmVG@Vevupm@QE<|2$3?@t`HzdSbf zvR3f9ZY4V4zFs|CF9Nf4n zdDhoy>lwad$CzgcvvNyCfMg#@J4!9OY@+X7v6*M`7v4W&;UVDPh4_O6+Tc$qe0?Hn zZ{Te^z39#YK)$+q&pmgp-F{7;6=OJV1fQWUFn1?L#jI#NiQyRwe-gJH}J1?yNEN9AjX)Wt2^?Cih@NA9q`YJxL)% zcdnu&A#RL->6-lW)6>Af`APqR$pbo^wA`(#>IKhhwJ|486_m0O?!6plFr}Qx`oh+N z2s6F_c3Ca*SaUSP6(0>*Oz$FixnopG;0&m57eWm!di9sxJ7%QzKbh!k=;aDh$_J7H zd>p+j6TWCC+Rb5dn3o_~6P*ghRwOD|3P_~VkOdf2$>p{x)+1+0-1Xel<$Wu#HGcSh z-SUhtXTOeE(73Y{AEvBqZmRa;@6=3_LgX_IA`3NAr_HR?zSdZI-i+`$>Kw=FAOOdv zF0)bc|42m1O`9zAf%=wrB=Ri=G_$r;IbH)JUW^uXhmtvkz)LNgvT!DfTIxHL6rL?| zmGUdjg|XldIZ_azK(p<0531gw?pk1l8UtgD049m_HB=;Ez@h>1y$axe?UzHPfp>VdDxEK0*U_a_~g8!B7 z50KnfAUMr>r+#+wN7rV;^!@HfH_Jji4*j0YWL@gIuUjiomX#^!ROj?(F3(dMKZJ5eV1AO{ zL1N6Kv#SV3bEU>@ZP(7ec!o12#!h3Az-ERd)kMsM_rl{)-b=`z?Zn|%J_zW4W zm&%@2cAFp~Y*ooUb}4%T!NIYyCeFv2l+CAp@>1Uq7DsN~s-j1OnFxhY!iq7$E(w8T zMsmIPt_VqBY8=Y1WAv(SnTf&ZVQUnKviU11>4|Rz z04iX~%uSjOK|A1E5mv}q0boiL0Qxh?fl1T92TYCn|GgB){{#e*BAEI0FH#+xA8klGJZv#w68)&y+0C9Lcm0tU!PfVOn=Aw zz4CW^0#iy&YGxQI6wMftBHPoH?9ki8;j7$VE9?uhSOA|G-BSW9`5>}S3km5jWwh5X z@dKsa;=G5QQr&~g7?`mYWMV2P2$7l@fw7EE7n<_ep6s{ObaZMI2kG{E87r*b^vxX> zd_k&+@;rQ~H^?tDP@$&AYFkRJT`Lz}AV5KG)b~E-;*XL>AbDcJu4S&dlt^!M>--J} ztI_4`jXz{!i0Q;=teIy=GZWn3iZp&RR-3p=Zyxfbxn!v-p`)1(^~w`zd8AubF%U;- z`nFS51cM>@{)f1^o7x%W+s12-vCgtn+m-@;+~drgjr1q{3u7U{75ipXV`~JmZ=x3X zxPpnLnbIA7gOwVLb&?2qQ-s6Xeb~7taPmiop5t%Z?Skh3qHZ(>6|*Q;8myU(=w%hL}z1y){wK52K?l|G(H=_JiPzF$-SX9G;>xGIFF<%kF4VMIF%K7ckeimREqNMNR%JX4T0kF z*(N}CA7bvkNN>duem+@;i&6|OlOfQe#?t>djCBQO25?xd$rP)rmn)y>q*`@NZgwyA z_}+L9KJEeW7Oxqq!E4`MM1P(`*pmvBXs9wwsWnO)=vTI*6qDo*h3e%die$QzJS*vz zewiidWi3~nH5^d41V7BRzjn-TS(d{#t}Wa2Kzn^{FD_%vhAUL2D~lzpoyd@> z4!$G=g?n4BI<@W=>*J$m_QX-N<$!~`ulF0)^9uifFl=-P z?CX+6!hTMa+d<9F|CzHQ>Btzrj|=salAvnu;l1tLsGR{}&+ZQYoE3?DrJ3t+pPr(w zu{-=%{2RFVSXV0>F}joLR0A+=23A=p#Ha;;8L}z{_QceLe*KALH7yJPgFy=WSCI4| zdAvY`8BD8oH8@t2uR`(7s{iWijV?MYRlD^6HqGA9K>i`kVzuSsV1i-Y|NnoE4~RH> z;u>u={HA6Q3%0duZ_aN{rSxywf2C%y8!J!57lDox>I$!wQYLdutTB^2cZKM9gu60NcOOV)Swgj^GowZ0>1C9SWhF>`W3( zALT`5!y#{ey*=L4vY{bx&7(_AnH;Rj36*gXPNL;xWGh=3&kd1I?k)oDrjD*7pEROt4!WgT!_wj`Z5TNj2I8y z-O@mwaX7r%w=V)PyHw*HpzvUm)^%wIa(#4a+D_Tvi1G>rPGq^!QNe{$AC7y-w4QPl zH1DYCvd(MUo<80k49A$9Bs!N1f(BCC%2X}~jg(X9ic32(JAPU1j_1jm9*R_6kd~Lg z6eARqA<>LeNf=uaw<9q!{@rLxvMu4yN#H5`PaPj=IshUvz7PVg%9}y?> z<~2KGRcf_d;LS)ui#s_bpYk_4f%jFAh3&LF`S8B|<*s%^l8l`HJ3MjIt776S(@K6n z=ZuJ`N7o=i{mfRNbOzOLW~2#w=x_~2oFop^a&+}U~MC0=;O2J z^Du~XkHNaV;Id=N9Y}bok3@Ve4C_X*M!J_P28l-Lcn`dXb3{KpPMiDXlaL>({V4O8 zsxigp$iaV|VjG`Ts;m^_&eW0JpPQn;qfH!pBPdjQA?;ZZ{VGYYw{~#pejpn@9Bzh zm3-KDr;WH_VPm#QlAHTT+fc?|ao(Oi(ROykNNmH7VV{t6PwS5^s5FYl1U3%_ePWKG zlVoRA?gHUu%x>*#J(4ikFiC?{v`V}9dRL@?dQ@sZH(Or=*zjw;mi7KjZ@U@taBQ z2^!FKuiOG&XqSst?rsQ1OO)ccj@6c$)wGQI&6cQ~}-ffV3%GKde-sQUNY3WsD* z>WI>859hwJ8>+SuNtZG8|UsNeySDQr<2D+)5XOPA1Vb+2ekY)v!rC~TscaMHpwTD^S4Tn^&m}YD;yho5ald>O_Y`}Uc+AqfZPny{ zV9suU0j1R8j^)Q@$qYK@2k)CidAiLjrRd+p{OjDDqUs?&_H4p`(Y(iA$o^s3E&qQ z-Nwex_I&l48Fp||3QDpGVf%LQr#R{!-`+~n5*q-+{P`aKJDCX7M&y_sneY*>vWJAWQN_039&h}P;wm84pl7-JCcR#pL-0_Cwv z-#_eQ+20=YUfzD`cuH_yEcr+l7I{^aTCZQ#VB;n@89#D0-GK8z0^3*CJ5zGFIdKeX zx@6n=ePeChI*V&5gL@l9qBK$|YC|m@9_L_HH zmr4Svjb?cTh0Ap0t6N=$2jU=|H!o~{Bg8VPus`s||JroGIzgjpo0di%l)`&ejKfL z_)c!C`-#216FKAtklRd)@eS)SuvZf3P_Qx+ec0w7h+m;@)4d+ciT|6MlcR%WwgH%1 zCnaS#@=^J#%%2n9jJMP7XtPMu9sQa1+&!~7rE2?9qnQIQoCd5|j6-YAKjci+IRZYksCwTtd7Os<)?Ax$=FTrGU)2kO~iOFq|{$;ngKSY@sDWXqGgW9vrIoI#<<%md* z2#BUSlfarxX+WmD34wf}f<$1eoXG5PvU&wlu{uka{r&8-rs(cs%K4TVR6R3hK_n-e zsucx^P~=4auCs*6!rY?+QYqQjpZj-B-@mE(R2HVG+;rB3(RJwl8LEoeOjImBBX&ij z2)#g6bcH>vVB*FCMGrmf50?ubjRsKZek$s0i+*)B`se$ig@n$(&I*hO7bXWxA~iR} zfK?Pw=!=ngeZ=4iCFzA8TpRRcmUEYIG8)4Pj{Ou7T=v2@`8&N*EM8V? z$IyNcEJ}hSOAX;@w>_sD#*F(>l7XYv6uFXdy%;D?dMqo|mx<=5l!|kyM8;h1WLr*` zr!|h$EGsFQ{6tmrmt?R@k-o{k)tVObx=}I6Z^0RCcTt;r$9oFDXmAGPe8$r=Pztyq zLC#+{znnI{px<-Pm-z_+fPTv)4UmCkTHub`0sj1VzcT$FS&Mf~-3sW%?uEjpdsCD} ze^UFu?f9^U4!Z=MDm$SON#bMUH#{*~ z{WwB1%evd67mflpjVfhvK1XKsYT_Uw#t|HYzT+^n+B>+Zp^2PHF3izl{ufy)kxS44 z0l`!;PlVO!_s3e+ ze)8D;%bS^=F8srQ+Mw>jq?a%{0=;qmpFcCd)GAk$b5v?@-70&ToB(LqG}82mD^hLy ziuLS&ELq_6u@+jb#-n{OIEnQr;RyK>qRVQQg&|GUP`mLUIP{&(K$Z6TUt}re*Nm)a z^s2$Xe*|hCT%*MjB6`eA6Ug6rr*$)5xP_?36rg(0&^H%Jvhs}2Oy9Q9EDV;ONzJ)) zeFq8yc>R0igJcS3Mf@MlB5$UxZ}M<_xLyFT z=8J|7U!zdF*%AFECXMP;APD z(vP-*q&@^~Q$B3NfUnLlJ~-jAHCln18=cA$!Ok60+LlTE_nNYylo!GY;d(MOTZ#Id zhDNz#qeft8@htec;P?Tx5SzHF558`$5pE3mCZ`Q2-wlwZg-Rp(xpAI{YzPlcvR%2+ zHIm$%EVZUeItp)5uQyM(eBw^FzAw5DdPpO4d^C&{{4t<7*`Pg2Hs8y=V!%5>N07xk zqO4bgwQql)>RUyQ&BndC*lw8DY(Wlpv?~<$*4jRPF$;7G5U7U3n~X69hCUW z2H&ajOe9bHin}@C$k0tcXO?U6REY9S)UK91e1~iOF1qys4xU%b{k7e)k{74gW;;yo^yu zd|Y8jx+7450zf`!-^cpu)d7n(vLgc-D<2wIN_c&EBp~vy<)7dOpVX-@<+Do0A{^IQ zfT0=|32mTFqU}p_?iL+$b)$(KjqZx}%&>l#bK+#ezCrFp zR}CWO4v(f)a@F9*?%FHVwM3oC7Z`syb<|w|z%6zMxc%+;9xrsGe#Y2?_G62IKLh{r z-vi1>Ft6-COqG=el4b7AIFv<(Wp_y}xZ&VO1l_^*FJ+p7gt!tZJ&E}XP=y7ifiQH} ztbG&Ko>1CPe_j%KuNrHs(z*G2vDhfUu$r%iwo-H=b%Y{aMiL{`)ihxVZ{-EnzyAwJ zE*=KjS|?`Yw5A*Nzxk`nc1-;wEt>SgTDq$OT1U5Aq6tm@t{VL^pOixSJ-XW16v72B%^_ZBJx?pO4pwi#bfA?M zI{-s@oeygAV@so)R32(hq35oMAm1XN-krOSs&}A%D=C82x~xVfJxInVcXNi&+jj_q zW~isRjG?Us?NLYgkfFA%{xPYF=dQ>cuI0*Eqodez$-FgB(HnBO?kO29(W?(9qI+l? zcueW`OZI(!SxFmE$5-}QXKW;l2TnWYnH@|h`~?*zR5g*6q34pKCSo!^1<7*lW?LCB zO%|AfUKrZXxF6z3Vuvd-WXk_LS6RL{@HVvy>eNmfgZYRJg#$#0*43s2M+NhQ^)eDL zf@%xf1SwSrg`hT#kgq)qTu}h%fl{4@2=Esa8Nk1;H89_vH)<&ub}spXkXe#Y<`|He zvJN&y^D~@ez|;lcQRM3#xK0pyQ`G}6De4QB`~HO0)e!1gi}>btF` zqKC~>vQ$%=jDm3qNa8u$!<+jvWx0aVi25g=9S%ov{q1*fKM~|?f2LH4mGyj;VGp~n zyXb)Z6kmE1;pVSHIAZ*A&m-O&xS%_8P;P}=3i#zPuw> zOHMQ(DA0aWXogr2U~b4b!c}S5A9_F^87tGjc0OH6`~KC%)u&#lTWj7pr4YX*Z2$m3 znO0B%WDZ*JKSf3JOQEI>pje4m|FV+JO2K;1I)(^9{v$W&zd+;Rw_w&3&>H=RNMZFg z{y`*2;*xnYV&GdK*>T)$;ZK^(+3R4Q=cm~e zq}~l#L?g+pxe9b|X~1bPKW9?PmqC_1Jg0F%i>>a$lD};$)k1Xlc*32V(}@w1E;MPEaZUSW zinH)Hlc%gYs!3rmSH0Wm2o#zeMeyET-JE$-%X_BMnehi8&uQ?Vp)$SSkRtD;Hn^f& zZ5bk`3A#&(z&!QXXBIlxVuU(D|3<6li)>jm?qo=fwRJ#)i4c=e$k|Vm;L0Jn@M6C|?L+co8gXk+L1u@OsLxzvrP1U79x(|fq`-GYkEJtw( zWBcAY40aFB|9h{i;YV7e84?VE6U_yx<0%3OzgZpofX*e`e$zN+T)5~o2AfWF3zgiq zqPBbiNZzLiR?XhU>n~nLHqKv_TQoA?`hog$E-L&b=b1S!6agIhjt~+2d*WIav#WCH zN-{oN*ZJ@3?lM%Ogc)_fYIfjHO`VG=>j^Us@$(7w?ReM493PVqy0PT8CuI_%>tGVE z)5&oi*DYZKNGhbcTs1Dr z-)}F;OEwsX+=b|Wh-(S09`{lnRi~T15h%2&oF%11=muKWDW-~-sz13L4qI7tmWKL# zCrzp``Q%VkG9l#UUSO9H3WiEKWWLGaSfTE`o7$U0>O6fo>-7ehgvZRrxq9_z&D6%W z?D#)A7un-qW_{q6XveigR!&8)DrmkvCxC)QLt&GGX|;fG2kP<%wEx~$Zt5?= z?CO_IU1Y{+%5?%}7XP_m{mrH*(?_j9*tCn1i%vY4_TKz5Mofn#H0{Cr`+zt67n1A+ z^Ui^+mLFfI+JqK51k_n4tJXg2xjxWjz0Y4mj8OOnf`pGMSXEJSh#`LKe-N?OzcHL3 zSy)nnkN6Ay{xu z^G#CE=UfyO6#bTZlYC(1gmaG&6mc^zHJp35brZ$UtZtL8`luZu{3inLS?nM_4kDl8##(6EvL@NY(j5X#kSc?9ab?5%{KXmZ!`%EkjATBpM3BM;(2!`$&^hIw zs?df1Vp=$Ii4oFTtbHEM{v%ufMB!|#`7v4yJY@qHSX+a#QzJL#w&@mcQHR;TqNM|l zdT|$6DACbc2ahIo8QL+139ybun+TXNxl#d~0#kh#>>kC@mNvD7J?_E0V!6Gu}KIcfD8IT(iW%UILy)(1M@qls7nXanA z&%hBFlLDDh6Q562H4PRelf}f?il1YI;EvxnV`18A-C53#9;n;q3GYg+t|z>$m0NVr zRQ&+e%uBu_4?~Dr5IKZ#6w#T4lBT*iO1DU&u_y`)=FxYdNE~v@Y9%RoRKatThh4ka z-K-Q?649Rhfix>aovDbrMKMP~5hKJ-LqpW6aAVdxd9c#h*uDY}GY2zGHFiUVWe{9Q zDpp+cDl0m*Yn-95flGs|7sfXLmC05+Z=oGMEP1m4)m3Cc1Tok@AHJGI)=g{_yq=G` z7_#`1U||H>aetUd57ak2WUGZqsdiCBkz^22$)2r7u zK}PH#>!&&+j^}QjH1u-~cVwR_!-}$>dge<#K5jY@e(}@kXjLNr8gOF7CN+AaV;qdW zNN$}~crGz@<^gMvE%*Mic(OeWM5dM)eAG$G8iyd&xmt$8b|clK zP+Q^NFGhQIEgT%k{@B?Iei6=vjDr9EvAI-NwwgR*g(gUmM1&Noogd*t#ef4Sc4as+ zN@lZ3iErVNze#Jm6=>Su3K+fN|7jybeF#2AnKy$rJ+fcQN>h&~(p&)=$E0c;!h3e41jPisUvI=}{`B1swV=m{ zqs2X5+p2u<73)qcuVL`qOBhd zfHt+|*Q)QW?~p98sNR@`7&-`4+_OxRtt+ddvg%hJG|1uh=J6_|QlaRwR8HRzpUrRQ zhsTt(M%z2{(7?|0!mj;EWrtGl->p5#lZ}ThX}|EHI%&6FO-Z8yVOUEWe3(~yH|f8d z&uy>TGB^~*7}sEO2M+ofb&(y78a=X}?fsrDsB#)xlUo-ZmQpMC_^23)Q3|2>*_e?^ ze{+Su-{}?m%o4zEu^XA-Y0a&;M4RiWAgF2-w#W>p-W$xUBMOzYf4RHnA3CTfk>3nb zy38J(8-iuFI~hzKtO+W1Xx3K8voy81q;BgLX5gkzY$SdwvQI8>Q7_=} z2-D93{%!vuO3&f1VFb4Xf{`RQ<@hvBdYVasF;xhHfdACb8pmhuH?-OJO+)JOKh|G& zl0SXAB&Qb+Gx=JSp$UNs0+8r%mIUT4>%aXb!Et{YG}@!5p*w$T_pV1;wHiai@Mm2g zo=ci7LImFWUu?4y%w+&34N&|R);!OAUNc>_>cGzJ-cAj+kHd!J2P3#>!5V4BqUvw- zE6@$`NZ~^^St4rL@ptc;6eP52t&uRU6U!G5Uuz+~E0CH@Q1}aNt|SL6ymBN|!sXXK zV}2d%26JmiLi9zM_?!S?ZNt>E%kiHubphM~uy0*Ob(%C8DfQM_!13UoS>AAeAFOpv za%ZOZ_u^~{J=@A%D=mu!4r%}19P7eTc$e1b9kwyWHJ_y)`#-IaP?IgT#_gX(=+fEd zOd~vI+Pu)Xq7Ia?>3%FS@?cqil>Im%zsigKus+w3A4u%(7nxIpF2KMv`4P+Gs>xVE z+Isq9J74J~jsd^#px0Pl zw>4#+)dEkB{m+45EDQVht9d)UshMe(Z}XYsg)mQ8&=vVgVEecCW-_G5oo7c(@G|ma z=8466QO?7rWVmT<^XNb`(cl^*0U(5cS4R7W*OU{sOi9x`>`x`HJQpR`!)LHJG5^k; zIYIIifM_;Ovvnl|A#GNzU$3h2E%et^(ozivUp~>w5m;PFThySLknX%nSZQIhS2<4C zmi0nBaNL-=Q2#y>wgzQl4Q=59k5Dt7_qEq|deT9g`gTzJcu)ZIb`BI0F1F-53D|fF zv99Rvxq&&J?~oBp#BMZ_oM~Y{e(n>g!6+u9sI_9k8i55QN<;MmVli-R4QVKjIeqlx z>&)S5WEKD%js80>)^1Z#126n__%=0(gx&uA520QU*7#}@R+6F^Vlq)dfmbirw=dgc zUu&PQ>OL*PQF(V`LvAV(bAqQJ4uTN|-N+4#QD$_c`Dx}gmMMNc#6bL{x-#d(a-cGv zYDVg)nacZ`eSdk+*txgyAe}TTfdBktK6}8JLKn2`%5Ei83BL&qMJ0audLFG*F3 zthrA|_X~<|xW|w;4ee^pI~H>3OmQ-W`C-yj5q!5hw}%{F^4{-;peDm3YAAcKYG=|H z)1||35A`jdIt z?UaeW9hN$w&dL&wHcT{~1koJ-c9RsF+!WrSgk23|SVy@{kGGz0NA}@(LCjj%FXi%y-NH_|tVc=kRh_?U*DqL_K zXIWRy4-E`B{162#_KT2-v)-xTdgT<;N5t0wY8t!pa}hAky@%^Sg3%eID;+NLsF#!* zxqx@C?19UtAXD^2Bjf~S!2*y%XCylGhGMz`{G|<*VU};Cch*n(OW!29Mo2J!^}&S2 zyyJKHLue(_7Zt#2+6KEax&0`A>_iEqlI#*92yWnsywF79M(oO;Y_JaTAgu>#7HDcl ze9HI%^&_m5MC_p>!{mBR6mXl!Yb({h!f2{L)b_r;57MO{%9z4Jumjw!E|#9hoF34l z(#L~|@-Q2r&4;=&;?%>s+?j`k6?cFvTJapjYk2*&!cU#ypVh19%L(>^e z+uHD+v3oaz( zxk#?B*~ahehS^iQi9gL3ym-u}yE1i(}JydUIE{cz(9NC za&&)ipI+iTchb4Dqkma-?lXNNBl;5tOUmQMD&^K69397lxyK9PypThw?RCggt2~&p z(TrsO^!U{lRaP&+r>-2C*f3YOIs$9yq(yX5B;g))09h%<9*wEiL(LvIa;HRFTBr;n zicm^Kix5Q&2`+T?>@w@9^*WB7tki+ZJyu{1F51yvv6;xO7P|knW2&5<);wMgKlkTO zH%A%3>Mm60R**~alMF8V_{bq=WzT1ZpSrMS&Sx0u@z%Fxw)4w#9T$W%{uleDlT5i= zF|7ydyGzeE$3K@*@-fM#JvY;=Nd4Xq;`D0K&CLw9uv`fuoD2NX7&XM zW=PkZ7~re8!Wa->jaiWrd7UWQSM;DbP_pDmG;* z!A%>6z9etE6J1mQlxor@uRBB~<^ZUXU&{eqWhKb#C_(^01n6iJgZ?TTQ^3F0V+P4f zaue1(M`J-oMOTM>{IR0xrcK>SMp0wm8J3iMm#jEF^<7Xs$7HhqDODIUOu7R&T3GV; z@*nXd82?G+Mx{y$E^cgQNZZ-L@;KON<+CT`ZM%Md{ITn0X?4vC)U+Dq6kTR)4IfDb zXKHbkSgDXqjaDnFB*^-J`XC2>7L zDuX%fw4#C(BMVmpbPR99E`#*fgF@OF$~lFGCb8VL>ke}QZuzQS1<|4hHAKDXl0Jr%W;&T;eF`@zj# zMsbqUu?D^VsLM%3aPF%@RV;phr=gL!yLq6~e5C0vl3El%vktW?gUpV#(TN$d;(D?8 zrzdxcD@fcho3wk}M_w~>ku;*R{DxcW;zCj-ro^70#L}3=XIaOfH#Msef2bi0w$zykEWO_pTDF!cV%8eCf4SS{&& zSs8Bm(52fPEuNEEyz6Uhb9!*)4pD{gq0N+fBV@fhv>tqRAD!u>Ux2yLdQwZ$c~L|Y zY(&j;5{6~r% zW!k+uV_2$r1UebFME!}FTvd9r5Q5~vSxjRwL*JQ)I;&Z3Mgpe5hUx%CuU-&OOA=m( z8e9;+0czMkulfc*f4~2^x^IY59t>fcqRB|YETHK*>G+x%2`QPNv)OfWE;5_dd067% z%IuDfflPprw-3*#R#D`UL!z^65p6Kq2iAp65(D~;QGOC-eo;n?mo3QVC-kowFR9~j zyp^_SX+77`Z;lMEw>j1;0y}z7zBf1I^S#8?FnP&XL}tx(A0Uk^YvzI&w>&dki0(Ew z2&0VWvYR;}<%k8}(cy7s<(|iBl>i_a>R-cHRVhq_#Q`Ap?S_Mxu#AsR{#2jg=a273 zPORgois zIW7CW*t(CwHt3K(Rw4>h3n8IL|B=Vi=JNwgT+V z{ut8X(b>8|)6%hH6oMqc5)Um3@j3ZOnN9172C7ycq-_E{d2$V^x{e<}UXox|wQ>{O zOzg|Ol7!*KPMcDhK7+A7b_x(gx>B&ciH=Og(9b`rLul1yp%H{W%Q@R$+ELj^dPCNl zu3NrOI=Sb>liMtwHXL!da_g7lXTPnpR|G*95YbZz3Hs-09{LeW7<)OxyA(x%;na6i zO}w5xFNLC+fQSocxbA_41NQHnoeqZre7)LloPl)WT1Q7*_gsJz=d2cM>Rd}|s|9+x zwt5*#is7)NFlZbN$iPrg@&XJ6T-M*{*_ABjP|_)sI=^@Z=s~wa9kQDS6TWkDkNUj6 zxB1i+vA~WSHJD}KD^MKcLw)(J7S9Aa0g32 zX-r%dtw}f8RiKoiI$AsmCNFVcf-<$kLAx^y*;2{p9FGHBZQMECVW2qFuH5E%$MRRj&29Hb0&|4hQ|Y<%VG!pID~-4UI15 zIx}kZfkudAAsUgG-F3vYy7jly9EW3CzZLa50qo1^SKIYCArJynn~@1Y^>E3e;ISeIO#9^O_E@hm_plFkEu^0HbnbUpBx0~FhpRuhBb(yw&P+M}YK!kq9j`|~%Yg97 zU={$+0D2Mv%B7f3xU+!r7+q>@t^X~q0`M%&00h-*!VMpaAU2?&paB2?7yx+SgNnwO z1l|c@b$-zZ3@d`3HIE1Zp!$>5Aq?>X>TuF&(tj%G&mH%~?dYlZ7;W6ORLePCv7!=U z={*UreS?A4>DP`lmeSDg9mlpR=ltnQ52-KaGfSpH>D>o5Rr5B(N`!;huAp8vhg0^! zd)%MfhhyLH2_wjRGj3gWjauP(F3doc*L2vslcsF1ez_K>;(U^!v$_*&RBT}$cDLp( zb1-K^q4F+0BCP*)^Prqa0-N5%I+wt~g8_UeHq13<1p8ZkK{$=edqq^igeZqQRNuat z@%w0|(`90v!d9$~u+8!3MbM`3vDmJt7qbiHA!zJvF4r*z(|C}SH6j@C2AOoUa=!%< zI6MwU@#MA)+~B~p6+P&e2-s%?ODhJZymE%uxUY9H1r^n-933+oOSLKu&~StZ!bA%A zjAXl1JTiSs(eEen>qV!?%J}S~Y1xS<3?yAdL%nQty=S)d7no$hplCXo=WzVqG>Q!l z+Xl={n3#;nzYChO1I$%?mWb1L7xQn2x^w@qado{3L^8U2h$emmg*<9t&pFU$WQ6;*#7?qHsAIVZi^ru!5AAv#Amb zLppE_Wh~g|4RT6Qqt$i(HQ_}vqa?EWB6D?sRX^8UWP{v8nCRm;7nYlS#bBD&rGL3d zS|>fZ&WQnb%@27+$_+l>(~4uYjT_0~jRqpp+D8PYicM*+d>MioMxx`|wqJ{9R>2w; zI#S#3Md}zU@yIO`(yG>q+jX1t)UTPbM^1P@nFFl>t8x!D%*qjqH<2z98)~Jf$NHh{ zc_N{Hw{5>#8bJxOS_{%OrAZx4&gC8MhayCi)!e&Ge7p2B0|d&PzG0nAG4* z`IW8QMw6I%hTzxC4BasDNl#m@#W?**VLge}>8+D^YCY!GfXOT&BG}ffpt=Q+X4i6L zPgk6y<|8g~AwKsl4c9(1+P2W)ah`QrjVQSJO5s=WP$6Oe5Xb>(`sJ^{qu@dQ+BjR6sknR+n|;a9V&MMvsQd)!eqfR3>#BnY{z`GG8?AWZ*1 zjW$Cn17N>%BThRE@UBQ*a<3f-afrc^GHC-72UL@657f0$#i>DFGxdHQB%74v4r=w? z>rD+o3>h^)wN5{$GU&>xYGTiSVQStQSql9@;(6wkiZ&}a4;O*kvxxv&kEUo0&TYJ3 z{M%;LvHzH1??@Q$i{XqQ=m>A{;9lU6o)x#+cBch*V2_iGD5b=f)sI$cwuS^FpY;|b z71-=Tw-U`Ic`f1ZYuk5T~I^YK$2T1~w(CGRHrz7A^LCWkFRNKnBlwSo zd~btyKn{|+jOYb;*2h$SYne8GyHxW05xN|AZm;jU|CK$)A*yaObxB+pRBv*M$+%0g zw2YA=N|JkpB?7VBq(y)OgcuBOdXL~mQPxi$!CVFu#imw|CP^|tAVM)7OLQcYpgo%v zZaoCpa)qqwOL#pCI}ghC z`1&GE<_aakf&$q9j4`68sa6YA~ zWS2-yMJW62Bc(IO-Ui64xzOQM zDJ2qvmG*g8$=?g*anEG7dl%+z!Re9yND%;v(i9z+z@@Lo12Z1MFkB^a)#6BV-a^_p z(nsh>>D;_dl`472_NSv`dE~3{8wt&aXoNE_OzBiNu;>x5kss0j9PO^z$r|z*iuJ&Y zWL`vbYBC>VwV8aG*Z3%29z0X;1=s!|9Pc?)ze-m{t-XW~w(AnEb%42khoHtiw*B0K z79Ae@1It|dcTHZa(Wl)LAu;+S0y~q=&x;%~BQlY|crX0i!GQ55waR*{5*GS8ChN_M z454GCa49}KCf`1Njjq9;hkOE|_>&p*a|tiaO~wE_I%3k+8R7hAp6$9skSK}964`37 zd+?%L|KN$;|urk+#zTt%|n2tO^kw)*Wh$p z?|ju;0`I*obC$t1o|{WoZ|lW81)ceV8iLahVsmE4C|r(HcK3i1;b%bcletz{=-40a zam(PeafFLPjksI073|i_Bw1A+?Ls$Oq)3?b?rL_&EJ(Tu&M3 zI^~FJ$+{+J$^LUD+fw6UtH~O-4x>Xy>y#`L-AB1^GQoPFjQo zM9V$#8$Ng>d9SwQ5%SM5;&2M%x{!f(J%6LKp>vl(7sfd3jHsK;idFD&c;14W?JvyG z3*OWQFt+H6LBzEPSRv>q5?Qe`LbOzy;`z2H`bI8;~;kWZ=OQfK+_&$;*p$ViVt! z8+$%`+}7teTC<(!9^znMjzw{=8twCcYM{CkO!3u7!i!kaJb^_#y`zBIe2gExov(tiNwM{xap_;(|8{#jO0bSxcKIR}Dlh zom`DPElVtsb0NmI=x#$7Nq#MCZai~qK)Q124%01Grxan;(L)OW{LKj$__hj4BdH;!*X<*N{`omMM4_B`b@zJ zGRA>~f{C_V&|v( zo6*s77bQ6WL>)MG{y)JB0Da*+5wBpz4=^7%1Y#eWP{f$Hl`~{qzAJ*Y)%ebp+DN86 z*gablAw)j0yv}{jIsYBe^#qmW6Fk?&Ap9uFx==YqeAL9%bvH6 zO{eVHSKM*DPLsVu8X1fQMi_9+&F?v6g(3G8hBqEHO<;vSnCku0Bbz-R!c}BDE?dIo zj2$O)97B%<7BT$KHwGD#Ij{CCCk6!zg@NC*!(IypkL!ova)zJu-spw)o`Uy|5c!^f z5=spIl*q$lP@GK8M|e*GQcwUc7ZL-zN~>D;bee~~wyIrAw81Dh>(mq~U#g-BWsMMtU21QNuD#WY zk^g*WaoxmFnAa#vSo*dvaXOp1fx)zxHld{QFtZpm<}xrz1nY9K?o-^QRcQl-mb9`+7es?tZaC|89_HZ%-kBiG??|?jKRz#0! zMC~u5eoY2|qqP5ZwFBJ$c2rP- zSNkVySnBcMIwX%@9_-vQP16+PTFy894PGA393fy?{*1>Ww0?2G2j`Cho+kh z^g@1~T%~Fzhjk_6w;xW>;uT-8o`qH)@urFsob$P^l1$`66B0=0uemTQ!$I|F&a0Fl zco-)Xiq7JJ@zjMS%>>QI_->#i+%r~ht-o`!WwV6tX=_B1krS@c<- z;y`?_HGV0&whn=wPAKSX?7ie4{KmZjD0hNoQ{2DSV`ew*^MX)jMa#!x*eHZ6)w(-_ z?oRgek}mzD`dfKE=fUsX4|d9fTl-uv>@o7XS$;M~3iohsv)DD?7j-n#=j?qwu&b$( zzhoJEW%H(0?-Fm}g;1eVmsL=ugBRzo-wK;KBqS*}Zqnlth>&%y{Z(FA?U6@B+#bqG z$9Y*f4J6v5rQfW$GLKVfF1%5u&7>2T2gem2{;>naKZR1z6br?aTx~p(yIr z+YsF9da!AtM=y)KeDk=g^N8)FV2W3%9C>u^K5k%2p_lrQ=&uc|4xX!**FnZ{{V~Qa za(4zd`h)AJ>FnCDZmS-^A;Gq|9NM;^RwZ*6o6d3!X=ZvHkb=M~4S3R_zZd3z&&)oh4lSO5z_&LClPG*O=#kYl^hD z-%!F+JR%=M5pz%=)>{g0Sn7|J0~S(UkfN#h0Yi6;Q<9|^OBsc4OrL|P%)~hHKf36b z?Sq8M>Pc=Jq={gNSq zzpHF)VkfeVjUTu(jcMfuX>$EklQs~-+W!uLS@%6ZhF#DWrk_xNP7%857DUK2gcZr+ z28QO55l4kg1x{7rTg&tL`pirHCrOmC)XL$MOe!YccIf8WQB5H$=Eo^HcMt_v75JzA zrjQ6Ey*a34AOj`-!UHw{Tnvw(ETB*#KWkmbx1m68G}lJS-1&B=)F`U}-@UBboWN!9 z;a+XaQPaQ^9-R^4ULHaypj<&Fje)%4^=lkK_Vg)GlLz4~4+RV+V#dH0Yn$gwz6X4oMC=p7wxb&O65Q)JEQdd^a=YJ`tv z7#s0HpCjuYfBbla;d)4)BeimSQsgF#0#_E_I%ls9oik!mSJ5ZB z!k{Qqr`NrDvF);j#IIa@#TL*jB|H^AZrMIshXA-cdu^%?i9=ZwO8Jn=L4(#ds^43C zEqJ>3(oD4#vQCa}o!=JhB)=et;-i@5i&lc=lEwpmM_$*?o=*T)rgVDevYz$>`j|RYZ$r$$lEIDBx$0@1CMNYJ1mGgDDbb z;%DA&5Po>^&emQwrlFY_W7-g|zZL-e1hTJ5l4l*$#CIt^2fLofsVTx1e_Us$P^W=0 zN?J@c!AoQI*({8f%v^ws^SO}sea-7y=>D%AKoW$rs{e65Fnk_@{vgbpY=SF1r`d?8 zz``TTrAErUG=1dMBQ1kA(1$$>m19$Ze-W|UW$b5qbru z1TsDex#+Rj5~>m!vEVhpm`a!2;0IpT6~_IzI)jf|8U#qO6gb99%O*iOWK!7xFf=|a zSFbt}`^`gV_a^8a!h*xVvyxB}TP&oNHx#5KF07aBRS5}vvrqIrv+7%>2FU{Ct0pC) zq4GIfR$%GsXYtaCX1Qe$XCJnI;l<pp+%H^?Do0SgqlbKxplgK=M7mQz8{EzZkJ2`OFNU6o88#l4-w zF~>-cj$ma0V7v({J5PDR5WVTxk=)I9%OA69&dj)56orpx%)n{yi7o^gvRuW=HeFrRFM1`t!;KhSv zk~2YQktu*osjnkQ(yBqx+0c3tY3;_2oeZ_6IE4qk9B@FjNEtrj8;(4+F9zW<@B86j zv%fKRa|8}9Ghu#^+`td?uggHFem%A@XGY@m$Z+S)nZ% zUIT{*uXENlsZe*o(YGy{n^P&nz}fM{SKHLPHx#j-i^!lqU&(u*^;`4SF_39`^rRwx zqkO)v1RT+DsH<>cDY5jH(n;&s(v4g~k!2WCE7KAsWr@O{(5NcXhC3z?I;?Y% z_oRmsB<~amQs-6tu=S4F7(1DFe#xQwdQMcu#|bly1>q6m*k;s`U<|-gzy17vGm;dw zz)mz~hneRXHS%Dg&o+39Mv3oOqm~6tsCUMU%sGc?zYv;LcbQlmc8uERe{75oMf^_$*;!sPEK9X$kF zt1dBeujn`ze`MTN7Yl}Z;`RnX1H|2qX}6W4E4WmsiN?xc;Y6sEp;vh@phJ(d_nNp@ zB=p&)y1&{`xjtPB>MM2$t(f6KeynIj?#Rf6tZuSD5fQ=sS$)E25j-nHczsE>=}Id! zP+G5ua{VD3`I|Syq)#mY^RLo|;6&YU5@29(02#OgnLQ8?#BzCBID2`fl+x4DL-lPh4LghZigSx(fxFv)b z$i_DVr>8(D`!~O{cje|roc{4zF^NF~40Ncz;qgA+eUdMBW7Y77| zEU!=Vmo`bx7ppzR!{pS}oY6!1E;!Bas`dy(ftmr3+J}yY7QzuwaA(~#z3cWvv@SC* zy?Udp`{VbAL*W9X2~UU$zTRB_g+jXwmxghd-yhnchc>tpn6N!ph6CHepQ z9OwOgA+hUs0dN2_UX;5v{GooW+UL+Wo%Z6^xMxV3OgM4?S*>*k6)3mZr{8IVeYrW_ zZ{xmcYN82a)*f^=%kR3GN6xKt~AV_$?lw%r*?Jo`E<| z9hQI$0EGkxl_>P8cqVf8XY;Vv{qC8dMxC|)EZqO!wgXZjoOSaLO0j&tm{Lipm^6Tg zf20N?U}osA2hd`QA(4~C?X4Z+&o)lh>GBt`_+iVZ2}5O+u=J<@dZb8Yw<(wFF1IAv z?VjI0UG3Al6K_~Y0_%_EiT|qV}Y;ADIAXwlJ&D zxORZ3cKmDA`0%Vp+DUr*(O!hCc?FvQCZW)p2REW9{kDei*T@Y;TYcl#%P@~JPAf&@ z^q9L*@$pT+{Zi@Z&Tm?oLd~{MDVN}iVG}&Y)LMn@1v^O2mIG$03ekgA`rfS zdWv2Xw2{%9RLby?K9@H*AnB1L{v8liYO{G&m(Z$A6p?MTSs1}3?o3v`XKnhchQ_f) zt|=3(gMQJwKLv>}NY+Ido?Y8xk$)3Zol;xsUut?o$ozpaF_faCw2G2mJs|HM_zhfr zmGAh}`Rw!|n4C)pdBGsNjk=v_PSs084-ISt zY|2qy&JA3Jh8}4tf{BLF4u>k!DN&_`n%#3BLr66ckkQMlnu$b1c0!`YN|`s?HY+lb zhx#%P+fxq04`>HBlhJjZ(u%}Yzu$-eZSwbackyg@dG#m-szpbS1oOY=|2vTa()Q5< zG3w!3h-hR)i(wj?BMkSuIQ(&0*uF$V(s|mi<>Huw(nGLBAu(?Er@saWpLO}4A3#cS z^Fpz5cfKcE^dELLN-Z)Br@j1Tsr^i&Yz~Ji`-N*t`j>Qkf>b;W9wP!`JybTDo&E^2 zYV5PQ)E8+{N7mCDY`h8QP3lZ@k4^j|(v2{+Xkree9M5!6)#j#UdML>v?*-a}7oJ%c z@KVA$s<$>lD4@M8%4K{JHJl;(-(;3pobTZrCCApq9qHjnLO@d*T3E3xWs{nwa>GNU zl(bzmbW>0QWh9hghC)Zg7a5v0)=;?*zH5O+$2E=fkysR_P!UJv`Lajtd4658$=Fqi z?sSZ|E_K52>iV(J$^FXovxb`Ttc`eEFq6hu0%O(?5^B0Fsf0X@ABhBpx1lO^ea(%Y z!EX)a33K`FPE_%%hN4qXQ0Twq{+}}!@c8#g07ziP2t=S)C&^^N?Q-6U^>z!69acK> zYYRv*`;@1)VIXCRQpm21Y5uT|sXTqkQ$2?&aQP_v^R@58Ct_YAGiq|2TrX3aHqI`y z2yTY^rRNzM0{1Q$fEf4VG!Mu>`z*aH`OBe~tNMjwc$;*xRdvu=!>UpPC5(@e)rG5? z!{#&m3v*{!0SG~4)2yI{xJ?_e!N(n@y-Yq}PDo*jOl!Hv%n^I$Ocf+BP;Rz+NGp$G zfl8|2O~f(m1{~Gfrv|^A4l5ipvMk;BgdokmU;Za`8h1H>>&FR(dACXBHwl*D<=FG_ ze2Rss1Fqa4I^GTy^Ns(!@AyCO3ou_CQSkCH_f?oN7>*>|6U#_1u`hKU zy($TZLT8PdEedGF6ffLl5`Xwhi*y3;%g@#5m)TkVE?CH5iLm$x@FEQJYLiZ(eEhS&;WE;7P5l{UP`#H~>SsYJ2j$8Vv8)nl3FG=H-S+1(A!) zbBbU;ldM&8L_O+UeFm=AjLl(%F9Gb8dC9+^$O-OCqF7G?f)m5^rOO*;47<8z`HvS$ zVpM%Ar!n0>;!3uPG4@HC!6AA9%dN9gkT&wNOT@f4YLsi|ILyffenYfv*wkihrrfRv z?t$oF$n1{ zH?i;&V_uDP%|n;$Y%RIZ)_u5nNvME1JY_)4zbu5GQ+}*m$;&@h|0ryLA@?GQRUO@( zV8%RTz0krcPC&72Vf>ZP$*400@=XQ7CE2&e*-B|FQSQ!;9;akg6Tvfq#-*uevglnOy5ZQ~h z6lfC?ThS6ZiDmSJH=wFgT~t=mw864gz64cWS7n3#;6gbIhcp2)AeWGp;Os!;z+27r zv!?Dk<62;kITCT|bFZ7YS0^i_S>=k+0KaRhXGMe4oxN*ZI)`zzYDU`K%8 z_vab%T;fS6R<^bnf8IFH8%q^l`KAbJV`IkV7xV5**wEOex}X+2P$zd4=&W4Qhu-e| zt+>VkEQuMNsry#|;P_TRPcfb8a5Nq@TrfC0aeNE_D)e{T_&=@yE8%>V{~+=EEXBie zXZVBVN5(*?L|l<&W*Cj~TW5epgB7)5)dy5Smg~o5YIN(FSPW<}&-f}Afe%;7iqfEo zpO8WxG9pK(2yl4Q3qJS#S_q#o)@d2gs2*mjU8vpXz`XjT6;%j)^4}dCEl9pI5Jui+ zoRR7d?z2yw)}%B2F4A!LPqf&wS`@J4dan}&9cUGA;R^`A`1RfP^Ifl{g+0j-5wIHwoACteP$0zJyj`_%d?$i7Sd z2g6#HL)7GFyDM|w7bY&~ZP^vN9$QjntSQ^l{Q>sBao-13(mMT4IK*b?^WFA!UH4@Z zCJ^8tDZe#~>l8>?hPa7mLQL^?|I(KEnOnU<(=&1j2_h^2rDx|G*Q({A;c_@W893#P zAKgs!Qw+5i(+Uw5GlI>k3X~Vgw}c4{0V9nFMDzwMiVD(Gyvucb+tNZ`@ok9_lGo$O zW_{OEsCeI}+u^RR=u>{w0_`eq>O_bs%o;g_{`FCn=VEa@tGs7vQ>KmIQk!z5vlHsd zq^w;;8TD){V|b+G*TquX>1Z6J7NdThVQLQ@#`PS`g}COR{6j6GbU&wyvZzL5|6Y~O>0 zb2YIZjDl}g&JEJ=KK_VDgO?GHnpuFvlX?oGI7-V>t{A2ljaDMsB6!j#d27Sh%++w( z;(y4TV+DKBI2Ae}^qv#;!Q8SgckFmvw|>0g&i|A>_Yd!ScGNofPKTqpFBG`_`Q|6- zrt{_z61|ErOQ@5%^H<@s9MoSoW>@5;=o&-OfUhzU{4nzwmALQ~XQz&4EQfEJ6+ zx)IKc%5@TAqc_SkYw-}ROBXzfne%YmX7?#=JisEUYo>@F#PT{CuxDjH9wYUW8%j<@ zIzYA9ylSbL_-# z@lorkKTwqd#rn&7X9tfnPS0uZVj~S7Fhktp2EZkGT!$w^#DYI%dMBX<#&1w*98;zj z7*g3GI(d^S^Dc>t85{DcL)}mM848Gw+(el%sQr&VuH6N!+Miyg=1$#cwb4^UqrH&>D}TX=3x~#w{&Y)V=1}gJFXL@s z2H@v{iFDOp5Jo$R;(RJP#1XlXvvLKkiiyUIOogzYq(c6<^4&GNymjw=LB`bBg?Z47 zc`w2(DVz1-N8Z?x<`GqB5J3fJEf~e<=Mwx_3AR|S7QEJDJOi>-1 zryyA5z5qg){}G4%AnE$?ef#O^cKea=mET_vBZ0TbiMu4~rW|&zz3HaWcRN1W=a02u z&hKjM0$4x9DPHc*Kewn{VKVzf|D<;m_a+@z_TJ+2Rsgi%6{#`v$7S2!m zPrk?2v_(Ga?WS4Ln5L{A>NcEpBaVYgo@fUxgsI% zisgdmT z`$rFU7k!d`S@PtPgs?+tR_2n;>j{?=qFCA;p}Z;9QHjQ=qPNOy?js6TB`{Yo@meov ztHkJ1hY+3Bv8e<52t(+!sO$^c$_Tt z2w_!S2@SN6P8?`IEx1Mk8c{%1N9eyM{nwg6?y(kN&S6dl+C-2sSGXhWA|lLlTJU-P zwe~MchJm7q#oP;!Bg3D(dtMnYz}x?ZcQTOtHXC70pUO(<_Jk*tO`>w@G+t4XZ9zK> zcb0uq27HdSQ(2%{QF&keKcWc6=gOOD+)$B=);fGuV8p7vs>Nd0X5Hr+DV1M!6E|oz z(p_-z)G~|BI_u~%B1Y+7rsscY?Z~je;yk1GQ2XhdDVUXeE#=8_lM0D{P>Y}E@Lems zG-+xVHJWFbwqP>REr}I;%aXmaHxI5)IQei2*%cZZ z0}oLM3K36uU^1gQ%LXlC%TXQzi-U%Ocu8eNpB*9NRL6oFbW1&|c_3P$rRsD8%gc2| zW5K1-FJg6mz&yj)y>Tr=RUBD7TE|RWfz}syFH)mIRl>Hc`&bKaAB9M+CK)5MWO`CQ zE^{QML2}>`Cn~kWi+&Fh8B$9U24*2}00xgR%BeGsi#)jjR8_(Hlom{QRiO6B*ico} zBpa&eW(Wb(Syx$T47gD6e>Sjy{}w#RPvPwDe^Ei0E%r)7Ww4ep9_F)ar7!N|^{U+m zwl!|xw3$^Lb7%4JXjx1U?{z%O^LN=c03k4fZ?C@q{k{wdikt`}u-Ued>nq5pO)vHZ za)drT7>MV5K^kmSU(V@`&7br1P15ip|=u7yb zuIv9)tAk?>iykOe9k#4lKrAWsfKu+lZbP1hR5i?tIt4#gdcyA*eKcemmc{yFFV&OLX1 z->jAJzR6l7lRbN$ZS(A(oE~E=J3*gTa!SgP{OyRjM2pN9RL3k#!Fi5ni8PH97RAx&RMLA|GAZ^&wq zWS5*~ldGaC`Vy{u>fPvtX)O}n!hJXx3JSQR(D~VgYo1HZdL0^sXs_XCj-M9-t@pvr zQ6ZY#pd(@;n;P}SG-@gcq!8*h?yyOVg#)6SwA(vlai4&{_Qtmz9g%orkv=mGBiY%> z+v#TU`|$h3kjj{IXnBAUvWm*p9s70OzPBeu9(biqR6e}Xf0iBCz|xa~P9lRN9Q@7M zH}*5{XYUMDAW8w4#@2x6y|#8}Ys;3JhKlMOXqK=3{KG$mhUkC}r?LimZ)i5lJq=sV z%q!A7j16LXjuB}B(zkQ!f->r#eS$*(@CdNOVln{%Q6|a{=}S9J`Ud=*RyqUiJJ32W z09{bo4(8JdYsD3%?oir4deoMPOeKlI6Lahsl4{ZOk3s>0g>qN_MzhsYnGEcCi~KCR zM+?i?KdK5gSALUGMqCEQDBX;c`z_L1ly`H?@wKFACkRLGdly$xNwZophRrK63`_X* zD2k-YRDq=}kVIKvstq2|nF-1>;cp(vNS|7pb+~^({?Q;$YXnh*k`+x!S7^8|hEm?% zB0U8Saw^8wc7t|GnGaAs%Xm`PN2EDy-#E&VwBAl{?amC@ zs(=$mwo9#{V$DVxOBQZfTu$Id`5PpB-c=VdSihd+_u8yCb&4o7>Kp4e4m>P9M0~{q zfC$(p&z|vTT99BJ2^F_9P&bam6-&ZGEBn}(+jzB>^0i4HHY+OpMbtd_VCGwN^^WoI zyz*%7(4zin^kU>qVfqICZQ$~@9=Ksk&o9H&>%5{p(eCy=_4H}gq-uf_Vg#A<>JD2v z3u^O)sI-%6cY`&>_k#4yEl8He`hI-xHZj*Wc8^o?wL`NC!;`x&Y@HmvX~^K%a`B$| z@tf)c;;lYK^@~j-y@OqpxG8ANkuV>Z&<6mO%Bs|Vr1w8mzyJkc?lTCo{C^r8JX$Ak zbt!CEovr@HJ)-C}@bGmt#whF4s>9GXSu7MT)KL5vdV zb#Ex6T+LKWn3)`e|Le4V2IjtkP%TnlAomQ@Te*d9XmZ4Sqbed8hkITt1x*kHXnua~ zTkpOEBf@3V6Kf2`x>Az%GDiUm;zc%En^Ga-L~>`=7K6NM_ebPQ$ZFuZ?05J+ft z0VShJM4Dg-2jabChXQ~lS5e#A8SaeSart!F5rhq;G7z-f9reet1n>l}X=Q}ax7gL; zDW4jDLyQn#oR)g8gDt=wT1ck|f}Z|nh&~cL6@nJ?V0fi$5nGy4-ZvZmsr13b5`$%z zQ+il^qyE(F*Ix-@LRD+^#*9*G?YBE!H&O<=KwS%h7FgG0yV_WMcBySXe3^xHc4nRyN)Dx}i0 z0_>@*uzs0lkTJ)n95v@MYzIZk7S{V({ek~<%dZs2{4YfvgSQfG+-*JgT8lNgtzt9+Y7-G>Uz|fc0 znctSx@VayKap17wxRzqv+~3lI>zn;kq@@~IROwb2XZX{xv7IvnbwTXXkMcyPvxvFd zd6o*a65l~P#(N;e2uf|dxG}n0M|oqB?ACYIto4p8wL6I&w>gA7&qI6&0Q~;1iz`&j zPkk@E+L0N}eDTXxb$&VD>uv93xmV(_=a`GgYh&Z&F_?rH>~`2d)ER+B#%q!asBI2L1*!P z<>8)JOe3fyc0a#CkvlD@>B3XZiv0gGnF3EJ{A~aj%@3Z9lu9pey}BF&gF^H2twM@a z$*-+d&Tnh?A5AwO1_x#t%>DYo6ZIDza1vIUXqy6sPl+>se!hQpW`yhmfX-=xGXkL2 zCoqh`^8V)#J1;PY5TyDhgpZJB#n4RqHk*XZr0}$LB7rv9hJr#6OjbWchvA74qTm9= zW|#4Xjob%pE5x3^eS{#36N4i2XCP@*dnF*4l}XA|!Sj8i9a0wN(vPI!ls7OC(0#MH zd(VQfd8;8s;l#YHW_`3dyq+IiADtEdo>{3M-nkeUCyIXfz($lz61l0Fs0oH$P%TMj z`Mm}{D!K9W2w9;-g1nxcjoa0ENJa`n1^#di+e{xL3c;{?++%jiChuZ}L`9yPI8swk z4)xrsZ7=Ychp7VTsy|SNK1PgLEkrjiCp3)+)C2L`)qn%eE zEG#bm4txyTGrEt$2DY2S|CaYnK>)1>a94JAd&vFdPnomh71DGhQ6pKX;xeGQU5mBWJhiI`irkj#jG~{o^4d61_H=3fvtdBT zQBGwBMJ3!gCe2)0#LV{U0}c)W-sqR_oQIs69607e2UH zG$O(E4B*TPc@?Ygcw-Dp5JS{(%LXVg(5 zp7#0&Vy=FMu37srbUmp8PXrz&#Uvw%r#Ara_LeYnXH=#rTIuG}?z8x!?MzRGJL!%= zu2dcq%nc*`(bm;{&21XCu>NyS^TI-IAan{LBBX-yCg_M{*k!_=$>Hi1m_6EM2Sl4y zL2aDr^bDpq!aRg*ZNcxZJ;|-FO-u#=9%T|ui1-mH3{y4Ax!X(xa8M~{`>Mlmf^*K3 zO;N-Zl<*S)M={mX(RbwoIw|!sQrjZu$n28m!Iy4(IPgH!f!VE zZ6t8)(R=G=HRfa2IMxGhbFmdL-k^~ejM+cZZu;^1hF8o$UpazN9)iXAxp`9|1F%J# zX0M7Pl?#G)T$sV+e&OQIWmXo=rshG0ENZQ7gf&KxV%=YXwz93=87QmuB%28F1xg*71zM7zC)i|nkYY*^Gw zv(ly>C9Nh=wAgrLmeUN)Q(5lfY4WG_Te13Dgejeh=P&!k4<;OydL#|*hRtpfGgr~f z(q<_pY@1lTou96O+RA?LGZNLNnMK^J!RLt;v_X8ZxF@jgy=+rEs39V zAQmZcwXg3w7s{=97o-zJhwAggkAN=O9@X`Y$z?KyWyTQ(jy(WDh7)>I4Zn#TaTCIz zjPSi-yus=H&DfBiXQSR<%Tj;!g3S{mh`y-oR@C(`x3@)nLrVChX1Nf_V-r~C>;ECq z_yP(PLg}tJ?2Jz+GW*NP$q^zhh(@$sxIuul;$Lo#NTD1e5K%m)-w6?(Yx=yz)9*TG z^9$stx}n@;>kdf&{_XpCSMQNZkaQPP6$!LdLc>H3q&FZ(^6A{PTdRFzn^=Rr{UDZA!nGvf<9*zQo2`>T_6xM|K5(^U4bAx1 zGn(XBe$?_LkB2NKYY{OD9T}V|dPB3;+6*ps!HJ%RyZJ+8elRiOL7&~%wv_{vA9-UF zbXLLCNu$xkx|KhZgc8uk7J%wl`?j)CGbQbZB`V=)OU=*he zRJ`_<076(JOVl4je*_-AyRRPFQ$h56$is7B>Du~>6C9PZk~6LiITqoOf#)@?2%j1*$0NObW= zrT#M(@6V`mOJU+nrW4t%w}qvwrzR$-6kKWhnp&0t7~~}tL_@+$2uHOO3}y3@RI@jm z&BJEtwRz-CB>s$>4!`4?{byBm&wi~dRqWyl5r^qN-OON5TdJ)r+x|Q993-V zOCkb*<7|xbH69!=IT8T~xS){)zQj8than*+Tt5-C5KJQZ^k8Zf(`Ah+fWPqu9PBqm z*7p-lP9xMbl?OVw#2;e-7=euV3*5YWF^wA#vl`H98RCmv1mAxb0tg#+(58opGneJD zW{YL2OwChN7!F9piyQTIVIh!dKjXMdeEub5N^?M*Xw-qp@Z2CPFJ~K#4AS>{zD6~s2z!0A$g^|~upM^f4OWbvRiWD)x)160qB_idS zekC>3M_XM3g$4e;`HBdWD*4dp59pC-)v+ezqbL31OCYs8F*-1PwBHX3TgBFWF>>{pB-XQ z@bJl#*gAut4;MaEB4jt|FZW2*KV8HY-%FT^Y6UmgXo6og6Y5ivwttc@+ke(t@SWPg zxSvL4j}@w&9_6Wz86W2QObvNLa6g!9KZWbMb;xHPn&c81f>k4(8HFlkM2UxJ#MK#Z zU%6bNjwa>l;lH-zj^Gw*bJ#T8r|4`oISPF%G^utam}9;->$ry!w1G>uGLiM$k9QLy zYjn%u^NS3pxD{9_G|_K<-zu>Igcn3tLeYJJAjB}^?_yJ6L2m#~INN1s3UuHcE8EiP1>459Fy0$1GPRd=}E#B&nD zXUu#JHynDDG0b<`(%O^+)iPfZ#a$uzB>lgS^^u1b4T!2i;b%3nv3r|?g9%2f4fntR zFSNkajTmK2Jy_stJuvi8HmP^v^ermte$i+GS8oGLO`E-Jwd|b_GX$z9)+>fc!XO%Sjoyc0*^t8a+RG1te94!<@6N z@P0hX7Z))=JD1%jC^za^$TzBmk zhi-5EC+gsu-(2cBwO8N;rF}5;7c`q1GrjJC!S&)epmi|Xz4h@Pnzgo;(`wYT7~GM+ti*?v(LZn=q%273;4(%CKQ>7Zn` zc|=hN+&DTjKN-H6=TbY@S^iQil!cJpVQPi2CO^&07{PT?w^X~T zY^cUApVR1p047Rm+Y5(yoYZ5Y6NF3ZM+qc*m@B?eQdVCQ%L2{=(pJ$EmvbZ!u|67#FO>gotPpXpBiyvyaq4$8TZWO;gvvR03?Cz4}*_) z-xYB4@c2-1JQZT^y|o!?OcgC`c5Cr!1Hq;CK0LTM`+V^Xyha6%*k&S){emW(*;|bL zKF=FJ)&6v@d*$!&t1TXuMNS_6UlqP;R!}ACa$||*s^ZV~`PVdBv3^23<(Ob-#}8g7 zl0(YpC!uqcon;PjOr$rCB{qXXu~Hqx1puP`daXBM%Jx=-rxrXxl>a^T9bG%l9-on# zZhtkS03Ho41wKGr5DERQ2Tn@>xIpDDC`o{`hI5*>Od-3ZiAT)C7P!t@9zJd%tw1Ftmz2yc2By9@b4l6@r2_K;{x zm1Mb0zcr{GPwZhl{xq4(wfu7J3pd+pQ=jhH)ZXlNT{6MhGAu9$n|oUB!TSy~b=5%h zcs_)Vmrje}Uj0o>R&bPz#k@Vu_NiW)YXGG$EBL~$76XDtVJ1=NZKqcvU_$L(zd;3sVHLrl1#+spMilv^na zqv-%W+8nl2F9j>@Cz<<~2pyTpr@YdPn}_}IpNrJT2pS!N+#)rQ3~#b+#=^8>mz0me zCP{Ug0Xt2;HEd0V@UUn~C$TAqU(S@!Ni@NC$=&f6U`DKw89tuwQ8>wDx^9z4tV=#$ zhLfU?f@`_hyt6#3(;Z$7>wM2NC_WO#aXh}Qs_fO0mUA1{wzXWh8>7MORY^W9GiPk) z#E3T|V~kuJ$sXgRXu=NX@DRrUN|ce)jstmQlcr}R8Mz|uiAT0N(VO%7u*nz2+&df0 zo(mHm+H-mwW9Odmn6?i)g0@IK8EZ8MiO^xJwGxgPOS^QDmBW? ztin0UL=I$EzC?_O5)Bb9R4CotKGLnF+{;s_OtIj@S!ubwv2VJ5$DpGbV+cV9+4%f_ zrR23&P?&3F=dsJHq)Xc(xK4|;$@RQ{*L?Xj?>B7DszA=dj8s~J%U%|REbIz@?QMON z@x@)@iVpLLWlJEZX;R;J4BvJ=R4&YQ);!OMNYmv7#$tZEVzca&RjNbTmdd>l)e$E| z4~{Y3!y!Fm`Z5$WksQHz+`4U|>rPeYxTXj_ z0uS=xWGs3I0P^ZRa>L$o@sJDv(0APpe_Mg~rK>`C8s6BI$MJ&1@AUFr6X2b_`2+xO z5KguV44G5N(}Tp46fF7WP%MQYzQfLtrKV~_B3hObp`rhWc5JBe^DW0Kz8hh&U7)`B z2K7bRrU5{0w}du-o2t|vHBdv8C+x#r>b3Np3pLi;K}{-9onl@CgUH+`r03d$AJ%yan0^0Nw@w|9Jo3bNC@bIi-JBhW-9( zX_{ZoXMoWoy3%iV;LLYca=@MC>!9FY394OzUnvk7Vmx=?XmWgB)J1_f1nalKf^Zk~ z!knUg%QCtCy;30TxBjYd60%*#o@JcFUK8H07Xs?*OC@$%_q=@BILWQ~^ zi**VIygkD7Jd6!#iKdoKYx~Qlc?20XZGk(GCgF<0VsMZ{Kx@_Fd+o8(u?d>|U(Vd_ zOGtcvX^3o97>O^jcm|(xM|72XKy`?G{JUP3flKk3x%W?FVJ0J7u@K8)){<#6><2{L z>kPLQs-@jt1^Ap12UcpoNOfvHZ4*EYRJ0*_v1_;KmLb(oRDZ?Ozc6iPLu=|x8$04${V=G@%=>46Rl z8ov4D3qWC((kv&^^y~pSPB)!?I!h0oem1Lq@K4`NA|kY~u~NNw0&!Z?b^2)nB&5ah zkLjh8#;VNb%?^_lce%28&SGA@K6t_HjNl{LI-svzy4+2K(MV;4xp7D*WrR85`#HAb z0-*!DEF(%>4V4doJ^m}B^!`*oOT}kQKNZp@&lQSe^#M^no;OLK4FSj>$I#BthQ6B6 zw}MyxpzW5Y=gl8Ust`*A)}jBzLo9}|E;Wb0-T$AR7r+I~83OS!+*{t!FXj>ncU4wQ zze#LBt<*cQ(jYUHgQ^ipRTW&l?pW3VgXa6E1()P_2QjWrZt;*mPRX@#!Bq)Tr+Q)F)!C3B(dSF3BXYuLLOp`J zsv}+ym94nVz|CZnh-X$zl=m2z8sINn~F^EhuV?v~DN#hEDImXc6aALNJg=D$48#SsLdO zdf(Xl+1-^#RaIA%htq7hRexE_1Oh^G8Q~yZcaa>kWi>n(c_qVlR~YlqN`=3{F^Nhk zR{Xeh;raAwBwtnuZZXim7WCUfi{LOaeRP7qb3&njZb9R})(LP^C}Z+pxcuZNYRPaE zlfC)_0$biAH5-n`94W~IH5RW?$&u#;0?)7D^%Y-gwEW5`ghgqI{yQG*W^Xkul;<7z zn#3e3QuliRpqT7fe{P3-`%i%&;Z&R(BI&h+1r$Vy7Zqbr52dlbKQ*;H)5ni%aI`Qz zM%P98yTn~X(@2PhM(<{v<9?^XQdvw(vcbCfIVZ&ob7cw*%#T(wl=yR4gx`>5EjFYU zw!4O%q`s^!6lSNkE-Z@dtCx5}Q%n{6UYCL+B@EyK$ zt8fG{zy6;AXk&b zY|)%j`}CuKCX=8#AHZ!dfHuk06?yfScNcuIz_*zXzE_R5|D=noP(0l*&KS#@=vbCN z8#QhbdF>sTfY0Kk`$H}oomiiU+zgOFo@2#3fdI!+Hp{q9gs2u^y#|QE=d0A&F(N3; z-WL4hD05v9q3G7_>YNFPr89zWir z!Hr7mlOZIMX=Imp^)S8n@Mn0RzWMz5V=$;2)StZCXY#A`yfe~?uHU#P!;ud>XxQjk z+tlEi-R;1Cc?7Yo9D6Coov2X{F>>0jlIbTcPH_5ZlTb$BC@(xxS5{lu&vFx9i@9u6 z-KRx)GSOI3Sa$!qQUw8)|3iHSP_WeVF; zIk~(&j^~DM&2O?)J#;!Om`IMEidV~j%?$YO#DwbNSS$L5E8oaPiwFc~l>;Ljzuxh! zk#VVNSnD5ZnT|)SN{lSCD?;6-$Vb3EQeuEDH1NMt-TCrcIp|l`ZZ%|LoJ-@){FOz3 zZh=z#TXp`|J4_MEIsY5^o)hx1ZW~xyn`K-eREm;D5CfjgTv<#^3R`pwzLj$*trq=o zRSLCWl-yr=!4MM?%x#Ae+_p_Ul^}@pF>O#_GZYSD!JxB>jF;9F@L_yb^>jHjH}|OU z`e9R;7jx(j#cSBHUBFJPH4w%s17GqhNCWmp+mNyOW(G0dUpTaYXrb6(2y(irq7sD= zp;8M3g;(w$AN+RxN`XwlbZB_tp~i;ICQCBGSsR(VW#ruZPg_k|k1+e!Z@&)JpY?h# zIQ{T`z^BfyR%x`ZlB*_mxZn@nd*$8^Fzu=KTa7hiBjs0W1PApcBjvkM|EN$yD+j{P zpfP1yv*a-mWpmTn;qvr$$)y|4x}YoN0eXArqZb926>mFa92dwK*}T8rEDcNA+-?Bmq0{)YOZPDGCMyXBhe`kAYh%L#yFgk><$_N~p!Q{CN~ zF7v5K{dW*%M1MrkI$zINBtfguQn4eQo52G6U=i`8+;ZOz1>F{=6!jA2;Kj@%(7xE3 zeKp*Gede2-i4_Cf6VTj;6ups#>`%c%f@z+8Ax znfikc(#?vcYr$D;P2Ufh#64I;O zkwjGrw!i$=}agFxl~ZBD=iKJlbht&so_kvhu~rwFNjS5U8m95myH z;$R&+Q?P7Y6t91$p(7k4aCX&YwK{Y}y_e#?($rSqY9Z=qhKiHn@C9G8KC+66-1^uC z0W8z*ByfG|u}CS1EaMo{kA6ghEizcf-Vh1e0~|rrynA| zjr`@Juha!X16njL4EBmp5W}`#!c%*sPy#gv)8X-Kh!M=`-{D1vpcw|0>^hAbXN*bE z<0$iV(HR8<1GpR&0Y?*sYjsdeX;_pr1RW2!)zQB{ACyvSqOEw6r4UTIaoz>vcr1u$ z+Z0=v1;Zw3H$~9TcI*4i%n+k}FtEyXn%}E2rXoLfUSpTXd1|yWgxcBe+TUs0a*ip_ zSX@91PBjig#{0oCp zSXZ&R0K2Q9hw`NezBPRSMs8E05N^rXHD}WKnBn@wQ+Q@;Elky?Qk95?5U-V&DK1p!>d+(Iy@SeEcYTq#w%363MX9zDrY zlHm#hZGHa^+8<$7=0u8XdHlE&h+u>J*T-WmsUUx>Lr&ZGJB_W|4A16T#XXJ)J9_68L0;);+ zGAl;#5%27onzFLJvTV!IRN1`Rd$%xI8zGeTYR_G^K)O-Ji|yO&8 zrfsJovFx&b65>ke;RAKfFxC-Nust!6eF=Thr5CYO;KJ(rlWdPjnEYhWG(M*LY&dEn zVktV|71g0gR?+Uxe7L7S!HP41gm@BKOhc8WB8hEo*cxzKS{w`x$I}YK@H%l{a2yW~ z9Sm$>*NZhHIor~}2UHH!gUn&0Vhv8Rw|UY-<(_Az2o#Y-FuLA!Mit*ToDFb z379?Q&edkr(AC%wpatjHtr+gI=+?_w9z3Lp*87Mm4ZM=t7Bu*+U@3H+kLFKluayQ6vDIi5Cm+^1RJrcg9HmlDZF_Hx~Q_7IVZZ3z_R4*GY${Jm>rhVaZZgmLk3A|x; z^R3%pz4C-cyA{mBTr;lj{92J}>_t@$22O;eJ3_L(f=^s?1tdc@Q1gm&U=(BuPtqRH zr|YDR_7R7Ajh#2B);fE!n(M|}qPvW(`Q1S8Y;19k?a^IS+4LsMQX;U}{kKqINmEPB;8->@ zwQ?E`nRPN3L;uK9$wX_V<6G|rd-34!uoAO%SSEP!z)j~*bk!?XK9?x5crZf?eSi)I zgT>2Af!Bm*m}?BH$ZRLbmKS~&i;HJ9JH6M8e3}A%0%|O(%I#L6Gow;b#RAO{g&Mz_ z*#o@=X)ESaY3lhAzH&OalmyF5q<55)#EDY+p?Vo!&@;(tZv{xkkfDT@Ph6+n==`@}L@TvDr%4LFx^WLqY!^ey-2 z`l@cqI47JV#wK(}AT;>e>(1BIp*q6so+UpdBxrrOINoDIs6jpEy(9h;)WU%mWAU?q!S7Bc7!uvmNQ&=|U+ett0L)zOUfmlZ#&EHk?MHfty(n zr*x1H9$*qEki_WUQH?QSTp6qUP>r#ZS3#acOxKxOGHRDjrm`HW2zbfckP|Lw30I6D zGGU5p!Sq;63pLmHYDz2Z$H$T4vM}X2<$dR4+gQ_Zrl(h+NO-`Pi;1Z~%e$DQSQIXf z*Q&D;(_mt!>2G3(Gb5(zozv_fF!+I7c^T$#8PtC1E~u8>qUHfm1>ouo)2kfIEBVqT zVAg6w`|iKXjN^iuv1^&%-Lw zVzZOoO^^}sIyjK?Tzp#s0)YZw>@yc!W}~~ypVg?O^#j_)BjyXGehS_vAJE7y6#n?= z{6G@KH#KQcz0Uunv-R0z=x{GvKnGr)qWo~?`?afGAg02}3b+41^84%WzxiAhV6GC# zSlPAqbQ>fL>P{f(QldabWrt)icob?;KQ#j6>5fpK@;NPY*F;N1#msiqa#+TRe$l;N zE+?MPIBOV`D^K(0C>16O_Nd~zw-Voop$a{ zIij%!dBZulvFw?B1ks?9Wx zSS)mdJIG8txGi6bdDib+G&kLLuq(D2&C4cj%gi%D^csJ{I=ddNxhM-9wFil^*d09S zQefmIufA?gKe<~m4jS1f*NzG)r-8(;#s~VJ6KLsDW7~1!bo(+t_iK+EpYZ0+w*H7q zg>Ko0>AF;<=wH*hA>gMVYq~Iec&p>y1S05TSkkSDP<)%=FOl#PDRWfzW54**xk(R= zb!&f@{@X^mu6v4*ZCt7klkoUGMPFx#e4?$2A=C zbg9r9d*Y^Q3$@eIML;>cM`p4mb*gZaQAz#`qqIWEC5bYLbF^HLg_Y_O4$h2fA%Y;!(;(XA~K_2SXjW}?I%#__9uJb=A+Oh=fv30hKh(nGit(toNzrgwVzNdd+W`pd;#8rH z>CL)<;i>n>U}Hi4 z8!!Si3FW%{jlU19-!?E^@-@!6Z6Aq12>n& zM~)rQz#!yb@F{*8(kOQs@jT=qo}!g=G;!daTt6w_2uH|)$76^b*>FgXTbb5 zr10rs*+2#H-6=|P&{J7^E8T~KI~Y0aIGl!bAO~scatMUa6fw+s;nLc#iL+6q%I3wo zk$W2aegx{!q%N$|>7}w&p&DSL_pN89HdL%~`9Itiw0h8~)e~O^*p>Iej4{dM1Frpz z9W5%qbqeLUN`AoL@5;otE7qT&^hvKa8b z!*-YEo!lZVH~%IJV}ZsSz-I1H@L91-K4pVi<||KE`#Rdlg4yi$1_&?3Csjjh1E!hM z>4_Ym!AD(DOFeBTvjolH*%X60VKQZ;)yq{Y&>6%WD{=*5X#v=+@R7Ac;SV17f)o&u zdt1k;(J6zv-cqsa`xz*WB{`i+lcn+B`9QrRmnExA8ZIdzm`TbP+@7L)6b!)f{@T?6 zNScDcvxC2`fIXn6|G1-yz})X3l%AC0JlupssAc=klyHN(5i>EVNmnvOKJUEzJ$h9HCDl}UpIxQo1;mtB*fZMD1Y(Pu*ntnC+Hg0=6niT~ zQcbzh)RoBA3t`nIn^8R6eb)FP+7e?J#b18+{Grz)2~gFiiy+Td-N}BdUg$c{(BtJY z^GSYjS}(ygr%V!eLyIfj+f)#zI*yRhbK+h{YhtFWzC-|EiXL$oQTb=rLTw;ZixyqfBYw1yJ-kt|lx2*Z278-=+vEHC{@m{gy zxA;4+!#6o zE#<;Y`G03Z4>^LK2xbod`J#N@CSNCGs%A#6(c`RFsPW}bK<&9vv?keYR4&cLMGz~> zM(J4x3>Hri%>x81PymS67^O(Apd|oEI zf%*M?x0eb+1m^}1op>dY9>w8XXo$~1utmwUSs(x9(0y?JMsx7EeS^kuGl4&+$G`%e zerU4WL<)#HclnUMwuEi-#Y<1QOue5vDcQhqO-i419TxzL(7_uFFghU;S z?_nOv8HtjT=+p(9P>=sei9}c=4}n;oLWO;loM@FE|NgTaPvWv%D7E40+X1fgfD$Vi z(F`%nsug`qdAy?SiqLyy5Cb9>DGwZ+X|dF&Lsv}M{0xjH2Yq4H9QC24>Z&NFLNyXD zI^)vs@1)S1gDb!6PYHPlkGy!CD~u|K&None1ud7>&f*fGofuYP+9<+l8dy7wRT z7NVwX-wF4n6Dh=;2CfVmB<9fS+2o|kE*{+^56Zp#AIg@Rggqu`mc(tQ1C~P-el=~v zmpw3q>@)V2?5-~c&nibjhaIAR9gJh4Kl@}ezn?Jii zEZ<1PYed?MZz-50?<=Z)`SD=1*aOFptgPea^_(R`)+$!nt(2yaY9Q8zq%|MtN~m^kSDJhk)2CyP43W_cnJi%6CDH^F=0#QBl4~jdIpePhML_3I$V%KQA0$MRypf! z`Bf6stBXJ$P`2k0BrD(8yb+;rO=(qHQ0m)kH0FM@*xDAUb#cppe^XgMSa9-Ei^`IK zMEoZ=#gQxvm*Tt$ifW7&xx3Yhrz!76zF7|uHW=5GP>2#3%HVqCY5tB`6*C(Q^xf_4 zP{q-i0tf$lnT_wM+^cEPbb4{`%yAzfIf+U8XHoOkPMGjQQtr6g$X6SPS5&bPm@9TB zxNlT0Z6@`de5?i0zHrCx2i6H)je62Ij7S!1kd& zXf}mYI(T1zgDmPJZC&IR1(`Hf9|d_urS(|v&$Wph!o9_u#TP_@?RI7i(-mQx*oZn! zOQlspR(gBr#t)+wZHGCR_@qOkpn>BJl~MJhFN~DuF$z)om(*tL2KAzh@6N*VhF^!p zoR8N}gCqLUpJE4bD|?cJJSG>+x!ZrqcFeu5Wic$H;!H1 z0(fL4f@&R*z#AvxEcjM~qdY=?TmE_SsZOHoFEBZ>W2KB(ch|ci2g{-*mX`0}hAks% z6d^J;)QnRu@wcVpiZIv=fh~zR3f;|FZLpAyFJw#yV^Ch`ly2swxv5rHPoGQ8`9QNo z&Ri!-ppEza^@Hfm6=Oag>L^cWQ(0steSX!;;PTZIS0lLsN}FD`^Lh#g)UXnAeoQd( zL%PCa>Un1^^{?n5Xeg8q%lj>aR(kv$)A%be^`B@1rBLSPe-Zjt)`rZN4GJ5LC%03T zFDtoL`8F91!5(krSh29fDf?S=MA1%4FWoCI{mL;UJR>}gdvHn6ltu>`ShPsRxM5;!gdIPz zjVcT1Ipk;d{73gr6E_VXuT|XRI-lOOjl7a|7JnRCIYxR*8NlL&+QdgtWbzX_C}xln zS`M*>ln`2^Cja2V?D?jz7?P(N_0oYxU1raRj}rn5PFhlqe>5o)XmYJD@k!!XQJN7^ zDDOgraWeD_B5W#W3$yxZ?Q~pDGe<&Yfvy_zvi#8%&Q5N5r0=OB(9)Gb;rzSx3Wmy~ zbh)u%CeV9W*aMl8rOdku>f4B3kPPM}Yv{J-lgbr9-^4+N3D#g8dgp6=a^hzDVh)^m zZW#xsaNq!|6GP3X5TrNUaB$F3UnO+yiLE0M($C^Y;})OmQ^r$O_!NuNW>F-1fq{=I ztNyRS2!hy5ZT6VsmUA;(DdEK&V2h-UKMlQ2c;EUd6i-55+V5ip4Xs!2=zW*hcI^}K zQVy+w-rrsQRy~SeslDOEfAdRadR8U)>5PKU*prYzw}(FrKxSgbc@L*G@*dsj3wM&i ze}T*6DY1oj4xtVs_FY3FW?l&=|*Wr#3(`tzvQVP>%ZuHqF}QlarW%PCOWjqipY6<>XVf!U1qXy2($Wji+OeH5%5d z3Z>Q*S@g=4}nNmT4;bMC1$r_Qt0Mfx^5Tqnb)f3ls2fKVFrn90gZ))wj3 z>XYVW*lL-Gw)%L8In`=&xLo%s^;<$=Tpq((Sz!#T$4<4u5C}H>ZeQYmc3i=G&9o`S zY;_EAUfk}gZoponCpe&QZWE>bH$%lX1K zMeuvy)pxRW&;JNUE#rWqp~jFlD-q}fV|$waQ-ZX|r=YNFJSLUSxguUt!Z7=S+&StE zL8_=0LOsSiNS-3J=h6)~x0b zBmIQ*p)_F?w<(d9E9AU)3`Y!B7&-=OlTMBtP_~t6_JaVv8|5H z1D<1hbfSbo+7N@Ei&fjyguJ*1BguM=C`S^NmG~s-`hM~ zvvJ3+)nWD4njhFGDekI1n86&a$JL`L8b8zTgF6c+@+lKJE5hwn#fRaMp7S_HQ3N=A zLD*0-Dr_X8HzbpIf?&_#gvG;xv{uxX!3p%%ezjsDF>z{XS56z}x;^A-X#o&$Is&Ya zaT>8{Na`-x-(P-PVEf=S62+q^vB1MK@>|dOO3!$3<`)NuA&NvV*<=6zW`raa7&Pji zjwffzUX|#Wh@#S9qAWx=M!_PK7_MN-JRb{LU<_sXX})rfY)c{X=g|wcz{O+Vt=PAg z|3}qV0M)TAT_4xr!0$=ibO}NbU&gvPeRi?DA2EfTv5Yb>bR zPlkQ*JFFBwywhuO-u+98}^OtbvuC}LtP9`>r)~Fluu^b+F;R#cQ3;i$GOYche5}O!+Z9$#pB0BQDtT&;51fREB6VK-04so zqY4TUT(}X4XRMDzG|^(RMtO+eIJe-N$QL9Ly^pF7q+gEoC(SHbNVF!)AMUWA&$T1l zDbJHkNdO=qeO%CjQqj@L+AFwNg=r%$$qBEX?>4dM0upuf<Ce@^Wh%Oc#b1%h?R))HO;Iljqfrpq{RWP|?RMLp2ziPmYHp{Wwm-Wa?%>r+Nd7W_ zO@Y^stkNGL{E~>r@m@GFE1+UJL*ka&10|V1kEIs+4Rzn#w|%5mHr?!L_Iv1nb8PT4 zE?bpoj?TqeG%ntUq%rH86rDv{0B{7UPpxPmHF)U-Gk>vr^kAm0X@&WaeytAv>bzx^ zic$IQc)zx{zjU2b(ePef803SasPxfE+ZVy8P7Eoa6_017dNrSQlg}|@`LMuFu`@>~ zjC^a)fY5y~CH@lGUDw}Aye=jL{h4G!YzwX3(4=>;+cOQ!rcrSszgB>J@prtb zHM_vj)tN1!J2b6;GrWD=rndvsdXn0qZ>&m9D5>+AB#IC*yZGe8gdgSk#kP zDdX~VG{2q7DBoDN)6~n3yMG2r&-wzrE=&s#ZY%{njf5$cA;hp#x;7WT9)o`3i4yJ2 zOxOKyF!xTkUB~q}gjU&oRIqQz7dxV0w><*gnnOkY6|&kf45LBXn||#+(<9$~v!wi* z`g^tezy5}gR()X@XXgu)T%Sy%63blUhW1eI6ed{O$@4Gg#>fHgKrQq04}aG=2Sy}9 z(U?Lyo2%VASATI>?zlXetA~&4CW!V+>T<=(XG#T7YU3N~JfkE8vv5-M`Oxeq)D~Hey5f>kMN@3g9^w zv*?oHBeAQ?f9kZnhYlFEQ8bKh^~Hmso|zqXJNm&YRxDowx#R;$UE!cf8|mBaCl2@| zgyG)5_Lhr@>kc`LJEx(=T9+Ohpp7DE>V1I&rD%HuuJboR#J0gE9Fmj1P6$MVZrqL}zV|%lqA&%SZS(SypIK zo1zF-;Q&GPo>Wo(@Mny6Q(0T6&=mwn=|ksSAd?Bk%xuX25E$9&kG;FqZU3Ekf3E+O z34sl3M8LoW)BnD#N~77OxxXDdoxwq&g$2_Zn+B9Qi36ATLLh?MKfk~HOZM-SX!yDD z+(hLDAvZ(gZD_+x%w6j6C?m4>XsWR#Mvr=7HJj~q9Z-f(19&Ql%;klG4DawBXvXyylnZH4uR$xRuDH($@v)OZNNr>|n&zG<4hd z9upk7x+D+iuXU!kK5pd~;Y~8I!cHiTbbtS**da4*hvj>^^INy>hq0%NA(|+2?_K}) zDB|lh!Vu`lnYR}&b%A0@BdoEw!J629->GKU4f7K}kHykpN>Y@cj!iQkb|Ki0)Rl3t zI2}DIah}0mJT5>q3#+z{V`yX&*S`ML{kgl_ta>ta!!%5d#BErn9CTrFwZF8(IL)B> zl96k0d@4+f2xMMV&ke~{*ca+D>cnQ=Kv~9S{*;I?>C#W@%mctq#SkaNm%g!FHowAHAn_%56#fwgy3W8PpZEoeP zXQa@GO!=4!$YFkUsshNOOPUY$khZw7+2UlXs(N|4+b@gL7kF1Yf9Za=ui6MEsn?$> zl_$Flm@)E)5@q3H_6>{(Pj(W0n~7rku=BfWsmGe=pZ<=gl6 z5@J+pUg6916a(Z9L`%0tG0ZAzR03SZH@_?A^H7|a_~7XfHjiLdK=O(=`oaqVIIq|Ofn9<8?_W7f|v@Azl6-D z0rIC>=F}p3Bs=X7Voop2SFZPa)sN7N$SE7pT%pd-R1V}G!tnWZ0e}sUQ=>iv)Iul; zJ#YLCSDP+x8$Bdy-gy^o*G%)u=oInWz*-H^6w^qpamK#@3RW~@o%UVDfc z?Oz=&sJ#70W=@$pP`6w0MrXBRoe4&`+ZML4F)Znz!>sSPZkC&FJkgW62V%+gmFH6# ziYlQam{663k;f&W;xaIWDXoMy26^#_M~RTZ|3mftgTF2x?Zu>~fysmzv0`t{KDJ^m^ zC4W9Z!G@GC)T=RDO46;4;4lVuh`>o}%;?C_ma_^=<0MoQV*S|u$pJH&vyJHu$gBMn zMOfr<`W>047xeW2GAVQ?l&^BjcHC$nz)pAzvULY7!h9_qLF7E;7)^2Db2l*^BW4*O zB)NX)-B_gC=6C$8#PGxov56p(ZYT6Y9_|p*3ns@^;HTrAx$(M0_vt08F;BLe9o9t^ zxwq48{&0>ljnLr)&T%wlzMP@q!CL1KFXB;(LK{Yv?8H9zE-ciQ-j|x#bQGOai-1I7>d(4l)FrRYmHaJ|N zZ+ek~YV`g}>VQoX0LA?#rJy2@0lhb;aodGPI)CM~6`OA_TKjC6z&ZbJ!<_wwRuwKV z_-qAgiM$8Ok*c8#=DcoXQynbYuG5(xo~zl|oUn(B`pvgm$;Eli6~Wb z1`fv#tK>kn8JTGyroQ`SLK_pyhO>}tZzfkZ$u1m4OUE|29bSbTCXs+adrqsFX~M`| z6{u3Ap$+kZeCC;b6%laRJ4o%Ol(LNPHxWup_MIEoopV8@uj6!m7_|0ima+IA!xe(8-7&za1w6Qb=Rfcj^@lBtL5Jpl$-47iC_jbhxks!{ zm>QW4kH({+r)NcfZyzbmEjqeNz0UUbT3UJRhg+=V`s|hJ68#=k&!#={gGN#$WeDCr z^LMYtG=X6~;%CYqX*|D0`(PW7Lub|*MQJ31jkQWlp?h;99H2H0bYM8O!A!OzYOffk z>7aup_&etJ^OdHzJv3qkA z3ANgWT4D-&ua3C9?Hc@%=E;A+RrprR%o4*REC4GD1HV5$UYtuuQK>aU^r&eglPW4Q zm!6IaT<=Pt;EqKxap@=VGK*_|RqMLMBr*?rIz@Ip%1&k;3S97`!-+~RG zCMHHkCt$Z8Dy8+M&zti#nUmak%&T$GW%2ue@8dZ*o+T#5A6r()j}1|i50siF>7>f@ z(+q064$D3B5?w3~`c6eHLVJ!IvJC+D_4_gV=%3vSh4zg>g7OpaeeZ7h^Z_?HxXIyW zt>p?fEcO>3l*%z<@Th5`hot5%P2eP!AjZLExuRNgXe=-~eQ5`z(p4uRvT9SvrRQ_R zZ9(MvYDXB<8X5n-+BO1r<1T!-=Qk)i+H+IeJcfD+$}Cqaeh)gYihNObOG{1sUun%A z^gbY{8fn)eS=;DIMv$uhF-p9eR26!|-xGLYAZRf+d;L7V_Xvey#Hq?G5%!@Yuw&pm_G}773+oUUlhzPK>;;Eg*Y!^!0$k^OR}JrP8z zHlf!dQd40x#r_4Tn`^>8@eA`&e6fqSR9=L>tj^wPPPRa_n9aLy%Gd-GX8Q2{jl@nR zgO8wvQvG)2gPMJQs zA?W7Ls8cs;S*$>TbDBYXfBCjiftc7bFX;5tG(E2iyul@m&FiYVsj?L>9ueRHLkOj_ z+BT2wI2IG&g*;LL-dKa`C6Z?=HIhxJ$H`KTFe79!-sg;P*vU-f)Tg+~8g%ek7txzT z>C9&~J8dlP&tk169S9Q{@F(#2x9b7KTPP#q|B})#ACmFKnGoc#UjBJw^izSYHMsJ*4R_DPH@JaWPjxtKQR9mKLqvw;y2}icZ(6A!l~S9 zaX)$_uITY&!quwgu)~nMw^EI1EqpVNP$wr)4pMY>UuU6egtKjV{3p6E#~p#KoET%UdDU8@8k57y)hAV3xMf;J5f31y=rFi)X$Oh+qryr$q4` zXWuMl%Jd#2{SMm78ES&317!_RYlLjZ>^;uElF;L%8LRVJ0{u)Ium}e1=ae-?ULYY8 zp}8$~>t@#QVzi_D%sqC0^Vr^H8X&2>;fwA&s7c)DR4qA?IL=_in)HO%30Oy{XN~J; zsY1k-P_J~;ux{dg5IzazGy#Fm8!`TXmky1GgT+JSkZSkaT0-^ML{F-k*gE8!X#Q51 zpaF0HOMf4{21EJRT;ha^wyG?VM;r0syxWD zezu*5@oKwCC=+6Nx|@r}v5IlM{j&k7X?bT~KHtk4Eh3CwqpGzhae_23F*Y{Ya6w`~ zD(^jnYjl1E{nAH0NV-s^AZASMDMWmLl%U@nl)Y*L1^AlnH<~X(T9qODe@5~{&U??t z{hv<&0FYri|8FA#z(y#$1c*+k{ug#*WPk>x6Fp3$uoUG3&{%|v59X*&UF4hnE!s95 z2~1SZVIs^3lq>U~yb7spXDD!D)%YF?l~nSZoMX^|0A$=G@f*75H-qtP#MvoOK=Xvr z8nl$^v{k%Pb7(8yW}=fXur>Ds7-4Ki=Kf&GU8s&wBlfHk2DabUAa!jxjs~J-EseC2 z79BRdjVE*sKKquotP<#wT9~9;<;vNV*utlPyCWEZio*AO5g~y1sS$b~@8*C%+0R?n zc;3L66E<$IX>yWst5|3?5`PsJl%D5z36j~l%+?P&Js4(U6R5#Lf$R}N9?oxEdTrRt z<7~8@)atSJ`l+dx@Wyxfqx!A-Xx~o~%GJBUbf*!w^f%+WKT`x5eZnKVqWg4da51-p zbMY$GRzFoufW^+$B_|%2HAP-;jOm7{oO1pmo?)S?n7KVs7&mJ+xoXHkT!a$u*}#>D zE=x67qY$vYOMF4U`Rt+<5Qj=Bk)@bm?OBPHL~=3upo9yv>G1_RU@2kxXj0 zjwzV48sjeJ-6>8itmCINT~!#Muz%YJR69W{)GgpDK!jnc7XUD4#cl!`^xsH>9VC0y zT8I3+ui;E5OoE4!FUYJU`P(ls74)pvvn^Eq zmPY(9MfoM}-{nV8Tve5C&#SIvZmoCw`NrGAtUUHZp54v+@KWtO)&9x3#|^f^whi%y zQEdtQJ>Fcn9gqvvzCjb61V|FwLd@3h*>aY&&EuX)SWm)&--lRC8T!|a%lpa&AWGM_ zhfGv42;jplEK>-TPKcG}JNjBO8KcjpoX40pImZ0er_cC)rT7aExQ*}rB-UuP6~1Mt zL7*_j#&Y90#3nS;wF)Mb9tH8E`&o(oy70|(+v(Hb!5iVKk79I1SzV>)GSA>H)v%TM z^<`EWPhdY~kI^BGb1FtR|DBB?!$BtjS}MiXlWWl%E3tfZH^vQ?GTA|w`DL1kqo$jS zf{Ldc)UPO<$LAjC!VzuFu+bdB@rfw@)8|Lhs0msQ8iVg zpR*gCk+HV9k8)Pa>{jxkmx!Aek~vAB(PHda&O4Zx&pfdhe4Fb_Of-%?uAnBe8PfHB zxy!QGh4hXtW1UV95`?CMO)veZ_8+R2zw&*X@s0e6ABig!tI?+od$xBdTk#fum_mcU zqy7{}jYE0C9OadrtF!V7@lcFyOxvmk18K$gV|{(Y0P7Q`9Wu1Kx_31dIf7Jkj!oVqHH!mOZSr}kQmzomQ1R0k={%oa@^06=dT?zkUtMq_tfs=a z5d)AzzC9pb$?z;5lq9c0g#|gq6)dDpe5g;mJr_ovGD3-&c&oQ`QE*YXJoWyr@=-6d zW_t20Oc`FvNQkxP;mz*&u(}N3?Y_F5y7J(o%4G#KzuipW^;d|%371#FS2fa10frdi zmWb$CiL{auMmZtT5}wJ!%R2>+y-M%)=7GhKvEQ@{y02Q^G@ex0>xXNZ{4tcpF$ z#g+&6$0RZxGQ;m*p31Gdk&C*p{pWo_=N+lwlW?Mm7H(`PIsJd+ffr^ZJ}l5rP^5^! zOU&n5ikvzEB^)W|0w*`pzCYdDx4$?5 zy!)S0JD^Y~`wZy&QT$&WpkbmzT4@Ojo?^qIr3;B7hKHSAjWLO6Ko=FXtsukVwBbJ0 z8EzRBpnT1t^^#%vPHt(O&_f;o3LVK!q|p^RPz-xTZ9G2!N~8Dn;!5L!YHIxPiE{|X z6cU!m5M4)(auvId4?^~r&?hDbPYRUW{%mL0@cR460Lzi!VgVc<#Mu4{4MUV?z{3-|la5?;u(6;~MP!KpKT>O$;jHBf@w@{CfTH%STMOoH4I1Rf zhgD%R@|eMyl7xcdcYK!Z63dx`tua@#(mIZ}1{c3o+vloUubd+#t7g}G)qmRQdKCmW zlLdslI`m-3Z2@;9=c&!^noW68w_3_{(`p@+bLlNX4UUKtvm~YWM*hP}{&NHX00eOWJ|+M>e|ipn!!&X@&A&_=Esz{oS0T!MNApdTP6+DEj83}< zh21D!ez)ByIQzHXA{OLhN?lEtsA+Y;?M3`FfD}@~QF3V;3q>0X6cE_0;A>MDC zp|3JRsNqI-QFz5Ax@(g+=5qU?vAJxvNYT>pS%#v&iY9+9%RuXJsfyM$$hi=K?gsN$ zo(E`5MxU5xJ8QiNIHX+u_9T-tqiYbk6Dh!EY`;J}?m#zGOzWaS-;u&SOkz&3{N}Te zts7{e4xv(2yV+-D#Gcjv$vXT8Jgm6&fqQ_Y6W++^Q-6(Q*IlzHn0vMx`$8Hg4phqWrbqpNoEl1a8{@a%+G)U%^`oQNGf& zuQQU^h>6qw9Fl$O)*soSkq^v#&)0F~#{~t_87s^EWKTHP-AK4pV zUGT%iZH#fS0}=>1mQ~jUw8|6w_9X2?!j7E~J0ENAkWi&9%8w+7a_pxeM1NE8nIchc zZ!A1a?j!>BB2wl@EIS+bBKJJFS=S7Bj?O~m^@&JCa6@L8Uvph19#|bI*XDd}=X>Wx zN$1m>L%#%ncQTvNsUm+uOP9qQ|B%=F83JW2Q4>GzmR%R6vC)D4Sx-k(1=_eO^98!c z*{aSFgW$7}i#%0f^!HlHrEGmph?ATlcUs~MUu#wWr2My{l^MM^nXzf-%D^)yzw)W2 zPCV)cz#iZ}!&^lM>2Z2liEI-rD)r|c`zH`&i_4D@HX=_pHz$TW!3L~7RCJopp7CXK&BU3;cZ6b{#ZzC1VT%HVa;^sTu3!T;tTN&b6it z4Sf>QF|Wn9;D}#9&*4b8^3q1Y!taD{r)@o|i+DJW^E^gRXyOK41P>-L#>0%ttHvx# z9gFgQh?x5+TL=_m4O|tyNt377vgP9@T>Hc+F1U9SJUR`RbT}ig^5i#Eebf^mtMgI0 z_YqImzeGl)#&8*L6|HWlceF#u+tld_RTpi35yg#7jO?7+J#U{&r0U62mrnz+dj2ndPgad>TgEr}a5_E0I|QLZO*l*LnzWE#aSv=ICmEZi-klBq7ALEAo)V zASukDMm!y(?UsCReA7$>`s^coPZ$7fQ$Do(VQ_`){J=HolAheA)%c>ovZI(+DxF~Q zNa}Af9(u)fV#m?BXyge}+oP@1@x*k-%tfFSPP!+`YRMEYc_eC&DpzMU?6hiv{@t~F zixtWu6YY>8nfsE+kTiNzYsN=4@s8hVBCiUEvDsTcm0d zAkL#OACpn+#a;fLsf1$yv$$*dC@d6i;5|HNVZi^yH2(q|XGp|k#VpK%F=-lsaPcl6|JyvnH zKc0s~rLa&szd86z%SE*NhHeS2cJ=LhFafn7i1|344a}Nx#4(b9b$;w0sT-ha|F9{> zg{k8%(Wp@uQCKD;ea~pGSo*(;fW!=u;$}{4TCvA-n{TKiF}FNPqqkTQ^=%40$fG2F z>GmFJXwiG~jl1)C-(u>jV?`=z2woZF-%DhiJ)j*k>w#M@SL!;durY;4G>ZeTP`6}S zFQKhnON2hGuq$R^83y*OdVPFK?7ar;+z=AqI3IF39)gx3>~8nLxifdUXKWmO0RW1{ zYZdLT4YQz^ceI`zj3!*GGY-ljohI)%`!KF_?gu9?_MJlaEu9L#s#Om)D|}ZC#Lu9J z?-HhTO_xk5jYzRoxzaN5NrtM0yqH+NFI{A( zfh&c2R3mMsCLVn4L}4>YRR}oS-hqTvbMq`%;|yd)3^sor#0L@zjQ{0AqRjl4JT;G{W!CClu%)DWlpjPtjO4jon4bRMWZ)a2?T7F zA)IEw9H6vRGIaLJ@&wvC3ZGrf0h0-3enTeedmvlrhL$@Rso^_3O%#f*<7J3BrFh(x zWJp9Fc3xRX*ZNqRS0;;HES;I%x4^fhwnwhV^(^Ss!jIXH-&;4qJ!b?xt{?9kQ%?l7 z-dk}!vw%N1L{zI?p@?mjm=bA6DT)>F{8TP)|pR&9G`IIc4PVy>lX zPK@;v1i8Lku^Q6)Kkr$-=YR47W^s-dcavYem2fL#54oB|A4@I*dAP0`N=&zy2Pq9- zEoTrIRhCMRI`C}nwLP9PkEZnqK=LZT`i8hQJx-srIM>JNPi;dfyrP#L&-9NW@zu{5 za-ihSkN?P`@s#RooE~XTES;QYX18Z|{;W{-WC{2;L*@m^Q3N*W<_Fib#_%VCyzQ{5 zjaj`d8-#v5!YG z%7Y&z!71A-%e>9HpH$sAZHQi`)Rm`RS`M=9-FGN=&*TlR0BgB^6TzVA+GWTvLexMiJX#@DjVxx; z1fCU6imhjpg%JbhxSDHu%T1V~>f@H3s{|9kfrDAlrgXs_%0=xEj1ARa@8GhIzYT!; zsCw77&$B3q`TA z;LeS;S?*GnK`#DOaiv|k$3C`<7U|Zs4L(Sxb?=U}0$puFzC1>S`XfWEKk4UlHy*{y z`gWE`A#)O6zzzIbyyab>@Pa^&*o57TZYoGUHTikv@eam`Tmv>Z}s=%b0v(TNRIxNSOH6&LZ z@p8Y#!SM?%mW>QxK?nl=wO{x0$V^w>4cODm#8jr$SSh2-(J0d1g3qUyD>7(eX z*=1t-Q?7YLy#|kCkDDaP$fWI)X<|cG8IJA3M71rL+3Z z$=F$F1XZbaf;8iYl7DQKcJ1OJT<-sTJ$52^0-4lX ze5pvrz<;0Pp->g@<_r3J_h{kAq2?Wm-!F?ED4Z_a)ZBX&F?aubnk(5ZMhptN6E0QS#0?{fUA8|UZZuxa=FE$vV(G-f4sNu4k6P4( z43+vzo3KBGKd~4BuFvnG7d_%(as27aR1gev_gb&a8q1Z$29QStOX=mZf}6UFyv{t_Jxb^;!cmQ`GYDIc~hNp+i(} z)k_Q4cCLSkf7ByVO}|fOQtTpuHZNSdBMOzSsy{xl$&NiW0%@#MAz86ypxnou{%WU@Z(z3xsQwWl{s z05M#r+XhzMsrSZG*&k3wYl}XQ z(a$lNy#N`5*Pq}`N`Yi%V-|FMxISTP`Nu#XPO*N^JyVK*@BU5ifn1>^Ajsty51NLt z{LIh6wB3b5>|9vTXT56n?5^sOd2UbHZVXzt@Mqg~(P7VA^80y}vwjQm=?G{cg{bSE z_jmhDSmW|=dpFH8GM48>dG3?;-8ZYx9X*^VfVRS+dh4@wjKqM-)nlGwr7k<%kxsRe zRU6AvJ^5#ox<&2ejC8sfEi_>Ov6g6lNmDK0TC4%vTE>?MD+MLC7e&&$Bn9`YkJS>D zVv|*O3=1Io4BAYPO%P7rNJx_%K;UVj*5q3LcGtI>v;&w!$3h5b;tnZ}XfIoaT|=%K z>zn?D7I1I}0Ix3X%d=V(-IH`g zhv=fiF&al-i(wz`TIZQ$smV>MvOIc!wQudgQLw+-K>t=am~rav4? zHRpf@4Mn;N?YzYu|M?0q6b8?~V)7N9Sn!X7>el! z0nplny&~ouPzWxtEY{WZVTX}lmw_5u8-4FuN4uo@_F1Gxmw{vzIjOsw?N?h+q8q-v5Ms2r`!8;^jC;6rIpRzk?k5h9kk9!7 zgW89x)OW*dxMg9iPi~WSjF!c|@OF9Zo~Cvf+`7ojle0e_6#`suqbi(!9t(-B@X!#y z2{~qksv29AFZ6G|=rV?f>fp^gyS&5-xt_CbC}tbZt@vwARb;6qgxrJ0)V_(Kqnp<+ z`f+nk7yy{u<$6IT+pm55%AeYkNPEby6nL75PtBpoiPsV1NTqhaeR=mzhZk;} zsdmu29|2H~;8kO;Er-aP zUwoyOo*mmhXKg&zH2*~VnUU!K52ZWh#a8hlwTw9BKct%l-Guz&VF?kPg3X zkUs;Lk9nH_fY%T|x{2xx7;e7CGFd)(DQ0z<0VNW{E*uKlY<;Y3aSr(w3K;^I?Vd3# z!lWp?aHIvlI5%Vx{{5ys`JroTfsqGAgCmoQn4C}K`E;(@D9Jrse3p+_Q&{FgskVvi3mzZRSw_;ls^_F$+XgYGOa@CC_aZW-wgSgIv=yy#f>rG@)u5a$jD9jZ&6v$ZdFF_gd#FOs z&N2DKU!}n>aL!HAk;rCpgjNls8;O!YEzJwf^sMXW z^gMiAmgG%qSlJg3&Y)@~OWCk1^U_pv8YPJ}j;K{YwPOVzyl=6#OonMP>@e8U8QIdr z^F_=iWdRT&n4ij%io|Ql)4~E~wjGfR2I7uJ1Sv3psxYuLrjdLgnkp^lQj||(w0 z>*CStogRvGv?6#A6%50`>xj3Cq2}{Utt{@|cZq6{kgMLm%^a3Zbh3owljP=EgTgCXcSj;T9c~m8NtvowSU#cs z@)0ec*G~DDKm-cT%!}WMx)LCi@2D3w!vYZGi$YZ1>X?8Ca62%Mf(lAu*5_20VEhKI z4(SKHPp`j_u=^TSLh;iu8%ag9R8EcV0Z0(oQ?`V9*ZbIS&oOO$D7so@QTj?D^#L<$ z!Sm6VfrpL0v02uXbl}~u_7eP>_xAW^g6Q?%=0Om?|jlIc>*mHLB}YYINDXB z{uw(WLV{1!Nql_ONae)NPC(^4K*w)V#H-fVMugL(fDZw!TbszrDu$w7)92SHlpp)& zN^@V?kr(_?HAppXKc8hNloerV&=SaD61#AgM=omf2N0`{UIJ)HcDRxS)l(L0ZH_R> z@Z^Xc=*)1%Edx^6U@Hh_Q>&&6q~Dr#M&R__3BwgQ?m0%P9)^`kz6l(D^H-*V@V637 z^fi>9$igIRm0u-f49?mDr3JSFaTU8HxmS>f)zj6d3$F`9fFuDU+3Lr87~RH~CB`jI z50{O<@){g;A>zQHR~EP_m@YO98yA^@`!W}JX6y9cSzG&AyI3R#$M$Q%UTRivfvp0z zS&q17E89$;A%m)la<^+;r5Jwk9{#G&s&O{^tp3|=NyxE8X6G=XOmhT`G1@qq5ITJaa!^cE z;efoP-RAQAipchg@r)xe!M*8;J${%Xs0H0mB@*Iwb>%%`A*hN60m`PHO@|Diae%m%O+_JhDII>(g_herdT0;85}N@DzNRhY^`Qnq}pUDEq_Gfg9+igF7m7(N{A zO$^`<4f!pgCQ=DNuyrJs8ppl@xg&>C=8$-si;d>L`E#OY`i0d1aDZAg`74K}-v3NJ zfSyp!>t8(G@{-1FpkUde>g+-3Pr!!<>IFo3ph*_CkgSr2@dC(Otk>@|J=fikE&Y{F zb(>ptR{E+>zUr4Ga4)DWknoDCI;MmrgIk?i#$)@Jqf%#jf!)iA{rBJSg8;XD1F9gD zl#Ebfcezv1CEVbKfca1z7~V-l^wd|J!dnJ#%7@5%Ys8f?M|XR9Ra5W(DrmASQ~&%_ zLl^)YA=CDsfgJ{ggMrK#;3%-*UzcIm^=vZJi=H-ahK~f)roMCDXlL1f6~Z7bF;FnX zu%4ZOdqcQUkM8?@&?K7z`hzg&CQn9tyn)PWEzg=h2LUOEUOT<)%2pqp(EfyICSZ+V z4&RnjS>3u{>?RqxLl6K7a076uyKV?ea!3nO#0>zzQZz#D)s|DRW_qTI0Xr=jcI!J^ zB|bKm8#LA7*KJcO=H!vmyiLdfPEWCdTmkzo@BlyqSZ*hiiKf|iLl-N%SCXEC_bGsF zsRMd7EG&3)b^lH`DK?Zg^>9p#1h7TU_^?%jr=p6*Jaro3rmdpdb*CqC;mj3)l^vE` zm)v*y7J?=ZV*8G2oWiG8*#v%;RVNldfio1M_dVX!>C2#YiyXX&FFNMai7wEIr4yE? zJOd?Ve}4exH9)8Q=kU+tjOzdY$^|?i*?50IQA<))YDH2W z16Q`cR&|WJkd%%WjbEY5BoXHR0Y6DTd*YOjjZ$9TqEHJIryWW3gu*x%Se?1qdh`SC z%s9(NjOTG!2&=|@uSyA_CipH2`^3b0&D`4jy9uaSV|)(nvUgG-u^8~o$yF=?Y0KGwSf z1t(CnUb-HZ#u1QdvNJf@GRY$$z#ds2+SI;oPi;e2;Kz8E07Up=9pG0Pd`Vo0sTOJh z$+4);#wOq$Mca&)&VEAWhc=9p)Zz_HRLR=sOO7)qt`5Uw;7-2O@!V(Q2Rqyg4OUpWjATQh8HkE0c z;~azb6}$D@f0J*tQ*25gz!JV^WKTYX3@)poA>V4h8w(r4K|!r*%rN5kYY z;8IOxjg}#+ET{vlQd3^osI6!q=?!lbl9cy3$hXO76@^hU*C}gWMVZNj@oTP1#;oFU zKs^^Tl-*&$5$5T=a9X(rgH1;~St8wn+}I=83~wEqRIP54Ih2S3_5Q?^ zz6uG4&DOuq!cuimr>`SuG&M5`XotPKEvCFi zGYU>9DZ<~zIrrIe3Xv(~3xvmg^3dojxXNcH;Wz|DTOwMRr{)khXev#bcf5;@(ZAz7 z25n+SvHU=!gdh{-n82aU$9Ak?$<*?K{>->%FUg2>HI3`%X0@Um2y}N_J+H6t-Xzh$ zsZ>BNUS27!CwyuhKQ!!X9vp>dl5bC@ScgG(FoRvD9m6O5RnGxTx!I6HDZ%e`ME(Ts zD1!>;g+*ZFfevwxN%c6V?yiv_KDQHn&QOkCctBhtH00M# zXB>s+?I>8uu+#e9eAP|M%fqx^138O`{>5((EckCgrQzvE5Tou?DXeamfI-+{&H-%$ zQz8I>4L$frFx1$20jFRlqvD@3$mv&3bf~R!e;z z8A&{Yz3$BVoi5fA2z&h_Xpr%X?u(O# zkg;C^3SquS?u_2?tuOs0ivJtPD+y%_{DrVvEmNeX+)2p`AA!Q1g{zDXgFAGQF1+0@ zGkl~=};eB&FP_s?lS?`(T4b8 zY6@FSJzFjqF~nis`qX?oTp=7aETL9DDNAhl)N7M;cr3Y~kZqAM5%ldeSd4Na<3#mK z5$D`|@v{=4ST9`L!)l-%TT5{!iS%}8xBLTh9sU?uVWU}vH`Yi5LWeeE3&q503<&}F za?nq3qyu}25<9|IKi=t9w%S$?icbkPnD>0I0+taiBJ{0mdUY+E?&#up9jeA>YF-rV zDa7Sn!>wCN<%n!6)k<)hGtkooidhM2Wli4VI_*^->qzDZdjrQO18>&fMD7a9XrIw3 z&%ayi@TdS)^$6!5d`x6$Kyv|b;FfW4AS9G}=%u<4x`H6RK-cuQ7WQYyI(;C-?@B;+ z7{t*GIW%NB(-%RlVA8;7h3?o!Pz8=kKc;&YY#L}I`*AsC)Q$0gx&QaeOd#2UK-|eb zYW3<=(RAa7ozv+-zm@G>(QJ|RpA*K-ImXFjWFWs8&U05qV{&KA`x7(vyg$B&5{-zW zh*F6O*NYXA>+6Pyc52US%Y()DVB^t(GYAtk@_<%4TEDd)lg$Y`3#n%5mMtadbuQ}ov6^%7PO2M51tegEAbk#*bCFF}z||}0CNB>Q_fgE-YNgDZN z%i$64G6{Sp=>>ogD_!4grR5VG&R$qSMpJ?FHvjrrv4c1K(iiy(kgbG7{(3%a+oQl! zzJvxd16-r0xZq{~V-_&Ml!#`!PqwiC)7b!`CX^xa4|bpf_W+LZD8kYdN*Ae&y=_s$ zDbSZUoh+vd<9wEx?lMxOB~~B@@PVEG8hv?m-^~H5_9AaMBiE0S(`tA^``Yli#2mE~ z6|Ir7Z+P^2Y<;C}YT@M&=8#}BvnqZ8D}6)6F1jIov&QOG&E+C07366m|fx8eNy^)G?zq3 z-Are{Cw*d6p?4XLAe>d!;efg~zydEN%jOh3#Q|lXJ`LlRJ>omuW#2c_uyBwQ44}fZ z2LR|W6a?NsK6tD%;fa}aE6LFiKv&PALgG_tpf5XbH28dNatI^$X2M&=TOR6*J6x1f zA(k>$ad*#UdO;M$)UVZYsJxtCO}m=&Ya7>wn>+{g5MaEVJ-;JQu{w;EmGZfb)FFJg z4aug7r5)&}h8=Tz=m1m6$b?g9P+&2U>%7Ye{Ptj%i3c(H#aT{GDaB(}90QBi|=AD$wJ3MIO6kG@{n=6z4IhFaE&jk!uUgp0zA^6k` zWaB`JU|MFgvN!PXHAHPmO;wqYqU4ck!DDRuI_b5tr?P_M*Ls!Rf5v&B0!Pn=YqrBq zZp(F_62*f`fk~1eH*U_X#*)&XKwbCRsrG!HqmM@7fP^u4N|k!eS-fkH4no(M!)Mr~ zib{SemKCM;f&k4v0v9b~Y9EN9%7qb3_MMRrb*C)9c)r-}eh?u6pdSo|Wn|=J6cQ1O z08pZMH~{`N$aT>VBUKawIBCB#i8^;WN$0M^hZYHC_~d1k-S+{eL?q~1dq{dtgoSS;tl1002DTKdTF@6iv63-G);$XCW#0 zxO`2u_s98-5#q7;!)-i5G{r<@V4;FDjoDH*hpca)s zZ|z=C-J?NtuxBf8oiYFh;rK;`5yZyT>F{=tkA~C zFA2Y;eKWVEJrtdDNuC7?=Hrd+*ml6DY8v4cPPwzC1}enpXj5gNZHiE& z`E&08_d0Fw+4n6HA?VT;T++&SIsMudO4tc+0=$mYhhK=Kz^)4?_h)16u7(A=Wh=rG z_1_)03XQL+H@HZbnpRkF$5wxyD&jlD_r#CQ%g;ywCle@9u+2O|=hV&&Oo=MTT`BN^ zJ?#859v!bznsN8R`Pec}o}=B3ht%{0t~&!zXaj?x=~Btf?Ab8ne5R!yTRl*)?lgg7 zzq^$VrZT>teHFhXvHGkR@F%@KkhIT-8uwA-%HhNq+ZrBQo_Ev1{3hxGTm%!h9$637 z?5`x`zs`h_Xm%)Q7(A~3jXqybPz}>9{Q@QyW~a3tJo2B01tzj`Dmt&_RyeLw6(WC} zuN~~om7}mE2Qxv)uBTQk+dHp!u4_EM!_9|Hfg0m8&uTnSz0t#l=!}gNLAnAh`hn@7 zGc@yYx%r--0WVsPL&J%Fn#ImZQP!Tcux`saVfdT-Ffz-*85D?5Y{4;@CTdhxBXDJy zL~!@40x4o>VRp67$J^RfN61PlMJgyxOtgE*pCT57Bh$mxpC6MVhAw)K^jW48JrWhE zBCXRFtjU~rNXG246~RPy;L6I)6m?-5eynxf1Ku9&pI`$hv=64uTZg$b#$%eCb(PV- z{WX$Sh``GQR6z!uBxrl`0=yfIZ4q+|3A3j#htGsh-AqHdAfSeM?5Ao*K;h_VirBCK zE6KvSwtjXbGmuqu*z^zjEBE)7Dm~>zJtaD9)7B z$`0lqwY=!iByblLzyN;Nz7bR#^g zaG);X!oT7znL+X7u>0Z&HYb0k{I|@*^Up-*S(!H`w(RzKRjfr46zpRqtEe?4qM%;h z=reI*CKdTTTBwL=e_VrFzKlRarQh>skU%}31c=D_(|i?wL(5=Cx2ravCnd!aq=zqtYK0Ge6%n!cjGpEfX6p!b6odWc7>y3|RO zaee*eD1VyjJ?;m;A=GQ=XZy##WxK>jV7Crlc$8^U3tF^j>pR%GR8C~sbPK+NkHXnf zFc+UG;4Rw-RGLLfoETMAUHM=6QQKi&=4vOG$*15lqwc@Hvdv(hw%ot>HVbQ1P=DVo zeybFX!+8enB7i3|KmaM=DgXc-2Dm~7g?~U7u+ku08T96YSK;2BsTd- zA$|-Fsz!gx<~m$}u5cz;N>M9_@|CFPNVLZW5het5eD)bZC41h*gQB&Ha1`V&T2Cjh zKIZNl<+di>lo~=MHPHUL&^=Hk#7Gw91|Ss!b>+yKuf;;XS2pq1 zAWRnJFWg$`!d@M;;5|@Q|1Ax*S9KH-Q%(5mG))t^ z?DW-qB8UVW?Rd0ob-BOZ)aKs%Xt&-$cQ&_KoQJ+8)g}`kXaJ9^h7rwaHJQ1>l34AR z-@bDn#h*0K0h`+)-awL$!NHpcn+lF%Mq+BhxWYoNKs7!G$nsH$a_t2Gm@mM7_F%5c z19VviZuROOebH}FjqJl1oDl*J4%%Br4w#RsL>7e zrmP_7f8~vD%Zc%?B>Y+f}Rt?CfjgPWbjIOnVzRJzQ8(DCl!@)gV5i4G9oLcJ}MwId9EgJfW*zTXSxV>V%!gFR$NyWI+G z*FHBdw%Y)^2?t+4YorOK7+*#2;{NRMqq&(~3RdUjCXNA`dps&~v7WR$F=s(}?a?4> z|7l=&D+Ai`9bF%RNJc?h6YJd%cH-?=8YhrZJE+Cj%jvhelsla)gZ>QkQpS~Haz6y| z6ic`5HIv>#FRh$md42M%U0aOKwe!_rFyT|P>$hYZEsa!hyI_qDJN{m(I7-WumeI%F z0Kyfl7moeU9ZqBusmW`rlOD?kAvL(}V}X9_I!&PDxpHXo$E%-T>bL}tgd7(fOvs_) z#nbb{a}DNs8~?3#;?2r*Ma)U(pEox!Z*O3mqLK%KjYq1&JAVvs$8!C6!PJ^@q=k19- zC#M5q?wR-};MFAE_-jLR%m52Q&<&>@#^D`iL6fmDSWNJu$n5aUP?)})_X)%+Zl-7M`*zBMFw9dJa+PKmtTK}YQ_0!?>EbgayrUJ73~j)&|4 zNSS<;p&Ou-1i)BRyz$mbi1jY?SeM@nlF9XfG&!uhY3)Zyq6l8_~ zc!~kN?dEjXQyB0NME=_5|1iSWJ1)-GAqFY$!)E^%a`&ERl{ZEK7Fht5GrQ%vCl{vyo2_tfUA~R=58S&yKTo!ynoxdA@28Nl355jWv+_&Y$bXDh5A>m`ja;sdCiu} zwh;lX_A;-bP&Aod+F+YcN6t^~HFUsWHmUvuZS)fpja)#lXmLadEFtYcSNbZxJ&e3x zEG(XioIrQA&x-lPGT0-cF=ie-07wD=UEuG+`_G%#n+gA_0Q_%gj0c!~0)m^ODkE&I zKq}53L$o&=@X*@Vjrq+5mA(j{9?*Gk0)N7@zv@d8;ujUnK%kWByTkxJ4nV>n`vVe2 zP;pRTJpmafDnd42r|0&PrH(6i4m96b)pXP8YEEx$xS%^18?5!+nfFnwNgJ;OFKLr* z4PS@a3{FW-f*1r$2nzh{S=9Gff6<#Etyx{VAVr+QD|{HcA%9hnYs^mnGkcnr%e&&H znA}Z0d3)^I`EVp< ztd|?-=ZiV&y1yTtH&E*aWu%U`(&uGru;OIp#H4NGT5(h^Wu*zYX2;+IO9K7EJ!K z)Fu|09~3dyL5vh7u~My*5cx|?{5=y7A}D{eT>dw11`H5Qzx*ef-$;00E(zPSlB@S7 z3O*X>_LY}N-#zI zqg6*NAxX6Howb>{lePN%Zl^AJ4$mdQA`_#){$6sa-J!SdeZ2D^jOj%8Wv@wWFB)YnVrUDhaQ zX2Ni5u2_mzPq(dCC6*ip-F?W8D$NXCq-xa$d5{=n-(9ZcDIOKX+fUeQ=F8BwCEQVm zCJoRj!MK9P%KYi&C4p|Y9vzx8w@N=udaN%e?rpQ4(s6`d2)aHjM?KZbX9OQSdbxcg z{pW=H#Di8D?Q}hIcGc8mFaDGm`urfya=kjcc{>_u{CWNm0JP;f4DBwXaNUfZ3FJtf zA{((Fv5KYqMzD57HSv0uig{C=o~eCOui*)42rhP zH@$tlcPEBpnN%3sRH%1za};S_Hyy&xU}VvGjeWHvVa@!0`T0u#SNCGqW?!~M2Zj!+ zwQ+Mn9mEtL*J4%Y;yTX{aSCZ1Ez$g8usJ!2&b>VFEiw_PMtFqPNo6TBuPr`j)6OgD zPY|$TJ4vzM#a@;wrzot_#1)n~>n^()k_`HO$?VAFOmhmNOks>rS3~4<_S^Ox!S-A~4j8aPK>73X#Q6ZzG z8kiBpVa-3Q{?z$a2xsvwsW=S}Wp1Du0YpKu(BlX9ilSF8S<>Uy3Q>(~zX}_4%&6c+ zIR0Mc=rRgG3j9|3E^WK&6ad=frb7{*0b+s)t5PJO@BWKHrlL8B|4KCPu=qAI{)i_R zGN=1CQOBByGZ(w?;g_D9wTP(~h^Y!qRo{zh@g@r*J$j}OK@92Sl0nK8@>A^W+JpB8B#}K8bBzXKYS4 z!ik!LCFAB;xjA^Ob!RH3SFSu!zL>SLKcj7-*@{n>&d>?jFpPi36j$mB@i-u!iTr57 zz68j-XEeoT-ezf+YWZ`<@wv-le{h-O2^k2GVFh zwMEDX#F&NA@t>&o1{H@e(~V(DU1K2ZKScqO$?c)QX&(Vd{D%a&vXvhdM?6;X$*%0S z*!^G$0KrG%%$|XgWMW6;k;}^q&^iNCz@hIx@Ld8xum8vG!~^Cqf$*l1V@^ZZEa7Hj zoEj-iDq7Iv`1dSmvA=`jY42Ymkv@xj3Ui~2`i6OKLiZp)C7UI;TK&#_uP=wHqqSz@ zYmh&UBeW%6 zdt!|Gv(d6Z$O-ncPGVZn@8}}H$Te0f8I*K2c1B0xx0axTj*4J)`VtzZD$-L%7&D~I zbm@~Z*tA6_`%9PLB9E4CApOreGbfchy+g<2=AttVA2#CL7%|j@^1L}?)sM(P8H7N= zf^EKKiAz#x$&w4!)zXxh7z$j=FmBBGee>N@#{Mb$lb>o{G+Pu15UtL`fOX1d;!s9q^$uo>B>uhlyK|?skGv%5lH&=k!5hJAOOzVSN zg(w_%DlL!{uTv4RNDo)SBnF4bB4WPn`pP$|#})cD^=PZ_^!YQ}a^;!qNsSR>U)p4B zg2i$3pUCP5>9( znmZZ7ffJf!!&KA8nO;$J8Q7l5|2XkymuC+e`2a*K>ZK?)6%g~|qg~QRTa|ApS)2ZK zbSxE*!bA*;z&eq0Kl6r}1kqVt1rP;-ZM9P4pEhB#acbUs;YJ)o`c5R(KMt|w-Oh#i zgjO0c5)ZjszG!RwW;d$6%-n&#o(MP6s^pktwmAM21JvX3i>AWX?MK+-g1U0x!jAy} zZGorHA7g}D9br|pK3HSb_kd>tk(4zZ9?mHJ&ylRUqbjbi&(~JY%~FzcbTswT0-SlDpicrZSWqB6sxf{=KU?ub)&(ALdE&J zvL}_91+~ti2j=II5mAMnp8(3NJ=@sV$D1I4294|eYc|FtDy@ncB}IXEVuOym+tTB~ z;#bdG`qh!#i`3usYra?`Q>hD=TprBb3_005cA05qyu_c_Xt|`f6Xdl?Rm7nu z-ODPrex7uwqN_ra23I4Y3C6%=YXBSn(t6xI=!8`1o~@%fHMl*J3LJF;PZ4qejzoY| zw}YjS05Fl8EhY#+THDHx?5_mP-EoHdOuAhe5Loz0((a{7hJbAhC2fnElqOLmHi+I- z4wjxXMO)D^&R%=u8(UYWk2MRlY5kAJABiibd-Vk`%gc{7A-*)uj)Ck$+!(k!#t_ox zQQ7Xh>J43PZyNcPP~x12S}1wSiTNfcDiogp%)>FP1#mU+PQ5JukrU{73-tIGQqlo) zoIpq!P*{NM4SD>DhsY?aS0pu#_d>)FrjgjF2sF@KsE{K0x2Pt{(w}+2zm%<5l(iGnN$6uL_-lUmYhKgF+Ne}1!`yq5j zw}14J?dZDDgq%e~x)!9&mRQ>u=yi$jm~y#tPc{1TD5A#G#E?SpufD4@g#fFe6~Bad zUbJU#=-u2(n>>m)N8X-~Fb^AvJ@=VzLup z=$O(1^bQY2fY=X?|Af5O-|JzSPAuu|RGl?YXBM)flt4~fw0MHIj zbOMGbliak|ItN1174<{!P}fTGYx79L%l+D!l(CX0Su#}x80q-fECZ%Gycb>H_CPWs zOz)!%4U0$+bUmYtE2E)>`F0-!sVMv`Bz+WW(gzl3sR4v>0_Y+4UIq&2ANM`)DeLdz zH~xu#y|D#){4G4-sAz`AKR_@e+PC_OD(^T3i1$PTfU~4r81t7B^%tTu1aniZWU$>i zo<_2Gk?H*US$mP)+d@5`YzUf3JR|3`o#LmZ5U+OittaPO>@U5jf*LbFbROKKBQS8y zpDp{Ieo0=m1e7Wa;_ATJoUgWt$&&}pZbddRW8ps4YzOx8jo%V!E3GVc*Ph*EewbK% zt%B|KuyKl-j3^*WoM?na^Y#97XVN@8amlrby0dL~;E-ZdWB|SuAe7VXlPM;+6M}7* zbfpVv?|Fhj`Jh}ny=rs{ng`(JaqPe6t+Wb`L=*%Ml@LgipDHj{q-)}XOUjNq=o4-eIupwE@h+0pPs}NHsEL%7d-4%~j&PA1*z-t$rEOfX zw#Q45XW6ENiGxS`hhxZ%Ww>9m#>}SZH<{JdM`4d=$3FBiX7zc-T2hzJNMxxID)-kN z5!)#7onNk{;1Hm(X|xCXKbj)beS!_awSkJVk@!%Uw({!>bOXwsGuH>~BGn z@p|Q@YMMrn`lZbqx@2A?Nz-a>CC>{0KRDRhA4t0<*jD9)lF;CF*&lT}9NmI^SdBMN z(q4>!(lEx=jBSE!GzQ?BCwoDO0K+grktbsf?xN2UEN3Lh@JrKxi<7CV9T3HvZmb6# zU&W(EryW)4nUwF%sFL~um&vhOL)8D=+48pyx&vo13Pgbi?Dpi$CFb;_>In^tRI)`? z>HK?^jmgF#gYLH~Y$JSTzCY_^0;X*@n;t8m-7f+_x_tEiAlw}H{@89FByeg403ZMm zz38n#^Z&_70(?btl0Z0w?cc@29<1=K?LgYYC=FS`>n%PWW~{-`5DU?2C3RHDMp1W_Ki}&JWCn3Rt*c|0&}h1IW)=50rzLi zQ{%cmwJE=_$eFX=!|2&^Qtzfkn9Qil#3NyW_*6Ye7p~WUP|G0&{xcnl1Q!YKar$9- z#cFI-NwiQW-%XkEpFbm!ry7uumdvmre8X)CO~ayr7C)C?yuqaz^JkRp`^#{%4Px~p z()8vhX!5tBxVP%_>Up6lq(mtp_ghd(6XmAMs~|MPi??*~@~o(HL>76Dhd<7kL?jT3h!=RC9N!%;!t`c z9#m!WqixuA!a&p;&0IDWI3T{iv27GX&zdTZMT1cg3Jb`{E~bz#V#MroLt3Ivok`uf z{^U$A?`mCH@oYF9sG&>7auWp>fI0ZWsZnF^ z;9j}o1hZu$_f@%y{WH%D`_PZ!qvAEZB9O5j-iM)(HJx5sx?OYP4h6hYOH#0mA=Py= zbuL!Qv6IY9U=#>{C|;y35i=>i(Rs?w@t0kObmN5HRA|1xqu_!kp4|mVy_fzL-pPDyYRwrjK#2T-P zIA^Ye1J)XPS|;as!z$*|MV(q#)6_laCXwKN&mGwm8=~x4|F#!e9*5?NL?DTwB@vU! zH*WOZc6Vpr<(h87v+mS5Y#u8v&q31HhoW|G7ekIa(s)XV)2p@u2!BF`0f66(+z_Wc z`EDG#-mG6dp^yPf5DOIV;!Y71f@ zVN+*@%pvNyqmU?rxl?WePOJrnk8WiDJ76l1`u);t@1Q|`(*|H?t;P6oC0 z_F`kr%N6+o{a~M9Ax+08bmNYXR|_huBxWaAXK%5USp5!pCVr47t_ZTGLFRPkP+(n5 zsTi3F=8TGBidWUTz%u9ljPA*Ak_;&0%uL_58!SeJwb7VssGU@M{l>|3*|02HST&s7 zV(;rasqtnwOg%*2T08~}&6-Nw&vTXRMI2-=^T?{2n9ybBt;e)njpY>v^OBQ0NKQ

UrDIHb!9o45FR{%M#Y9-hR#`REkxQ2eiF34~B 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 ABme*a literal 0 HcmV?d00001 From eda5e5f4a685cfee40ceb9aa5aa9eab27e315062 Mon Sep 17 00:00:00 2001 From: Eli Mallon Date: Sun, 18 Aug 2024 15:40:08 -0700 Subject: [PATCH 26/42] remove build restrictions --- pkg/c2pa/c2pa.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkg/c2pa/c2pa.go b/pkg/c2pa/c2pa.go index 96dac7f8..89a39e39 100644 --- a/pkg/c2pa/c2pa.go +++ b/pkg/c2pa/c2pa.go @@ -1,5 +1,3 @@ -//go:build (darwin && cgo) || (dragonfly && cgo) || (freebsd && cgo) || (linux && cgo) || (netbsd && cgo) || (openbsd && cgo) - package c2pa import ( From bdbae2a0497b08c830af420b881d359e1f5c4b2c Mon Sep 17 00:00:00 2001 From: Eli Mallon Date: Tue, 20 Aug 2024 16:52:04 -0700 Subject: [PATCH 27/42] pkg/c2pa: refactor to allow external signers --- pkg/c2pa/algorithms.go | 76 +++++++++++++++ pkg/c2pa/c2pa.go | 183 +------------------------------------ pkg/c2pa/c2pa_test.go | 7 +- pkg/c2pa/callbacksigner.go | 30 ++++++ pkg/c2pa/demo/demo.go | 12 ++- pkg/c2pa/staticsigner.go | 132 ++++++++++++++++++++++++++ 6 files changed, 258 insertions(+), 182 deletions(-) create mode 100644 pkg/c2pa/algorithms.go create mode 100644 pkg/c2pa/callbacksigner.go create mode 100644 pkg/c2pa/staticsigner.go diff --git a/pkg/c2pa/algorithms.go b/pkg/c2pa/algorithms.go new file mode 100644 index 00000000..fe6b8442 --- /dev/null +++ b/pkg/c2pa/algorithms.go @@ -0,0 +1,76 @@ +package c2pa + +import ( + "crypto" + "crypto/rsa" + "fmt" + + rustC2PA "git.aquareum.tv/aquareum-tv/c2pa-go/pkg/c2pa/generated/c2pa" +) + +type SigningAlgorithmName string + +const ( + ED25519 SigningAlgorithmName = "ed25519" + ES256 SigningAlgorithmName = "es256" + ES256K SigningAlgorithmName = "es256k" + ES384 SigningAlgorithmName = "es384" + ES512 SigningAlgorithmName = "es512" + PS256 SigningAlgorithmName = "ps256" + PS384 SigningAlgorithmName = "ps384" + PS512 SigningAlgorithmName = "ps512" +) + +type SigningAlgorithm struct { + Name SigningAlgorithmName + RustC2PAType rustC2PA.SigningAlg + Hash crypto.Hash +} + +func GetSigningAlgorithm(algStr string) (*SigningAlgorithm, error) { + alg := SigningAlgorithmName(algStr) + switch alg { + case ED25519: + return &SigningAlgorithm{ED25519, rustC2PA.SigningAlgEd25519, crypto.Hash(0)}, nil + case ES256: + return &SigningAlgorithm{ES256, rustC2PA.SigningAlgEs256, crypto.SHA256}, nil + case ES256K: + return &SigningAlgorithm{ES256K, rustC2PA.SigningAlgEs256k, crypto.SHA256}, nil + case ES384: + return &SigningAlgorithm{ES384, rustC2PA.SigningAlgEs384, crypto.SHA384}, nil + case ES512: + return &SigningAlgorithm{ES512, rustC2PA.SigningAlgEs512, crypto.SHA512}, nil + case PS256: + return &SigningAlgorithm{PS256, rustC2PA.SigningAlgPs256, crypto.SHA256}, nil + case PS384: + return &SigningAlgorithm{PS384, rustC2PA.SigningAlgPs384, crypto.SHA384}, nil + case PS512: + return &SigningAlgorithm{PS512, rustC2PA.SigningAlgPs512, crypto.SHA512}, nil + default: + return nil, fmt.Errorf("algorithm not found: %s", alg) + } +} + +// get digest and crypto options for passing to the actual signer +func (alg *SigningAlgorithm) Digest(data []byte) ([]byte, crypto.SignerOpts, error) { + switch alg.Name { + case ED25519: + // ed25519 handles its own hashing + return data, alg.Hash, nil + case ES256, ES256K, ES384, ES512: + h := alg.Hash.New() + h.Write(data) + digest := h.Sum(nil) + return digest, alg.Hash, nil + case PS256, PS384, PS512: + h := alg.Hash.New() + h.Write(data) + digest := h.Sum(nil) + opts := &rsa.PSSOptions{ + Hash: alg.Hash, + SaltLength: rsa.PSSSaltLengthEqualsHash, + } + return digest, opts, nil + } + return nil, nil, fmt.Errorf("unknown algorithm: %s", alg.Name) +} diff --git a/pkg/c2pa/c2pa.go b/pkg/c2pa/c2pa.go index 89a39e39..c8265266 100644 --- a/pkg/c2pa/c2pa.go +++ b/pkg/c2pa/c2pa.go @@ -2,27 +2,16 @@ package c2pa import ( "crypto" - "crypto/ecdsa" - "crypto/ed25519" - "crypto/rand" - "crypto/rsa" - "crypto/x509" - "crypto/x509/pkix" - "encoding/asn1" "encoding/json" - "encoding/pem" - "errors" "fmt" "io" "mime" "os" "path/filepath" - "reflect" rustC2PA "git.aquareum.tv/aquareum-tv/c2pa-go/pkg/c2pa/generated/c2pa" "git.aquareum.tv/aquareum-tv/c2pa-go/pkg/c2pa/generated/manifestdefinition" "git.aquareum.tv/aquareum-tv/c2pa-go/pkg/c2pa/generated/manifeststore" - "github.com/decred/dcrd/dcrec/secp256k1" ) // #cgo LDFLAGS: -L../../target/release -lc2pa -lm @@ -90,9 +79,9 @@ type Builder interface { type BuilderParams struct { Cert []byte - Key []byte + Signer crypto.Signer TAURL string - Algorithm string + Algorithm *SigningAlgorithm } type C2PABuilder struct { @@ -117,37 +106,12 @@ func NewBuilder(manifest *ManifestDefinition, params *BuilderParams) (Builder, e return &C2PABuilder{builder: b, manifest: manifest, params: params}, nil } -var algMap = map[string]rustC2PA.SigningAlg{ - "es256": rustC2PA.SigningAlgEs256, - "es256k": rustC2PA.SigningAlgEs256k, - "es384": rustC2PA.SigningAlgEs384, - "es512": rustC2PA.SigningAlgEs512, - "ed25519": rustC2PA.SigningAlgEd25519, - "ps256": rustC2PA.SigningAlgPs256, - "ps384": rustC2PA.SigningAlgPs384, - "ps512": rustC2PA.SigningAlgPs512, -} - -var hashMap = map[string]crypto.Hash{ - "es256": crypto.SHA256, - "es256k": crypto.SHA256, - "es384": crypto.SHA384, - "es512": crypto.SHA512, - "ed25519": crypto.Hash(0), - "ps256": crypto.SHA256, - "ps384": crypto.SHA384, - "ps512": crypto.SHA512, -} - func (b *C2PABuilder) Sign(input io.ReadSeeker, output io.ReadWriteSeeker, mimeType string) error { mySigner := &C2PACallbackSigner{ - params: b.params, + signer: b.params.Signer, + algorithm: *b.params.Algorithm, } - alg, ok := algMap[b.params.Algorithm] - if !ok { - return fmt.Errorf("unknown algorithm: %s", b.params.Algorithm) - } - signer := rustC2PA.NewCallbackSigner(mySigner, alg, b.params.Cert, &b.params.TAURL) + signer := rustC2PA.NewCallbackSigner(mySigner, b.params.Algorithm.RustC2PAType, b.params.Cert, &b.params.TAURL) _, err := b.builder.Sign(mimeType, &C2PAStreamReader{input}, &C2PAStreamWriter{output}, signer) if err != nil { return err @@ -174,140 +138,3 @@ func (b *C2PABuilder) SignFile(infile, outfile string) error { defer output.Close() return b.Sign(input, output, mimeType) } - -type C2PACallbackSigner struct { - params *BuilderParams -} - -type pkcs8 struct { - Version int - Algo pkix.AlgorithmIdentifier - PrivateKey []byte -} - -type ecPrivateKey struct { - Version int - PrivateKey []byte - NamedCurveOID asn1.ObjectIdentifier `asn1:"optional,explicit,tag:0"` - PublicKey asn1.BitString `asn1:"optional,explicit,tag:1"` -} - -func (s *C2PACallbackSigner) Sign(data []byte) ([]byte, *rustC2PA.Error) { - bs, err := s._sign(data) - if err != nil { - return []byte{}, rustC2PA.NewErrorOther(err.Error()) - } - return bs, nil -} - -type C2PASignerOpts struct{} - -func (s *C2PACallbackSigner) _sign(data []byte) ([]byte, error) { - block, _ := pem.Decode(s.params.Key) - - if block == nil { - return []byte{}, fmt.Errorf("failed to parse PEM block containing the private key") - } - key, err := parsePrivateKey(block.Bytes) - if err != nil { - return []byte{}, fmt.Errorf("parsePrivateKey failed: %s", err.Error()) - } - - var opts crypto.SignerOpts - var digest []byte - - hash, ok := hashMap[s.params.Algorithm] - if !ok { - return []byte{}, fmt.Errorf("hash not found for %s", s.params.Algorithm) - } - - switch key.(type) { - case *ed25519.PrivateKey: - // ed25519 handles its own hashing - opts = hash - digest = data - case *rsa.PrivateKey: - h := hash.New() - h.Write(data) - digest = h.Sum(nil) - opts = &rsa.PSSOptions{ - Hash: hash, - SaltLength: rsa.PSSSaltLengthEqualsHash, - } - default: - h := hash.New() - h.Write(data) - digest = h.Sum(nil) - opts = hash - } - - bs, err := key.Sign(rand.Reader, digest, opts) - - if err != nil { - return []byte{}, fmt.Errorf("ecdsa.SignASN1 failed: %s", err.Error()) - } - return bs, nil -} - -func parsePrivateKey(der []byte) (crypto.Signer, error) { - if key, err := x509.ParsePKCS1PrivateKey(der); err == nil { - return key, nil - } - - if key, err := x509.ParseECPrivateKey(der); err == nil { - return key, nil - } - - key, err := x509.ParsePKCS8PrivateKey(der) - if err == nil { - switch key := key.(type) { - case *rsa.PrivateKey: - return key, nil - case *ecdsa.PrivateKey: - return key, nil - case ed25519.PrivateKey: - return &key, nil - default: - return nil, errors.New("crypto/tls: found unknown private key type in PKCS#8 wrapping") - } - } - - // Last resort... handle some key types Go doesn't know about. - return parsePKCS8PrivateKey(der) -} - -var OID_RSA_PSS asn1.ObjectIdentifier = []int{1, 2, 840, 113549, 1, 1, 10} -var OID_EC asn1.ObjectIdentifier = []int{1, 2, 840, 10045, 2, 1} -var OID_SECP256K1 asn1.ObjectIdentifier = []int{1, 3, 132, 0, 10} - -func parsePKCS8PrivateKey(der []byte) (crypto.Signer, error) { - var privKey pkcs8 - _, err := asn1.Unmarshal(der, &privKey) - if err != nil { - return nil, fmt.Errorf("asn1.Unmarshal failed: %s", err.Error()) - } - if reflect.DeepEqual(privKey.Algo.Algorithm, OID_RSA_PSS) { - return x509.ParsePKCS1PrivateKey(privKey.PrivateKey) - } else if reflect.DeepEqual(privKey.Algo.Algorithm, OID_EC) { - return parseES256KPrivateKey(privKey) - } else { - return nil, fmt.Errorf("unknown pkcs8 OID: %s", privKey.Algo.Algorithm) - } -} - -func parseES256KPrivateKey(privKey pkcs8) (crypto.Signer, error) { - var namedCurveOID asn1.ObjectIdentifier - if _, err := asn1.Unmarshal(privKey.Algo.Parameters.FullBytes, &namedCurveOID); err != nil { - return nil, fmt.Errorf("asn1.Unmarshal for oid failed: %w", err) - } - if !reflect.DeepEqual(namedCurveOID, OID_SECP256K1) { - return nil, fmt.Errorf("unknown named curve OID: %s", namedCurveOID.String()) - } - var curveKey ecPrivateKey - _, err := asn1.Unmarshal(privKey.PrivateKey, &curveKey) - if err != nil { - return nil, fmt.Errorf("asn1.Unmarshal for private key failed: %w", err) - } - key, _ := secp256k1.PrivKeyFromBytes(curveKey.PrivateKey) - return key.ToECDSA(), nil -} diff --git a/pkg/c2pa/c2pa_test.go b/pkg/c2pa/c2pa_test.go index 2ee9da76..4418b1d5 100644 --- a/pkg/c2pa/c2pa_test.go +++ b/pkg/c2pa/c2pa_test.go @@ -71,10 +71,13 @@ func TestSigning(t *testing.T) { var manifest ManifestDefinition err = json.Unmarshal(manifestBs, &manifest) require.NoError(t, err) + alg, err := GetSigningAlgorithm(test.name) + require.NoError(t, err) + signer := MakeStaticSigner(certBytes, keyBytes, alg) b, err := NewBuilder(&manifest, &BuilderParams{ Cert: certBytes, - Key: keyBytes, - Algorithm: test.name, + Signer: signer, + Algorithm: alg, TAURL: "http://timestamp.digicert.com", }) require.NoError(t, err) diff --git a/pkg/c2pa/callbacksigner.go b/pkg/c2pa/callbacksigner.go new file mode 100644 index 00000000..ce545816 --- /dev/null +++ b/pkg/c2pa/callbacksigner.go @@ -0,0 +1,30 @@ +package c2pa + +import ( + "crypto" + "crypto/rand" + + rustC2PA "git.aquareum.tv/aquareum-tv/c2pa-go/pkg/c2pa/generated/c2pa" +) + +type C2PACallbackSigner struct { + signer crypto.Signer + algorithm SigningAlgorithm +} + +func (s *C2PACallbackSigner) Sign(data []byte) ([]byte, *rustC2PA.Error) { + bs, err := s._sign(data) + if err != nil { + return nil, rustC2PA.NewErrorSignature(err.Error()) + } + return bs, nil +} + +func (s *C2PACallbackSigner) _sign(data []byte) ([]byte, error) { + digest, opts, err := s.algorithm.Digest(data) + if err != nil { + return nil, err + } + + return s.signer.Sign(rand.Reader, digest, opts) +} diff --git a/pkg/c2pa/demo/demo.go b/pkg/c2pa/demo/demo.go index 7efde428..104a4679 100644 --- a/pkg/c2pa/demo/demo.go +++ b/pkg/c2pa/demo/demo.go @@ -58,10 +58,18 @@ func Start() error { if err != nil { return err } + alg, err := c2pa.GetSigningAlgorithm(*alg) + if err != nil { + return err + } + signer := c2pa.MakeStaticSigner(certBytes, keyBytes, alg) + if err != nil { + return err + } b, err := c2pa.NewBuilder(&manifest, &c2pa.BuilderParams{ Cert: certBytes, - Key: keyBytes, - Algorithm: *alg, + Signer: signer, + Algorithm: alg, TAURL: "http://timestamp.digicert.com", }) if err != nil { diff --git a/pkg/c2pa/staticsigner.go b/pkg/c2pa/staticsigner.go new file mode 100644 index 00000000..7694e5ed --- /dev/null +++ b/pkg/c2pa/staticsigner.go @@ -0,0 +1,132 @@ +package c2pa + +import ( + "crypto" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" + "encoding/pem" + "errors" + "fmt" + "io" + "reflect" + + "github.com/decred/dcrd/dcrec/secp256k1" +) + +// helper signer for when you have cert and key as PEM-encoded bytes +type StaticSigner struct { + Cert []byte + Key []byte + Algorithm *SigningAlgorithm +} + +func MakeStaticSigner(cert, key []byte, alg *SigningAlgorithm) crypto.Signer { + return &StaticSigner{ + Cert: cert, + Key: key, + Algorithm: alg, + } +} + +func (s *StaticSigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) { + block, _ := pem.Decode(s.Key) + + if block == nil { + return []byte{}, fmt.Errorf("failed to parse PEM block containing the private key") + } + key, err := parsePrivateKey(block.Bytes) + if err != nil { + return []byte{}, fmt.Errorf("parsePrivateKey failed: %s", err.Error()) + } + + bs, err := key.Sign(rand, digest, opts) + + if err != nil { + return []byte{}, fmt.Errorf("ecdsa.SignASN1 failed: %s", err.Error()) + } + return bs, nil +} + +func (s *StaticSigner) Public() crypto.PublicKey { + panic("not implemented") +} + +func parsePrivateKey(der []byte) (crypto.Signer, error) { + if key, err := x509.ParsePKCS1PrivateKey(der); err == nil { + return key, nil + } + + if key, err := x509.ParseECPrivateKey(der); err == nil { + return key, nil + } + + key, err := x509.ParsePKCS8PrivateKey(der) + if err == nil { + switch key := key.(type) { + case *rsa.PrivateKey: + return key, nil + case *ecdsa.PrivateKey: + return key, nil + case ed25519.PrivateKey: + return &key, nil + default: + return nil, errors.New("crypto/tls: found unknown private key type in PKCS#8 wrapping") + } + } + + // Last resort... handle some key types Go doesn't know about. + return parsePKCS8PrivateKey(der) +} + +var OID_RSA_PSS asn1.ObjectIdentifier = []int{1, 2, 840, 113549, 1, 1, 10} +var OID_EC asn1.ObjectIdentifier = []int{1, 2, 840, 10045, 2, 1} +var OID_SECP256K1 asn1.ObjectIdentifier = []int{1, 3, 132, 0, 10} + +func parsePKCS8PrivateKey(der []byte) (crypto.Signer, error) { + var privKey pkcs8 + _, err := asn1.Unmarshal(der, &privKey) + if err != nil { + return nil, fmt.Errorf("asn1.Unmarshal failed: %s", err.Error()) + } + if reflect.DeepEqual(privKey.Algo.Algorithm, OID_RSA_PSS) { + return x509.ParsePKCS1PrivateKey(privKey.PrivateKey) + } else if reflect.DeepEqual(privKey.Algo.Algorithm, OID_EC) { + return parseES256KPrivateKey(privKey) + } else { + return nil, fmt.Errorf("unknown pkcs8 OID: %s", privKey.Algo.Algorithm) + } +} + +func parseES256KPrivateKey(privKey pkcs8) (crypto.Signer, error) { + var namedCurveOID asn1.ObjectIdentifier + if _, err := asn1.Unmarshal(privKey.Algo.Parameters.FullBytes, &namedCurveOID); err != nil { + return nil, fmt.Errorf("asn1.Unmarshal for oid failed: %w", err) + } + if !reflect.DeepEqual(namedCurveOID, OID_SECP256K1) { + return nil, fmt.Errorf("unknown named curve OID: %s", namedCurveOID.String()) + } + var curveKey ecPrivateKey + _, err := asn1.Unmarshal(privKey.PrivateKey, &curveKey) + if err != nil { + return nil, fmt.Errorf("asn1.Unmarshal for private key failed: %w", err) + } + key, _ := secp256k1.PrivKeyFromBytes(curveKey.PrivateKey) + return key.ToECDSA(), nil +} + +type pkcs8 struct { + Version int + Algo pkix.AlgorithmIdentifier + PrivateKey []byte +} + +type ecPrivateKey struct { + Version int + PrivateKey []byte + NamedCurveOID asn1.ObjectIdentifier `asn1:"optional,explicit,tag:0"` + PublicKey asn1.BitString `asn1:"optional,explicit,tag:1"` +} From 2f6cb265cc810b13aa248721bd10611d96b02105 Mon Sep 17 00:00:00 2001 From: Eli Mallon Date: Tue, 20 Aug 2024 16:54:48 -0700 Subject: [PATCH 28/42] pkg/c2pa: remove unncessary alg in signer --- pkg/c2pa/c2pa_test.go | 2 +- pkg/c2pa/demo/demo.go | 2 +- pkg/c2pa/staticsigner.go | 12 +++++------- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/pkg/c2pa/c2pa_test.go b/pkg/c2pa/c2pa_test.go index 4418b1d5..2b7feeee 100644 --- a/pkg/c2pa/c2pa_test.go +++ b/pkg/c2pa/c2pa_test.go @@ -73,7 +73,7 @@ func TestSigning(t *testing.T) { require.NoError(t, err) alg, err := GetSigningAlgorithm(test.name) require.NoError(t, err) - signer := MakeStaticSigner(certBytes, keyBytes, alg) + signer := MakeStaticSigner(certBytes, keyBytes) b, err := NewBuilder(&manifest, &BuilderParams{ Cert: certBytes, Signer: signer, diff --git a/pkg/c2pa/demo/demo.go b/pkg/c2pa/demo/demo.go index 104a4679..f016f484 100644 --- a/pkg/c2pa/demo/demo.go +++ b/pkg/c2pa/demo/demo.go @@ -62,7 +62,7 @@ func Start() error { if err != nil { return err } - signer := c2pa.MakeStaticSigner(certBytes, keyBytes, alg) + signer := c2pa.MakeStaticSigner(certBytes, keyBytes) if err != nil { return err } diff --git a/pkg/c2pa/staticsigner.go b/pkg/c2pa/staticsigner.go index 7694e5ed..22e3fd02 100644 --- a/pkg/c2pa/staticsigner.go +++ b/pkg/c2pa/staticsigner.go @@ -19,16 +19,14 @@ import ( // helper signer for when you have cert and key as PEM-encoded bytes type StaticSigner struct { - Cert []byte - Key []byte - Algorithm *SigningAlgorithm + Cert []byte + Key []byte } -func MakeStaticSigner(cert, key []byte, alg *SigningAlgorithm) crypto.Signer { +func MakeStaticSigner(cert, key []byte) crypto.Signer { return &StaticSigner{ - Cert: cert, - Key: key, - Algorithm: alg, + Cert: cert, + Key: key, } } From c7339a12f0263b4242077649f83a25b60e797021 Mon Sep 17 00:00:00 2001 From: Eli Mallon Date: Wed, 21 Aug 2024 11:24:12 -0700 Subject: [PATCH 29/42] add validation of signed media --- pkg/c2pa/c2pa.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pkg/c2pa/c2pa.go b/pkg/c2pa/c2pa.go index c8265266..4d947a08 100644 --- a/pkg/c2pa/c2pa.go +++ b/pkg/c2pa/c2pa.go @@ -37,6 +37,13 @@ func FromStream(target io.ReadWriteSeeker, mType string) (Reader, error) { if err != nil { return nil, err } + if len(store.ValidationStatus) > 0 { + errBs, err := json.Marshal(store.ValidationStatus) + if err != nil { + return nil, err + } + return &C2PAReader{store: &store}, fmt.Errorf("validation error: %s", string(errBs)) + } return &C2PAReader{store: &store}, nil } @@ -116,6 +123,10 @@ func (b *C2PABuilder) Sign(input io.ReadSeeker, output io.ReadWriteSeeker, mimeT if err != nil { return err } + _, err = FromStream(output, mimeType) + if err != nil { + return fmt.Errorf("unable to validate newly-signed file: %w", err) + } return nil } From dc4fd1d2c4257e8243677d061022567dcb59d994 Mon Sep 17 00:00:00 2001 From: Eli Mallon Date: Tue, 27 Aug 2024 12:40:57 -0700 Subject: [PATCH 30/42] remove logging --- pkg/c2pa/c2pa.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/c2pa/c2pa.go b/pkg/c2pa/c2pa.go index 4d947a08..662c2e0b 100644 --- a/pkg/c2pa/c2pa.go +++ b/pkg/c2pa/c2pa.go @@ -28,7 +28,6 @@ func FromStream(target io.ReadWriteSeeker, mType string) (Reader, error) { r := rustC2PA.NewReader() r.FromStream(mType, &stream) ret, err := r.Json() - fmt.Println(ret) if err != nil { return nil, err } From 1d2a5e4318694160ff27da4ef45e6bcdf73437ea Mon Sep 17 00:00:00 2001 From: Eli Mallon Date: Thu, 12 Sep 2024 14:57:12 -0700 Subject: [PATCH 31/42] add GetProvenanceCertChain --- Cargo.toml | 2 +- pkg/c2pa/c2pa.go | 16 +++++++++++++--- pkg/c2pa/generated/c2pa/c2pa.go | 24 ++++++++++++++++++++++++ pkg/c2pa/generated/c2pa/c2pa.h | 9 +++++++++ src/c2pa.udl | 5 ++++- src/lib.rs | 8 ++++++++ 6 files changed, 59 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ba6a69f5..abb48d21 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ name = "c2pa" crate-type = ["staticlib"] [dependencies] -c2pa = { git = "https://git.aquareum.tv/aquareum-tv/c2pa-rs.git", rev = "a0b0d3298ae14c4fd71413232eff159cda8d7bcb", features = [ +c2pa = { git = "https://git.aquareum.tv/aquareum-tv/c2pa-rs.git", rev = "5ca8c085a05daa36a91fb9edfdee6098cffac69b", features = [ "unstable_api", "openssl", ] } diff --git a/pkg/c2pa/c2pa.go b/pkg/c2pa/c2pa.go index 662c2e0b..b9c76fc5 100644 --- a/pkg/c2pa/c2pa.go +++ b/pkg/c2pa/c2pa.go @@ -21,9 +21,10 @@ import "C" type Reader interface { GetManifest(label string) *manifeststore.Manifest GetActiveManifest() *manifeststore.Manifest + GetProvenanceCertChain() string } -func FromStream(target io.ReadWriteSeeker, mType string) (Reader, error) { +func FromStream(target io.ReadSeeker, mType string) (Reader, error) { stream := C2PAStreamReader{target} r := rustC2PA.NewReader() r.FromStream(mType, &stream) @@ -31,6 +32,10 @@ func FromStream(target io.ReadWriteSeeker, mType string) (Reader, error) { if err != nil { return nil, err } + certs, err := r.GetProvenanceCertChain() + if err != nil { + return nil, err + } var store manifeststore.ManifestStoreSchemaJson err = json.Unmarshal([]byte(ret), &store) if err != nil { @@ -41,9 +46,9 @@ func FromStream(target io.ReadWriteSeeker, mType string) (Reader, error) { if err != nil { return nil, err } - return &C2PAReader{store: &store}, fmt.Errorf("validation error: %s", string(errBs)) + return &C2PAReader{store: &store, certs: certs}, fmt.Errorf("validation error: %s", string(errBs)) } - return &C2PAReader{store: &store}, nil + return &C2PAReader{store: &store, certs: certs}, nil } func FromFile(fname string) (Reader, error) { @@ -61,6 +66,7 @@ func FromFile(fname string) (Reader, error) { type C2PAReader struct { store *manifeststore.ManifestStoreSchemaJson + certs string } func (r *C2PAReader) GetManifest(label string) *manifeststore.Manifest { @@ -78,6 +84,10 @@ func (r *C2PAReader) GetActiveManifest() *manifeststore.Manifest { return r.GetManifest(*r.store.ActiveManifest) } +func (r *C2PAReader) GetProvenanceCertChain() string { + return r.certs +} + type Builder interface { Sign(input io.ReadSeeker, output io.ReadWriteSeeker, mimeType string) error SignFile(infile, outfile string) error diff --git a/pkg/c2pa/generated/c2pa/c2pa.go b/pkg/c2pa/generated/c2pa/c2pa.go index c3f1931c..e66649e2 100644 --- a/pkg/c2pa/generated/c2pa/c2pa.go +++ b/pkg/c2pa/generated/c2pa/c2pa.go @@ -428,6 +428,15 @@ func uniffiCheckChecksums() { panic("c2pa: uniffi_c2pa_checksum_method_reader_from_stream: UniFFI API checksum mismatch") } } + { + checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_c2pa_checksum_method_reader_get_provenance_cert_chain(uniffiStatus) + }) + if checksum != 38020 { + // If this happens try cleaning and rebuilding your project + panic("c2pa: uniffi_c2pa_checksum_method_reader_get_provenance_cert_chain: UniFFI API checksum mismatch") + } + } { checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { return C.uniffi_c2pa_checksum_method_reader_json(uniffiStatus) @@ -911,6 +920,21 @@ func (_self *Reader) FromStream(format string, reader Stream) (string, error) { } } +func (_self *Reader) GetProvenanceCertChain() (string, error) { + _pointer := _self.ffiObject.incrementPointer("*Reader") + defer _self.ffiObject.decrementPointer() + _uniffiRV, _uniffiErr := rustCallWithError(FfiConverterTypeError{}, func(_uniffiStatus *C.RustCallStatus) RustBufferI { + return C.uniffi_c2pa_fn_method_reader_get_provenance_cert_chain( + _pointer, _uniffiStatus) + }) + if _uniffiErr != nil { + var _uniffiDefaultValue string + return _uniffiDefaultValue, _uniffiErr + } else { + return FfiConverterStringINSTANCE.Lift(_uniffiRV), _uniffiErr + } +} + func (_self *Reader) Json() (string, error) { _pointer := _self.ffiObject.incrementPointer("*Reader") defer _self.ffiObject.decrementPointer() diff --git a/pkg/c2pa/generated/c2pa/c2pa.h b/pkg/c2pa/generated/c2pa/c2pa.h index 39ea6801..05f4d3b3 100644 --- a/pkg/c2pa/generated/c2pa/c2pa.h +++ b/pkg/c2pa/generated/c2pa/c2pa.h @@ -148,6 +148,11 @@ RustBuffer uniffi_c2pa_fn_method_reader_from_stream( RustCallStatus* out_status ); +RustBuffer uniffi_c2pa_fn_method_reader_get_provenance_cert_chain( + void* ptr, + RustCallStatus* out_status +); + RustBuffer uniffi_c2pa_fn_method_reader_json( void* ptr, RustCallStatus* out_status @@ -513,6 +518,10 @@ uint16_t uniffi_c2pa_checksum_method_reader_from_stream( RustCallStatus* out_status ); +uint16_t uniffi_c2pa_checksum_method_reader_get_provenance_cert_chain( + RustCallStatus* out_status +); + uint16_t uniffi_c2pa_checksum_method_reader_json( RustCallStatus* out_status ); diff --git a/src/c2pa.udl b/src/c2pa.udl index 7dd3870a..857d6711 100644 --- a/src/c2pa.udl +++ b/src/c2pa.udl @@ -61,6 +61,9 @@ interface Reader { [Throws=Error] string json(); + [Throws=Error] + string get_provenance_cert_chain(); + [Throws=Error] u64 resource_to_stream([ByRef] string uri, [ByRef] Stream stream); }; @@ -76,7 +79,7 @@ interface CallbackSigner { interface Builder { constructor(); - + [Throws=Error] void with_json([ByRef] string json); diff --git a/src/lib.rs b/src/lib.rs index 23b0d52d..a903eafd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -85,6 +85,14 @@ impl Reader { } } + pub fn get_provenance_cert_chain(&self) -> Result { + if let Ok(st) = self.reader.try_read() { + Ok(st.get_provenance_cert_chain()?) + } else { + Err(Error::RwLock) + } + } + pub fn resource_to_stream(&self, uri: &str, stream: &dyn Stream) -> Result { if let Ok(reader) = self.reader.try_read() { let mut stream = StreamAdapter::from(stream); From 68f9878542d49981443d304f27d508235d45633d Mon Sep 17 00:00:00 2001 From: Eli Mallon Date: Fri, 13 Sep 2024 15:34:08 -0700 Subject: [PATCH 32/42] simplify signing flow --- pkg/c2pa/c2pa_test.go | 3 ++- pkg/c2pa/demo/demo.go | 2 +- pkg/c2pa/staticsigner.go | 34 +++++----------------------------- 3 files changed, 8 insertions(+), 31 deletions(-) diff --git a/pkg/c2pa/c2pa_test.go b/pkg/c2pa/c2pa_test.go index 2b7feeee..81430151 100644 --- a/pkg/c2pa/c2pa_test.go +++ b/pkg/c2pa/c2pa_test.go @@ -73,7 +73,8 @@ func TestSigning(t *testing.T) { require.NoError(t, err) alg, err := GetSigningAlgorithm(test.name) require.NoError(t, err) - signer := MakeStaticSigner(certBytes, keyBytes) + signer, err := MakeStaticSigner(keyBytes) + require.NoError(t, err) b, err := NewBuilder(&manifest, &BuilderParams{ Cert: certBytes, Signer: signer, diff --git a/pkg/c2pa/demo/demo.go b/pkg/c2pa/demo/demo.go index f016f484..9c1c4a8e 100644 --- a/pkg/c2pa/demo/demo.go +++ b/pkg/c2pa/demo/demo.go @@ -62,7 +62,7 @@ func Start() error { if err != nil { return err } - signer := c2pa.MakeStaticSigner(certBytes, keyBytes) + signer, err := c2pa.MakeStaticSigner(keyBytes) if err != nil { return err } diff --git a/pkg/c2pa/staticsigner.go b/pkg/c2pa/staticsigner.go index 22e3fd02..174d1789 100644 --- a/pkg/c2pa/staticsigner.go +++ b/pkg/c2pa/staticsigner.go @@ -11,46 +11,22 @@ import ( "encoding/pem" "errors" "fmt" - "io" "reflect" "github.com/decred/dcrd/dcrec/secp256k1" ) -// helper signer for when you have cert and key as PEM-encoded bytes -type StaticSigner struct { - Cert []byte - Key []byte -} - -func MakeStaticSigner(cert, key []byte) crypto.Signer { - return &StaticSigner{ - Cert: cert, - Key: key, - } -} - -func (s *StaticSigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) { - block, _ := pem.Decode(s.Key) +func MakeStaticSigner(keybs []byte) (crypto.Signer, error) { + block, _ := pem.Decode(keybs) if block == nil { - return []byte{}, fmt.Errorf("failed to parse PEM block containing the private key") + return nil, fmt.Errorf("failed to parse PEM block containing the private key") } key, err := parsePrivateKey(block.Bytes) if err != nil { - return []byte{}, fmt.Errorf("parsePrivateKey failed: %s", err.Error()) + return nil, fmt.Errorf("parsePrivateKey failed: %s", err.Error()) } - - bs, err := key.Sign(rand, digest, opts) - - if err != nil { - return []byte{}, fmt.Errorf("ecdsa.SignASN1 failed: %s", err.Error()) - } - return bs, nil -} - -func (s *StaticSigner) Public() crypto.PublicKey { - panic("not implemented") + return key, nil } func parsePrivateKey(der []byte) (crypto.Signer, error) { From 055feef5c4e81de9d198625973fa936aa928aaf2 Mon Sep 17 00:00:00 2001 From: Eli Mallon Date: Mon, 23 Sep 2024 14:18:55 -0700 Subject: [PATCH 33/42] meson: fix errors when there are spaces in our path --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 0681fda5..e5244ee5 100644 --- a/meson.build +++ b/meson.build @@ -21,7 +21,7 @@ c2pa_go_dep = custom_target( command: [ 'sh', '-c', - 'cd @2@ && @0@ && cp @3@/release/libc2pa.a @1@/libc2pa.a'.format(cargo_cmd, meson.current_build_dir(), meson.current_source_dir(), archive_dir), + 'cd "@2@" && @0@ && cp "@3@/release/libc2pa.a" "@1@/libc2pa.a"'.format(cargo_cmd, meson.current_build_dir(), meson.current_source_dir(), archive_dir), ], build_by_default: true, ) From 7504f25bd8013c607dc18370b56b30edb2536546 Mon Sep 17 00:00:00 2001 From: Eli Mallon Date: Mon, 23 Sep 2024 14:25:22 -0700 Subject: [PATCH 34/42] meson: another quoting error --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index e5244ee5..6bd3bc09 100644 --- a/meson.build +++ b/meson.build @@ -5,7 +5,7 @@ project( prog_cargo = find_program('cargo') -cargo_cmd = '@0@ build --release --target-dir @1@'.format(prog_cargo.full_path(), meson.current_build_dir()) +cargo_cmd = '@0@ build --release --target-dir "@1@"'.format(prog_cargo.full_path(), meson.current_build_dir()) archive_dir = meson.current_build_dir() triple = get_option('RUST_TRIPLE') From 1f2771794f97efbf784c9bb15881da6a6f1d0b7b Mon Sep 17 00:00:00 2001 From: Eli Mallon Date: Tue, 3 Dec 2024 15:07:41 -0800 Subject: [PATCH 35/42] meson: use declare_dependency to bundle include directory --- meson.build | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 6bd3bc09..ea5ad191 100644 --- a/meson.build +++ b/meson.build @@ -14,7 +14,7 @@ if triple != '' archive_dir += '/' + triple endif -c2pa_go_dep = custom_target( +libc2pa = custom_target( 'libc2pa', input: [], output: ['libc2pa.a'], @@ -25,3 +25,8 @@ c2pa_go_dep = custom_target( ], build_by_default: true, ) + +c2pa_go_dep = declare_dependency( + link_with: libc2pa, + include_directories: include_directories('.'), +) From 2944f24890be042959d03b33558ac1867fc8eca3 Mon Sep 17 00:00:00 2001 From: Eli Mallon Date: Thu, 5 Dec 2024 13:06:14 -0800 Subject: [PATCH 36/42] Revert "meson: use declare_dependency to bundle include directory" This reverts commit 1f2771794f97efbf784c9bb15881da6a6f1d0b7b. --- meson.build | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/meson.build b/meson.build index ea5ad191..6bd3bc09 100644 --- a/meson.build +++ b/meson.build @@ -14,7 +14,7 @@ if triple != '' archive_dir += '/' + triple endif -libc2pa = custom_target( +c2pa_go_dep = custom_target( 'libc2pa', input: [], output: ['libc2pa.a'], @@ -25,8 +25,3 @@ libc2pa = custom_target( ], build_by_default: true, ) - -c2pa_go_dep = declare_dependency( - link_with: libc2pa, - include_directories: include_directories('.'), -) From 2931a03e981410584390a048372290f38adccf30 Mon Sep 17 00:00:00 2001 From: Eli Mallon Date: Wed, 15 Jan 2025 22:02:41 -0800 Subject: [PATCH 37/42] rename: streamplace --- go.mod | 2 +- pkg/c2pa/algorithms.go | 2 +- pkg/c2pa/c2pa.go | 6 +++--- pkg/c2pa/callbacksigner.go | 2 +- pkg/c2pa/demo/demo.go | 2 +- pkg/c2pa/io.go | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index c7ad90d9..e0776b92 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module git.aquareum.tv/aquareum-tv/c2pa-go +module git.aquareum.tv/streamplace/c2pa-go go 1.22.2 diff --git a/pkg/c2pa/algorithms.go b/pkg/c2pa/algorithms.go index fe6b8442..29cae9bb 100644 --- a/pkg/c2pa/algorithms.go +++ b/pkg/c2pa/algorithms.go @@ -5,7 +5,7 @@ import ( "crypto/rsa" "fmt" - rustC2PA "git.aquareum.tv/aquareum-tv/c2pa-go/pkg/c2pa/generated/c2pa" + rustC2PA "git.aquareum.tv/streamplace/c2pa-go/pkg/c2pa/generated/c2pa" ) type SigningAlgorithmName string diff --git a/pkg/c2pa/c2pa.go b/pkg/c2pa/c2pa.go index b9c76fc5..5289beca 100644 --- a/pkg/c2pa/c2pa.go +++ b/pkg/c2pa/c2pa.go @@ -9,9 +9,9 @@ import ( "os" "path/filepath" - rustC2PA "git.aquareum.tv/aquareum-tv/c2pa-go/pkg/c2pa/generated/c2pa" - "git.aquareum.tv/aquareum-tv/c2pa-go/pkg/c2pa/generated/manifestdefinition" - "git.aquareum.tv/aquareum-tv/c2pa-go/pkg/c2pa/generated/manifeststore" + rustC2PA "git.aquareum.tv/streamplace/c2pa-go/pkg/c2pa/generated/c2pa" + "git.aquareum.tv/streamplace/c2pa-go/pkg/c2pa/generated/manifestdefinition" + "git.aquareum.tv/streamplace/c2pa-go/pkg/c2pa/generated/manifeststore" ) // #cgo LDFLAGS: -L../../target/release -lc2pa -lm diff --git a/pkg/c2pa/callbacksigner.go b/pkg/c2pa/callbacksigner.go index ce545816..d9dc6656 100644 --- a/pkg/c2pa/callbacksigner.go +++ b/pkg/c2pa/callbacksigner.go @@ -4,7 +4,7 @@ import ( "crypto" "crypto/rand" - rustC2PA "git.aquareum.tv/aquareum-tv/c2pa-go/pkg/c2pa/generated/c2pa" + rustC2PA "git.aquareum.tv/streamplace/c2pa-go/pkg/c2pa/generated/c2pa" ) type C2PACallbackSigner struct { diff --git a/pkg/c2pa/demo/demo.go b/pkg/c2pa/demo/demo.go index 9c1c4a8e..74d2ddb5 100644 --- a/pkg/c2pa/demo/demo.go +++ b/pkg/c2pa/demo/demo.go @@ -6,7 +6,7 @@ import ( "fmt" "os" - "git.aquareum.tv/aquareum-tv/c2pa-go/pkg/c2pa" + "git.aquareum.tv/streamplace/c2pa-go/pkg/c2pa" ) func Start() error { diff --git a/pkg/c2pa/io.go b/pkg/c2pa/io.go index 6f4c2ccd..94377469 100644 --- a/pkg/c2pa/io.go +++ b/pkg/c2pa/io.go @@ -5,7 +5,7 @@ import ( "fmt" "io" - rustC2PA "git.aquareum.tv/aquareum-tv/c2pa-go/pkg/c2pa/generated/c2pa" + rustC2PA "git.aquareum.tv/streamplace/c2pa-go/pkg/c2pa/generated/c2pa" ) // Wrapped io.ReadSeeker for passing to Rust. Doesn't write. From a26b6bf7d3b0ee120d59d445f72f0b47a3c5d5f3 Mon Sep 17 00:00:00 2001 From: Eli Mallon Date: Tue, 28 Jan 2025 17:12:18 -0800 Subject: [PATCH 38/42] update package name with new git server --- Cargo.toml | 2 +- Makefile | 2 +- go.mod | 2 +- pkg/c2pa/algorithms.go | 2 +- pkg/c2pa/c2pa.go | 6 +++--- pkg/c2pa/callbacksigner.go | 2 +- pkg/c2pa/demo/demo.go | 2 +- pkg/c2pa/io.go | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index abb48d21..5b3445cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ name = "c2pa" crate-type = ["staticlib"] [dependencies] -c2pa = { git = "https://git.aquareum.tv/aquareum-tv/c2pa-rs.git", rev = "5ca8c085a05daa36a91fb9edfdee6098cffac69b", features = [ +c2pa = { git = "https://git.stream.place/aquareum-tv/c2pa-rs.git", rev = "5ca8c085a05daa36a91fb9edfdee6098cffac69b", features = [ "unstable_api", "openssl", ] } diff --git a/Makefile b/Makefile index 15042948..70865b9c 100644 --- a/Makefile +++ b/Makefile @@ -25,5 +25,5 @@ go: # need es256k-enabled c2patool .PHONY: test test: - cargo install --git https://git.aquareum.tv/aquareum-tv/c2patool.git + cargo install --git https://git.stream.place/aquareum-tv/c2patool.git go test ./pkg/... \ No newline at end of file diff --git a/go.mod b/go.mod index e0776b92..ef6766f8 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module git.aquareum.tv/streamplace/c2pa-go +module git.stream.place/streamplace/c2pa-go go 1.22.2 diff --git a/pkg/c2pa/algorithms.go b/pkg/c2pa/algorithms.go index 29cae9bb..6daecf77 100644 --- a/pkg/c2pa/algorithms.go +++ b/pkg/c2pa/algorithms.go @@ -5,7 +5,7 @@ import ( "crypto/rsa" "fmt" - rustC2PA "git.aquareum.tv/streamplace/c2pa-go/pkg/c2pa/generated/c2pa" + rustC2PA "git.stream.place/streamplace/c2pa-go/pkg/c2pa/generated/c2pa" ) type SigningAlgorithmName string diff --git a/pkg/c2pa/c2pa.go b/pkg/c2pa/c2pa.go index 5289beca..590be252 100644 --- a/pkg/c2pa/c2pa.go +++ b/pkg/c2pa/c2pa.go @@ -9,9 +9,9 @@ import ( "os" "path/filepath" - rustC2PA "git.aquareum.tv/streamplace/c2pa-go/pkg/c2pa/generated/c2pa" - "git.aquareum.tv/streamplace/c2pa-go/pkg/c2pa/generated/manifestdefinition" - "git.aquareum.tv/streamplace/c2pa-go/pkg/c2pa/generated/manifeststore" + rustC2PA "git.stream.place/streamplace/c2pa-go/pkg/c2pa/generated/c2pa" + "git.stream.place/streamplace/c2pa-go/pkg/c2pa/generated/manifestdefinition" + "git.stream.place/streamplace/c2pa-go/pkg/c2pa/generated/manifeststore" ) // #cgo LDFLAGS: -L../../target/release -lc2pa -lm diff --git a/pkg/c2pa/callbacksigner.go b/pkg/c2pa/callbacksigner.go index d9dc6656..46dc0420 100644 --- a/pkg/c2pa/callbacksigner.go +++ b/pkg/c2pa/callbacksigner.go @@ -4,7 +4,7 @@ import ( "crypto" "crypto/rand" - rustC2PA "git.aquareum.tv/streamplace/c2pa-go/pkg/c2pa/generated/c2pa" + rustC2PA "git.stream.place/streamplace/c2pa-go/pkg/c2pa/generated/c2pa" ) type C2PACallbackSigner struct { diff --git a/pkg/c2pa/demo/demo.go b/pkg/c2pa/demo/demo.go index 74d2ddb5..53908c06 100644 --- a/pkg/c2pa/demo/demo.go +++ b/pkg/c2pa/demo/demo.go @@ -6,7 +6,7 @@ import ( "fmt" "os" - "git.aquareum.tv/streamplace/c2pa-go/pkg/c2pa" + "git.stream.place/streamplace/c2pa-go/pkg/c2pa" ) func Start() error { diff --git a/pkg/c2pa/io.go b/pkg/c2pa/io.go index 94377469..bb436021 100644 --- a/pkg/c2pa/io.go +++ b/pkg/c2pa/io.go @@ -5,7 +5,7 @@ import ( "fmt" "io" - rustC2PA "git.aquareum.tv/streamplace/c2pa-go/pkg/c2pa/generated/c2pa" + rustC2PA "git.stream.place/streamplace/c2pa-go/pkg/c2pa/generated/c2pa" ) // Wrapped io.ReadSeeker for passing to Rust. Doesn't write. From a912787e792879150cef0609a651d5baed53cdde Mon Sep 17 00:00:00 2001 From: Eli Mallon Date: Wed, 30 Apr 2025 14:58:05 -0700 Subject: [PATCH 39/42] upgrade to uniffi-bindgen-go v0.3.0+v0.28.3 --- Cargo.toml | 6 +- Makefile | 4 +- pkg/c2pa/c2pa.go | 24 +- pkg/c2pa/generated/c2pa/c2pa.c | 8 - pkg/c2pa/generated/c2pa/c2pa.go | 999 ++++++----- pkg/c2pa/generated/c2pa/c2pa.h | 1468 +++++++++++------ .../manifestdefinition/manifestdefinition.go | 4 +- .../generated/manifeststore/manifeststore.go | 4 +- pkg/c2pa/generated/settings/settings.go | 4 - src/streams.rs | 4 - 10 files changed, 1529 insertions(+), 996 deletions(-) delete mode 100644 pkg/c2pa/generated/c2pa/c2pa.c diff --git a/Cargo.toml b/Cargo.toml index 5b3445cf..a1ff7180 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "c2pa-go" version = "0.5.0" edition = "2021" -authors = ["Eli Mallon "] +authors = ["Eli Mallon "] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib] @@ -21,7 +21,7 @@ serde = { version = "1.0.197", features = ["derive"] } serde_derive = "1.0" serde_json = "1.0" thiserror = "1.0.49" -uniffi = "= 0.25.0" +uniffi = "= 0.28.3" [build-dependencies] -uniffi = { version = "= 0.25.0", features = ["build"] } +uniffi = { version = "= 0.28.3", features = ["build"] } diff --git a/Makefile b/Makefile index 70865b9c..7edc2377 100644 --- a/Makefile +++ b/Makefile @@ -4,12 +4,12 @@ all: rust types go .PHONY: rust rust: - cargo install uniffi-bindgen-go --git https://github.com/NordSecurity/uniffi-bindgen-go --tag v0.2.1+v0.25.0 + cargo install uniffi-bindgen-go --git https://github.com/NordSecurity/uniffi-bindgen-go --tag v0.3.0+v0.28.3 cargo build --release .PHONY: types types: - go install github.com/atombender/go-jsonschema@latest + go install github.com/atombender/go-jsonschema@v0.17.0 cargo install --git https://github.com/aquareum-tv/c2pa-rs export_schema export_schema go-jsonschema --only-models -p manifeststore ./target/schema/ManifestStore.schema.json -o pkg/c2pa/generated/manifeststore/manifeststore.go diff --git a/pkg/c2pa/c2pa.go b/pkg/c2pa/c2pa.go index 590be252..a7faa8dc 100644 --- a/pkg/c2pa/c2pa.go +++ b/pkg/c2pa/c2pa.go @@ -28,16 +28,16 @@ func FromStream(target io.ReadSeeker, mType string) (Reader, error) { stream := C2PAStreamReader{target} r := rustC2PA.NewReader() r.FromStream(mType, &stream) - ret, err := r.Json() - if err != nil { - return nil, err + ret, rErr := r.Json() + if rErr.AsError() != nil { + return nil, rErr.AsError() } - certs, err := r.GetProvenanceCertChain() - if err != nil { - return nil, err + certs, rErr := r.GetProvenanceCertChain() + if rErr.AsError() != nil { + return nil, rErr.AsError() } var store manifeststore.ManifestStoreSchemaJson - err = json.Unmarshal([]byte(ret), &store) + err := json.Unmarshal([]byte(ret), &store) if err != nil { return nil, err } @@ -114,7 +114,7 @@ func NewBuilder(manifest *ManifestDefinition, params *BuilderParams) (Builder, e if err != nil { return nil, err } - err = b.WithJson(string(bs)) + err = b.WithJson(string(bs)).AsError() if err != nil { return nil, err } @@ -128,11 +128,11 @@ func (b *C2PABuilder) Sign(input io.ReadSeeker, output io.ReadWriteSeeker, mimeT algorithm: *b.params.Algorithm, } signer := rustC2PA.NewCallbackSigner(mySigner, b.params.Algorithm.RustC2PAType, b.params.Cert, &b.params.TAURL) - _, err := b.builder.Sign(mimeType, &C2PAStreamReader{input}, &C2PAStreamWriter{output}, signer) - if err != nil { - return err + _, rErr := b.builder.Sign(mimeType, &C2PAStreamReader{input}, &C2PAStreamWriter{output}, signer) + if rErr.AsError() != nil { + return rErr.AsError() } - _, err = FromStream(output, mimeType) + _, err := FromStream(output, mimeType) if err != nil { return fmt.Errorf("unable to validate newly-signed file: %w", err) } diff --git a/pkg/c2pa/generated/c2pa/c2pa.c b/pkg/c2pa/generated/c2pa/c2pa.c deleted file mode 100644 index f9a0d0f9..00000000 --- a/pkg/c2pa/generated/c2pa/c2pa.c +++ /dev/null @@ -1,8 +0,0 @@ -#include - -// This file exists beacause of -// https://github.com/golang/go/issues/11263 - -void cgo_rust_task_callback_bridge_c2pa(RustTaskCallback cb, const void * taskData, int8_t status) { - cb(taskData, status); -} \ No newline at end of file diff --git a/pkg/c2pa/generated/c2pa/c2pa.go b/pkg/c2pa/generated/c2pa/c2pa.go index e66649e2..a156b8a1 100644 --- a/pkg/c2pa/generated/c2pa/c2pa.go +++ b/pkg/c2pa/generated/c2pa/c2pa.go @@ -15,60 +15,67 @@ import ( "unsafe" ) -type RustBuffer = C.RustBuffer +// This is needed, because as of go 1.24 +// type RustBuffer C.RustBuffer cannot have methods, +// RustBuffer is treated as non-local type +type GoRustBuffer struct { + inner C.RustBuffer +} type RustBufferI interface { AsReader() *bytes.Reader Free() ToGoBytes() []byte Data() unsafe.Pointer - Len() int - Capacity() int + Len() uint64 + Capacity() uint64 } -func RustBufferFromExternal(b RustBufferI) RustBuffer { - return RustBuffer{ - capacity: C.int(b.Capacity()), - len: C.int(b.Len()), - data: (*C.uchar)(b.Data()), +func RustBufferFromExternal(b RustBufferI) GoRustBuffer { + return GoRustBuffer{ + inner: C.RustBuffer{ + capacity: C.uint64_t(b.Capacity()), + len: C.uint64_t(b.Len()), + data: (*C.uchar)(b.Data()), + }, } } -func (cb RustBuffer) Capacity() int { - return int(cb.capacity) +func (cb GoRustBuffer) Capacity() uint64 { + return uint64(cb.inner.capacity) } -func (cb RustBuffer) Len() int { - return int(cb.len) +func (cb GoRustBuffer) Len() uint64 { + return uint64(cb.inner.len) } -func (cb RustBuffer) Data() unsafe.Pointer { - return unsafe.Pointer(cb.data) +func (cb GoRustBuffer) Data() unsafe.Pointer { + return unsafe.Pointer(cb.inner.data) } -func (cb RustBuffer) AsReader() *bytes.Reader { - b := unsafe.Slice((*byte)(cb.data), C.int(cb.len)) +func (cb GoRustBuffer) AsReader() *bytes.Reader { + b := unsafe.Slice((*byte)(cb.inner.data), C.uint64_t(cb.inner.len)) return bytes.NewReader(b) } -func (cb RustBuffer) Free() { +func (cb GoRustBuffer) Free() { rustCall(func(status *C.RustCallStatus) bool { - C.ffi_c2pa_rustbuffer_free(cb, status) + C.ffi_c2pa_rustbuffer_free(cb.inner, status) return false }) } -func (cb RustBuffer) ToGoBytes() []byte { - return C.GoBytes(unsafe.Pointer(cb.data), C.int(cb.len)) +func (cb GoRustBuffer) ToGoBytes() []byte { + return C.GoBytes(unsafe.Pointer(cb.inner.data), C.int(cb.inner.len)) } -func stringToRustBuffer(str string) RustBuffer { +func stringToRustBuffer(str string) C.RustBuffer { return bytesToRustBuffer([]byte(str)) } -func bytesToRustBuffer(b []byte) RustBuffer { +func bytesToRustBuffer(b []byte) C.RustBuffer { if len(b) == 0 { - return RustBuffer{} + return C.RustBuffer{} } // We can pass the pointer along here, as it is pinned // for the duration of this call @@ -77,7 +84,7 @@ func bytesToRustBuffer(b []byte) RustBuffer { data: (*C.uchar)(unsafe.Pointer(&b[0])), } - return rustCall(func(status *C.RustCallStatus) RustBuffer { + return rustCall(func(status *C.RustCallStatus) C.RustBuffer { return C.ffi_c2pa_rustbuffer_from_bytes(foreign, status) }) } @@ -87,12 +94,7 @@ type BufLifter[GoType any] interface { } type BufLowerer[GoType any] interface { - Lower(value GoType) RustBuffer -} - -type FfiConverter[GoType any, FfiType any] interface { - Lift(value FfiType) GoType - Lower(value GoType) FfiType + Lower(value GoType) C.RustBuffer } type BufReader[GoType any] interface { @@ -103,12 +105,7 @@ type BufWriter[GoType any] interface { Write(writer io.Writer, value GoType) } -type FfiRustBufConverter[GoType any, FfiType any] interface { - FfiConverter[GoType, FfiType] - BufReader[GoType] -} - -func LowerIntoRustBuffer[GoType any](bufWriter BufWriter[GoType], value GoType) RustBuffer { +func LowerIntoRustBuffer[GoType any](bufWriter BufWriter[GoType], value GoType) C.RustBuffer { // This might be not the most efficient way but it does not require knowing allocation size // beforehand var buffer bytes.Buffer @@ -133,31 +130,30 @@ func LiftFromRustBuffer[GoType any](bufReader BufReader[GoType], rbuf RustBuffer return item } -func rustCallWithError[U any](converter BufLifter[error], callback func(*C.RustCallStatus) U) (U, error) { +func rustCallWithError[E any, U any](converter BufReader[*E], callback func(*C.RustCallStatus) U) (U, *E) { var status C.RustCallStatus returnValue := callback(&status) err := checkCallStatus(converter, status) - return returnValue, err } -func checkCallStatus(converter BufLifter[error], status C.RustCallStatus) error { +func checkCallStatus[E any](converter BufReader[*E], status C.RustCallStatus) *E { switch status.code { case 0: return nil case 1: - return converter.Lift(status.errorBuf) + return LiftFromRustBuffer(converter, GoRustBuffer{inner: status.errorBuf}) case 2: - // when the rust code sees a panic, it tries to construct a rustbuffer + // when the rust code sees a panic, it tries to construct a rustBuffer // with the message. but if that code panics, then it just sends back // an empty buffer. if status.errorBuf.len > 0 { - panic(fmt.Errorf("%s", FfiConverterStringINSTANCE.Lift(status.errorBuf))) + panic(fmt.Errorf("%s", FfiConverterStringINSTANCE.Lift(GoRustBuffer{inner: status.errorBuf}))) } else { panic(fmt.Errorf("Rust panicked while handling Rust panic")) } default: - return fmt.Errorf("unknown status code: %d", status.code) + panic(fmt.Errorf("unknown status code: %d", status.code)) } } @@ -168,11 +164,13 @@ func checkCallStatusUnknown(status C.RustCallStatus) error { case 1: panic(fmt.Errorf("function not returning an error returned an error")) case 2: - // when the rust code sees a panic, it tries to construct a rustbuffer + // when the rust code sees a panic, it tries to construct a C.RustBuffer // with the message. but if that code panics, then it just sends back // an empty buffer. if status.errorBuf.len > 0 { - panic(fmt.Errorf("%s", FfiConverterStringINSTANCE.Lift(status.errorBuf))) + panic(fmt.Errorf("%s", FfiConverterStringINSTANCE.Lift(GoRustBuffer{ + inner: status.errorBuf, + }))) } else { panic(fmt.Errorf("Rust panicked while handling Rust panic")) } @@ -182,13 +180,17 @@ func checkCallStatusUnknown(status C.RustCallStatus) error { } func rustCall[U any](callback func(*C.RustCallStatus) U) U { - returnValue, err := rustCallWithError(nil, callback) + returnValue, err := rustCallWithError[error](nil, callback) if err != nil { panic(err) } return returnValue } +type NativeError interface { + AsError() error +} + func writeInt8(writer io.Writer, value int8) { if err := binary.Write(writer, binary.BigEndian, value); err != nil { panic(err) @@ -331,25 +333,25 @@ func readFloat64(reader io.Reader) float64 { func init() { - (&FfiConverterCallbackInterfaceSignerCallback{}).register() - (&FfiConverterCallbackInterfaceStream{}).register() + FfiConverterCallbackInterfaceSignerCallbackINSTANCE.register() + FfiConverterCallbackInterfaceStreamINSTANCE.register() uniffiCheckChecksums() } func uniffiCheckChecksums() { // Get the bindings contract version from our ComponentInterface - bindingsContractVersion := 24 + bindingsContractVersion := 26 // Get the scaffolding contract version by calling the into the dylib - scaffoldingContractVersion := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint32_t { - return C.ffi_c2pa_uniffi_contract_version(uniffiStatus) + scaffoldingContractVersion := rustCall(func(_uniffiStatus *C.RustCallStatus) C.uint32_t { + return C.ffi_c2pa_uniffi_contract_version() }) if bindingsContractVersion != int(scaffoldingContractVersion) { // If this happens try cleaning and rebuilding your project panic("c2pa: UniFFI contract version mismatch") } { - checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { - return C.uniffi_c2pa_checksum_func_sdk_version(uniffiStatus) + checksum := rustCall(func(_uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_c2pa_checksum_func_sdk_version() }) if checksum != 37245 { // If this happens try cleaning and rebuilding your project @@ -357,8 +359,8 @@ func uniffiCheckChecksums() { } } { - checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { - return C.uniffi_c2pa_checksum_func_version(uniffiStatus) + checksum := rustCall(func(_uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_c2pa_checksum_func_version() }) if checksum != 61576 { // If this happens try cleaning and rebuilding your project @@ -366,154 +368,154 @@ func uniffiCheckChecksums() { } } { - checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { - return C.uniffi_c2pa_checksum_method_builder_add_ingredient(uniffiStatus) + checksum := rustCall(func(_uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_c2pa_checksum_method_builder_add_ingredient() }) - if checksum != 54967 { + if checksum != 56163 { // If this happens try cleaning and rebuilding your project panic("c2pa: uniffi_c2pa_checksum_method_builder_add_ingredient: UniFFI API checksum mismatch") } } { - checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { - return C.uniffi_c2pa_checksum_method_builder_add_resource(uniffiStatus) + checksum := rustCall(func(_uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_c2pa_checksum_method_builder_add_resource() }) - if checksum != 12018 { + if checksum != 52123 { // If this happens try cleaning and rebuilding your project panic("c2pa: uniffi_c2pa_checksum_method_builder_add_resource: UniFFI API checksum mismatch") } } { - checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { - return C.uniffi_c2pa_checksum_method_builder_from_archive(uniffiStatus) + checksum := rustCall(func(_uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_c2pa_checksum_method_builder_from_archive() }) - if checksum != 17341 { + if checksum != 45068 { // If this happens try cleaning and rebuilding your project panic("c2pa: uniffi_c2pa_checksum_method_builder_from_archive: UniFFI API checksum mismatch") } } { - checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { - return C.uniffi_c2pa_checksum_method_builder_sign(uniffiStatus) + checksum := rustCall(func(_uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_c2pa_checksum_method_builder_sign() }) - if checksum != 8729 { + if checksum != 31394 { // If this happens try cleaning and rebuilding your project panic("c2pa: uniffi_c2pa_checksum_method_builder_sign: UniFFI API checksum mismatch") } } { - checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { - return C.uniffi_c2pa_checksum_method_builder_to_archive(uniffiStatus) + checksum := rustCall(func(_uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_c2pa_checksum_method_builder_to_archive() }) - if checksum != 44718 { + if checksum != 56076 { // If this happens try cleaning and rebuilding your project panic("c2pa: uniffi_c2pa_checksum_method_builder_to_archive: UniFFI API checksum mismatch") } } { - checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { - return C.uniffi_c2pa_checksum_method_builder_with_json(uniffiStatus) + checksum := rustCall(func(_uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_c2pa_checksum_method_builder_with_json() }) - if checksum != 29392 { + if checksum != 60973 { // If this happens try cleaning and rebuilding your project panic("c2pa: uniffi_c2pa_checksum_method_builder_with_json: UniFFI API checksum mismatch") } } { - checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { - return C.uniffi_c2pa_checksum_method_reader_from_stream(uniffiStatus) + checksum := rustCall(func(_uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_c2pa_checksum_method_reader_from_stream() }) - if checksum != 3255 { + if checksum != 62816 { // If this happens try cleaning and rebuilding your project panic("c2pa: uniffi_c2pa_checksum_method_reader_from_stream: UniFFI API checksum mismatch") } } { - checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { - return C.uniffi_c2pa_checksum_method_reader_get_provenance_cert_chain(uniffiStatus) + checksum := rustCall(func(_uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_c2pa_checksum_method_reader_get_provenance_cert_chain() }) - if checksum != 38020 { + if checksum != 22683 { // If this happens try cleaning and rebuilding your project panic("c2pa: uniffi_c2pa_checksum_method_reader_get_provenance_cert_chain: UniFFI API checksum mismatch") } } { - checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { - return C.uniffi_c2pa_checksum_method_reader_json(uniffiStatus) + checksum := rustCall(func(_uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_c2pa_checksum_method_reader_json() }) - if checksum != 33242 { + if checksum != 25079 { // If this happens try cleaning and rebuilding your project panic("c2pa: uniffi_c2pa_checksum_method_reader_json: UniFFI API checksum mismatch") } } { - checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { - return C.uniffi_c2pa_checksum_method_reader_resource_to_stream(uniffiStatus) + checksum := rustCall(func(_uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_c2pa_checksum_method_reader_resource_to_stream() }) - if checksum != 44049 { + if checksum != 32633 { // If this happens try cleaning and rebuilding your project panic("c2pa: uniffi_c2pa_checksum_method_reader_resource_to_stream: UniFFI API checksum mismatch") } } { - checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { - return C.uniffi_c2pa_checksum_constructor_builder_new(uniffiStatus) + checksum := rustCall(func(_uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_c2pa_checksum_constructor_builder_new() }) - if checksum != 8924 { + if checksum != 43948 { // If this happens try cleaning and rebuilding your project panic("c2pa: uniffi_c2pa_checksum_constructor_builder_new: UniFFI API checksum mismatch") } } { - checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { - return C.uniffi_c2pa_checksum_constructor_callbacksigner_new(uniffiStatus) + checksum := rustCall(func(_uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_c2pa_checksum_constructor_callbacksigner_new() }) - if checksum != 51503 { + if checksum != 65452 { // If this happens try cleaning and rebuilding your project panic("c2pa: uniffi_c2pa_checksum_constructor_callbacksigner_new: UniFFI API checksum mismatch") } } { - checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { - return C.uniffi_c2pa_checksum_constructor_reader_new(uniffiStatus) + checksum := rustCall(func(_uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_c2pa_checksum_constructor_reader_new() }) - if checksum != 7340 { + if checksum != 19939 { // If this happens try cleaning and rebuilding your project panic("c2pa: uniffi_c2pa_checksum_constructor_reader_new: UniFFI API checksum mismatch") } } { - checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { - return C.uniffi_c2pa_checksum_method_signercallback_sign(uniffiStatus) + checksum := rustCall(func(_uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_c2pa_checksum_method_signercallback_sign() }) - if checksum != 15928 { + if checksum != 64776 { // If this happens try cleaning and rebuilding your project panic("c2pa: uniffi_c2pa_checksum_method_signercallback_sign: UniFFI API checksum mismatch") } } { - checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { - return C.uniffi_c2pa_checksum_method_stream_read_stream(uniffiStatus) + checksum := rustCall(func(_uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_c2pa_checksum_method_stream_read_stream() }) - if checksum != 4594 { + if checksum != 16779 { // If this happens try cleaning and rebuilding your project panic("c2pa: uniffi_c2pa_checksum_method_stream_read_stream: UniFFI API checksum mismatch") } } { - checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { - return C.uniffi_c2pa_checksum_method_stream_seek_stream(uniffiStatus) + checksum := rustCall(func(_uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_c2pa_checksum_method_stream_seek_stream() }) - if checksum != 32219 { + if checksum != 39220 { // If this happens try cleaning and rebuilding your project panic("c2pa: uniffi_c2pa_checksum_method_stream_seek_stream: UniFFI API checksum mismatch") } } { - checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { - return C.uniffi_c2pa_checksum_method_stream_write_stream(uniffiStatus) + checksum := rustCall(func(_uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_c2pa_checksum_method_stream_write_stream() }) - if checksum != 37641 { + if checksum != 63217 { // If this happens try cleaning and rebuilding your project panic("c2pa: uniffi_c2pa_checksum_method_stream_write_stream: UniFFI API checksum mismatch") } @@ -595,7 +597,7 @@ func (FfiConverterString) Read(reader io.Reader) string { return string(buffer) } -func (FfiConverterString) Lower(value string) RustBuffer { +func (FfiConverterString) Lower(value string) C.RustBuffer { return stringToRustBuffer(value) } @@ -622,7 +624,7 @@ type FfiConverterBytes struct{} var FfiConverterBytesINSTANCE = FfiConverterBytes{} -func (c FfiConverterBytes) Lower(value []byte) RustBuffer { +func (c FfiConverterBytes) Lower(value []byte) C.RustBuffer { return LowerIntoRustBuffer[[]byte](c, value) } @@ -666,16 +668,22 @@ func (FfiDestroyerBytes) Destroy(_ []byte) {} // https://github.com/mozilla/uniffi-rs/blob/0dc031132d9493ca812c3af6e7dd60ad2ea95bf0/uniffi_bindgen/src/bindings/kotlin/templates/ObjectRuntime.kt#L31 type FfiObject struct { - pointer unsafe.Pointer - callCounter atomic.Int64 - freeFunction func(unsafe.Pointer, *C.RustCallStatus) - destroyed atomic.Bool -} - -func newFfiObject(pointer unsafe.Pointer, freeFunction func(unsafe.Pointer, *C.RustCallStatus)) FfiObject { + pointer unsafe.Pointer + callCounter atomic.Int64 + cloneFunction func(unsafe.Pointer, *C.RustCallStatus) unsafe.Pointer + freeFunction func(unsafe.Pointer, *C.RustCallStatus) + destroyed atomic.Bool +} + +func newFfiObject( + pointer unsafe.Pointer, + cloneFunction func(unsafe.Pointer, *C.RustCallStatus) unsafe.Pointer, + freeFunction func(unsafe.Pointer, *C.RustCallStatus), +) FfiObject { return FfiObject{ - pointer: pointer, - freeFunction: freeFunction, + pointer: pointer, + cloneFunction: cloneFunction, + freeFunction: freeFunction, } } @@ -693,7 +701,9 @@ func (ffiObject *FfiObject) incrementPointer(debugName string) unsafe.Pointer { } } - return ffiObject.pointer + return rustCall(func(status *C.RustCallStatus) unsafe.Pointer { + return ffiObject.cloneFunction(ffiObject.pointer, status) + }) } func (ffiObject *FfiObject) decrementPointer() { @@ -717,6 +727,14 @@ func (ffiObject *FfiObject) freeRustArcPtr() { }) } +type BuilderInterface interface { + AddIngredient(ingredientJson string, format string, stream Stream) *Error + AddResource(uri string, stream Stream) *Error + FromArchive(stream Stream) *Error + Sign(format string, input Stream, output Stream, signer *CallbackSigner) ([]byte, *Error) + ToArchive(stream Stream) *Error + WithJson(json string) *Error +} type Builder struct { ffiObject FfiObject } @@ -727,10 +745,10 @@ func NewBuilder() *Builder { })) } -func (_self *Builder) AddIngredient(ingredientJson string, format string, stream Stream) error { +func (_self *Builder) AddIngredient(ingredientJson string, format string, stream Stream) *Error { _pointer := _self.ffiObject.incrementPointer("*Builder") defer _self.ffiObject.decrementPointer() - _, _uniffiErr := rustCallWithError(FfiConverterTypeError{}, func(_uniffiStatus *C.RustCallStatus) bool { + _, _uniffiErr := rustCallWithError[Error](FfiConverterError{}, func(_uniffiStatus *C.RustCallStatus) bool { C.uniffi_c2pa_fn_method_builder_add_ingredient( _pointer, FfiConverterStringINSTANCE.Lower(ingredientJson), FfiConverterStringINSTANCE.Lower(format), FfiConverterCallbackInterfaceStreamINSTANCE.Lower(stream), _uniffiStatus) return false @@ -738,10 +756,10 @@ func (_self *Builder) AddIngredient(ingredientJson string, format string, stream return _uniffiErr } -func (_self *Builder) AddResource(uri string, stream Stream) error { +func (_self *Builder) AddResource(uri string, stream Stream) *Error { _pointer := _self.ffiObject.incrementPointer("*Builder") defer _self.ffiObject.decrementPointer() - _, _uniffiErr := rustCallWithError(FfiConverterTypeError{}, func(_uniffiStatus *C.RustCallStatus) bool { + _, _uniffiErr := rustCallWithError[Error](FfiConverterError{}, func(_uniffiStatus *C.RustCallStatus) bool { C.uniffi_c2pa_fn_method_builder_add_resource( _pointer, FfiConverterStringINSTANCE.Lower(uri), FfiConverterCallbackInterfaceStreamINSTANCE.Lower(stream), _uniffiStatus) return false @@ -749,10 +767,10 @@ func (_self *Builder) AddResource(uri string, stream Stream) error { return _uniffiErr } -func (_self *Builder) FromArchive(stream Stream) error { +func (_self *Builder) FromArchive(stream Stream) *Error { _pointer := _self.ffiObject.incrementPointer("*Builder") defer _self.ffiObject.decrementPointer() - _, _uniffiErr := rustCallWithError(FfiConverterTypeError{}, func(_uniffiStatus *C.RustCallStatus) bool { + _, _uniffiErr := rustCallWithError[Error](FfiConverterError{}, func(_uniffiStatus *C.RustCallStatus) bool { C.uniffi_c2pa_fn_method_builder_from_archive( _pointer, FfiConverterCallbackInterfaceStreamINSTANCE.Lower(stream), _uniffiStatus) return false @@ -760,12 +778,14 @@ func (_self *Builder) FromArchive(stream Stream) error { return _uniffiErr } -func (_self *Builder) Sign(format string, input Stream, output Stream, signer *CallbackSigner) ([]byte, error) { +func (_self *Builder) Sign(format string, input Stream, output Stream, signer *CallbackSigner) ([]byte, *Error) { _pointer := _self.ffiObject.incrementPointer("*Builder") defer _self.ffiObject.decrementPointer() - _uniffiRV, _uniffiErr := rustCallWithError(FfiConverterTypeError{}, func(_uniffiStatus *C.RustCallStatus) RustBufferI { - return C.uniffi_c2pa_fn_method_builder_sign( - _pointer, FfiConverterStringINSTANCE.Lower(format), FfiConverterCallbackInterfaceStreamINSTANCE.Lower(input), FfiConverterCallbackInterfaceStreamINSTANCE.Lower(output), FfiConverterCallbackSignerINSTANCE.Lower(signer), _uniffiStatus) + _uniffiRV, _uniffiErr := rustCallWithError[Error](FfiConverterError{}, func(_uniffiStatus *C.RustCallStatus) RustBufferI { + return GoRustBuffer{ + inner: C.uniffi_c2pa_fn_method_builder_sign( + _pointer, FfiConverterStringINSTANCE.Lower(format), FfiConverterCallbackInterfaceStreamINSTANCE.Lower(input), FfiConverterCallbackInterfaceStreamINSTANCE.Lower(output), FfiConverterCallbackSignerINSTANCE.Lower(signer), _uniffiStatus), + } }) if _uniffiErr != nil { var _uniffiDefaultValue []byte @@ -775,10 +795,10 @@ func (_self *Builder) Sign(format string, input Stream, output Stream, signer *C } } -func (_self *Builder) ToArchive(stream Stream) error { +func (_self *Builder) ToArchive(stream Stream) *Error { _pointer := _self.ffiObject.incrementPointer("*Builder") defer _self.ffiObject.decrementPointer() - _, _uniffiErr := rustCallWithError(FfiConverterTypeError{}, func(_uniffiStatus *C.RustCallStatus) bool { + _, _uniffiErr := rustCallWithError[Error](FfiConverterError{}, func(_uniffiStatus *C.RustCallStatus) bool { C.uniffi_c2pa_fn_method_builder_to_archive( _pointer, FfiConverterCallbackInterfaceStreamINSTANCE.Lower(stream), _uniffiStatus) return false @@ -786,17 +806,16 @@ func (_self *Builder) ToArchive(stream Stream) error { return _uniffiErr } -func (_self *Builder) WithJson(json string) error { +func (_self *Builder) WithJson(json string) *Error { _pointer := _self.ffiObject.incrementPointer("*Builder") defer _self.ffiObject.decrementPointer() - _, _uniffiErr := rustCallWithError(FfiConverterTypeError{}, func(_uniffiStatus *C.RustCallStatus) bool { + _, _uniffiErr := rustCallWithError[Error](FfiConverterError{}, func(_uniffiStatus *C.RustCallStatus) bool { C.uniffi_c2pa_fn_method_builder_with_json( _pointer, FfiConverterStringINSTANCE.Lower(json), _uniffiStatus) return false }) return _uniffiErr } - func (object *Builder) Destroy() { runtime.SetFinalizer(object, nil) object.ffiObject.destroy() @@ -810,9 +829,13 @@ func (c FfiConverterBuilder) Lift(pointer unsafe.Pointer) *Builder { result := &Builder{ newFfiObject( pointer, + func(pointer unsafe.Pointer, status *C.RustCallStatus) unsafe.Pointer { + return C.uniffi_c2pa_fn_clone_builder(pointer, status) + }, func(pointer unsafe.Pointer, status *C.RustCallStatus) { C.uniffi_c2pa_fn_free_builder(pointer, status) - }), + }, + ), } runtime.SetFinalizer(result, (*Builder).Destroy) return result @@ -829,6 +852,7 @@ func (c FfiConverterBuilder) Lower(value *Builder) unsafe.Pointer { pointer := value.ffiObject.incrementPointer("*Builder") defer value.ffiObject.decrementPointer() return pointer + } func (c FfiConverterBuilder) Write(writer io.Writer, value *Builder) { @@ -841,13 +865,15 @@ func (_ FfiDestroyerBuilder) Destroy(value *Builder) { value.Destroy() } +type CallbackSignerInterface interface { +} type CallbackSigner struct { ffiObject FfiObject } func NewCallbackSigner(callback SignerCallback, alg SigningAlg, certs []byte, taUrl *string) *CallbackSigner { return FfiConverterCallbackSignerINSTANCE.Lift(rustCall(func(_uniffiStatus *C.RustCallStatus) unsafe.Pointer { - return C.uniffi_c2pa_fn_constructor_callbacksigner_new(FfiConverterCallbackInterfaceSignerCallbackINSTANCE.Lower(callback), FfiConverterTypeSigningAlgINSTANCE.Lower(alg), FfiConverterBytesINSTANCE.Lower(certs), FfiConverterOptionalStringINSTANCE.Lower(taUrl), _uniffiStatus) + return C.uniffi_c2pa_fn_constructor_callbacksigner_new(FfiConverterCallbackInterfaceSignerCallbackINSTANCE.Lower(callback), FfiConverterSigningAlgINSTANCE.Lower(alg), FfiConverterBytesINSTANCE.Lower(certs), FfiConverterOptionalStringINSTANCE.Lower(taUrl), _uniffiStatus) })) } @@ -864,9 +890,13 @@ func (c FfiConverterCallbackSigner) Lift(pointer unsafe.Pointer) *CallbackSigner result := &CallbackSigner{ newFfiObject( pointer, + func(pointer unsafe.Pointer, status *C.RustCallStatus) unsafe.Pointer { + return C.uniffi_c2pa_fn_clone_callbacksigner(pointer, status) + }, func(pointer unsafe.Pointer, status *C.RustCallStatus) { C.uniffi_c2pa_fn_free_callbacksigner(pointer, status) - }), + }, + ), } runtime.SetFinalizer(result, (*CallbackSigner).Destroy) return result @@ -883,6 +913,7 @@ func (c FfiConverterCallbackSigner) Lower(value *CallbackSigner) unsafe.Pointer pointer := value.ffiObject.incrementPointer("*CallbackSigner") defer value.ffiObject.decrementPointer() return pointer + } func (c FfiConverterCallbackSigner) Write(writer io.Writer, value *CallbackSigner) { @@ -895,6 +926,12 @@ func (_ FfiDestroyerCallbackSigner) Destroy(value *CallbackSigner) { value.Destroy() } +type ReaderInterface interface { + FromStream(format string, reader Stream) (string, *Error) + GetProvenanceCertChain() (string, *Error) + Json() (string, *Error) + ResourceToStream(uri string, stream Stream) (uint64, *Error) +} type Reader struct { ffiObject FfiObject } @@ -905,12 +942,14 @@ func NewReader() *Reader { })) } -func (_self *Reader) FromStream(format string, reader Stream) (string, error) { +func (_self *Reader) FromStream(format string, reader Stream) (string, *Error) { _pointer := _self.ffiObject.incrementPointer("*Reader") defer _self.ffiObject.decrementPointer() - _uniffiRV, _uniffiErr := rustCallWithError(FfiConverterTypeError{}, func(_uniffiStatus *C.RustCallStatus) RustBufferI { - return C.uniffi_c2pa_fn_method_reader_from_stream( - _pointer, FfiConverterStringINSTANCE.Lower(format), FfiConverterCallbackInterfaceStreamINSTANCE.Lower(reader), _uniffiStatus) + _uniffiRV, _uniffiErr := rustCallWithError[Error](FfiConverterError{}, func(_uniffiStatus *C.RustCallStatus) RustBufferI { + return GoRustBuffer{ + inner: C.uniffi_c2pa_fn_method_reader_from_stream( + _pointer, FfiConverterStringINSTANCE.Lower(format), FfiConverterCallbackInterfaceStreamINSTANCE.Lower(reader), _uniffiStatus), + } }) if _uniffiErr != nil { var _uniffiDefaultValue string @@ -920,12 +959,14 @@ func (_self *Reader) FromStream(format string, reader Stream) (string, error) { } } -func (_self *Reader) GetProvenanceCertChain() (string, error) { +func (_self *Reader) GetProvenanceCertChain() (string, *Error) { _pointer := _self.ffiObject.incrementPointer("*Reader") defer _self.ffiObject.decrementPointer() - _uniffiRV, _uniffiErr := rustCallWithError(FfiConverterTypeError{}, func(_uniffiStatus *C.RustCallStatus) RustBufferI { - return C.uniffi_c2pa_fn_method_reader_get_provenance_cert_chain( - _pointer, _uniffiStatus) + _uniffiRV, _uniffiErr := rustCallWithError[Error](FfiConverterError{}, func(_uniffiStatus *C.RustCallStatus) RustBufferI { + return GoRustBuffer{ + inner: C.uniffi_c2pa_fn_method_reader_get_provenance_cert_chain( + _pointer, _uniffiStatus), + } }) if _uniffiErr != nil { var _uniffiDefaultValue string @@ -935,12 +976,14 @@ func (_self *Reader) GetProvenanceCertChain() (string, error) { } } -func (_self *Reader) Json() (string, error) { +func (_self *Reader) Json() (string, *Error) { _pointer := _self.ffiObject.incrementPointer("*Reader") defer _self.ffiObject.decrementPointer() - _uniffiRV, _uniffiErr := rustCallWithError(FfiConverterTypeError{}, func(_uniffiStatus *C.RustCallStatus) RustBufferI { - return C.uniffi_c2pa_fn_method_reader_json( - _pointer, _uniffiStatus) + _uniffiRV, _uniffiErr := rustCallWithError[Error](FfiConverterError{}, func(_uniffiStatus *C.RustCallStatus) RustBufferI { + return GoRustBuffer{ + inner: C.uniffi_c2pa_fn_method_reader_json( + _pointer, _uniffiStatus), + } }) if _uniffiErr != nil { var _uniffiDefaultValue string @@ -950,10 +993,10 @@ func (_self *Reader) Json() (string, error) { } } -func (_self *Reader) ResourceToStream(uri string, stream Stream) (uint64, error) { +func (_self *Reader) ResourceToStream(uri string, stream Stream) (uint64, *Error) { _pointer := _self.ffiObject.incrementPointer("*Reader") defer _self.ffiObject.decrementPointer() - _uniffiRV, _uniffiErr := rustCallWithError(FfiConverterTypeError{}, func(_uniffiStatus *C.RustCallStatus) C.uint64_t { + _uniffiRV, _uniffiErr := rustCallWithError[Error](FfiConverterError{}, func(_uniffiStatus *C.RustCallStatus) C.uint64_t { return C.uniffi_c2pa_fn_method_reader_resource_to_stream( _pointer, FfiConverterStringINSTANCE.Lower(uri), FfiConverterCallbackInterfaceStreamINSTANCE.Lower(stream), _uniffiStatus) }) @@ -964,7 +1007,6 @@ func (_self *Reader) ResourceToStream(uri string, stream Stream) (uint64, error) return FfiConverterUint64INSTANCE.Lift(_uniffiRV), _uniffiErr } } - func (object *Reader) Destroy() { runtime.SetFinalizer(object, nil) object.ffiObject.destroy() @@ -978,9 +1020,13 @@ func (c FfiConverterReader) Lift(pointer unsafe.Pointer) *Reader { result := &Reader{ newFfiObject( pointer, + func(pointer unsafe.Pointer, status *C.RustCallStatus) unsafe.Pointer { + return C.uniffi_c2pa_fn_clone_reader(pointer, status) + }, func(pointer unsafe.Pointer, status *C.RustCallStatus) { C.uniffi_c2pa_fn_free_reader(pointer, status) - }), + }, + ), } runtime.SetFinalizer(result, (*Reader).Destroy) return result @@ -997,6 +1043,7 @@ func (c FfiConverterReader) Lower(value *Reader) unsafe.Pointer { pointer := value.ffiObject.incrementPointer("*Reader") defer value.ffiObject.decrementPointer() return pointer + } func (c FfiConverterReader) Write(writer io.Writer, value *Reader) { @@ -1013,6 +1060,16 @@ type Error struct { err error } +// Convience method to turn *Error into error +// Avoiding treating nil pointer as non nil error interface +func (err *Error) AsError() error { + if err == nil { + return nil + } else { + return err + } +} + func (err Error) Error() string { return fmt.Sprintf("Error: %s", err.err.Error()) } @@ -1047,11 +1104,12 @@ type ErrorAssertion struct { func NewErrorAssertion( reason string, ) *Error { - return &Error{ - err: &ErrorAssertion{ - Reason: reason, - }, - } + return &Error{err: &ErrorAssertion{ + Reason: reason}} +} + +func (e ErrorAssertion) destroy() { + FfiDestroyerString{}.Destroy(e.Reason) } func (err ErrorAssertion) Error() string { @@ -1074,11 +1132,12 @@ type ErrorAssertionNotFound struct { func NewErrorAssertionNotFound( reason string, ) *Error { - return &Error{ - err: &ErrorAssertionNotFound{ - Reason: reason, - }, - } + return &Error{err: &ErrorAssertionNotFound{ + Reason: reason}} +} + +func (e ErrorAssertionNotFound) destroy() { + FfiDestroyerString{}.Destroy(e.Reason) } func (err ErrorAssertionNotFound) Error() string { @@ -1101,11 +1160,12 @@ type ErrorDecoding struct { func NewErrorDecoding( reason string, ) *Error { - return &Error{ - err: &ErrorDecoding{ - Reason: reason, - }, - } + return &Error{err: &ErrorDecoding{ + Reason: reason}} +} + +func (e ErrorDecoding) destroy() { + FfiDestroyerString{}.Destroy(e.Reason) } func (err ErrorDecoding) Error() string { @@ -1128,11 +1188,12 @@ type ErrorEncoding struct { func NewErrorEncoding( reason string, ) *Error { - return &Error{ - err: &ErrorEncoding{ - Reason: reason, - }, - } + return &Error{err: &ErrorEncoding{ + Reason: reason}} +} + +func (e ErrorEncoding) destroy() { + FfiDestroyerString{}.Destroy(e.Reason) } func (err ErrorEncoding) Error() string { @@ -1155,11 +1216,12 @@ type ErrorFileNotFound struct { func NewErrorFileNotFound( reason string, ) *Error { - return &Error{ - err: &ErrorFileNotFound{ - Reason: reason, - }, - } + return &Error{err: &ErrorFileNotFound{ + Reason: reason}} +} + +func (e ErrorFileNotFound) destroy() { + FfiDestroyerString{}.Destroy(e.Reason) } func (err ErrorFileNotFound) Error() string { @@ -1182,11 +1244,12 @@ type ErrorIo struct { func NewErrorIo( reason string, ) *Error { - return &Error{ - err: &ErrorIo{ - Reason: reason, - }, - } + return &Error{err: &ErrorIo{ + Reason: reason}} +} + +func (e ErrorIo) destroy() { + FfiDestroyerString{}.Destroy(e.Reason) } func (err ErrorIo) Error() string { @@ -1209,11 +1272,12 @@ type ErrorJson struct { func NewErrorJson( reason string, ) *Error { - return &Error{ - err: &ErrorJson{ - Reason: reason, - }, - } + return &Error{err: &ErrorJson{ + Reason: reason}} +} + +func (e ErrorJson) destroy() { + FfiDestroyerString{}.Destroy(e.Reason) } func (err ErrorJson) Error() string { @@ -1236,11 +1300,12 @@ type ErrorManifest struct { func NewErrorManifest( reason string, ) *Error { - return &Error{ - err: &ErrorManifest{ - Reason: reason, - }, - } + return &Error{err: &ErrorManifest{ + Reason: reason}} +} + +func (e ErrorManifest) destroy() { + FfiDestroyerString{}.Destroy(e.Reason) } func (err ErrorManifest) Error() string { @@ -1263,11 +1328,12 @@ type ErrorManifestNotFound struct { func NewErrorManifestNotFound( reason string, ) *Error { - return &Error{ - err: &ErrorManifestNotFound{ - Reason: reason, - }, - } + return &Error{err: &ErrorManifestNotFound{ + Reason: reason}} +} + +func (e ErrorManifestNotFound) destroy() { + FfiDestroyerString{}.Destroy(e.Reason) } func (err ErrorManifestNotFound) Error() string { @@ -1290,11 +1356,12 @@ type ErrorNotSupported struct { func NewErrorNotSupported( reason string, ) *Error { - return &Error{ - err: &ErrorNotSupported{ - Reason: reason, - }, - } + return &Error{err: &ErrorNotSupported{ + Reason: reason}} +} + +func (e ErrorNotSupported) destroy() { + FfiDestroyerString{}.Destroy(e.Reason) } func (err ErrorNotSupported) Error() string { @@ -1317,11 +1384,12 @@ type ErrorOther struct { func NewErrorOther( reason string, ) *Error { - return &Error{ - err: &ErrorOther{ - Reason: reason, - }, - } + return &Error{err: &ErrorOther{ + Reason: reason}} +} + +func (e ErrorOther) destroy() { + FfiDestroyerString{}.Destroy(e.Reason) } func (err ErrorOther) Error() string { @@ -1344,11 +1412,12 @@ type ErrorRemoteManifest struct { func NewErrorRemoteManifest( reason string, ) *Error { - return &Error{ - err: &ErrorRemoteManifest{ - Reason: reason, - }, - } + return &Error{err: &ErrorRemoteManifest{ + Reason: reason}} +} + +func (e ErrorRemoteManifest) destroy() { + FfiDestroyerString{}.Destroy(e.Reason) } func (err ErrorRemoteManifest) Error() string { @@ -1371,11 +1440,12 @@ type ErrorResourceNotFound struct { func NewErrorResourceNotFound( reason string, ) *Error { - return &Error{ - err: &ErrorResourceNotFound{ - Reason: reason, - }, - } + return &Error{err: &ErrorResourceNotFound{ + Reason: reason}} +} + +func (e ErrorResourceNotFound) destroy() { + FfiDestroyerString{}.Destroy(e.Reason) } func (err ErrorResourceNotFound) Error() string { @@ -1395,9 +1465,10 @@ type ErrorRwLock struct { } func NewErrorRwLock() *Error { - return &Error{ - err: &ErrorRwLock{}, - } + return &Error{err: &ErrorRwLock{}} +} + +func (e ErrorRwLock) destroy() { } func (err ErrorRwLock) Error() string { @@ -1415,11 +1486,12 @@ type ErrorSignature struct { func NewErrorSignature( reason string, ) *Error { - return &Error{ - err: &ErrorSignature{ - Reason: reason, - }, - } + return &Error{err: &ErrorSignature{ + Reason: reason}} +} + +func (e ErrorSignature) destroy() { + FfiDestroyerString{}.Destroy(e.Reason) } func (err ErrorSignature) Error() string { @@ -1442,11 +1514,12 @@ type ErrorVerify struct { func NewErrorVerify( reason string, ) *Error { - return &Error{ - err: &ErrorVerify{ - Reason: reason, - }, - } + return &Error{err: &ErrorVerify{ + Reason: reason}} +} + +func (e ErrorVerify) destroy() { + FfiDestroyerString{}.Destroy(e.Reason) } func (err ErrorVerify) Error() string { @@ -1462,19 +1535,19 @@ func (self ErrorVerify) Is(target error) bool { return target == ErrErrorVerify } -type FfiConverterTypeError struct{} +type FfiConverterError struct{} -var FfiConverterTypeErrorINSTANCE = FfiConverterTypeError{} +var FfiConverterErrorINSTANCE = FfiConverterError{} -func (c FfiConverterTypeError) Lift(eb RustBufferI) error { - return LiftFromRustBuffer[error](c, eb) +func (c FfiConverterError) Lift(eb RustBufferI) *Error { + return LiftFromRustBuffer[*Error](c, eb) } -func (c FfiConverterTypeError) Lower(value *Error) RustBuffer { +func (c FfiConverterError) Lower(value *Error) C.RustBuffer { return LowerIntoRustBuffer[*Error](c, value) } -func (c FfiConverterTypeError) Read(reader io.Reader) error { +func (c FfiConverterError) Read(reader io.Reader) *Error { errorID := readUint32(reader) switch errorID { @@ -1541,11 +1614,11 @@ func (c FfiConverterTypeError) Read(reader io.Reader) error { Reason: FfiConverterStringINSTANCE.Read(reader), }} default: - panic(fmt.Sprintf("Unknown error code %d in FfiConverterTypeError.Read()", errorID)) + panic(fmt.Sprintf("Unknown error code %d in FfiConverterError.Read()", errorID)) } } -func (c FfiConverterTypeError) Write(writer io.Writer, value *Error) { +func (c FfiConverterError) Write(writer io.Writer, value *Error) { switch variantValue := value.err.(type) { case *ErrorAssertion: writeInt32(writer, 1) @@ -1596,7 +1669,49 @@ func (c FfiConverterTypeError) Write(writer io.Writer, value *Error) { FfiConverterStringINSTANCE.Write(writer, variantValue.Reason) default: _ = variantValue - panic(fmt.Sprintf("invalid error value `%v` in FfiConverterTypeError.Write", value)) + panic(fmt.Sprintf("invalid error value `%v` in FfiConverterError.Write", value)) + } +} + +type FfiDestroyerError struct{} + +func (_ FfiDestroyerError) Destroy(value *Error) { + switch variantValue := value.err.(type) { + case ErrorAssertion: + variantValue.destroy() + case ErrorAssertionNotFound: + variantValue.destroy() + case ErrorDecoding: + variantValue.destroy() + case ErrorEncoding: + variantValue.destroy() + case ErrorFileNotFound: + variantValue.destroy() + case ErrorIo: + variantValue.destroy() + case ErrorJson: + variantValue.destroy() + case ErrorManifest: + variantValue.destroy() + case ErrorManifestNotFound: + variantValue.destroy() + case ErrorNotSupported: + variantValue.destroy() + case ErrorOther: + variantValue.destroy() + case ErrorRemoteManifest: + variantValue.destroy() + case ErrorResourceNotFound: + variantValue.destroy() + case ErrorRwLock: + variantValue.destroy() + case ErrorSignature: + variantValue.destroy() + case ErrorVerify: + variantValue.destroy() + default: + _ = variantValue + panic(fmt.Sprintf("invalid error value `%v` in FfiDestroyerError.Destroy", value)) } } @@ -1608,29 +1723,29 @@ const ( SeekModeCurrent SeekMode = 3 ) -type FfiConverterTypeSeekMode struct{} +type FfiConverterSeekMode struct{} -var FfiConverterTypeSeekModeINSTANCE = FfiConverterTypeSeekMode{} +var FfiConverterSeekModeINSTANCE = FfiConverterSeekMode{} -func (c FfiConverterTypeSeekMode) Lift(rb RustBufferI) SeekMode { +func (c FfiConverterSeekMode) Lift(rb RustBufferI) SeekMode { return LiftFromRustBuffer[SeekMode](c, rb) } -func (c FfiConverterTypeSeekMode) Lower(value SeekMode) RustBuffer { +func (c FfiConverterSeekMode) Lower(value SeekMode) C.RustBuffer { return LowerIntoRustBuffer[SeekMode](c, value) } -func (FfiConverterTypeSeekMode) Read(reader io.Reader) SeekMode { +func (FfiConverterSeekMode) Read(reader io.Reader) SeekMode { id := readInt32(reader) return SeekMode(id) } -func (FfiConverterTypeSeekMode) Write(writer io.Writer, value SeekMode) { +func (FfiConverterSeekMode) Write(writer io.Writer, value SeekMode) { writeInt32(writer, int32(value)) } -type FfiDestroyerTypeSeekMode struct{} +type FfiDestroyerSeekMode struct{} -func (_ FfiDestroyerTypeSeekMode) Destroy(value SeekMode) { +func (_ FfiDestroyerSeekMode) Destroy(value SeekMode) { } type SigningAlg uint @@ -1646,32 +1761,68 @@ const ( SigningAlgEd25519 SigningAlg = 8 ) -type FfiConverterTypeSigningAlg struct{} +type FfiConverterSigningAlg struct{} -var FfiConverterTypeSigningAlgINSTANCE = FfiConverterTypeSigningAlg{} +var FfiConverterSigningAlgINSTANCE = FfiConverterSigningAlg{} -func (c FfiConverterTypeSigningAlg) Lift(rb RustBufferI) SigningAlg { +func (c FfiConverterSigningAlg) Lift(rb RustBufferI) SigningAlg { return LiftFromRustBuffer[SigningAlg](c, rb) } -func (c FfiConverterTypeSigningAlg) Lower(value SigningAlg) RustBuffer { +func (c FfiConverterSigningAlg) Lower(value SigningAlg) C.RustBuffer { return LowerIntoRustBuffer[SigningAlg](c, value) } -func (FfiConverterTypeSigningAlg) Read(reader io.Reader) SigningAlg { +func (FfiConverterSigningAlg) Read(reader io.Reader) SigningAlg { id := readInt32(reader) return SigningAlg(id) } -func (FfiConverterTypeSigningAlg) Write(writer io.Writer, value SigningAlg) { +func (FfiConverterSigningAlg) Write(writer io.Writer, value SigningAlg) { writeInt32(writer, int32(value)) } -type FfiDestroyerTypeSigningAlg struct{} +type FfiDestroyerSigningAlg struct{} + +func (_ FfiDestroyerSigningAlg) Destroy(value SigningAlg) { +} + +type SignerCallback interface { + Sign(data []byte) ([]byte, *Error) +} + +type FfiConverterCallbackInterfaceSignerCallback struct { + handleMap *concurrentHandleMap[SignerCallback] +} + +var FfiConverterCallbackInterfaceSignerCallbackINSTANCE = FfiConverterCallbackInterfaceSignerCallback{ + handleMap: newConcurrentHandleMap[SignerCallback](), +} + +func (c FfiConverterCallbackInterfaceSignerCallback) Lift(handle uint64) SignerCallback { + val, ok := c.handleMap.tryGet(handle) + if !ok { + panic(fmt.Errorf("no callback in handle map: %d", handle)) + } + return val +} -func (_ FfiDestroyerTypeSigningAlg) Destroy(value SigningAlg) { +func (c FfiConverterCallbackInterfaceSignerCallback) Read(reader io.Reader) SignerCallback { + return c.Lift(readUint64(reader)) } -type uniffiCallbackResult C.int32_t +func (c FfiConverterCallbackInterfaceSignerCallback) Lower(value SignerCallback) C.uint64_t { + return C.uint64_t(c.handleMap.insert(value)) +} + +func (c FfiConverterCallbackInterfaceSignerCallback) Write(writer io.Writer, value SignerCallback) { + writeUint64(writer, uint64(c.Lower(value))) +} + +type FfiDestroyerCallbackInterfaceSignerCallback struct{} + +func (FfiDestroyerCallbackInterfaceSignerCallback) Destroy(value SignerCallback) {} + +type uniffiCallbackResult C.int8_t const ( uniffiIdxCallbackFree uniffiCallbackResult = 0 @@ -1682,150 +1833,89 @@ const ( ) type concurrentHandleMap[T any] struct { - leftMap map[uint64]*T - rightMap map[*T]uint64 + handles map[uint64]T currentHandle uint64 lock sync.RWMutex } func newConcurrentHandleMap[T any]() *concurrentHandleMap[T] { return &concurrentHandleMap[T]{ - leftMap: map[uint64]*T{}, - rightMap: map[*T]uint64{}, + handles: map[uint64]T{}, } } -func (cm *concurrentHandleMap[T]) insert(obj *T) uint64 { +func (cm *concurrentHandleMap[T]) insert(obj T) uint64 { cm.lock.Lock() defer cm.lock.Unlock() - if existingHandle, ok := cm.rightMap[obj]; ok { - return existingHandle - } cm.currentHandle = cm.currentHandle + 1 - cm.leftMap[cm.currentHandle] = obj - cm.rightMap[obj] = cm.currentHandle + cm.handles[cm.currentHandle] = obj return cm.currentHandle } -func (cm *concurrentHandleMap[T]) remove(handle uint64) bool { +func (cm *concurrentHandleMap[T]) remove(handle uint64) { cm.lock.Lock() defer cm.lock.Unlock() - if val, ok := cm.leftMap[handle]; ok { - delete(cm.leftMap, handle) - delete(cm.rightMap, val) - } - return false + delete(cm.handles, handle) } -func (cm *concurrentHandleMap[T]) tryGet(handle uint64) (*T, bool) { +func (cm *concurrentHandleMap[T]) tryGet(handle uint64) (T, bool) { cm.lock.RLock() defer cm.lock.RUnlock() - val, ok := cm.leftMap[handle] + val, ok := cm.handles[handle] return val, ok } -type FfiConverterCallbackInterface[CallbackInterface any] struct { - handleMap *concurrentHandleMap[CallbackInterface] -} - -func (c *FfiConverterCallbackInterface[CallbackInterface]) drop(handle uint64) RustBuffer { - c.handleMap.remove(handle) - return RustBuffer{} -} - -func (c *FfiConverterCallbackInterface[CallbackInterface]) Lift(handle uint64) CallbackInterface { - val, ok := c.handleMap.tryGet(handle) +//export c2pa_cgo_dispatchCallbackInterfaceSignerCallbackMethod0 +func c2pa_cgo_dispatchCallbackInterfaceSignerCallbackMethod0(uniffiHandle C.uint64_t, data C.RustBuffer, uniffiOutReturn *C.RustBuffer, callStatus *C.RustCallStatus) { + handle := uint64(uniffiHandle) + uniffiObj, ok := FfiConverterCallbackInterfaceSignerCallbackINSTANCE.handleMap.tryGet(handle) if !ok { panic(fmt.Errorf("no callback in handle map: %d", handle)) } - return *val -} - -func (c *FfiConverterCallbackInterface[CallbackInterface]) Read(reader io.Reader) CallbackInterface { - return c.Lift(readUint64(reader)) -} - -func (c *FfiConverterCallbackInterface[CallbackInterface]) Lower(value CallbackInterface) C.uint64_t { - return C.uint64_t(c.handleMap.insert(&value)) -} -func (c *FfiConverterCallbackInterface[CallbackInterface]) Write(writer io.Writer, value CallbackInterface) { - writeUint64(writer, uint64(c.Lower(value))) -} - -type SignerCallback interface { - Sign(data []byte) ([]byte, *Error) -} - -// foreignCallbackCallbackInterfaceSignerCallback cannot be callable be a compiled function at a same time -type foreignCallbackCallbackInterfaceSignerCallback struct{} - -//export c2pa_cgo_SignerCallback -func c2pa_cgo_SignerCallback(handle C.uint64_t, method C.int32_t, argsPtr *C.uint8_t, argsLen C.int32_t, outBuf *C.RustBuffer) C.int32_t { - cb := FfiConverterCallbackInterfaceSignerCallbackINSTANCE.Lift(uint64(handle)) - switch method { - case 0: - // 0 means Rust is done with the callback, and the callback - // can be dropped by the foreign language. - *outBuf = FfiConverterCallbackInterfaceSignerCallbackINSTANCE.drop(uint64(handle)) - // See docs of ForeignCallback in `uniffi/src/ffi/foreigncallbacks.rs` - return C.int32_t(uniffiIdxCallbackFree) - - case 1: - var result uniffiCallbackResult - args := unsafe.Slice((*byte)(argsPtr), argsLen) - result = foreignCallbackCallbackInterfaceSignerCallback{}.InvokeSign(cb, args, outBuf) - return C.int32_t(result) - - default: - // This should never happen, because an out of bounds method index won't - // ever be used. Once we can catch errors, we should return an InternalException. - // https://github.com/mozilla/uniffi-rs/issues/351 - return C.int32_t(uniffiCallbackUnexpectedResultError) - } -} - -func (foreignCallbackCallbackInterfaceSignerCallback) InvokeSign(callback SignerCallback, args []byte, outBuf *C.RustBuffer) uniffiCallbackResult { - reader := bytes.NewReader(args) - result, err := callback.Sign(FfiConverterBytesINSTANCE.Read(reader)) + res, err := + uniffiObj.Sign( + FfiConverterBytesINSTANCE.Lift(GoRustBuffer{ + inner: data, + }), + ) if err != nil { // The only way to bypass an unexpected error is to bypass pointer to an empty // instance of the error if err.err == nil { - return uniffiCallbackUnexpectedResultError + *callStatus = C.RustCallStatus{ + code: C.int8_t(uniffiCallbackUnexpectedResultError), + } + return } - *outBuf = LowerIntoRustBuffer[*Error](FfiConverterTypeErrorINSTANCE, err) - return uniffiCallbackResultError + + *callStatus = C.RustCallStatus{ + code: C.int8_t(uniffiCallbackResultError), + errorBuf: FfiConverterErrorINSTANCE.Lower(err), + } + return } - *outBuf = LowerIntoRustBuffer[[]byte](FfiConverterBytesINSTANCE, result) - return uniffiCallbackResultSuccess -} -type FfiConverterCallbackInterfaceSignerCallback struct { - FfiConverterCallbackInterface[SignerCallback] + *uniffiOutReturn = FfiConverterBytesINSTANCE.Lower(res) } -var FfiConverterCallbackInterfaceSignerCallbackINSTANCE = &FfiConverterCallbackInterfaceSignerCallback{ - FfiConverterCallbackInterface: FfiConverterCallbackInterface[SignerCallback]{ - handleMap: newConcurrentHandleMap[SignerCallback](), - }, -} +var UniffiVTableCallbackInterfaceSignerCallbackINSTANCE = C.UniffiVTableCallbackInterfaceSignerCallback{ + sign: (C.UniffiCallbackInterfaceSignerCallbackMethod0)(C.c2pa_cgo_dispatchCallbackInterfaceSignerCallbackMethod0), -// This is a static function because only 1 instance is supported for registering -func (c *FfiConverterCallbackInterfaceSignerCallback) register() { - rustCall(func(status *C.RustCallStatus) int32 { - C.uniffi_c2pa_fn_init_callback_signercallback(C.ForeignCallback(C.c2pa_cgo_SignerCallback), status) - return 0 - }) + uniffiFree: (C.UniffiCallbackInterfaceFree)(C.c2pa_cgo_dispatchCallbackInterfaceSignerCallbackFree), } -type FfiDestroyerCallbackInterfaceSignerCallback struct{} +//export c2pa_cgo_dispatchCallbackInterfaceSignerCallbackFree +func c2pa_cgo_dispatchCallbackInterfaceSignerCallbackFree(handle C.uint64_t) { + FfiConverterCallbackInterfaceSignerCallbackINSTANCE.handleMap.remove(uint64(handle)) +} -func (FfiDestroyerCallbackInterfaceSignerCallback) Destroy(value SignerCallback) { +func (c FfiConverterCallbackInterfaceSignerCallback) register() { + C.uniffi_c2pa_fn_init_callback_vtable_signercallback(&UniffiVTableCallbackInterfaceSignerCallbackINSTANCE) } type Stream interface { @@ -1836,114 +1926,157 @@ type Stream interface { WriteStream(data []byte) (uint64, *Error) } -// foreignCallbackCallbackInterfaceStream cannot be callable be a compiled function at a same time -type foreignCallbackCallbackInterfaceStream struct{} - -//export c2pa_cgo_Stream -func c2pa_cgo_Stream(handle C.uint64_t, method C.int32_t, argsPtr *C.uint8_t, argsLen C.int32_t, outBuf *C.RustBuffer) C.int32_t { - cb := FfiConverterCallbackInterfaceStreamINSTANCE.Lift(uint64(handle)) - switch method { - case 0: - // 0 means Rust is done with the callback, and the callback - // can be dropped by the foreign language. - *outBuf = FfiConverterCallbackInterfaceStreamINSTANCE.drop(uint64(handle)) - // See docs of ForeignCallback in `uniffi/src/ffi/foreigncallbacks.rs` - return C.int32_t(uniffiIdxCallbackFree) +type FfiConverterCallbackInterfaceStream struct { + handleMap *concurrentHandleMap[Stream] +} - case 1: - var result uniffiCallbackResult - args := unsafe.Slice((*byte)(argsPtr), argsLen) - result = foreignCallbackCallbackInterfaceStream{}.InvokeReadStream(cb, args, outBuf) - return C.int32_t(result) - case 2: - var result uniffiCallbackResult - args := unsafe.Slice((*byte)(argsPtr), argsLen) - result = foreignCallbackCallbackInterfaceStream{}.InvokeSeekStream(cb, args, outBuf) - return C.int32_t(result) - case 3: - var result uniffiCallbackResult - args := unsafe.Slice((*byte)(argsPtr), argsLen) - result = foreignCallbackCallbackInterfaceStream{}.InvokeWriteStream(cb, args, outBuf) - return C.int32_t(result) +var FfiConverterCallbackInterfaceStreamINSTANCE = FfiConverterCallbackInterfaceStream{ + handleMap: newConcurrentHandleMap[Stream](), +} - default: - // This should never happen, because an out of bounds method index won't - // ever be used. Once we can catch errors, we should return an InternalException. - // https://github.com/mozilla/uniffi-rs/issues/351 - return C.int32_t(uniffiCallbackUnexpectedResultError) +func (c FfiConverterCallbackInterfaceStream) Lift(handle uint64) Stream { + val, ok := c.handleMap.tryGet(handle) + if !ok { + panic(fmt.Errorf("no callback in handle map: %d", handle)) } + return val +} + +func (c FfiConverterCallbackInterfaceStream) Read(reader io.Reader) Stream { + return c.Lift(readUint64(reader)) } -func (foreignCallbackCallbackInterfaceStream) InvokeReadStream(callback Stream, args []byte, outBuf *C.RustBuffer) uniffiCallbackResult { - reader := bytes.NewReader(args) - result, err := callback.ReadStream(FfiConverterUint64INSTANCE.Read(reader)) +func (c FfiConverterCallbackInterfaceStream) Lower(value Stream) C.uint64_t { + return C.uint64_t(c.handleMap.insert(value)) +} + +func (c FfiConverterCallbackInterfaceStream) Write(writer io.Writer, value Stream) { + writeUint64(writer, uint64(c.Lower(value))) +} + +type FfiDestroyerCallbackInterfaceStream struct{} + +func (FfiDestroyerCallbackInterfaceStream) Destroy(value Stream) {} + +//export c2pa_cgo_dispatchCallbackInterfaceStreamMethod0 +func c2pa_cgo_dispatchCallbackInterfaceStreamMethod0(uniffiHandle C.uint64_t, length C.uint64_t, uniffiOutReturn *C.RustBuffer, callStatus *C.RustCallStatus) { + handle := uint64(uniffiHandle) + uniffiObj, ok := FfiConverterCallbackInterfaceStreamINSTANCE.handleMap.tryGet(handle) + if !ok { + panic(fmt.Errorf("no callback in handle map: %d", handle)) + } + + res, err := + uniffiObj.ReadStream( + FfiConverterUint64INSTANCE.Lift(length), + ) if err != nil { // The only way to bypass an unexpected error is to bypass pointer to an empty // instance of the error if err.err == nil { - return uniffiCallbackUnexpectedResultError + *callStatus = C.RustCallStatus{ + code: C.int8_t(uniffiCallbackUnexpectedResultError), + } + return + } + + *callStatus = C.RustCallStatus{ + code: C.int8_t(uniffiCallbackResultError), + errorBuf: FfiConverterErrorINSTANCE.Lower(err), } - *outBuf = LowerIntoRustBuffer[*Error](FfiConverterTypeErrorINSTANCE, err) - return uniffiCallbackResultError + return } - *outBuf = LowerIntoRustBuffer[[]byte](FfiConverterBytesINSTANCE, result) - return uniffiCallbackResultSuccess + + *uniffiOutReturn = FfiConverterBytesINSTANCE.Lower(res) } -func (foreignCallbackCallbackInterfaceStream) InvokeSeekStream(callback Stream, args []byte, outBuf *C.RustBuffer) uniffiCallbackResult { - reader := bytes.NewReader(args) - result, err := callback.SeekStream(FfiConverterInt64INSTANCE.Read(reader), FfiConverterTypeSeekModeINSTANCE.Read(reader)) + +//export c2pa_cgo_dispatchCallbackInterfaceStreamMethod1 +func c2pa_cgo_dispatchCallbackInterfaceStreamMethod1(uniffiHandle C.uint64_t, pos C.int64_t, mode C.RustBuffer, uniffiOutReturn *C.uint64_t, callStatus *C.RustCallStatus) { + handle := uint64(uniffiHandle) + uniffiObj, ok := FfiConverterCallbackInterfaceStreamINSTANCE.handleMap.tryGet(handle) + if !ok { + panic(fmt.Errorf("no callback in handle map: %d", handle)) + } + + res, err := + uniffiObj.SeekStream( + FfiConverterInt64INSTANCE.Lift(pos), + FfiConverterSeekModeINSTANCE.Lift(GoRustBuffer{ + inner: mode, + }), + ) if err != nil { // The only way to bypass an unexpected error is to bypass pointer to an empty // instance of the error if err.err == nil { - return uniffiCallbackUnexpectedResultError + *callStatus = C.RustCallStatus{ + code: C.int8_t(uniffiCallbackUnexpectedResultError), + } + return } - *outBuf = LowerIntoRustBuffer[*Error](FfiConverterTypeErrorINSTANCE, err) - return uniffiCallbackResultError + + *callStatus = C.RustCallStatus{ + code: C.int8_t(uniffiCallbackResultError), + errorBuf: FfiConverterErrorINSTANCE.Lower(err), + } + return } - *outBuf = LowerIntoRustBuffer[uint64](FfiConverterUint64INSTANCE, result) - return uniffiCallbackResultSuccess + + *uniffiOutReturn = FfiConverterUint64INSTANCE.Lower(res) } -func (foreignCallbackCallbackInterfaceStream) InvokeWriteStream(callback Stream, args []byte, outBuf *C.RustBuffer) uniffiCallbackResult { - reader := bytes.NewReader(args) - result, err := callback.WriteStream(FfiConverterBytesINSTANCE.Read(reader)) + +//export c2pa_cgo_dispatchCallbackInterfaceStreamMethod2 +func c2pa_cgo_dispatchCallbackInterfaceStreamMethod2(uniffiHandle C.uint64_t, data C.RustBuffer, uniffiOutReturn *C.uint64_t, callStatus *C.RustCallStatus) { + handle := uint64(uniffiHandle) + uniffiObj, ok := FfiConverterCallbackInterfaceStreamINSTANCE.handleMap.tryGet(handle) + if !ok { + panic(fmt.Errorf("no callback in handle map: %d", handle)) + } + + res, err := + uniffiObj.WriteStream( + FfiConverterBytesINSTANCE.Lift(GoRustBuffer{ + inner: data, + }), + ) if err != nil { // The only way to bypass an unexpected error is to bypass pointer to an empty // instance of the error if err.err == nil { - return uniffiCallbackUnexpectedResultError + *callStatus = C.RustCallStatus{ + code: C.int8_t(uniffiCallbackUnexpectedResultError), + } + return + } + + *callStatus = C.RustCallStatus{ + code: C.int8_t(uniffiCallbackResultError), + errorBuf: FfiConverterErrorINSTANCE.Lower(err), } - *outBuf = LowerIntoRustBuffer[*Error](FfiConverterTypeErrorINSTANCE, err) - return uniffiCallbackResultError + return } - *outBuf = LowerIntoRustBuffer[uint64](FfiConverterUint64INSTANCE, result) - return uniffiCallbackResultSuccess -} -type FfiConverterCallbackInterfaceStream struct { - FfiConverterCallbackInterface[Stream] + *uniffiOutReturn = FfiConverterUint64INSTANCE.Lower(res) } -var FfiConverterCallbackInterfaceStreamINSTANCE = &FfiConverterCallbackInterfaceStream{ - FfiConverterCallbackInterface: FfiConverterCallbackInterface[Stream]{ - handleMap: newConcurrentHandleMap[Stream](), - }, -} +var UniffiVTableCallbackInterfaceStreamINSTANCE = C.UniffiVTableCallbackInterfaceStream{ + readStream: (C.UniffiCallbackInterfaceStreamMethod0)(C.c2pa_cgo_dispatchCallbackInterfaceStreamMethod0), + seekStream: (C.UniffiCallbackInterfaceStreamMethod1)(C.c2pa_cgo_dispatchCallbackInterfaceStreamMethod1), + writeStream: (C.UniffiCallbackInterfaceStreamMethod2)(C.c2pa_cgo_dispatchCallbackInterfaceStreamMethod2), -// This is a static function because only 1 instance is supported for registering -func (c *FfiConverterCallbackInterfaceStream) register() { - rustCall(func(status *C.RustCallStatus) int32 { - C.uniffi_c2pa_fn_init_callback_stream(C.ForeignCallback(C.c2pa_cgo_Stream), status) - return 0 - }) + uniffiFree: (C.UniffiCallbackInterfaceFree)(C.c2pa_cgo_dispatchCallbackInterfaceStreamFree), } -type FfiDestroyerCallbackInterfaceStream struct{} +//export c2pa_cgo_dispatchCallbackInterfaceStreamFree +func c2pa_cgo_dispatchCallbackInterfaceStreamFree(handle C.uint64_t) { + FfiConverterCallbackInterfaceStreamINSTANCE.handleMap.remove(uint64(handle)) +} -func (FfiDestroyerCallbackInterfaceStream) Destroy(value Stream) { +func (c FfiConverterCallbackInterfaceStream) register() { + C.uniffi_c2pa_fn_init_callback_vtable_stream(&UniffiVTableCallbackInterfaceStreamINSTANCE) } type FfiConverterOptionalString struct{} @@ -1962,7 +2095,7 @@ func (_ FfiConverterOptionalString) Read(reader io.Reader) *string { return &temp } -func (c FfiConverterOptionalString) Lower(value *string) RustBuffer { +func (c FfiConverterOptionalString) Lower(value *string) C.RustBuffer { return LowerIntoRustBuffer[*string](c, value) } @@ -1985,12 +2118,16 @@ func (_ FfiDestroyerOptionalString) Destroy(value *string) { func SdkVersion() string { return FfiConverterStringINSTANCE.Lift(rustCall(func(_uniffiStatus *C.RustCallStatus) RustBufferI { - return C.uniffi_c2pa_fn_func_sdk_version(_uniffiStatus) + return GoRustBuffer{ + inner: C.uniffi_c2pa_fn_func_sdk_version(_uniffiStatus), + } })) } func Version() string { return FfiConverterStringINSTANCE.Lift(rustCall(func(_uniffiStatus *C.RustCallStatus) RustBufferI { - return C.uniffi_c2pa_fn_func_version(_uniffiStatus) + return GoRustBuffer{ + inner: C.uniffi_c2pa_fn_func_version(_uniffiStatus), + } })) } diff --git a/pkg/c2pa/generated/c2pa/c2pa.h b/pkg/c2pa/generated/c2pa/c2pa.h index 05f4d3b3..6e057308 100644 --- a/pkg/c2pa/generated/c2pa/c2pa.h +++ b/pkg/c2pa/generated/c2pa/c2pa.h @@ -24,25 +24,11 @@ // ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V6 in this file. ⚠️ typedef struct RustBuffer { - int32_t capacity; - int32_t len; + uint64_t capacity; + uint64_t len; uint8_t *data; } RustBuffer; -typedef int32_t (*ForeignCallback)(uint64_t, int32_t, uint8_t *, int32_t, RustBuffer *); - -// Task defined in Rust that Go executes -typedef void (*RustTaskCallback)(const void *, int8_t); - -// Callback to execute Rust tasks using a Go routine -// -// Args: -// executor: ForeignExecutor lowered into a uint64_t value -// delay: Delay in MS -// task: RustTaskCallback to call -// task_data: data to pass the task callback -typedef int8_t (*ForeignExecutorCallback)(uint64_t, uint32_t, RustTaskCallback, void *); - typedef struct ForeignBytes { int32_t len; const uint8_t *data; @@ -54,515 +40,941 @@ typedef struct RustCallStatus { RustBuffer errorBuf; } RustCallStatus; -// Continuation callback for UniFFI Futures -typedef void (*RustFutureContinuation)(void * , int8_t); - -// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️ -// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V6 in this file. ⚠️ -#endif // def UNIFFI_SHARED_H - -// Needed because we can't execute the callback directly from go. -void cgo_rust_task_callback_bridge_c2pa(RustTaskCallback, const void *, int8_t); - -int8_t uniffiForeignExecutorCallbackc2pa(uint64_t, uint32_t, RustTaskCallback, void*); - -void uniffiFutureContinuationCallbackc2pa(void*, int8_t); - -void uniffi_c2pa_fn_free_builder( - void* ptr, - RustCallStatus* out_status -); - -void* uniffi_c2pa_fn_constructor_builder_new( - RustCallStatus* out_status -); - -void uniffi_c2pa_fn_method_builder_add_ingredient( - void* ptr, - RustBuffer ingredient_json, - RustBuffer format, - uint64_t stream, - RustCallStatus* out_status -); - -void uniffi_c2pa_fn_method_builder_add_resource( - void* ptr, - RustBuffer uri, - uint64_t stream, - RustCallStatus* out_status -); - -void uniffi_c2pa_fn_method_builder_from_archive( - void* ptr, - uint64_t stream, - RustCallStatus* out_status -); - -RustBuffer uniffi_c2pa_fn_method_builder_sign( - void* ptr, - RustBuffer format, - uint64_t input, - uint64_t output, - void* signer, - RustCallStatus* out_status -); - -void uniffi_c2pa_fn_method_builder_to_archive( - void* ptr, - uint64_t stream, - RustCallStatus* out_status -); - -void uniffi_c2pa_fn_method_builder_with_json( - void* ptr, - RustBuffer json, - RustCallStatus* out_status -); - -void uniffi_c2pa_fn_free_callbacksigner( - void* ptr, - RustCallStatus* out_status -); - -void* uniffi_c2pa_fn_constructor_callbacksigner_new( - uint64_t callback, - RustBuffer alg, - RustBuffer certs, - RustBuffer ta_url, - RustCallStatus* out_status -); - -void uniffi_c2pa_fn_free_reader( - void* ptr, - RustCallStatus* out_status -); - -void* uniffi_c2pa_fn_constructor_reader_new( - RustCallStatus* out_status -); - -RustBuffer uniffi_c2pa_fn_method_reader_from_stream( - void* ptr, - RustBuffer format, - uint64_t reader, - RustCallStatus* out_status -); - -RustBuffer uniffi_c2pa_fn_method_reader_get_provenance_cert_chain( - void* ptr, - RustCallStatus* out_status -); - -RustBuffer uniffi_c2pa_fn_method_reader_json( - void* ptr, - RustCallStatus* out_status -); - -uint64_t uniffi_c2pa_fn_method_reader_resource_to_stream( - void* ptr, - RustBuffer uri, - uint64_t stream, - RustCallStatus* out_status -); - -void uniffi_c2pa_fn_init_callback_signercallback( - ForeignCallback callback_stub, - RustCallStatus* out_status -); - -void uniffi_c2pa_fn_init_callback_stream( - ForeignCallback callback_stub, - RustCallStatus* out_status -); - -RustBuffer uniffi_c2pa_fn_func_sdk_version( - RustCallStatus* out_status -); - -RustBuffer uniffi_c2pa_fn_func_version( - RustCallStatus* out_status -); - -RustBuffer ffi_c2pa_rustbuffer_alloc( - int32_t size, - RustCallStatus* out_status -); - -RustBuffer ffi_c2pa_rustbuffer_from_bytes( - ForeignBytes bytes, - RustCallStatus* out_status -); - -void ffi_c2pa_rustbuffer_free( - RustBuffer buf, - RustCallStatus* out_status -); - -RustBuffer ffi_c2pa_rustbuffer_reserve( - RustBuffer buf, - int32_t additional, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_continuation_callback_set( - RustFutureContinuation callback, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_poll_u8( - void* handle, - void* uniffi_callback, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_cancel_u8( - void* handle, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_free_u8( - void* handle, - RustCallStatus* out_status -); - -uint8_t ffi_c2pa_rust_future_complete_u8( - void* handle, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_poll_i8( - void* handle, - void* uniffi_callback, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_cancel_i8( - void* handle, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_free_i8( - void* handle, - RustCallStatus* out_status -); - -int8_t ffi_c2pa_rust_future_complete_i8( - void* handle, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_poll_u16( - void* handle, - void* uniffi_callback, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_cancel_u16( - void* handle, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_free_u16( - void* handle, - RustCallStatus* out_status -); - -uint16_t ffi_c2pa_rust_future_complete_u16( - void* handle, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_poll_i16( - void* handle, - void* uniffi_callback, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_cancel_i16( - void* handle, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_free_i16( - void* handle, - RustCallStatus* out_status -); - -int16_t ffi_c2pa_rust_future_complete_i16( - void* handle, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_poll_u32( - void* handle, - void* uniffi_callback, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_cancel_u32( - void* handle, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_free_u32( - void* handle, - RustCallStatus* out_status -); - -uint32_t ffi_c2pa_rust_future_complete_u32( - void* handle, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_poll_i32( - void* handle, - void* uniffi_callback, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_cancel_i32( - void* handle, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_free_i32( - void* handle, - RustCallStatus* out_status -); - -int32_t ffi_c2pa_rust_future_complete_i32( - void* handle, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_poll_u64( - void* handle, - void* uniffi_callback, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_cancel_u64( - void* handle, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_free_u64( - void* handle, - RustCallStatus* out_status -); - -uint64_t ffi_c2pa_rust_future_complete_u64( - void* handle, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_poll_i64( - void* handle, - void* uniffi_callback, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_cancel_i64( - void* handle, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_free_i64( - void* handle, - RustCallStatus* out_status -); - -int64_t ffi_c2pa_rust_future_complete_i64( - void* handle, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_poll_f32( - void* handle, - void* uniffi_callback, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_cancel_f32( - void* handle, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_free_f32( - void* handle, - RustCallStatus* out_status -); - -float ffi_c2pa_rust_future_complete_f32( - void* handle, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_poll_f64( - void* handle, - void* uniffi_callback, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_cancel_f64( - void* handle, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_free_f64( - void* handle, - RustCallStatus* out_status -); - -double ffi_c2pa_rust_future_complete_f64( - void* handle, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_poll_pointer( - void* handle, - void* uniffi_callback, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_cancel_pointer( - void* handle, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_free_pointer( - void* handle, - RustCallStatus* out_status -); - -void* ffi_c2pa_rust_future_complete_pointer( - void* handle, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_poll_rust_buffer( - void* handle, - void* uniffi_callback, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_cancel_rust_buffer( - void* handle, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_free_rust_buffer( - void* handle, - RustCallStatus* out_status -); - -RustBuffer ffi_c2pa_rust_future_complete_rust_buffer( - void* handle, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_poll_void( - void* handle, - void* uniffi_callback, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_cancel_void( - void* handle, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_free_void( - void* handle, - RustCallStatus* out_status -); - -void ffi_c2pa_rust_future_complete_void( - void* handle, - RustCallStatus* out_status -); - -uint16_t uniffi_c2pa_checksum_func_sdk_version( - RustCallStatus* out_status -); - -uint16_t uniffi_c2pa_checksum_func_version( - RustCallStatus* out_status -); - -uint16_t uniffi_c2pa_checksum_method_builder_add_ingredient( - RustCallStatus* out_status -); - -uint16_t uniffi_c2pa_checksum_method_builder_add_resource( - RustCallStatus* out_status -); - -uint16_t uniffi_c2pa_checksum_method_builder_from_archive( - RustCallStatus* out_status -); - -uint16_t uniffi_c2pa_checksum_method_builder_sign( - RustCallStatus* out_status -); - -uint16_t uniffi_c2pa_checksum_method_builder_to_archive( - RustCallStatus* out_status -); - -uint16_t uniffi_c2pa_checksum_method_builder_with_json( - RustCallStatus* out_status -); - -uint16_t uniffi_c2pa_checksum_method_reader_from_stream( - RustCallStatus* out_status -); - -uint16_t uniffi_c2pa_checksum_method_reader_get_provenance_cert_chain( - RustCallStatus* out_status -); - -uint16_t uniffi_c2pa_checksum_method_reader_json( - RustCallStatus* out_status -); - -uint16_t uniffi_c2pa_checksum_method_reader_resource_to_stream( - RustCallStatus* out_status -); - -uint16_t uniffi_c2pa_checksum_constructor_builder_new( - RustCallStatus* out_status -); - -uint16_t uniffi_c2pa_checksum_constructor_callbacksigner_new( - RustCallStatus* out_status -); - -uint16_t uniffi_c2pa_checksum_constructor_reader_new( - RustCallStatus* out_status -); - -uint16_t uniffi_c2pa_checksum_method_signercallback_sign( - RustCallStatus* out_status -); - -uint16_t uniffi_c2pa_checksum_method_stream_read_stream( - RustCallStatus* out_status -); - -uint16_t uniffi_c2pa_checksum_method_stream_seek_stream( - RustCallStatus* out_status -); - -uint16_t uniffi_c2pa_checksum_method_stream_write_stream( - RustCallStatus* out_status -); - -uint32_t ffi_c2pa_uniffi_contract_version( - RustCallStatus* out_status -); - - -int32_t c2pa_cgo_SignerCallback(uint64_t, int32_t, uint8_t *, int32_t, RustBuffer *); -int32_t c2pa_cgo_Stream(uint64_t, int32_t, uint8_t *, int32_t, RustBuffer *); - +#endif // UNIFFI_SHARED_H + + +#ifndef UNIFFI_FFIDEF_RUST_FUTURE_CONTINUATION_CALLBACK +#define UNIFFI_FFIDEF_RUST_FUTURE_CONTINUATION_CALLBACK +typedef void (*UniffiRustFutureContinuationCallback)(uint64_t data, int8_t poll_result); + +// Making function static works arround: +// https://github.com/golang/go/issues/11263 +static void call_UniffiRustFutureContinuationCallback( + UniffiRustFutureContinuationCallback cb, uint64_t data, int8_t poll_result) +{ + return cb(data, poll_result); +} + + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_FREE +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_FREE +typedef void (*UniffiForeignFutureFree)(uint64_t handle); + +// Making function static works arround: +// https://github.com/golang/go/issues/11263 +static void call_UniffiForeignFutureFree( + UniffiForeignFutureFree cb, uint64_t handle) +{ + return cb(handle); +} + + +#endif +#ifndef UNIFFI_FFIDEF_CALLBACK_INTERFACE_FREE +#define UNIFFI_FFIDEF_CALLBACK_INTERFACE_FREE +typedef void (*UniffiCallbackInterfaceFree)(uint64_t handle); + +// Making function static works arround: +// https://github.com/golang/go/issues/11263 +static void call_UniffiCallbackInterfaceFree( + UniffiCallbackInterfaceFree cb, uint64_t handle) +{ + return cb(handle); +} + + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE +#define UNIFFI_FFIDEF_FOREIGN_FUTURE +typedef struct UniffiForeignFuture { + uint64_t handle; + UniffiForeignFutureFree free; +} UniffiForeignFuture; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U8 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U8 +typedef struct UniffiForeignFutureStructU8 { + uint8_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructU8; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U8 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U8 +typedef void (*UniffiForeignFutureCompleteU8)(uint64_t callback_data, UniffiForeignFutureStructU8 result); + +// Making function static works arround: +// https://github.com/golang/go/issues/11263 +static void call_UniffiForeignFutureCompleteU8( + UniffiForeignFutureCompleteU8 cb, uint64_t callback_data, UniffiForeignFutureStructU8 result) +{ + return cb(callback_data, result); +} + + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I8 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I8 +typedef struct UniffiForeignFutureStructI8 { + int8_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructI8; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I8 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I8 +typedef void (*UniffiForeignFutureCompleteI8)(uint64_t callback_data, UniffiForeignFutureStructI8 result); + +// Making function static works arround: +// https://github.com/golang/go/issues/11263 +static void call_UniffiForeignFutureCompleteI8( + UniffiForeignFutureCompleteI8 cb, uint64_t callback_data, UniffiForeignFutureStructI8 result) +{ + return cb(callback_data, result); +} + + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U16 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U16 +typedef struct UniffiForeignFutureStructU16 { + uint16_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructU16; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U16 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U16 +typedef void (*UniffiForeignFutureCompleteU16)(uint64_t callback_data, UniffiForeignFutureStructU16 result); + +// Making function static works arround: +// https://github.com/golang/go/issues/11263 +static void call_UniffiForeignFutureCompleteU16( + UniffiForeignFutureCompleteU16 cb, uint64_t callback_data, UniffiForeignFutureStructU16 result) +{ + return cb(callback_data, result); +} + + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I16 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I16 +typedef struct UniffiForeignFutureStructI16 { + int16_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructI16; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I16 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I16 +typedef void (*UniffiForeignFutureCompleteI16)(uint64_t callback_data, UniffiForeignFutureStructI16 result); + +// Making function static works arround: +// https://github.com/golang/go/issues/11263 +static void call_UniffiForeignFutureCompleteI16( + UniffiForeignFutureCompleteI16 cb, uint64_t callback_data, UniffiForeignFutureStructI16 result) +{ + return cb(callback_data, result); +} + + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U32 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U32 +typedef struct UniffiForeignFutureStructU32 { + uint32_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructU32; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U32 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U32 +typedef void (*UniffiForeignFutureCompleteU32)(uint64_t callback_data, UniffiForeignFutureStructU32 result); + +// Making function static works arround: +// https://github.com/golang/go/issues/11263 +static void call_UniffiForeignFutureCompleteU32( + UniffiForeignFutureCompleteU32 cb, uint64_t callback_data, UniffiForeignFutureStructU32 result) +{ + return cb(callback_data, result); +} + + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I32 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I32 +typedef struct UniffiForeignFutureStructI32 { + int32_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructI32; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I32 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I32 +typedef void (*UniffiForeignFutureCompleteI32)(uint64_t callback_data, UniffiForeignFutureStructI32 result); + +// Making function static works arround: +// https://github.com/golang/go/issues/11263 +static void call_UniffiForeignFutureCompleteI32( + UniffiForeignFutureCompleteI32 cb, uint64_t callback_data, UniffiForeignFutureStructI32 result) +{ + return cb(callback_data, result); +} + + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U64 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U64 +typedef struct UniffiForeignFutureStructU64 { + uint64_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructU64; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U64 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U64 +typedef void (*UniffiForeignFutureCompleteU64)(uint64_t callback_data, UniffiForeignFutureStructU64 result); + +// Making function static works arround: +// https://github.com/golang/go/issues/11263 +static void call_UniffiForeignFutureCompleteU64( + UniffiForeignFutureCompleteU64 cb, uint64_t callback_data, UniffiForeignFutureStructU64 result) +{ + return cb(callback_data, result); +} + + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I64 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I64 +typedef struct UniffiForeignFutureStructI64 { + int64_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructI64; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I64 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I64 +typedef void (*UniffiForeignFutureCompleteI64)(uint64_t callback_data, UniffiForeignFutureStructI64 result); + +// Making function static works arround: +// https://github.com/golang/go/issues/11263 +static void call_UniffiForeignFutureCompleteI64( + UniffiForeignFutureCompleteI64 cb, uint64_t callback_data, UniffiForeignFutureStructI64 result) +{ + return cb(callback_data, result); +} + + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_F32 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_F32 +typedef struct UniffiForeignFutureStructF32 { + float returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructF32; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_F32 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_F32 +typedef void (*UniffiForeignFutureCompleteF32)(uint64_t callback_data, UniffiForeignFutureStructF32 result); + +// Making function static works arround: +// https://github.com/golang/go/issues/11263 +static void call_UniffiForeignFutureCompleteF32( + UniffiForeignFutureCompleteF32 cb, uint64_t callback_data, UniffiForeignFutureStructF32 result) +{ + return cb(callback_data, result); +} + + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_F64 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_F64 +typedef struct UniffiForeignFutureStructF64 { + double returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructF64; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_F64 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_F64 +typedef void (*UniffiForeignFutureCompleteF64)(uint64_t callback_data, UniffiForeignFutureStructF64 result); + +// Making function static works arround: +// https://github.com/golang/go/issues/11263 +static void call_UniffiForeignFutureCompleteF64( + UniffiForeignFutureCompleteF64 cb, uint64_t callback_data, UniffiForeignFutureStructF64 result) +{ + return cb(callback_data, result); +} + + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_POINTER +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_POINTER +typedef struct UniffiForeignFutureStructPointer { + void* returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructPointer; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_POINTER +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_POINTER +typedef void (*UniffiForeignFutureCompletePointer)(uint64_t callback_data, UniffiForeignFutureStructPointer result); + +// Making function static works arround: +// https://github.com/golang/go/issues/11263 +static void call_UniffiForeignFutureCompletePointer( + UniffiForeignFutureCompletePointer cb, uint64_t callback_data, UniffiForeignFutureStructPointer result) +{ + return cb(callback_data, result); +} + + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_RUST_BUFFER +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_RUST_BUFFER +typedef struct UniffiForeignFutureStructRustBuffer { + RustBuffer returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructRustBuffer; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_RUST_BUFFER +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_RUST_BUFFER +typedef void (*UniffiForeignFutureCompleteRustBuffer)(uint64_t callback_data, UniffiForeignFutureStructRustBuffer result); + +// Making function static works arround: +// https://github.com/golang/go/issues/11263 +static void call_UniffiForeignFutureCompleteRustBuffer( + UniffiForeignFutureCompleteRustBuffer cb, uint64_t callback_data, UniffiForeignFutureStructRustBuffer result) +{ + return cb(callback_data, result); +} + + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_VOID +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_VOID +typedef struct UniffiForeignFutureStructVoid { + RustCallStatus callStatus; +} UniffiForeignFutureStructVoid; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_VOID +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_VOID +typedef void (*UniffiForeignFutureCompleteVoid)(uint64_t callback_data, UniffiForeignFutureStructVoid result); + +// Making function static works arround: +// https://github.com/golang/go/issues/11263 +static void call_UniffiForeignFutureCompleteVoid( + UniffiForeignFutureCompleteVoid cb, uint64_t callback_data, UniffiForeignFutureStructVoid result) +{ + return cb(callback_data, result); +} + + +#endif +#ifndef UNIFFI_FFIDEF_CALLBACK_INTERFACE_SIGNER_CALLBACK_METHOD0 +#define UNIFFI_FFIDEF_CALLBACK_INTERFACE_SIGNER_CALLBACK_METHOD0 +typedef void (*UniffiCallbackInterfaceSignerCallbackMethod0)(uint64_t uniffi_handle, RustBuffer data, RustBuffer* uniffi_out_return, RustCallStatus* callStatus ); + +// Making function static works arround: +// https://github.com/golang/go/issues/11263 +static void call_UniffiCallbackInterfaceSignerCallbackMethod0( + UniffiCallbackInterfaceSignerCallbackMethod0 cb, uint64_t uniffi_handle, RustBuffer data, RustBuffer* uniffi_out_return, RustCallStatus* callStatus ) +{ + return cb(uniffi_handle, data, uniffi_out_return, callStatus ); +} + + +#endif +#ifndef UNIFFI_FFIDEF_CALLBACK_INTERFACE_STREAM_METHOD0 +#define UNIFFI_FFIDEF_CALLBACK_INTERFACE_STREAM_METHOD0 +typedef void (*UniffiCallbackInterfaceStreamMethod0)(uint64_t uniffi_handle, uint64_t length, RustBuffer* uniffi_out_return, RustCallStatus* callStatus ); + +// Making function static works arround: +// https://github.com/golang/go/issues/11263 +static void call_UniffiCallbackInterfaceStreamMethod0( + UniffiCallbackInterfaceStreamMethod0 cb, uint64_t uniffi_handle, uint64_t length, RustBuffer* uniffi_out_return, RustCallStatus* callStatus ) +{ + return cb(uniffi_handle, length, uniffi_out_return, callStatus ); +} + + +#endif +#ifndef UNIFFI_FFIDEF_CALLBACK_INTERFACE_STREAM_METHOD1 +#define UNIFFI_FFIDEF_CALLBACK_INTERFACE_STREAM_METHOD1 +typedef void (*UniffiCallbackInterfaceStreamMethod1)(uint64_t uniffi_handle, int64_t pos, RustBuffer mode, uint64_t* uniffi_out_return, RustCallStatus* callStatus ); + +// Making function static works arround: +// https://github.com/golang/go/issues/11263 +static void call_UniffiCallbackInterfaceStreamMethod1( + UniffiCallbackInterfaceStreamMethod1 cb, uint64_t uniffi_handle, int64_t pos, RustBuffer mode, uint64_t* uniffi_out_return, RustCallStatus* callStatus ) +{ + return cb(uniffi_handle, pos, mode, uniffi_out_return, callStatus ); +} + + +#endif +#ifndef UNIFFI_FFIDEF_CALLBACK_INTERFACE_STREAM_METHOD2 +#define UNIFFI_FFIDEF_CALLBACK_INTERFACE_STREAM_METHOD2 +typedef void (*UniffiCallbackInterfaceStreamMethod2)(uint64_t uniffi_handle, RustBuffer data, uint64_t* uniffi_out_return, RustCallStatus* callStatus ); + +// Making function static works arround: +// https://github.com/golang/go/issues/11263 +static void call_UniffiCallbackInterfaceStreamMethod2( + UniffiCallbackInterfaceStreamMethod2 cb, uint64_t uniffi_handle, RustBuffer data, uint64_t* uniffi_out_return, RustCallStatus* callStatus ) +{ + return cb(uniffi_handle, data, uniffi_out_return, callStatus ); +} + + +#endif +#ifndef UNIFFI_FFIDEF_V_TABLE_CALLBACK_INTERFACE_SIGNER_CALLBACK +#define UNIFFI_FFIDEF_V_TABLE_CALLBACK_INTERFACE_SIGNER_CALLBACK +typedef struct UniffiVTableCallbackInterfaceSignerCallback { + UniffiCallbackInterfaceSignerCallbackMethod0 sign; + UniffiCallbackInterfaceFree uniffiFree; +} UniffiVTableCallbackInterfaceSignerCallback; + +#endif +#ifndef UNIFFI_FFIDEF_V_TABLE_CALLBACK_INTERFACE_STREAM +#define UNIFFI_FFIDEF_V_TABLE_CALLBACK_INTERFACE_STREAM +typedef struct UniffiVTableCallbackInterfaceStream { + UniffiCallbackInterfaceStreamMethod0 readStream; + UniffiCallbackInterfaceStreamMethod1 seekStream; + UniffiCallbackInterfaceStreamMethod2 writeStream; + UniffiCallbackInterfaceFree uniffiFree; +} UniffiVTableCallbackInterfaceStream; + +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_C2PA_FN_CLONE_BUILDER +#define UNIFFI_FFIDEF_UNIFFI_C2PA_FN_CLONE_BUILDER +void* uniffi_c2pa_fn_clone_builder(void* ptr, RustCallStatus *out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_C2PA_FN_FREE_BUILDER +#define UNIFFI_FFIDEF_UNIFFI_C2PA_FN_FREE_BUILDER +void uniffi_c2pa_fn_free_builder(void* ptr, RustCallStatus *out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_C2PA_FN_CONSTRUCTOR_BUILDER_NEW +#define UNIFFI_FFIDEF_UNIFFI_C2PA_FN_CONSTRUCTOR_BUILDER_NEW +void* uniffi_c2pa_fn_constructor_builder_new(RustCallStatus *out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_C2PA_FN_METHOD_BUILDER_ADD_INGREDIENT +#define UNIFFI_FFIDEF_UNIFFI_C2PA_FN_METHOD_BUILDER_ADD_INGREDIENT +void uniffi_c2pa_fn_method_builder_add_ingredient(void* ptr, RustBuffer ingredient_json, RustBuffer format, uint64_t stream, RustCallStatus *out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_C2PA_FN_METHOD_BUILDER_ADD_RESOURCE +#define UNIFFI_FFIDEF_UNIFFI_C2PA_FN_METHOD_BUILDER_ADD_RESOURCE +void uniffi_c2pa_fn_method_builder_add_resource(void* ptr, RustBuffer uri, uint64_t stream, RustCallStatus *out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_C2PA_FN_METHOD_BUILDER_FROM_ARCHIVE +#define UNIFFI_FFIDEF_UNIFFI_C2PA_FN_METHOD_BUILDER_FROM_ARCHIVE +void uniffi_c2pa_fn_method_builder_from_archive(void* ptr, uint64_t stream, RustCallStatus *out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_C2PA_FN_METHOD_BUILDER_SIGN +#define UNIFFI_FFIDEF_UNIFFI_C2PA_FN_METHOD_BUILDER_SIGN +RustBuffer uniffi_c2pa_fn_method_builder_sign(void* ptr, RustBuffer format, uint64_t input, uint64_t output, void* signer, RustCallStatus *out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_C2PA_FN_METHOD_BUILDER_TO_ARCHIVE +#define UNIFFI_FFIDEF_UNIFFI_C2PA_FN_METHOD_BUILDER_TO_ARCHIVE +void uniffi_c2pa_fn_method_builder_to_archive(void* ptr, uint64_t stream, RustCallStatus *out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_C2PA_FN_METHOD_BUILDER_WITH_JSON +#define UNIFFI_FFIDEF_UNIFFI_C2PA_FN_METHOD_BUILDER_WITH_JSON +void uniffi_c2pa_fn_method_builder_with_json(void* ptr, RustBuffer json, RustCallStatus *out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_C2PA_FN_CLONE_CALLBACKSIGNER +#define UNIFFI_FFIDEF_UNIFFI_C2PA_FN_CLONE_CALLBACKSIGNER +void* uniffi_c2pa_fn_clone_callbacksigner(void* ptr, RustCallStatus *out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_C2PA_FN_FREE_CALLBACKSIGNER +#define UNIFFI_FFIDEF_UNIFFI_C2PA_FN_FREE_CALLBACKSIGNER +void uniffi_c2pa_fn_free_callbacksigner(void* ptr, RustCallStatus *out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_C2PA_FN_CONSTRUCTOR_CALLBACKSIGNER_NEW +#define UNIFFI_FFIDEF_UNIFFI_C2PA_FN_CONSTRUCTOR_CALLBACKSIGNER_NEW +void* uniffi_c2pa_fn_constructor_callbacksigner_new(uint64_t callback, RustBuffer alg, RustBuffer certs, RustBuffer ta_url, RustCallStatus *out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_C2PA_FN_CLONE_READER +#define UNIFFI_FFIDEF_UNIFFI_C2PA_FN_CLONE_READER +void* uniffi_c2pa_fn_clone_reader(void* ptr, RustCallStatus *out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_C2PA_FN_FREE_READER +#define UNIFFI_FFIDEF_UNIFFI_C2PA_FN_FREE_READER +void uniffi_c2pa_fn_free_reader(void* ptr, RustCallStatus *out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_C2PA_FN_CONSTRUCTOR_READER_NEW +#define UNIFFI_FFIDEF_UNIFFI_C2PA_FN_CONSTRUCTOR_READER_NEW +void* uniffi_c2pa_fn_constructor_reader_new(RustCallStatus *out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_C2PA_FN_METHOD_READER_FROM_STREAM +#define UNIFFI_FFIDEF_UNIFFI_C2PA_FN_METHOD_READER_FROM_STREAM +RustBuffer uniffi_c2pa_fn_method_reader_from_stream(void* ptr, RustBuffer format, uint64_t reader, RustCallStatus *out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_C2PA_FN_METHOD_READER_GET_PROVENANCE_CERT_CHAIN +#define UNIFFI_FFIDEF_UNIFFI_C2PA_FN_METHOD_READER_GET_PROVENANCE_CERT_CHAIN +RustBuffer uniffi_c2pa_fn_method_reader_get_provenance_cert_chain(void* ptr, RustCallStatus *out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_C2PA_FN_METHOD_READER_JSON +#define UNIFFI_FFIDEF_UNIFFI_C2PA_FN_METHOD_READER_JSON +RustBuffer uniffi_c2pa_fn_method_reader_json(void* ptr, RustCallStatus *out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_C2PA_FN_METHOD_READER_RESOURCE_TO_STREAM +#define UNIFFI_FFIDEF_UNIFFI_C2PA_FN_METHOD_READER_RESOURCE_TO_STREAM +uint64_t uniffi_c2pa_fn_method_reader_resource_to_stream(void* ptr, RustBuffer uri, uint64_t stream, RustCallStatus *out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_C2PA_FN_INIT_CALLBACK_VTABLE_SIGNERCALLBACK +#define UNIFFI_FFIDEF_UNIFFI_C2PA_FN_INIT_CALLBACK_VTABLE_SIGNERCALLBACK +void uniffi_c2pa_fn_init_callback_vtable_signercallback(UniffiVTableCallbackInterfaceSignerCallback* vtable +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_C2PA_FN_INIT_CALLBACK_VTABLE_STREAM +#define UNIFFI_FFIDEF_UNIFFI_C2PA_FN_INIT_CALLBACK_VTABLE_STREAM +void uniffi_c2pa_fn_init_callback_vtable_stream(UniffiVTableCallbackInterfaceStream* vtable +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_C2PA_FN_FUNC_SDK_VERSION +#define UNIFFI_FFIDEF_UNIFFI_C2PA_FN_FUNC_SDK_VERSION +RustBuffer uniffi_c2pa_fn_func_sdk_version(RustCallStatus *out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_C2PA_FN_FUNC_VERSION +#define UNIFFI_FFIDEF_UNIFFI_C2PA_FN_FUNC_VERSION +RustBuffer uniffi_c2pa_fn_func_version(RustCallStatus *out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_C2PA_RUSTBUFFER_ALLOC +#define UNIFFI_FFIDEF_FFI_C2PA_RUSTBUFFER_ALLOC +RustBuffer ffi_c2pa_rustbuffer_alloc(uint64_t size, RustCallStatus *out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_C2PA_RUSTBUFFER_FROM_BYTES +#define UNIFFI_FFIDEF_FFI_C2PA_RUSTBUFFER_FROM_BYTES +RustBuffer ffi_c2pa_rustbuffer_from_bytes(ForeignBytes bytes, RustCallStatus *out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_C2PA_RUSTBUFFER_FREE +#define UNIFFI_FFIDEF_FFI_C2PA_RUSTBUFFER_FREE +void ffi_c2pa_rustbuffer_free(RustBuffer buf, RustCallStatus *out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_C2PA_RUSTBUFFER_RESERVE +#define UNIFFI_FFIDEF_FFI_C2PA_RUSTBUFFER_RESERVE +RustBuffer ffi_c2pa_rustbuffer_reserve(RustBuffer buf, uint64_t additional, RustCallStatus *out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_POLL_U8 +#define UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_POLL_U8 +void ffi_c2pa_rust_future_poll_u8(uint64_t handle, UniffiRustFutureContinuationCallback callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_CANCEL_U8 +#define UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_CANCEL_U8 +void ffi_c2pa_rust_future_cancel_u8(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_FREE_U8 +#define UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_FREE_U8 +void ffi_c2pa_rust_future_free_u8(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_COMPLETE_U8 +#define UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_COMPLETE_U8 +uint8_t ffi_c2pa_rust_future_complete_u8(uint64_t handle, RustCallStatus *out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_POLL_I8 +#define UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_POLL_I8 +void ffi_c2pa_rust_future_poll_i8(uint64_t handle, UniffiRustFutureContinuationCallback callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_CANCEL_I8 +#define UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_CANCEL_I8 +void ffi_c2pa_rust_future_cancel_i8(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_FREE_I8 +#define UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_FREE_I8 +void ffi_c2pa_rust_future_free_i8(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_COMPLETE_I8 +#define UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_COMPLETE_I8 +int8_t ffi_c2pa_rust_future_complete_i8(uint64_t handle, RustCallStatus *out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_POLL_U16 +#define UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_POLL_U16 +void ffi_c2pa_rust_future_poll_u16(uint64_t handle, UniffiRustFutureContinuationCallback callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_CANCEL_U16 +#define UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_CANCEL_U16 +void ffi_c2pa_rust_future_cancel_u16(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_FREE_U16 +#define UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_FREE_U16 +void ffi_c2pa_rust_future_free_u16(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_COMPLETE_U16 +#define UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_COMPLETE_U16 +uint16_t ffi_c2pa_rust_future_complete_u16(uint64_t handle, RustCallStatus *out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_POLL_I16 +#define UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_POLL_I16 +void ffi_c2pa_rust_future_poll_i16(uint64_t handle, UniffiRustFutureContinuationCallback callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_CANCEL_I16 +#define UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_CANCEL_I16 +void ffi_c2pa_rust_future_cancel_i16(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_FREE_I16 +#define UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_FREE_I16 +void ffi_c2pa_rust_future_free_i16(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_COMPLETE_I16 +#define UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_COMPLETE_I16 +int16_t ffi_c2pa_rust_future_complete_i16(uint64_t handle, RustCallStatus *out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_POLL_U32 +#define UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_POLL_U32 +void ffi_c2pa_rust_future_poll_u32(uint64_t handle, UniffiRustFutureContinuationCallback callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_CANCEL_U32 +#define UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_CANCEL_U32 +void ffi_c2pa_rust_future_cancel_u32(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_FREE_U32 +#define UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_FREE_U32 +void ffi_c2pa_rust_future_free_u32(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_COMPLETE_U32 +#define UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_COMPLETE_U32 +uint32_t ffi_c2pa_rust_future_complete_u32(uint64_t handle, RustCallStatus *out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_POLL_I32 +#define UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_POLL_I32 +void ffi_c2pa_rust_future_poll_i32(uint64_t handle, UniffiRustFutureContinuationCallback callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_CANCEL_I32 +#define UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_CANCEL_I32 +void ffi_c2pa_rust_future_cancel_i32(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_FREE_I32 +#define UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_FREE_I32 +void ffi_c2pa_rust_future_free_i32(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_COMPLETE_I32 +#define UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_COMPLETE_I32 +int32_t ffi_c2pa_rust_future_complete_i32(uint64_t handle, RustCallStatus *out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_POLL_U64 +#define UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_POLL_U64 +void ffi_c2pa_rust_future_poll_u64(uint64_t handle, UniffiRustFutureContinuationCallback callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_CANCEL_U64 +#define UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_CANCEL_U64 +void ffi_c2pa_rust_future_cancel_u64(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_FREE_U64 +#define UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_FREE_U64 +void ffi_c2pa_rust_future_free_u64(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_COMPLETE_U64 +#define UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_COMPLETE_U64 +uint64_t ffi_c2pa_rust_future_complete_u64(uint64_t handle, RustCallStatus *out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_POLL_I64 +#define UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_POLL_I64 +void ffi_c2pa_rust_future_poll_i64(uint64_t handle, UniffiRustFutureContinuationCallback callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_CANCEL_I64 +#define UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_CANCEL_I64 +void ffi_c2pa_rust_future_cancel_i64(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_FREE_I64 +#define UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_FREE_I64 +void ffi_c2pa_rust_future_free_i64(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_COMPLETE_I64 +#define UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_COMPLETE_I64 +int64_t ffi_c2pa_rust_future_complete_i64(uint64_t handle, RustCallStatus *out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_POLL_F32 +#define UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_POLL_F32 +void ffi_c2pa_rust_future_poll_f32(uint64_t handle, UniffiRustFutureContinuationCallback callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_CANCEL_F32 +#define UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_CANCEL_F32 +void ffi_c2pa_rust_future_cancel_f32(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_FREE_F32 +#define UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_FREE_F32 +void ffi_c2pa_rust_future_free_f32(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_COMPLETE_F32 +#define UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_COMPLETE_F32 +float ffi_c2pa_rust_future_complete_f32(uint64_t handle, RustCallStatus *out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_POLL_F64 +#define UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_POLL_F64 +void ffi_c2pa_rust_future_poll_f64(uint64_t handle, UniffiRustFutureContinuationCallback callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_CANCEL_F64 +#define UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_CANCEL_F64 +void ffi_c2pa_rust_future_cancel_f64(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_FREE_F64 +#define UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_FREE_F64 +void ffi_c2pa_rust_future_free_f64(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_COMPLETE_F64 +#define UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_COMPLETE_F64 +double ffi_c2pa_rust_future_complete_f64(uint64_t handle, RustCallStatus *out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_POLL_POINTER +#define UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_POLL_POINTER +void ffi_c2pa_rust_future_poll_pointer(uint64_t handle, UniffiRustFutureContinuationCallback callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_CANCEL_POINTER +#define UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_CANCEL_POINTER +void ffi_c2pa_rust_future_cancel_pointer(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_FREE_POINTER +#define UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_FREE_POINTER +void ffi_c2pa_rust_future_free_pointer(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_COMPLETE_POINTER +#define UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_COMPLETE_POINTER +void* ffi_c2pa_rust_future_complete_pointer(uint64_t handle, RustCallStatus *out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_POLL_RUST_BUFFER +#define UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_POLL_RUST_BUFFER +void ffi_c2pa_rust_future_poll_rust_buffer(uint64_t handle, UniffiRustFutureContinuationCallback callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_CANCEL_RUST_BUFFER +#define UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_CANCEL_RUST_BUFFER +void ffi_c2pa_rust_future_cancel_rust_buffer(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_FREE_RUST_BUFFER +#define UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_FREE_RUST_BUFFER +void ffi_c2pa_rust_future_free_rust_buffer(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_COMPLETE_RUST_BUFFER +#define UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_COMPLETE_RUST_BUFFER +RustBuffer ffi_c2pa_rust_future_complete_rust_buffer(uint64_t handle, RustCallStatus *out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_POLL_VOID +#define UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_POLL_VOID +void ffi_c2pa_rust_future_poll_void(uint64_t handle, UniffiRustFutureContinuationCallback callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_CANCEL_VOID +#define UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_CANCEL_VOID +void ffi_c2pa_rust_future_cancel_void(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_FREE_VOID +#define UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_FREE_VOID +void ffi_c2pa_rust_future_free_void(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_COMPLETE_VOID +#define UNIFFI_FFIDEF_FFI_C2PA_RUST_FUTURE_COMPLETE_VOID +void ffi_c2pa_rust_future_complete_void(uint64_t handle, RustCallStatus *out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_C2PA_CHECKSUM_FUNC_SDK_VERSION +#define UNIFFI_FFIDEF_UNIFFI_C2PA_CHECKSUM_FUNC_SDK_VERSION +uint16_t uniffi_c2pa_checksum_func_sdk_version(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_C2PA_CHECKSUM_FUNC_VERSION +#define UNIFFI_FFIDEF_UNIFFI_C2PA_CHECKSUM_FUNC_VERSION +uint16_t uniffi_c2pa_checksum_func_version(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_C2PA_CHECKSUM_METHOD_BUILDER_ADD_INGREDIENT +#define UNIFFI_FFIDEF_UNIFFI_C2PA_CHECKSUM_METHOD_BUILDER_ADD_INGREDIENT +uint16_t uniffi_c2pa_checksum_method_builder_add_ingredient(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_C2PA_CHECKSUM_METHOD_BUILDER_ADD_RESOURCE +#define UNIFFI_FFIDEF_UNIFFI_C2PA_CHECKSUM_METHOD_BUILDER_ADD_RESOURCE +uint16_t uniffi_c2pa_checksum_method_builder_add_resource(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_C2PA_CHECKSUM_METHOD_BUILDER_FROM_ARCHIVE +#define UNIFFI_FFIDEF_UNIFFI_C2PA_CHECKSUM_METHOD_BUILDER_FROM_ARCHIVE +uint16_t uniffi_c2pa_checksum_method_builder_from_archive(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_C2PA_CHECKSUM_METHOD_BUILDER_SIGN +#define UNIFFI_FFIDEF_UNIFFI_C2PA_CHECKSUM_METHOD_BUILDER_SIGN +uint16_t uniffi_c2pa_checksum_method_builder_sign(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_C2PA_CHECKSUM_METHOD_BUILDER_TO_ARCHIVE +#define UNIFFI_FFIDEF_UNIFFI_C2PA_CHECKSUM_METHOD_BUILDER_TO_ARCHIVE +uint16_t uniffi_c2pa_checksum_method_builder_to_archive(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_C2PA_CHECKSUM_METHOD_BUILDER_WITH_JSON +#define UNIFFI_FFIDEF_UNIFFI_C2PA_CHECKSUM_METHOD_BUILDER_WITH_JSON +uint16_t uniffi_c2pa_checksum_method_builder_with_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_C2PA_CHECKSUM_METHOD_READER_FROM_STREAM +#define UNIFFI_FFIDEF_UNIFFI_C2PA_CHECKSUM_METHOD_READER_FROM_STREAM +uint16_t uniffi_c2pa_checksum_method_reader_from_stream(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_C2PA_CHECKSUM_METHOD_READER_GET_PROVENANCE_CERT_CHAIN +#define UNIFFI_FFIDEF_UNIFFI_C2PA_CHECKSUM_METHOD_READER_GET_PROVENANCE_CERT_CHAIN +uint16_t uniffi_c2pa_checksum_method_reader_get_provenance_cert_chain(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_C2PA_CHECKSUM_METHOD_READER_JSON +#define UNIFFI_FFIDEF_UNIFFI_C2PA_CHECKSUM_METHOD_READER_JSON +uint16_t uniffi_c2pa_checksum_method_reader_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_C2PA_CHECKSUM_METHOD_READER_RESOURCE_TO_STREAM +#define UNIFFI_FFIDEF_UNIFFI_C2PA_CHECKSUM_METHOD_READER_RESOURCE_TO_STREAM +uint16_t uniffi_c2pa_checksum_method_reader_resource_to_stream(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_C2PA_CHECKSUM_CONSTRUCTOR_BUILDER_NEW +#define UNIFFI_FFIDEF_UNIFFI_C2PA_CHECKSUM_CONSTRUCTOR_BUILDER_NEW +uint16_t uniffi_c2pa_checksum_constructor_builder_new(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_C2PA_CHECKSUM_CONSTRUCTOR_CALLBACKSIGNER_NEW +#define UNIFFI_FFIDEF_UNIFFI_C2PA_CHECKSUM_CONSTRUCTOR_CALLBACKSIGNER_NEW +uint16_t uniffi_c2pa_checksum_constructor_callbacksigner_new(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_C2PA_CHECKSUM_CONSTRUCTOR_READER_NEW +#define UNIFFI_FFIDEF_UNIFFI_C2PA_CHECKSUM_CONSTRUCTOR_READER_NEW +uint16_t uniffi_c2pa_checksum_constructor_reader_new(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_C2PA_CHECKSUM_METHOD_SIGNERCALLBACK_SIGN +#define UNIFFI_FFIDEF_UNIFFI_C2PA_CHECKSUM_METHOD_SIGNERCALLBACK_SIGN +uint16_t uniffi_c2pa_checksum_method_signercallback_sign(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_C2PA_CHECKSUM_METHOD_STREAM_READ_STREAM +#define UNIFFI_FFIDEF_UNIFFI_C2PA_CHECKSUM_METHOD_STREAM_READ_STREAM +uint16_t uniffi_c2pa_checksum_method_stream_read_stream(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_C2PA_CHECKSUM_METHOD_STREAM_SEEK_STREAM +#define UNIFFI_FFIDEF_UNIFFI_C2PA_CHECKSUM_METHOD_STREAM_SEEK_STREAM +uint16_t uniffi_c2pa_checksum_method_stream_seek_stream(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_C2PA_CHECKSUM_METHOD_STREAM_WRITE_STREAM +#define UNIFFI_FFIDEF_UNIFFI_C2PA_CHECKSUM_METHOD_STREAM_WRITE_STREAM +uint16_t uniffi_c2pa_checksum_method_stream_write_stream(void + +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_C2PA_UNIFFI_CONTRACT_VERSION +#define UNIFFI_FFIDEF_FFI_C2PA_UNIFFI_CONTRACT_VERSION +uint32_t ffi_c2pa_uniffi_contract_version(void + +); +#endif + + void c2pa_cgo_dispatchCallbackInterfaceSignerCallbackMethod0(uint64_t uniffi_handle, RustBuffer data, RustBuffer* uniffi_out_return, RustCallStatus* callStatus ); + void c2pa_cgo_dispatchCallbackInterfaceSignerCallbackFree(uint64_t handle); + void c2pa_cgo_dispatchCallbackInterfaceStreamMethod0(uint64_t uniffi_handle, uint64_t length, RustBuffer* uniffi_out_return, RustCallStatus* callStatus ); + void c2pa_cgo_dispatchCallbackInterfaceStreamMethod1(uint64_t uniffi_handle, int64_t pos, RustBuffer mode, uint64_t* uniffi_out_return, RustCallStatus* callStatus ); + void c2pa_cgo_dispatchCallbackInterfaceStreamMethod2(uint64_t uniffi_handle, RustBuffer data, uint64_t* uniffi_out_return, RustCallStatus* callStatus ); + void c2pa_cgo_dispatchCallbackInterfaceStreamFree(uint64_t handle); diff --git a/pkg/c2pa/generated/manifestdefinition/manifestdefinition.go b/pkg/c2pa/generated/manifestdefinition/manifestdefinition.go index 3d1a457f..8cc82ccd 100644 --- a/pkg/c2pa/generated/manifestdefinition/manifestdefinition.go +++ b/pkg/c2pa/generated/manifestdefinition/manifestdefinition.go @@ -46,7 +46,7 @@ type ClaimGeneratorInfo struct { // A human readable string of the product's version Version *string `json:"version,omitempty" yaml:"version,omitempty" mapstructure:"version,omitempty"` - AdditionalProperties interface{} + AdditionalProperties interface{} `mapstructure:",remain"` } // An x, y coordinate used for specifying vertices in polygons. @@ -224,7 +224,7 @@ type Metadata struct { // ReviewRatings corresponds to the JSON schema field "reviewRatings". ReviewRatings []ReviewRating `json:"reviewRatings,omitempty" yaml:"reviewRatings,omitempty" mapstructure:"reviewRatings,omitempty"` - AdditionalProperties interface{} + AdditionalProperties interface{} `mapstructure:",remain"` } // A spatial, temporal, frame, or textual range describing the region of interest. diff --git a/pkg/c2pa/generated/manifeststore/manifeststore.go b/pkg/c2pa/generated/manifeststore/manifeststore.go index 653252cb..237e1b1c 100644 --- a/pkg/c2pa/generated/manifeststore/manifeststore.go +++ b/pkg/c2pa/generated/manifeststore/manifeststore.go @@ -34,7 +34,7 @@ type ClaimGeneratorInfo struct { // A human readable string of the product's version Version *string `json:"version,omitempty" yaml:"version,omitempty" mapstructure:"version,omitempty"` - AdditionalProperties interface{} + AdditionalProperties interface{} `mapstructure:",remain"` } // An x, y coordinate used for specifying vertices in polygons. @@ -270,7 +270,7 @@ type Metadata struct { // ReviewRatings corresponds to the JSON schema field "reviewRatings". ReviewRatings []ReviewRating `json:"reviewRatings,omitempty" yaml:"reviewRatings,omitempty" mapstructure:"reviewRatings,omitempty"` - AdditionalProperties interface{} + AdditionalProperties interface{} `mapstructure:",remain"` } // A spatial, temporal, frame, or textual range describing the region of interest. diff --git a/pkg/c2pa/generated/settings/settings.go b/pkg/c2pa/generated/settings/settings.go index ad07c6dc..5be47a31 100644 --- a/pkg/c2pa/generated/settings/settings.go +++ b/pkg/c2pa/generated/settings/settings.go @@ -60,10 +60,6 @@ type Trust struct { } type Verify struct { - // CheckIngredientTrust corresponds to the JSON schema field - // "check_ingredient_trust". - CheckIngredientTrust bool `json:"check_ingredient_trust" yaml:"check_ingredient_trust" mapstructure:"check_ingredient_trust"` - // OcspFetch corresponds to the JSON schema field "ocsp_fetch". OcspFetch bool `json:"ocsp_fetch" yaml:"ocsp_fetch" mapstructure:"ocsp_fetch"` diff --git a/src/streams.rs b/src/streams.rs index 62001e0f..960c9b43 100644 --- a/src/streams.rs +++ b/src/streams.rs @@ -51,10 +51,6 @@ impl Stream for Box { } } -unsafe impl uniffi::LiftRef for Box { - type LiftType = Box; -} - impl AsMut for dyn Stream { fn as_mut(&mut self) -> &mut Self { self From e689e31efaa714143f57612e1c27724acc34f66e Mon Sep 17 00:00:00 2001 From: Eli Mallon Date: Wed, 30 Apr 2025 14:59:18 -0700 Subject: [PATCH 40/42] v0.7.0 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index a1ff7180..af59e050 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "c2pa-go" -version = "0.5.0" +version = "0.7.0" edition = "2021" authors = ["Eli Mallon "] From 9db3fd34435ae3409d145c103394e6061125a9be Mon Sep 17 00:00:00 2001 From: Eli Mallon Date: Thu, 11 Sep 2025 16:31:02 -0700 Subject: [PATCH 41/42] build: add meson install behavior --- meson.build | 2 ++ 1 file changed, 2 insertions(+) diff --git a/meson.build b/meson.build index 6bd3bc09..5aaa7e5f 100644 --- a/meson.build +++ b/meson.build @@ -24,4 +24,6 @@ c2pa_go_dep = custom_target( 'cd "@2@" && @0@ && cp "@3@/release/libc2pa.a" "@1@/libc2pa.a"'.format(cargo_cmd, meson.current_build_dir(), meson.current_source_dir(), archive_dir), ], build_by_default: true, + install: true, + install_dir : get_option('libdir'), ) From 75f87d1a10ea78f26721fa7e3e224f4b73ac0a18 Mon Sep 17 00:00:00 2001 From: Eli Mallon Date: Fri, 12 Sep 2025 14:13:26 -0700 Subject: [PATCH 42/42] build: static and dynamic builds --- Cargo.toml | 2 +- meson.build | 94 +++++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 74 insertions(+), 22 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index af59e050..212d83fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ authors = ["Eli Mallon "] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib] name = "c2pa" -crate-type = ["staticlib"] +crate-type = ["staticlib", "cdylib"] [dependencies] c2pa = { git = "https://git.stream.place/aquareum-tv/c2pa-rs.git", rev = "5ca8c085a05daa36a91fb9edfdee6098cffac69b", features = [ diff --git a/meson.build b/meson.build index 5aaa7e5f..371ac514 100644 --- a/meson.build +++ b/meson.build @@ -1,29 +1,81 @@ project( 'c2pa-go', - default_options: ['default_library=static'], + default_options: [], ) prog_cargo = find_program('cargo') -cargo_cmd = '@0@ build --release --target-dir "@1@"'.format(prog_cargo.full_path(), meson.current_build_dir()) -archive_dir = meson.current_build_dir() +# cargo_cmd = '@0@ build --release --target-dir "@1@"'.format(prog_cargo.full_path(), meson.current_build_dir()) +# archive_dir = meson.current_build_dir() -triple = get_option('RUST_TRIPLE') -if triple != '' - cargo_cmd += ' --target @0@'.format(triple) - archive_dir += '/' + triple -endif +# triple = get_option('RUST_TRIPLE') +# if triple != '' +# cargo_cmd += ' --target @0@'.format(triple) +# archive_dir += '/' + triple +# endif -c2pa_go_dep = custom_target( - 'libc2pa', - input: [], - output: ['libc2pa.a'], - command: [ - 'sh', - '-c', - 'cd "@2@" && @0@ && cp "@3@/release/libc2pa.a" "@1@/libc2pa.a"'.format(cargo_cmd, meson.current_build_dir(), meson.current_source_dir(), archive_dir), - ], - build_by_default: true, - install: true, - install_dir : get_option('libdir'), -) +# c2pa_go_dep = custom_target( +# 'libc2pa', +# input: [], +# output: ['libc2pa.a'], +# command: [ +# 'sh', +# '-c', +# 'cd "@2@" && @0@ && cp "@3@/release/libc2pa.a" "@1@/libc2pa.a"'.format(cargo_cmd, meson.current_build_dir(), meson.current_source_dir(), archive_dir), +# ], +# build_by_default: true, +# install: true, +# install_dir : get_option('libdir'), +# ) + +# prog_cargo = find_program('cargo') + +if get_option('default_library') == 'shared' + cargo_cmd = '@0@ build --target-dir "@1@"'.format(prog_cargo.full_path(), meson.current_build_dir()) + archive_dir = meson.current_build_dir() + + if host_machine.system() == 'darwin' + ext = 'dylib' + elif host_machine.system() == 'linux' + ext = 'so' + else + error('Unsupported dev system: @0@'.format(host_machine.system())) + endif + + c2pa_go_dep = custom_target( + 'libc2pa', + input: [], + output: ['libc2pa.' + ext], + command: [ + 'sh', + '-c', + 'cd "@2@" && @0@ && cp "@3@/debug/libc2pa.@4@" "@1@/libc2pa.@4@"'.format(cargo_cmd, meson.current_build_dir(), meson.current_source_dir(), archive_dir, ext), + ], + build_by_default: true, + install: true, + install_dir : get_option('libdir'), + ) +else + cargo_cmd = '@0@ build --release --target-dir "@1@"'.format(prog_cargo.full_path(), meson.current_build_dir()) + archive_dir = meson.current_build_dir() + + triple = meson.get_external_property('RUST_TRIPLE', '') + if triple != '' + cargo_cmd += ' --target @0@'.format(triple) + archive_dir += '/' + triple + endif + + c2pa_go_dep = custom_target( + 'libc2pa', + input: [], + output: ['libc2pa.a'], + command: [ + 'sh', + '-c', + 'cd "@2@" && @0@ && cp "@3@/release/libc2pa.a" "@1@/libc2pa.a"'.format(cargo_cmd, meson.current_build_dir(), meson.current_source_dir(), archive_dir), + ], + build_by_default: true, + install: true, + install_dir : get_option('libdir'), + ) +endif