@@ -55,7 +55,15 @@ struct JsonLocalOpTransactionError {
5555enum LocalOpMode {
5656 Immediate ,
5757 Prepare ,
58- Commit ( String ) ,
58+ Commit {
59+ precondition : String ,
60+ proof : LocalOpProof ,
61+ } ,
62+ }
63+
64+ struct LocalOpProof {
65+ sig : Vec < u8 > ,
66+ proof_ref : Vec < u8 > ,
5967}
6068
6169enum LocalOpDispatchResult {
@@ -109,20 +117,37 @@ fn parse_local_op_mode(
109117 args : & [ * mut sqlite3_value ] ,
110118 base_argc : usize ,
111119) -> Result < LocalOpMode , ( ) > {
112- if argc as usize == base_argc {
113- return Ok ( LocalOpMode :: Immediate ) ;
120+ match argc as usize {
121+ n if n == base_argc => return Ok ( LocalOpMode :: Immediate ) ,
122+ n if n == base_argc + 1 => {
123+ return ( read_text ( args[ base_argc] ) == "prepare" )
124+ . then_some ( LocalOpMode :: Prepare )
125+ . ok_or ( ( ) ) ;
126+ }
127+ n if n == base_argc + 3 => { }
128+ _ => return Err ( ( ) ) ,
114129 }
115- if argc as usize != base_argc + 1 {
130+
131+ let precondition = read_text ( args[ base_argc] ) ;
132+ if !precondition. starts_with ( "v1:" ) {
116133 return Err ( ( ) ) ;
117134 }
118- let phase = read_text ( args[ base_argc] ) ;
119- if phase == "prepare" {
120- Ok ( LocalOpMode :: Prepare )
121- } else if phase . starts_with ( "v1:" ) {
122- Ok ( LocalOpMode :: Commit ( phase ) )
123- } else {
124- Err ( ( ) )
135+ let sig = read_required_blob ( args[ base_argc + 1 ] ) ? ;
136+ if sig . len ( ) != 64 {
137+ return Err ( ( ) ) ;
138+ }
139+ let proof_ref = read_required_blob ( args [ base_argc + 2 ] ) ? ;
140+ if proof_ref . len ( ) != 16 {
141+ return Err ( ( ) ) ;
125142 }
143+ Ok ( LocalOpMode :: Commit {
144+ precondition,
145+ proof : LocalOpProof { sig, proof_ref } ,
146+ } )
147+ }
148+
149+ fn valid_local_op_argc ( argc : c_int , base_argc : usize ) -> bool {
150+ matches ! ( argc as usize , n if n == base_argc || n == base_argc + 1 || n == base_argc + 3 )
126151}
127152
128153fn sqlite_busy_or_locked ( rc : c_int ) -> bool {
@@ -285,6 +310,72 @@ fn local_precondition(
285310 Ok ( Some ( format ! ( "v1:{}" , hasher. finalize( ) . to_hex( ) ) ) )
286311}
287312
313+ fn persist_local_op_proof (
314+ db : * mut sqlite3 ,
315+ doc_id : & [ u8 ] ,
316+ op : & Operation ,
317+ proof : & LocalOpProof ,
318+ ) -> Result < ( ) , c_int > {
319+ let sql = CString :: new (
320+ "INSERT OR REPLACE INTO treecrdt_sync_op_auth \
321+ (doc_id, op_ref, sig, proof_ref, created_at_ms) \
322+ VALUES (?1, ?2, ?3, ?4, CAST(strftime('%s','now') AS INTEGER) * 1000)",
323+ )
324+ . expect ( "local op proof sql" ) ;
325+ let mut stmt: * mut sqlite3_stmt = null_mut ( ) ;
326+ let rc = sqlite_prepare_v2 ( db, sql. as_ptr ( ) , -1 , & mut stmt, null_mut ( ) ) ;
327+ if rc != SQLITE_OK as c_int {
328+ return Err ( rc) ;
329+ }
330+
331+ let op_ref = derive_op_ref_v0 ( doc_id, op. meta . id . replica . as_bytes ( ) , op. meta . id . counter ) ;
332+ let mut bind_err = false ;
333+ unsafe {
334+ bind_err |= sqlite_bind_text (
335+ stmt,
336+ 1 ,
337+ doc_id. as_ptr ( ) as * const c_char ,
338+ doc_id. len ( ) as c_int ,
339+ None ,
340+ ) != SQLITE_OK as c_int ;
341+ bind_err |= sqlite_bind_blob (
342+ stmt,
343+ 2 ,
344+ op_ref. as_ptr ( ) as * const c_void ,
345+ op_ref. len ( ) as c_int ,
346+ None ,
347+ ) != SQLITE_OK as c_int ;
348+ bind_err |= sqlite_bind_blob (
349+ stmt,
350+ 3 ,
351+ proof. sig . as_ptr ( ) as * const c_void ,
352+ proof. sig . len ( ) as c_int ,
353+ None ,
354+ ) != SQLITE_OK as c_int ;
355+ bind_err |= sqlite_bind_blob (
356+ stmt,
357+ 4 ,
358+ proof. proof_ref . as_ptr ( ) as * const c_void ,
359+ proof. proof_ref . len ( ) as c_int ,
360+ None ,
361+ ) != SQLITE_OK as c_int ;
362+ }
363+ if bind_err {
364+ unsafe { sqlite_finalize ( stmt) } ;
365+ return Err ( SQLITE_ERROR as c_int ) ;
366+ }
367+
368+ let step_rc = unsafe { sqlite_step ( stmt) } ;
369+ let finalize_rc = unsafe { sqlite_finalize ( stmt) } ;
370+ if step_rc != SQLITE_DONE as c_int {
371+ return Err ( step_rc) ;
372+ }
373+ if finalize_rc != SQLITE_OK as c_int {
374+ return Err ( finalize_rc) ;
375+ }
376+ Ok ( ( ) )
377+ }
378+
288379fn begin_local_core_op (
289380 db : * mut sqlite3 ,
290381 doc_id : & [ u8 ] ,
@@ -493,7 +584,10 @@ where
493584 } ;
494585 finish_local_core_op ( session, op, plan) . map ( LocalOpDispatchResult :: Committed )
495586 }
496- LocalOpMode :: Commit ( expected_precondition) => {
587+ LocalOpMode :: Commit {
588+ precondition : expected_precondition,
589+ proof,
590+ } => {
497591 let mut session = match begin_optimistic_local_core_op ( db, & replica, savepoint_name) {
498592 Ok ( v) => v,
499593 Err ( OptimisticBeginError :: Conflict ) => return Ok ( LocalOpDispatchResult :: Conflict ) ,
@@ -525,6 +619,9 @@ where
525619 Ok ( v) => v,
526620 Err ( err) => return Err ( session. rollback ( sqlite_err_from_core ( err) ) ) ,
527621 } ;
622+ if let Err ( rc) = persist_local_op_proof ( session. db , & session. doc_id , & op, & proof) {
623+ return Err ( session. rollback ( rc) ) ;
624+ }
528625 finish_local_core_op ( session, op, plan) . map ( LocalOpDispatchResult :: Committed )
529626 }
530627 }
@@ -556,10 +653,10 @@ pub(super) unsafe extern "C" fn treecrdt_local_insert(
556653 return ;
557654 }
558655
559- if argc != 6 && argc != 7 {
656+ if ! valid_local_op_argc ( argc, 6 ) {
560657 sqlite_result_error (
561658 ctx,
562- b"treecrdt_local_insert expects 6 args plus optional prepare/precondition \0 " . as_ptr ( )
659+ b"treecrdt_local_insert expects 6 args, 7 for prepare, or 9 for commit \0 " . as_ptr ( )
563660 as * const c_char ,
564661 ) ;
565662 return ;
@@ -571,7 +668,7 @@ pub(super) unsafe extern "C" fn treecrdt_local_insert(
571668 Err ( _) => {
572669 sqlite_result_error (
573670 ctx,
574- b"treecrdt_local_insert: invalid prepare/precondition argument \0 " . as_ptr ( )
671+ b"treecrdt_local_insert: invalid prepare or commit arguments \0 " . as_ptr ( )
575672 as * const c_char ,
576673 ) ;
577674 return ;
@@ -655,10 +752,10 @@ pub(super) unsafe extern "C" fn treecrdt_local_move(
655752 return ;
656753 }
657754
658- if argc != 5 && argc != 6 {
755+ if ! valid_local_op_argc ( argc, 5 ) {
659756 sqlite_result_error (
660757 ctx,
661- b"treecrdt_local_move expects 5 args plus optional prepare/precondition \0 " . as_ptr ( )
758+ b"treecrdt_local_move expects 5 args, 6 for prepare, or 8 for commit \0 " . as_ptr ( )
662759 as * const c_char ,
663760 ) ;
664761 return ;
@@ -670,7 +767,7 @@ pub(super) unsafe extern "C" fn treecrdt_local_move(
670767 Err ( _) => {
671768 sqlite_result_error (
672769 ctx,
673- b"treecrdt_local_move: invalid prepare/precondition argument \0 " . as_ptr ( )
770+ b"treecrdt_local_move: invalid prepare or commit arguments \0 " . as_ptr ( )
674771 as * const c_char ,
675772 ) ;
676773 return ;
@@ -752,10 +849,10 @@ pub(super) unsafe extern "C" fn treecrdt_local_delete(
752849 return ;
753850 }
754851
755- if argc != 2 && argc != 3 {
852+ if ! valid_local_op_argc ( argc, 2 ) {
756853 sqlite_result_error (
757854 ctx,
758- b"treecrdt_local_delete expects 2 args plus optional prepare/precondition \0 " . as_ptr ( )
855+ b"treecrdt_local_delete expects 2 args, 3 for prepare, or 5 for commit \0 " . as_ptr ( )
759856 as * const c_char ,
760857 ) ;
761858 return ;
@@ -767,7 +864,7 @@ pub(super) unsafe extern "C" fn treecrdt_local_delete(
767864 Err ( _) => {
768865 sqlite_result_error (
769866 ctx,
770- b"treecrdt_local_delete: invalid prepare/precondition argument \0 " . as_ptr ( )
867+ b"treecrdt_local_delete: invalid prepare or commit arguments \0 " . as_ptr ( )
771868 as * const c_char ,
772869 ) ;
773870 return ;
@@ -818,10 +915,10 @@ pub(super) unsafe extern "C" fn treecrdt_local_payload(
818915 return ;
819916 }
820917
821- if argc != 3 && argc != 4 {
918+ if ! valid_local_op_argc ( argc, 3 ) {
822919 sqlite_result_error (
823920 ctx,
824- b"treecrdt_local_payload expects 3 args plus optional prepare/precondition \0 " . as_ptr ( )
921+ b"treecrdt_local_payload expects 3 args, 4 for prepare, or 6 for commit \0 " . as_ptr ( )
825922 as * const c_char ,
826923 ) ;
827924 return ;
@@ -833,7 +930,7 @@ pub(super) unsafe extern "C" fn treecrdt_local_payload(
833930 Err ( _) => {
834931 sqlite_result_error (
835932 ctx,
836- b"treecrdt_local_payload: invalid prepare/precondition argument \0 " . as_ptr ( )
933+ b"treecrdt_local_payload: invalid prepare or commit arguments \0 " . as_ptr ( )
837934 as * const c_char ,
838935 ) ;
839936 return ;
0 commit comments