@@ -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 : Option < Vec < u8 > > ,
5967}
6068
6169enum LocalOpDispatchResult {
@@ -109,20 +117,42 @@ 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 ( ( ) ) ;
125138 }
139+ let proof_ref = if unsafe { sqlite_value_type ( args[ base_argc + 2 ] ) } == SQLITE_NULL as c_int {
140+ None
141+ } else {
142+ let value = read_required_blob ( args[ base_argc + 2 ] ) ?;
143+ if value. len ( ) != 16 {
144+ return Err ( ( ) ) ;
145+ }
146+ Some ( value)
147+ } ;
148+ Ok ( LocalOpMode :: Commit {
149+ precondition,
150+ proof : LocalOpProof { sig, proof_ref } ,
151+ } )
152+ }
153+
154+ fn valid_local_op_argc ( argc : c_int , base_argc : usize ) -> bool {
155+ matches ! ( argc as usize , n if n == base_argc || n == base_argc + 1 || n == base_argc + 3 )
126156}
127157
128158fn sqlite_busy_or_locked ( rc : c_int ) -> bool {
@@ -285,6 +315,75 @@ fn local_precondition(
285315 Ok ( Some ( format ! ( "v1:{}" , hasher. finalize( ) . to_hex( ) ) ) )
286316}
287317
318+ fn persist_local_op_proof (
319+ db : * mut sqlite3 ,
320+ doc_id : & [ u8 ] ,
321+ op : & Operation ,
322+ proof : & LocalOpProof ,
323+ ) -> Result < ( ) , c_int > {
324+ let sql = CString :: new (
325+ "INSERT OR REPLACE INTO treecrdt_sync_op_auth \
326+ (doc_id, op_ref, sig, proof_ref, created_at_ms) \
327+ VALUES (?1, ?2, ?3, ?4, CAST(strftime('%s','now') AS INTEGER) * 1000)",
328+ )
329+ . expect ( "local op proof sql" ) ;
330+ let mut stmt: * mut sqlite3_stmt = null_mut ( ) ;
331+ let rc = sqlite_prepare_v2 ( db, sql. as_ptr ( ) , -1 , & mut stmt, null_mut ( ) ) ;
332+ if rc != SQLITE_OK as c_int {
333+ return Err ( rc) ;
334+ }
335+
336+ let op_ref = derive_op_ref_v0 ( doc_id, op. meta . id . replica . as_bytes ( ) , op. meta . id . counter ) ;
337+ let mut bind_err = false ;
338+ unsafe {
339+ bind_err |= sqlite_bind_text (
340+ stmt,
341+ 1 ,
342+ doc_id. as_ptr ( ) as * const c_char ,
343+ doc_id. len ( ) as c_int ,
344+ None ,
345+ ) != SQLITE_OK as c_int ;
346+ bind_err |= sqlite_bind_blob (
347+ stmt,
348+ 2 ,
349+ op_ref. as_ptr ( ) as * const c_void ,
350+ op_ref. len ( ) as c_int ,
351+ None ,
352+ ) != SQLITE_OK as c_int ;
353+ bind_err |= sqlite_bind_blob (
354+ stmt,
355+ 3 ,
356+ proof. sig . as_ptr ( ) as * const c_void ,
357+ proof. sig . len ( ) as c_int ,
358+ None ,
359+ ) != SQLITE_OK as c_int ;
360+ bind_err |= match proof. proof_ref . as_ref ( ) {
361+ Some ( value) => sqlite_bind_blob (
362+ stmt,
363+ 4 ,
364+ value. as_ptr ( ) as * const c_void ,
365+ value. len ( ) as c_int ,
366+ None ,
367+ ) ,
368+ None => sqlite_bind_null ( stmt, 4 ) ,
369+ } != SQLITE_OK as c_int ;
370+ }
371+ if bind_err {
372+ unsafe { sqlite_finalize ( stmt) } ;
373+ return Err ( SQLITE_ERROR as c_int ) ;
374+ }
375+
376+ let step_rc = unsafe { sqlite_step ( stmt) } ;
377+ let finalize_rc = unsafe { sqlite_finalize ( stmt) } ;
378+ if step_rc != SQLITE_DONE as c_int {
379+ return Err ( step_rc) ;
380+ }
381+ if finalize_rc != SQLITE_OK as c_int {
382+ return Err ( finalize_rc) ;
383+ }
384+ Ok ( ( ) )
385+ }
386+
288387fn begin_local_core_op (
289388 db : * mut sqlite3 ,
290389 doc_id : & [ u8 ] ,
@@ -493,7 +592,10 @@ where
493592 } ;
494593 finish_local_core_op ( session, op, plan) . map ( LocalOpDispatchResult :: Committed )
495594 }
496- LocalOpMode :: Commit ( expected_precondition) => {
595+ LocalOpMode :: Commit {
596+ precondition : expected_precondition,
597+ proof,
598+ } => {
497599 let mut session = match begin_optimistic_local_core_op ( db, & replica, savepoint_name) {
498600 Ok ( v) => v,
499601 Err ( OptimisticBeginError :: Conflict ) => return Ok ( LocalOpDispatchResult :: Conflict ) ,
@@ -525,6 +627,9 @@ where
525627 Ok ( v) => v,
526628 Err ( err) => return Err ( session. rollback ( sqlite_err_from_core ( err) ) ) ,
527629 } ;
630+ if let Err ( rc) = persist_local_op_proof ( session. db , & session. doc_id , & op, & proof) {
631+ return Err ( session. rollback ( rc) ) ;
632+ }
528633 finish_local_core_op ( session, op, plan) . map ( LocalOpDispatchResult :: Committed )
529634 }
530635 }
@@ -556,10 +661,10 @@ pub(super) unsafe extern "C" fn treecrdt_local_insert(
556661 return ;
557662 }
558663
559- if argc != 6 && argc != 7 {
664+ if ! valid_local_op_argc ( argc, 6 ) {
560665 sqlite_result_error (
561666 ctx,
562- b"treecrdt_local_insert expects 6 args plus optional prepare/precondition \0 " . as_ptr ( )
667+ b"treecrdt_local_insert expects 6 args, 7 for prepare, or 9 for commit \0 " . as_ptr ( )
563668 as * const c_char ,
564669 ) ;
565670 return ;
@@ -571,7 +676,7 @@ pub(super) unsafe extern "C" fn treecrdt_local_insert(
571676 Err ( _) => {
572677 sqlite_result_error (
573678 ctx,
574- b"treecrdt_local_insert: invalid prepare/precondition argument \0 " . as_ptr ( )
679+ b"treecrdt_local_insert: invalid prepare or commit arguments \0 " . as_ptr ( )
575680 as * const c_char ,
576681 ) ;
577682 return ;
@@ -655,10 +760,10 @@ pub(super) unsafe extern "C" fn treecrdt_local_move(
655760 return ;
656761 }
657762
658- if argc != 5 && argc != 6 {
763+ if ! valid_local_op_argc ( argc, 5 ) {
659764 sqlite_result_error (
660765 ctx,
661- b"treecrdt_local_move expects 5 args plus optional prepare/precondition \0 " . as_ptr ( )
766+ b"treecrdt_local_move expects 5 args, 6 for prepare, or 8 for commit \0 " . as_ptr ( )
662767 as * const c_char ,
663768 ) ;
664769 return ;
@@ -670,7 +775,7 @@ pub(super) unsafe extern "C" fn treecrdt_local_move(
670775 Err ( _) => {
671776 sqlite_result_error (
672777 ctx,
673- b"treecrdt_local_move: invalid prepare/precondition argument \0 " . as_ptr ( )
778+ b"treecrdt_local_move: invalid prepare or commit arguments \0 " . as_ptr ( )
674779 as * const c_char ,
675780 ) ;
676781 return ;
@@ -752,10 +857,10 @@ pub(super) unsafe extern "C" fn treecrdt_local_delete(
752857 return ;
753858 }
754859
755- if argc != 2 && argc != 3 {
860+ if ! valid_local_op_argc ( argc, 2 ) {
756861 sqlite_result_error (
757862 ctx,
758- b"treecrdt_local_delete expects 2 args plus optional prepare/precondition \0 " . as_ptr ( )
863+ b"treecrdt_local_delete expects 2 args, 3 for prepare, or 5 for commit \0 " . as_ptr ( )
759864 as * const c_char ,
760865 ) ;
761866 return ;
@@ -767,7 +872,7 @@ pub(super) unsafe extern "C" fn treecrdt_local_delete(
767872 Err ( _) => {
768873 sqlite_result_error (
769874 ctx,
770- b"treecrdt_local_delete: invalid prepare/precondition argument \0 " . as_ptr ( )
875+ b"treecrdt_local_delete: invalid prepare or commit arguments \0 " . as_ptr ( )
771876 as * const c_char ,
772877 ) ;
773878 return ;
@@ -818,10 +923,10 @@ pub(super) unsafe extern "C" fn treecrdt_local_payload(
818923 return ;
819924 }
820925
821- if argc != 3 && argc != 4 {
926+ if ! valid_local_op_argc ( argc, 3 ) {
822927 sqlite_result_error (
823928 ctx,
824- b"treecrdt_local_payload expects 3 args plus optional prepare/precondition \0 " . as_ptr ( )
929+ b"treecrdt_local_payload expects 3 args, 4 for prepare, or 6 for commit \0 " . as_ptr ( )
825930 as * const c_char ,
826931 ) ;
827932 return ;
@@ -833,7 +938,7 @@ pub(super) unsafe extern "C" fn treecrdt_local_payload(
833938 Err ( _) => {
834939 sqlite_result_error (
835940 ctx,
836- b"treecrdt_local_payload: invalid prepare/precondition argument \0 " . as_ptr ( )
941+ b"treecrdt_local_payload: invalid prepare or commit arguments \0 " . as_ptr ( )
837942 as * const c_char ,
838943 ) ;
839944 return ;
0 commit comments