From 1a60f3616efef904917dd77a12170912defc7637 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Fri, 14 Mar 2014 02:09:01 -0400 Subject: move encrypt/decrypt functions to crypto module --- client/src/leap/soledad/client/__init__.py | 103 ++------- client/src/leap/soledad/client/crypto.py | 322 +++++++++++++++++++++++++++- client/src/leap/soledad/client/sqlcipher.py | 2 +- client/src/leap/soledad/client/target.py | 217 ++----------------- 4 files changed, 345 insertions(+), 299 deletions(-) diff --git a/client/src/leap/soledad/client/__init__.py b/client/src/leap/soledad/client/__init__.py index 6b0fa6a6..5f1d1a98 100644 --- a/client/src/leap/soledad/client/__init__.py +++ b/client/src/leap/soledad/client/__init__.py @@ -108,10 +108,11 @@ except ImportError: from leap.soledad.common import soledad_assert, soledad_assert_type from leap.soledad.common.document import SoledadDocument from leap.soledad.client.crypto import SoledadCrypto +from leap.soledad.client.crypto import SyncEncrypterPool, SyncDecrypterPool from leap.soledad.client.shared_db import SoledadSharedDatabase from leap.soledad.client.sqlcipher import open as sqlcipher_open from leap.soledad.client.sqlcipher import SQLCipherDatabase -from leap.soledad.client.target import SoledadSyncTarget, encrypt_docstr +from leap.soledad.client.target import SoledadSyncTarget logger = logging.getLogger(name=__name__) @@ -153,85 +154,6 @@ class BootstrapSequenceError(Exception): """ -def encrypt_doc_task(doc_id, doc_rev, content, key, secret): - encrypted_content = encrypt_docstr( - content, doc_id, doc_rev, key, secret) - return doc_id, doc_rev, encrypted_content - - -class SyncEncrypterPool(object): - """ - Pool of workers that spawn subprocesses to execute the symmetric encryption - of documents to be synced. - """ - # TODO implement throttling to reduce cpu usage?? - # TODO move to its own module - - WORKERS = 10 - TABLE_NAME = "docs_tosync" - FIELD_NAMES = "doc_id", "rev", "content" - - def __init__(self, crypto, sync_db): - """ - Initialize the pool of encryption-workers. - - :param crypto: A SoledadCryto instance to perform the encryption. - :type crypto: leap.soledad.crypto.SoledadCrypto - - :param sync_db: a database connection handle - :type sync_db: handle - """ - self._pool = multiprocessing.Pool(self.WORKERS) - self._crypto = crypto - self._sync_db = sync_db - - def encrypt_doc(self, doc): - """ - Symmetrically encrypt a document. - - :param doc: The document with contents to be encrypted. - :type doc: SoledadDocument - """ - print "ENCRYPTING DOC --->", doc - soledad_assert(not doc.is_tombstone()) - docstr = doc.get_json() - key = self._crypto.doc_passphrase(doc.doc_id) - secret = self._crypto.secret - args = doc.doc_id, doc.rev, docstr, key, secret - - try: - self._pool.apply_async(encrypt_doc_task, args, - callback=self.encrypt_doc_cb) - except Exception as exc: - logger.exception(exc) - - def encrypt_doc_cb(self, result): - doc_id, doc_rev, content = result - self.insert_encrypted_doc(doc_id, doc_rev, content) - - def insert_encrypted_doc(self, doc_id, doc_rev, content): - """ - Insert the contents of the encrypted doc into the local sync - database. - - :param doc: The document with contents to be encrypted. - :type doc: SoledadDocument - :param content: The encrypted document. - :type content: str - """ - print ">>>>>>>>>>>> inserting encrypted doc: ", content - c = self._sync_db.cursor() - sql_del = "DELETE FROM '%s' WHERE doc_id=?" % (self.TABLE_NAME,) - c.execute(sql_del, (doc_id, )) - sql_ins = "INSERT INTO '%s' VALUES (?, ?, ?)" % (self.TABLE_NAME,) - print "inserting encrypted -------------", doc_id, doc_rev - print "content: ", content - c.execute(sql_ins, (doc_id, doc_rev, content)) - self._sync_db.commit() - - # TODO have to cleanly handle removals too - - class Soledad(object): """ Soledad provides encrypted data storage and sync. @@ -377,7 +299,8 @@ class Soledad(object): :type auth_token: str :raise BootstrapSequenceError: Raised when the secret generation and - storage on server sequence has failed for some reason. + storage on server sequence has failed + for some reason. """ # get config params self._uuid = uuid @@ -623,7 +546,6 @@ class Soledad(object): Initialize the Symmetrically-Encrypted document to be synced database, and the queue to communicate with subprocess workers. """ - print "INITIALIZING SYNC DB" self._sync_db = sqlite3.connect(self._local_sync_path, check_same_thread=False) self._create_sync_db() @@ -633,10 +555,16 @@ class Soledad(object): """ Create local sync documents db if needed. """ - sql = ("""CREATE TABLE IF NOT EXISTS %s """ - """(doc_id, rev, content)""" % SyncEncrypterPool.TABLE_NAME) + encr = SyncEncrypterPool + decr = SyncDecrypterPool + sql_encr = ("CREATE TABLE IF NOT EXISTS %s (%s)" % ( + encr.TABLE_NAME, encr.FIELD_NAMES)) + sql_decr = ("CREATE TABLE IF NOT EXISTS %s (%s)" % ( + decr.TABLE_NAME, decr.FIELD_NAMES)) + c = self._sync_db.cursor() - c.execute(sql) + c.execute(sql_encr) + c.execute(sql_decr) self._sync_db.commit() def close(self): @@ -1460,13 +1388,14 @@ class Soledad(object): return self._passphrase.encode('utf-8') # - # Symmetric encryption + # Symmetric encryption / decryption # def _encrypt_syncing_docs(self): """ Process the syncing queue and send the documents there - to be encrypted in the sync db. + to be encrypted in the sync db. They will be read by the + SoledadSyncTarget during the sync_exchange. """ lock = self.encrypting_lock # optional wait flag used to avoid blocking diff --git a/client/src/leap/soledad/client/crypto.py b/client/src/leap/soledad/client/crypto.py index c1f65171..2ada4937 100644 --- a/client/src/leap/soledad/client/crypto.py +++ b/client/src/leap/soledad/client/crypto.py @@ -14,28 +14,41 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . - - """ Cryptographic utilities for Soledad. """ - - import os import binascii import hmac import hashlib - +import json +import logging +import multiprocessing from pycryptopp.cipher.aes import AES from pycryptopp.cipher.xsalsa20 import XSalsa20 - -from leap.soledad.common import ( - soledad_assert, - soledad_assert_type, +from leap.soledad.common import soledad_assert +from leap.soledad.common import soledad_assert_type + + +from leap.soledad.common.crypto import ( + EncryptionSchemes, + UnknownEncryptionScheme, + MacMethods, + UnknownMacMethod, + WrongMac, + ENC_JSON_KEY, + ENC_SCHEME_KEY, + ENC_METHOD_KEY, + ENC_IV_KEY, + MAC_KEY, + MAC_METHOD_KEY, ) +logger = logging.getLogger(__name__) + + MAC_KEY_LENGTH = 64 @@ -47,6 +60,17 @@ class EncryptionMethods(object): AES_256_CTR = 'aes-256-ctr' XSALSA20 = 'xsalsa20' +# +# Exceptions +# + + +class DocumentNotEncrypted(Exception): + """ + Raised for failures in document encryption. + """ + pass + class UnknownEncryptionMethod(Exception): """ @@ -168,9 +192,9 @@ def doc_mac_key(doc_id, secret): class SoledadCrypto(object): """ - General cryptographic functionality. + General cryptographic functionality encapsulated in a + object that can be passed along. """ - def __init__(self, soledad): """ Initialize the crypto object. @@ -228,3 +252,279 @@ class SoledadCrypto(object): secret = property( _get_secret, doc='The secret used for symmetric encryption') + +# +# Crypto utilities for a SoledadDocument. +# + + +def mac_doc(doc_id, doc_rev, ciphertext, mac_method, secret): + """ + Calculate a MAC for C{doc} using C{ciphertext}. + + Current MAC method used is HMAC, with the following parameters: + + * key: sha256(storage_secret, doc_id) + * msg: doc_id + doc_rev + ciphertext + * digestmod: sha256 + + :param doc_id: The id of the document. + :type doc_id: str + :param doc_rev: The revision of the document. + :type doc_rev: str + :param ciphertext: The content of the document. + :type ciphertext: str + :param mac_method: The MAC method to use. + :type mac_method: str + :param secret: soledad secret + :type secret: Soledad.secret_storage + + :return: The calculated MAC. + :rtype: str + """ + if mac_method == MacMethods.HMAC: + return hmac.new( + doc_mac_key(doc_id, secret), + str(doc_id) + str(doc_rev) + ciphertext, + hashlib.sha256).digest() + # raise if we do not know how to handle this MAC method + raise UnknownMacMethod('Unknown MAC method: %s.' % mac_method) + + +def encrypt_docstr(docstr, doc_id, doc_rev, key, secret): + """ + Encrypt C{doc}'s content. + + Encrypt doc's contents using AES-256 CTR mode and return a valid JSON + string representing the following: + + { + ENC_JSON_KEY: '', + ENC_SCHEME_KEY: 'symkey', + ENC_METHOD_KEY: EncryptionMethods.AES_256_CTR, + ENC_IV_KEY: '', + MAC_KEY: '' + MAC_METHOD_KEY: 'hmac' + } + + :param docstr: A representation of the document to be encrypted. + :type docstr: str or unicode. + + :param doc_id: The document id. + :type doc_id: str + + :param doc_rev: The document revision. + :type doc_rev: str + + :param key: The key used to encrypt ``data`` (must be 256 bits long). + :type key: str + + :param secret: + :type secret: + + :return: The JSON serialization of the dict representing the encrypted + content. + :rtype: str + """ + # encrypt content using AES-256 CTR mode + iv, ciphertext = encrypt_sym( + str(docstr), # encryption/decryption routines expect str + key, method=EncryptionMethods.AES_256_CTR) + # Return a representation for the encrypted content. In the following, we + # convert binary data to hexadecimal representation so the JSON + # serialization does not complain about what it tries to serialize. + hex_ciphertext = binascii.b2a_hex(ciphertext) + return json.dumps({ + ENC_JSON_KEY: hex_ciphertext, + ENC_SCHEME_KEY: EncryptionSchemes.SYMKEY, + ENC_METHOD_KEY: EncryptionMethods.AES_256_CTR, + ENC_IV_KEY: iv, + MAC_KEY: binascii.b2a_hex(mac_doc( # store the mac as hex. + doc_id, doc_rev, ciphertext, + MacMethods.HMAC, secret)), + MAC_METHOD_KEY: MacMethods.HMAC, + }) + + +# XXX change to docstr... +def decrypt_doc(crypto, doc): + """ + Decrypt C{doc}'s content. + + Return the JSON string representation of the document's decrypted content. + + The content of the document should have the following structure: + + { + ENC_JSON_KEY: '', + ENC_SCHEME_KEY: '', + ENC_METHOD_KEY: '', + ENC_IV_KEY: '', # (optional) + MAC_KEY: '' + MAC_METHOD_KEY: 'hmac' + } + + C{enc_blob} is the encryption of the JSON serialization of the document's + content. For now Soledad just deals with documents whose C{enc_scheme} is + EncryptionSchemes.SYMKEY and C{enc_method} is + EncryptionMethods.AES_256_CTR. + + :param crypto: A SoledadCryto instance to perform the encryption. + :type crypto: leap.soledad.crypto.SoledadCrypto + :param doc: The document to be decrypted. + :type doc: SoledadDocument + + :return: The JSON serialization of the decrypted content. + :rtype: str + """ + soledad_assert(doc.is_tombstone() is False) + soledad_assert(ENC_JSON_KEY in doc.content) + soledad_assert(ENC_SCHEME_KEY in doc.content) + soledad_assert(ENC_METHOD_KEY in doc.content) + soledad_assert(MAC_KEY in doc.content) + soledad_assert(MAC_METHOD_KEY in doc.content) + # verify MAC + ciphertext = binascii.a2b_hex( # content is stored as hex. + doc.content[ENC_JSON_KEY]) + mac = mac_doc( + doc.doc_id, doc.rev, + ciphertext, + doc.content[MAC_METHOD_KEY], crypto.secret) + # we compare mac's hashes to avoid possible timing attacks that might + # exploit python's builtin comparison operator behaviour, which fails + # immediatelly when non-matching bytes are found. + doc_mac_hash = hashlib.sha256( + binascii.a2b_hex( # the mac is stored as hex + doc.content[MAC_KEY])).digest() + calculated_mac_hash = hashlib.sha256(mac).digest() + if doc_mac_hash != calculated_mac_hash: + raise WrongMac('Could not authenticate document\'s contents.') + # decrypt doc's content + enc_scheme = doc.content[ENC_SCHEME_KEY] + plainjson = None + if enc_scheme == EncryptionSchemes.SYMKEY: + enc_method = doc.content[ENC_METHOD_KEY] + if enc_method == EncryptionMethods.AES_256_CTR: + soledad_assert(ENC_IV_KEY in doc.content) + plainjson = crypto.decrypt_sym( + ciphertext, + crypto.doc_passphrase(doc.doc_id), + method=enc_method, + iv=doc.content[ENC_IV_KEY]) + else: + raise UnknownEncryptionMethod(enc_method) + else: + raise UnknownEncryptionScheme(enc_scheme) + return plainjson + + +def is_symmetrically_encrypted(doc): + """ + Return True if the document was symmetrically encrypted. + + :param doc: The document to check. + :type doc: SoledadDocument + + :rtype: bool + """ + if doc.content and ENC_SCHEME_KEY in doc.content: + if doc.content[ENC_SCHEME_KEY] == EncryptionSchemes.SYMKEY: + return True + return False + + +# +# Encrypt/decrypt pools of workers +# + +class SyncEncryptDecryptPool(object): + """ + Base class for encrypter/decrypter pools + """ + + def __init__(self, crypto, sync_db): + """ + Initialize the pool of encryption-workers. + + :param crypto: A SoledadCryto instance to perform the encryption. + :type crypto: leap.soledad.crypto.SoledadCrypto + + :param sync_db: a database connection handle + :type sync_db: handle + """ + self._pool = multiprocessing.Pool(self.WORKERS) + self._crypto = crypto + self._sync_db = sync_db + + +def encrypt_doc_task(doc_id, doc_rev, content, key, secret): + encrypted_content = encrypt_docstr( + content, doc_id, doc_rev, key, secret) + return doc_id, doc_rev, encrypted_content + + +class SyncEncrypterPool(SyncEncryptDecryptPool): + """ + of documents to be synced. + """ + # TODO implement throttling to reduce cpu usage?? + WORKERS = 10 + TABLE_NAME = "docs_tosync" + FIELD_NAMES = "doc_id, rev, content" + + def encrypt_doc(self, doc): + """ + Symmetrically encrypt a document. + + :param doc: The document with contents to be encrypted. + :type doc: SoledadDocument + """ + docstr = doc.get_json() + key = self._crypto.doc_passphrase(doc.doc_id) + secret = self._crypto.secret + args = doc.doc_id, doc.rev, docstr, key, secret + + try: + self._pool.apply_async(encrypt_doc_task, args, + callback=self.encrypt_doc_cb) + except Exception as exc: + logger.exception(exc) + + def encrypt_doc_cb(self, result): + doc_id, doc_rev, content = result + self.insert_encrypted_doc(doc_id, doc_rev, content) + + def insert_encrypted_doc(self, doc_id, doc_rev, content): + """ + Insert the contents of the encrypted doc into the local sync + database. + + :param doc: The document with contents to be encrypted. + :type doc: SoledadDocument + :param content: The encrypted document. + :type content: str + """ + c = self._sync_db.cursor() + sql_del = "DELETE FROM '%s' WHERE doc_id=?" % (self.TABLE_NAME,) + c.execute(sql_del, (doc_id, )) + sql_ins = "INSERT INTO '%s' VALUES (?, ?, ?)" % (self.TABLE_NAME,) + c.execute(sql_ins, (doc_id, doc_rev, content)) + self._sync_db.commit() + + +class SyncDecrypterPool(SyncEncryptDecryptPool): + """ + Pool of workers that spawn subprocesses to execute the symmetric decryption + of documents that were received. + """ + WORKERS = 10 + TABLE_NAME = "docs_received" + FIELD_NAMES = "doc_id, rev, content, gen, trans_id" + + def decrypt_doc(self, doc_id, rev): + """ + Symmetrically decrypt a document. + + :param doc: The document with contents to be encrypted. + :type doc: SoledadDocument + """ diff --git a/client/src/leap/soledad/client/sqlcipher.py b/client/src/leap/soledad/client/sqlcipher.py index 9183afac..c7cf79a2 100644 --- a/client/src/leap/soledad/client/sqlcipher.py +++ b/client/src/leap/soledad/client/sqlcipher.py @@ -392,7 +392,7 @@ class SQLCipherDatabase(sqlite_backend.SQLitePartialExpandDatabase): :param url: The url of the target replica to sync with. :type url: str :param creds: optional dictionary giving credentials. - to authorize the operation with the server. + to authorize the operation with the server. :type creds: dict """ if self._syncer is None: diff --git a/client/src/leap/soledad/client/target.py b/client/src/leap/soledad/client/target.py index 56652b0b..dc2a0420 100644 --- a/client/src/leap/soledad/client/target.py +++ b/client/src/leap/soledad/client/target.py @@ -18,11 +18,8 @@ A U1DB backend for encrypting data before sending to server and decrypting after receiving. """ -import binascii import cStringIO import gzip -import hashlib -import hmac import logging import os import sqlite3 @@ -37,28 +34,9 @@ from u1db import errors from u1db.remote.http_target import HTTPSyncTarget from u1db.remote.http_client import _encode_query_parameter - -from leap.soledad.common import soledad_assert -from leap.soledad.common.crypto import ( - EncryptionSchemes, - UnknownEncryptionScheme, - MacMethods, - UnknownMacMethod, - WrongMac, - ENC_JSON_KEY, - ENC_SCHEME_KEY, - ENC_METHOD_KEY, - ENC_IV_KEY, - MAC_KEY, - MAC_METHOD_KEY, -) from leap.soledad.common.document import SoledadDocument from leap.soledad.client.auth import TokenBasedAuth -from leap.soledad.client.crypto import ( - EncryptionMethods, - UnknownEncryptionMethod, -) -from leap.soledad.client.crypto import encrypt_sym, doc_mac_key +from leap.soledad.client.crypto import is_symmetrically_encrypted, decrypt_doc from leap.common.check import leap_check @@ -69,177 +47,6 @@ logger = logging.getLogger(__name__) # -class DocumentNotEncrypted(Exception): - """ - Raised for failures in document encryption. - """ - pass - - -# -# Crypto utilities for a SoledadDocument. -# - - -def mac_doc(doc_id, doc_rev, ciphertext, mac_method, secret): - """ - Calculate a MAC for C{doc} using C{ciphertext}. - - Current MAC method used is HMAC, with the following parameters: - - * key: sha256(storage_secret, doc_id) - * msg: doc_id + doc_rev + ciphertext - * digestmod: sha256 - - :param doc_id: The id of the document. - :type doc_id: str - :param doc_rev: The revision of the document. - :type doc_rev: str - :param ciphertext: The content of the document. - :type ciphertext: str - :param mac_method: The MAC method to use. - :type mac_method: str - :param secret: soledad secret - :type secret: Soledad.secret_storage - - :return: The calculated MAC. - :rtype: str - """ - if mac_method == MacMethods.HMAC: - return hmac.new( - doc_mac_key(doc_id, secret), - str(doc_id) + str(doc_rev) + ciphertext, - hashlib.sha256).digest() - # raise if we do not know how to handle this MAC method - raise UnknownMacMethod('Unknown MAC method: %s.' % mac_method) - - -def encrypt_docstr(docstr, doc_id, doc_rev, key, secret): - """ - Encrypt C{doc}'s content. - - Encrypt doc's contents using AES-256 CTR mode and return a valid JSON - string representing the following: - - { - ENC_JSON_KEY: '', - ENC_SCHEME_KEY: 'symkey', - ENC_METHOD_KEY: EncryptionMethods.AES_256_CTR, - ENC_IV_KEY: '', - MAC_KEY: '' - MAC_METHOD_KEY: 'hmac' - } - - :param docstr: A representation of the document to be encrypted. - :type docstr: str or unicode. - - :param doc_id: The document id. - :type doc_id: str - - :param doc_rev: The document revision. - :type doc_rev: str - - :param key: The key used to encrypt ``data`` (must be 256 bits long). - :type key: str - - :param secret: - :type secret: - - :return: The JSON serialization of the dict representing the encrypted - content. - :rtype: str - """ - # encrypt content using AES-256 CTR mode - iv, ciphertext = encrypt_sym( - str(docstr), # encryption/decryption routines expect str - key, method=EncryptionMethods.AES_256_CTR) - # Return a representation for the encrypted content. In the following, we - # convert binary data to hexadecimal representation so the JSON - # serialization does not complain about what it tries to serialize. - hex_ciphertext = binascii.b2a_hex(ciphertext) - return json.dumps({ - ENC_JSON_KEY: hex_ciphertext, - ENC_SCHEME_KEY: EncryptionSchemes.SYMKEY, - ENC_METHOD_KEY: EncryptionMethods.AES_256_CTR, - ENC_IV_KEY: iv, - MAC_KEY: binascii.b2a_hex(mac_doc( # store the mac as hex. - doc_id, doc_rev, ciphertext, - MacMethods.HMAC, secret)), - MAC_METHOD_KEY: MacMethods.HMAC, - }) - - -def decrypt_doc(crypto, doc): - """ - Decrypt C{doc}'s content. - - Return the JSON string representation of the document's decrypted content. - - The content of the document should have the following structure: - - { - ENC_JSON_KEY: '', - ENC_SCHEME_KEY: '', - ENC_METHOD_KEY: '', - ENC_IV_KEY: '', # (optional) - MAC_KEY: '' - MAC_METHOD_KEY: 'hmac' - } - - C{enc_blob} is the encryption of the JSON serialization of the document's - content. For now Soledad just deals with documents whose C{enc_scheme} is - EncryptionSchemes.SYMKEY and C{enc_method} is - EncryptionMethods.AES_256_CTR. - - :param crypto: A SoledadCryto instance to perform the encryption. - :type crypto: leap.soledad.crypto.SoledadCrypto - :param doc: The document to be decrypted. - :type doc: SoledadDocument - - :return: The JSON serialization of the decrypted content. - :rtype: str - """ - soledad_assert(doc.is_tombstone() is False) - soledad_assert(ENC_JSON_KEY in doc.content) - soledad_assert(ENC_SCHEME_KEY in doc.content) - soledad_assert(ENC_METHOD_KEY in doc.content) - soledad_assert(MAC_KEY in doc.content) - soledad_assert(MAC_METHOD_KEY in doc.content) - # verify MAC - ciphertext = binascii.a2b_hex( # content is stored as hex. - doc.content[ENC_JSON_KEY]) - mac = mac_doc( - doc.doc_id, doc.rev, - ciphertext, - doc.content[MAC_METHOD_KEY], crypto.secret) - # we compare mac's hashes to avoid possible timing attacks that might - # exploit python's builtin comparison operator behaviour, which fails - # immediatelly when non-matching bytes are found. - doc_mac_hash = hashlib.sha256( - binascii.a2b_hex( # the mac is stored as hex - doc.content[MAC_KEY])).digest() - calculated_mac_hash = hashlib.sha256(mac).digest() - if doc_mac_hash != calculated_mac_hash: - raise WrongMac('Could not authenticate document\'s contents.') - # decrypt doc's content - enc_scheme = doc.content[ENC_SCHEME_KEY] - plainjson = None - if enc_scheme == EncryptionSchemes.SYMKEY: - enc_method = doc.content[ENC_METHOD_KEY] - if enc_method == EncryptionMethods.AES_256_CTR: - soledad_assert(ENC_IV_KEY in doc.content) - plainjson = crypto.decrypt_sym( - ciphertext, - crypto.doc_passphrase(doc.doc_id), - method=enc_method, - iv=doc.content[ENC_IV_KEY]) - else: - raise UnknownEncryptionMethod(enc_method) - else: - raise UnknownEncryptionScheme(enc_scheme) - return plainjson - - def _gunzip(data): """ Uncompress data that is gzipped. @@ -323,18 +130,22 @@ class SoledadSyncTarget(HTTPSyncTarget, TokenBasedAuth): :type data: str :param return_doc_cb: A callback to insert docs from target. - :type return_doc_cb: function + :type return_doc_cb: callable :param ensure_callback: A callback to ensure we have the correct target_replica_uid, if it was just created. - :type ensure_callback: function + :type ensure_callback: callable - :raise BrokenSyncStream: If C{data} is malformed. + :raise BrokenSyncStream: If `data` is malformed. :return: A dictionary representing the first line of the response got from remote replica. :rtype: dict """ + # we keep a reference to the callback in case + # we defer the decryption + self._return_doc_cb = return_doc_cb + parts = data.splitlines() # one at a time if not parts or parts[0] != '[': raise BrokenSyncStream @@ -469,15 +280,22 @@ class SoledadSyncTarget(HTTPSyncTarget, TokenBasedAuth): the last local generation the remote replica knows about. :type docs_by_generations: list of tuples + :param source_replica_uid: The uid of the source replica. :type source_replica_uid: str + :param last_known_generation: Target's last known generation. :type last_known_generation: int + :param last_known_trans_id: Target's last known transaction id. :type last_known_trans_id: str + :param return_doc_cb: A callback for inserting received documents from - target. + target. If not overriden, this will call u1db + insert_doc_from_target in synchronizer, which + implements the TAKE OTHER semantics. :type return_doc_cb: function + :param ensure_callback: A callback that ensures we know the target replica uid if the target replica was just created. @@ -623,8 +441,7 @@ class SoledadSyncTarget(HTTPSyncTarget, TokenBasedAuth): """ c = self._sync_db.cursor() # XXX interpolate table name - sql = ("SELECT content FROM docs_tosync " - "WHERE doc_id=? and rev=?") + sql = ("SELECT content FROM docs_tosync WHERE doc_id=? and rev=?") c.execute(sql, (doc_id, doc_rev)) res = c.fetchall() if len(res) != 0: -- cgit v1.2.3