diff --git a/lib/ops/integrity-store-ops.js b/lib/ops/integrity-store-ops.js --- a/lib/ops/integrity-store-ops.js +++ b/lib/ops/integrity-store-ops.js @@ -40,6 +40,15 @@ | RemoveIntegrityThreadHashesOperation | RemoveAllIntegrityThreadHashesOperation; +function convertIntegrityThreadHashesToClientDBIntegrityThreadHashes( + threadHashes: ThreadHashes, +): $ReadOnlyArray { + return entries(threadHashes).map(([id, threadHash]) => ({ + id: id, + threadHash: threadHash.toString(), + })); +} + const integrityStoreOpsHandlers: BaseStoreOpsHandlers< IntegrityStore, IntegrityStoreOperation, @@ -84,12 +93,10 @@ } const { threadHashes } = integrityStoreOperation.payload; - const dbIntegrityThreadHashes: ClientDBIntegrityThreadHash[] = entries( - threadHashes, - ).map(([id, threadHash]) => ({ - id: id, - threadHash: threadHash.toString(), - })); + const dbIntegrityThreadHashes: $ReadOnlyArray = + convertIntegrityThreadHashesToClientDBIntegrityThreadHashes( + threadHashes, + ); if (dbIntegrityThreadHashes.length === 0) { return undefined; @@ -115,4 +122,7 @@ }, }; -export { integrityStoreOpsHandlers }; +export { + integrityStoreOpsHandlers, + convertIntegrityThreadHashesToClientDBIntegrityThreadHashes, +}; diff --git a/native/cpp/CommonCpp/DatabaseManagers/DatabaseQueryExecutor.h b/native/cpp/CommonCpp/DatabaseManagers/DatabaseQueryExecutor.h --- a/native/cpp/CommonCpp/DatabaseManagers/DatabaseQueryExecutor.h +++ b/native/cpp/CommonCpp/DatabaseManagers/DatabaseQueryExecutor.h @@ -3,6 +3,7 @@ #include "../CryptoTools/Persist.h" #include "entities/CommunityInfo.h" #include "entities/Draft.h" +#include "entities/IntegrityThreadHash.h" #include "entities/KeyserverInfo.h" #include "entities/Message.h" #include "entities/MessageStoreThread.h" @@ -78,6 +79,13 @@ virtual void removeCommunities(const std::vector &ids) const = 0; virtual void removeAllCommunities() const = 0; virtual std::vector getAllCommunities() const = 0; + virtual void replaceIntegrityThreadHashes( + const std::vector &thread_hashes) const = 0; + virtual void + removeIntegrityThreadHashes(const std::vector &ids) const = 0; + virtual void removeAllIntegrityThreadHashes() const = 0; + virtual std::vector + getAllIntegrityThreadHashes() const = 0; virtual void beginTransaction() const = 0; virtual void commitTransaction() const = 0; virtual void rollbackTransaction() const = 0; diff --git a/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.h b/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.h --- a/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.h +++ b/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.h @@ -5,6 +5,7 @@ #include "NativeSQLiteConnectionManager.h" #include "entities/CommunityInfo.h" #include "entities/Draft.h" +#include "entities/IntegrityThreadHash.h" #include "entities/KeyserverInfo.h" #include "entities/UserInfo.h" @@ -92,6 +93,12 @@ void removeCommunities(const std::vector &ids) const override; void removeAllCommunities() const override; std::vector getAllCommunities() const override; + void replaceIntegrityThreadHashes( + const std::vector &thread_hashes) const override; + void removeIntegrityThreadHashes( + const std::vector &ids) const override; + void removeAllIntegrityThreadHashes() const override; + std::vector getAllIntegrityThreadHashes() const override; void beginTransaction() const override; void commitTransaction() const override; void rollbackTransaction() const override; diff --git a/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.cpp b/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.cpp --- a/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.cpp +++ b/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.cpp @@ -3,6 +3,7 @@ #include "entities/CommunityInfo.h" #include "entities/EntityQueryHelpers.h" +#include "entities/IntegrityThreadHash.h" #include "entities/KeyserverInfo.h" #include "entities/Metadata.h" #include "entities/UserInfo.h" @@ -1583,6 +1584,53 @@ SQLiteQueryExecutor::getConnection(), getAllCommunitiesSQL); } +void SQLiteQueryExecutor::replaceIntegrityThreadHashes( + const std::vector &threadHashes) const { + static std::string replaceIntegrityThreadHashSQL = + "REPLACE INTO integrity_store (id, thread_hash) " + "VALUES (?, ?);"; + for (const IntegrityThreadHash &integrityThreadHash : threadHashes) { + replaceEntity( + SQLiteQueryExecutor::getConnection(), + replaceIntegrityThreadHashSQL, + integrityThreadHash); + } +} + +void SQLiteQueryExecutor::removeAllIntegrityThreadHashes() const { + static std::string removeAllIntegrityThreadHashesSQL = + "DELETE FROM integrity_store;"; + removeAllEntities( + SQLiteQueryExecutor::getConnection(), removeAllIntegrityThreadHashesSQL); +} + +void SQLiteQueryExecutor::removeIntegrityThreadHashes( + const std::vector &ids) const { + if (!ids.size()) { + return; + } + + std::stringstream removeIntegrityThreadHashesByKeysSQLStream; + removeIntegrityThreadHashesByKeysSQLStream << "DELETE FROM integrity_store " + "WHERE id IN " + << getSQLStatementArray(ids.size()) + << ";"; + + removeEntitiesByKeys( + SQLiteQueryExecutor::getConnection(), + removeIntegrityThreadHashesByKeysSQLStream.str(), + ids); +} + +std::vector +SQLiteQueryExecutor::getAllIntegrityThreadHashes() const { + static std::string getAllIntegrityThreadHashesSQL = + "SELECT * " + "FROM integrity_store;"; + return getAllEntities( + SQLiteQueryExecutor::getConnection(), getAllIntegrityThreadHashesSQL); +} + void SQLiteQueryExecutor::beginTransaction() const { executeQuery(SQLiteQueryExecutor::getConnection(), "BEGIN TRANSACTION;"); } diff --git a/native/cpp/CommonCpp/DatabaseManagers/entities/IntegrityThreadHash.h b/native/cpp/CommonCpp/DatabaseManagers/entities/IntegrityThreadHash.h new file mode 100644 --- /dev/null +++ b/native/cpp/CommonCpp/DatabaseManagers/entities/IntegrityThreadHash.h @@ -0,0 +1,24 @@ +#pragma once + +#include "SQLiteDataConverters.h" +#include +#include + +namespace comm { + +struct IntegrityThreadHash { + std::string id; + std::string thread_hash; + + static IntegrityThreadHash fromSQLResult(sqlite3_stmt *sqlRow, int idx) { + return IntegrityThreadHash{ + getStringFromSQLRow(sqlRow, idx), getStringFromSQLRow(sqlRow, idx + 1)}; + } + + int bindToSQL(sqlite3_stmt *sql, int idx) const { + bindStringToSQL(id, sql, idx); + return bindStringToSQL(thread_hash, sql, idx + 1); + } +}; + +} // namespace comm diff --git a/native/ios/Comm.xcodeproj/project.pbxproj b/native/ios/Comm.xcodeproj/project.pbxproj --- a/native/ios/Comm.xcodeproj/project.pbxproj +++ b/native/ios/Comm.xcodeproj/project.pbxproj @@ -138,6 +138,7 @@ 13B07FB61A68108700A75B9A /* Info.release.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.release.plist; path = Comm/Info.release.plist; sourceTree = ""; }; 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = Comm/main.m; sourceTree = ""; }; 2DDA0A22FECC9DAA5C19C35D /* Metadata.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Metadata.h; sourceTree = ""; }; + 34329B452B9EC96200233438 /* IntegrityThreadHash.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IntegrityThreadHash.h; sourceTree = ""; }; 3EE4DCB430B05EC9DE7D7B01 /* libPods-NotificationService.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-NotificationService.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 3EEB3E70587B0ADAD05237B0 /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-Comm/ExpoModulesProvider.swift"; sourceTree = ""; }; 71142A7526C2650A0039DCBD /* CommSecureStoreIOSWrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CommSecureStoreIOSWrapper.h; path = Comm/CommSecureStoreIOSWrapper.h; sourceTree = ""; }; @@ -513,6 +514,7 @@ CB01F0C02B67CDC20089E1F9 /* SQLiteDataConverters.h */, CB01F0BF2B67CDC20089E1F9 /* SQLiteStatementWrapper.h */, CBF9DAE22B595934000EE771 /* EntityQueryHelpers.h */, + 34329B452B9EC96200233438 /* IntegrityThreadHash.h */, B7906F6A27209091009BBBF5 /* OlmPersistAccount.h */, B7906F6B27209091009BBBF5 /* OlmPersistSession.h */, B7906F6C27209091009BBBF5 /* Thread.h */, diff --git a/web/cpp/SQLiteQueryExecutorBindings.cpp b/web/cpp/SQLiteQueryExecutorBindings.cpp --- a/web/cpp/SQLiteQueryExecutorBindings.cpp +++ b/web/cpp/SQLiteQueryExecutorBindings.cpp @@ -52,6 +52,9 @@ value_object("CommunityInfo") .field("id", &CommunityInfo::id) .field("communityInfo", &CommunityInfo::community_info); + value_object("IntegrityThreadHash") + .field("id", &IntegrityThreadHash::id) + .field("threadHash", &IntegrityThreadHash::thread_hash); value_object("WebThread") .field("id", &WebThread::id) @@ -175,6 +178,18 @@ .function( "removeAllCommunities", &SQLiteQueryExecutor::removeAllCommunities) .function("getAllCommunities", &SQLiteQueryExecutor::getAllCommunities) + .function( + "replaceIntegrityThreadHashes", + &SQLiteQueryExecutor::replaceIntegrityThreadHashes) + .function( + "removeIntegrityThreadHashes", + &SQLiteQueryExecutor::removeIntegrityThreadHashes) + .function( + "removeAllIntegrityThreadHashes", + &SQLiteQueryExecutor::removeAllIntegrityThreadHashes) + .function( + "getAllIntegrityThreadHashes", + &SQLiteQueryExecutor::getAllIntegrityThreadHashes) .function("beginTransaction", &SQLiteQueryExecutor::beginTransaction) .function("commitTransaction", &SQLiteQueryExecutor::commitTransaction) .function( diff --git a/web/shared-worker/_generated/comm-query-executor.js b/web/shared-worker/_generated/comm-query-executor.js --- a/web/shared-worker/_generated/comm-query-executor.js +++ b/web/shared-worker/_generated/comm-query-executor.js @@ -116,7 +116,7 @@ V.prototype.xd=function(a){this.Tc&&(a=this.Tc(a));return a};V.prototype.Hc=function(a){this.pb&&this.pb(a)};V.prototype.argPackAdvance=8;V.prototype.readValueFromPointer=lb;V.prototype.deleteObject=function(a){if(null!==a)a["delete"]()}; V.prototype.fromWireType=function(a){function b(){return this.Vb?Jb(this.Ta.Eb,{Xa:this.Md,Sa:c,fb:this,ab:a}):Jb(this.Ta.Eb,{Xa:this,Sa:a})}var c=this.xd(a);if(!c)return this.Hc(a),null;var d=Ib(this.Ta,c);if(void 0!==d){if(0===d.Na.count.value)return d.Na.Sa=c,d.Na.ab=a,d.clone();d=d.clone();this.Hc(a);return d}d=this.Ta.vd(c);d=Db[d];if(!d)return b.call(this);d=this.Tb?d.jd:d.pointerType;var f=Cb(c,this.Ta,d.Ta);return null===f?b.call(this):this.Vb?Jb(d.Ta.Eb,{Xa:d,Sa:f,fb:this,ab:a}):Jb(d.Ta.Eb, {Xa:d,Sa:f})};Wb=e.UnboundTypeError=rb("UnboundTypeError");e.count_emval_handles=function(){for(var a=0,b=5;bf?-28:N.Fc(d,f).fd;case 1:case 2:return 0;case 3:return d.flags;case 4:return f=hb(),d.flags|=f,0;case 5:return f=hb(),D[f+0>>1]=2,0;case 6:case 7:return 0;case 16:case 8:return-28;case 9:return E[Kc()>>2]=28,-1;default:return-28}}catch(g){if("undefined"== typeof N||!(g instanceof N.Ma))throw g;return-g.Ra}},X:function(a,b){try{var c=Q(a);return fb(N.stat,c.path,b)}catch(d){if("undefined"==typeof N||!(d instanceof N.Ma))throw d;return-d.Ra}},E:function(a,b,c){try{b=c+2097152>>>0<4194305-!!b?(b>>>0)+4294967296*c:NaN;if(isNaN(b))return-61;N.ud(a,b);return 0}catch(d){if("undefined"==typeof N||!(d instanceof N.Ma))throw d;return-d.Ra}},S:function(a,b){try{if(0===b)return-28;var c=N.cwd(),d=ta(c)+1;if(b>>0,(G=h,1<=+Math.abs(G)?0>>0:~~+Math.ceil((G-+(~~G>>>0))/4294967296)>>> @@ -129,7 +129,7 @@ c();for(p in h)h[p].write(t,q[p]);null!==n&&n.push(d,t);return t},argPackAdvance:8,readValueFromPointer:lb,ib:d}]})},F:function(){},fa:function(a,b,c,d,f){var g=vb(c);b=S(b);R(a,{name:b,fromWireType:function(k){return!!k},toWireType:function(k,h){return h?d:f},argPackAdvance:8,readValueFromPointer:function(k){if(1===c)var h=B;else if(2===c)h=D;else if(4===c)h=E;else throw new TypeError("Unknown boolean type size: "+b);return this.fromWireType(h[k>>g])},ib:null})},ka:function(a,b,c,d,f,g,k,h,n,q,p, t,x){p=S(p);g=W(f,g);h&&(h=W(k,h));q&&(q=W(n,q));x=W(t,x);var l=pb(p);Mb(l,function(){Zb("Cannot construct "+p+" due to unbound types",[d])});ub([a,b,c],d?[d]:[],function(u){u=u[0];if(d){var w=u.Ta;var C=w.Eb}else C=U.prototype;u=qb(l,function(){if(Object.getPrototypeOf(this)!==K)throw new xb("Use 'new' to construct "+p);if(void 0===H.tb)throw new xb(p+" has no accessible constructor");var I=H.tb[arguments.length];if(void 0===I)throw new xb("Tried to invoke ctor of "+p+" with invalid number of parameters ("+ arguments.length+") - expected ("+Object.keys(H.tb).toString()+") parameters instead!");return I.apply(this,arguments)});var K=Object.create(C,{constructor:{value:u}});u.prototype=K;var H=new Nb(p,u,K,x,w,g,h,q);w=new V(p,H,!0,!1);C=new V(p+"*",H,!1,!1);var Ga=new V(p+" const*",H,!1,!0);Db[a]={pointerType:C,jd:Ga};Ub(l,u);return[w,C,Ga]})},ja:function(a,b,c,d,f,g){0{Zb("Cannot construct "+h.name+" due to unbound types",k)};ub([],k,function(q){q.splice(1,0,null);h.Ta.tb[b-1]=bc(n,q,null,f,g);return[]});return[]})},b:function(a,b,c,d,f,g,k,h){var n=$b(c,d);b=S(b);g=W(f,g);ub([],[a],function(q){function p(){Zb("Cannot call "+ +1])throw new xb("Cannot register multiple constructors with identical number of parameters ("+(b-1)+") for class '"+h.name+"'! Overload resolution is currently only performed using the parameter count, not actual type info!");h.Ta.tb[b-1]=()=>{Zb("Cannot construct "+h.name+" due to unbound types",k)};ub([],k,function(q){q.splice(1,0,null);h.Ta.tb[b-1]=bc(n,q,null,f,g);return[]});return[]})},a:function(a,b,c,d,f,g,k,h){var n=$b(c,d);b=S(b);g=W(f,g);ub([],[a],function(q){function p(){Zb("Cannot call "+ t+" due to unbound types",n)}q=q[0];var t=q.name+"."+b;b.startsWith("@@")&&(b=Symbol[b.substring(2)]);h&&q.Ta.Nd.push(b);var x=q.Ta.Eb,l=x[b];void 0===l||void 0===l.$a&&l.className!==q.name&&l.Qb===c-2?(p.Qb=c-2,p.className=q.name,x[b]=p):(Lb(x,b,t),x[b].$a[c-2]=p);ub([],n,function(u){u=bc(t,u,q,g,k);void 0===x[b].$a?(u.Qb=c-2,x[b]=u):x[b].$a[c-2]=u;return[]});return[]})},ea:function(a,b){b=S(b);R(a,{name:b,fromWireType:function(c){var d=ec(c);dc(c);return d},toWireType:function(c,d){return Sb(d)}, argPackAdvance:8,readValueFromPointer:lb,ib:null})},x:function(a,b,c){c=vb(c);b=S(b);R(a,{name:b,fromWireType:function(d){return d},toWireType:function(d,f){return f},argPackAdvance:8,readValueFromPointer:fc(b,c),ib:null})},ha:function(a,b,c,d,f,g){var k=$b(b,c);a=S(a);f=W(d,f);Mb(a,function(){Zb("Cannot call "+a+" due to unbound types",k)},b-1);ub([],k,function(h){h=[h[0],null].concat(h.slice(1));Ub(a,bc(a,h,null,f,g),b-1);return[]})},i:function(a,b,c,d,f){b=S(b);-1===f&&(f=4294967295);f=vb(c);var g= h=>h;if(0===d){var k=32-8*c;g=h=>h<>>k}c=b.includes("unsigned")?function(h,n){return n>>>0}:function(h,n){return n};R(a,{name:b,fromWireType:g,toWireType:c,argPackAdvance:8,readValueFromPointer:gc(b,f,0!==d),ib:null})},e:function(a,b,c){function d(g){g>>=2;var k=F;return new f(ua,k[g+1],k[g])}var f=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array][b];c=S(c);R(a,{name:c,fromWireType:d,argPackAdvance:8,readValueFromPointer:d},{Ad:!0})},y:function(a,b){b= diff --git a/web/shared-worker/_generated/comm_query_executor.wasm b/web/shared-worker/_generated/comm_query_executor.wasm index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 GIT binary patch literal 0 Hc$@ { + let queryExecutor; + let dbModule; + + beforeAll(async () => { + dbModule = getDatabaseModule(); + }); + + beforeEach(() => { + if (!dbModule) { + throw new Error('Database module is missing'); + } + queryExecutor = new dbModule.SQLiteQueryExecutor(FILE_PATH); + if (!queryExecutor) { + throw new Error('SQLiteQueryExecutor is missing'); + } + + queryExecutor.replaceIntegrityThreadHashes( + convertIntegrityThreadHashesToClientDBIntegrityThreadHashes( + TEST_THREAD_HASHES_1, + ), + ); + }); + + afterEach(() => { + clearSensitiveData(dbModule, FILE_PATH, queryExecutor); + }); + + it('should return all thread hashes', () => { + const threadHashes = queryExecutor?.getAllIntegrityThreadHashes(); + + expect(threadHashes).toHaveLength(5); + }); + + it('should update two thread hashes and add a new thread hash', () => { + queryExecutor?.replaceIntegrityThreadHashes( + convertIntegrityThreadHashesToClientDBIntegrityThreadHashes( + TEST_THREAD_HASHES_UPDATE, + ), + ); + + const threadHashes = queryExecutor?.getAllIntegrityThreadHashes(); + if (!threadHashes) { + throw new Error('thread hashes are not defined'); + } + + expect(threadHashes).toHaveLength(6); + + const threadHashesFromDB = + integrityStoreOpsHandlers.translateClientDBData(threadHashes); + + expect(threadHashesFromDB).toStrictEqual({ + ...TEST_THREAD_HASHES_1, + ...TEST_THREAD_HASHES_UPDATE, + }); + }); + + it('should remove two thread hashes', () => { + queryExecutor?.removeIntegrityThreadHashes(['256|2204191', '256|2205980']); + + const threadHashes = queryExecutor?.getAllIntegrityThreadHashes(); + + if (!threadHashes) { + throw new Error('thread hashes not defined'); + } + + expect(threadHashes.length).toBe(3); + }); + + it('should remove all thread hashes', () => { + queryExecutor?.removeAllIntegrityThreadHashes(); + const threadHashes = queryExecutor?.getAllIntegrityThreadHashes(); + + expect(threadHashes).toHaveLength(0); + }); +}); diff --git a/web/shared-worker/types/sqlite-query-executor.js b/web/shared-worker/types/sqlite-query-executor.js --- a/web/shared-worker/types/sqlite-query-executor.js +++ b/web/shared-worker/types/sqlite-query-executor.js @@ -1,6 +1,7 @@ // @flow import type { ClientDBCommunityInfo } from 'lib/ops/community-store-ops.js'; +import type { ClientDBIntegrityThreadHash } from 'lib/ops/integrity-store-ops.js'; import type { ClientDBKeyserverInfo } from 'lib/ops/keyserver-store-ops.js'; import type { ClientDBReport } from 'lib/ops/report-store-ops.js'; import type { ClientDBUserInfo } from 'lib/ops/user-store-ops.js'; @@ -114,6 +115,13 @@ removeAllCommunities(): void; getAllCommunities(): ClientDBCommunityInfo[]; + replaceIntegrityThreadHashes( + threadHashes: $ReadOnlyArray, + ): void; + removeIntegrityThreadHashes(ids: $ReadOnlyArray): void; + removeAllIntegrityThreadHashes(): void; + getAllIntegrityThreadHashes(): ClientDBIntegrityThreadHash[]; + beginTransaction(): void; commitTransaction(): void; rollbackTransaction(): void;