diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..ae3d45a8c --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "LibSession-Util"] + path = LibSession-Util + url = git@github.com:oxen-io/libsession-util.git diff --git a/LibSession-Util b/LibSession-Util new file mode 160000 index 000000000..53c824de0 --- /dev/null +++ b/LibSession-Util @@ -0,0 +1 @@ +Subproject commit 53c824de0d514307f3bad6a62449166bd10da6f8 diff --git a/Scripts/build_libSession_util.sh b/Scripts/build_libSession_util.sh new file mode 100755 index 000000000..cfd934ac6 --- /dev/null +++ b/Scripts/build_libSession_util.sh @@ -0,0 +1,83 @@ +#!/bin/bash + +# XCode will error during it's dependency graph construction (which happens before the build +# stage starts and any target "Run Script" phases are triggered) +# +# In order to avoid this error we need to build the framework before actually getting to the +# build stage so XCode is able to build the dependency graph +# +# XCode's Pre-action scripts don't output anything into XCode so the only way to emit a useful +# error is to return a success status and have the project detect and log the error itself then +# log it, stopping the build at that point +# +# The other step to get this to work properly is to ensure the framework in "Link Binary with +# Libraries" isn't using a relative directory, unfortunately there doesn't seem to be a good +# way to do this directly so we need to modify the '.pbxproj' file directly, updating the +# framework entry to have the following (on a single line): +# { +# isa = PBXFileReference; +# explicitFileType = wrapper.xcframework; +# includeInIndex = 0; +# path = "{FRAMEWORK NAME GOES HERE}"; +# sourceTree = BUILD_DIR; +# }; + +# Need to set the path or we won't find cmake +PATH=${PATH}:/usr/local/bin:/opt/homebrew/bin:/sbin/md5 + +# Direct the output to a log file +exec > "${TARGET_BUILD_DIR}/libsession_util_output.log" 2>&1 + +# Remove any old build errors +rm -rf "${TARGET_BUILD_DIR}/libsession_util_error.log" + +# First ensure cmake is installed (store the error in a log and exit with a success status - xcode will output the error) +if ! which cmake > /dev/null; then + echo "error: cmake is required to build, please install (can install via homebrew with 'brew install cmake')." > "${TARGET_BUILD_DIR}/error.log" + exit 0 +fi + +# Generate a hash of the libSession-util source files and check if they differ from the last hash +NEW_SOURCE_HASH=$(find "${SRCROOT}/LibSession-Util/src" -type f -exec md5 {} + | awk '{print $NF}' | sort | md5 | awk '{print $NF}') +NEW_HEADER_HASH=$(find "${SRCROOT}/LibSession-Util/include" -type f -exec md5 {} + | awk '{print $NF}' | sort | md5 | awk '{print $NF}') + +if [ -f "${TARGET_BUILD_DIR}/libsession_util_source_hash.log" ]; then + read -r OLD_SOURCE_HASH < "${TARGET_BUILD_DIR}/libsession_util_source_hash.log" +fi + +if [ -f "${TARGET_BUILD_DIR}/libsession_util_header_hash.log" ]; then + read -r OLD_HEADER_HASH < "${TARGET_BUILD_DIR}/libsession_util_header_hash.log" +fi + +if [ -f "${TARGET_BUILD_DIR}/libsession_util_archs.log" ]; then + read -r OLD_ARCHS < "${TARGET_BUILD_DIR}/libsession_util_archs.log" +fi + +# Start the libSession-util build if it doesn't already exists +if [ "${NEW_SOURCE_HASH}" != "${OLD_SOURCE_HASH}" ] || [ "${NEW_HEADER_HASH}" != "${OLD_HEADER_HASH}" ] || [ "${ARCHS[*]}" != "${OLD_ARCHS}" ] || [ ! -d "${TARGET_BUILD_DIR}/libsession-util.xcframework" ]; then + echo "info: Build is not up-to-date - creating new build" + echo "" + + # Remove any existing build files (just to be safe) + rm -rf "${TARGET_BUILD_DIR}/libsession-util.a" + rm -rf "${TARGET_BUILD_DIR}/libsession-util.xcframework" + rm -rf "${BUILD_DIR}/libsession-util.xcframework" + + # Trigger the new build + cd "${SRCROOT}/LibSession-Util" + result=$(./utils/ios.sh "libsession-util" false) + + if [ $? -ne 0 ]; then + echo "error: Failed to build libsession-util (See details in '${TARGET_BUILD_DIR}/pre-action-output.log')." > "${TARGET_BUILD_DIR}/error.log" + exit 0 + fi + + # Save the updated source hash to disk to prevent rebuilds when there were no changes + echo "${NEW_SOURCE_HASH}" > "${TARGET_BUILD_DIR}/libsession_util_source_hash.log" + echo "${NEW_HEADER_HASH}" > "${TARGET_BUILD_DIR}/libsession_util_header_hash.log" + echo "${ARCHS[*]}" > "${TARGET_BUILD_DIR}/libsession_util_archs.log" +fi + +# Move the target-specific libSession-util build to the parent build directory (so XCode can have a reference to a single build) +rm -rf "${BUILD_DIR}/libsession-util.xcframework" +cp -r "${TARGET_BUILD_DIR}/libsession-util.xcframework" "${BUILD_DIR}/libsession-util.xcframework" diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 65b1a294c..e70d113f3 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 52; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -436,7 +436,6 @@ C3C2A5A7255385C100C340D1 /* SessionSnodeKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A59F255385C100C340D1 /* SessionSnodeKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; C3C2A5C0255385EE00C340D1 /* Snode.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5B7255385EC00C340D1 /* Snode.swift */; }; C3C2A5C2255385EE00C340D1 /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5B9255385ED00C340D1 /* Configuration.swift */; }; - C3C2A5DE2553860B00C340D1 /* String+Trimming.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5D22553860900C340D1 /* String+Trimming.swift */; }; C3C2A5E02553860B00C340D1 /* Threading.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5D42553860A00C340D1 /* Threading.swift */; }; C3C2A5E42553860B00C340D1 /* Data+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5D82553860B00C340D1 /* Data+Utilities.swift */; }; C3C2A67D255388CC00C340D1 /* SessionUtilitiesKit.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A67B255388CC00C340D1 /* SessionUtilitiesKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -735,7 +734,6 @@ FD87DCFE28B7582C00AF0F98 /* BlockedContactsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD87DCFD28B7582C00AF0F98 /* BlockedContactsViewModel.swift */; }; FD87DD0028B820F200AF0F98 /* BlockedContactCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD87DCFF28B820F200AF0F98 /* BlockedContactCell.swift */; }; FD87DD0428B8727D00AF0F98 /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD87DD0328B8727D00AF0F98 /* Configuration.swift */; }; - FD8ECF7929340F7200C0D1BB /* libsession-util.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = FD8ECF7829340F7100C0D1BB /* libsession-util.xcframework */; }; FD8ECF7B29340FFD00C0D1BB /* SessionUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF7A29340FFD00C0D1BB /* SessionUtil.swift */; }; FD8ECF7D2934293A00C0D1BB /* _013_SessionUtilChanges.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF7C2934293A00C0D1BB /* _013_SessionUtilChanges.swift */; }; FD8ECF7F2934298100C0D1BB /* ConfigDump.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF7E2934298100C0D1BB /* ConfigDump.swift */; }; @@ -906,12 +904,15 @@ FDF848F329413DB0007DCAE5 /* ImagePickerHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF848F229413DB0007DCAE5 /* ImagePickerHandler.swift */; }; FDF848F529413EEC007DCAE5 /* SessionCell+Styling.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF848F429413EEC007DCAE5 /* SessionCell+Styling.swift */; }; FDF848F729414477007DCAE5 /* CurrentUserPoller.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF848F629414477007DCAE5 /* CurrentUserPoller.swift */; }; + FDFC4D9A29F0C51500992FB6 /* String+Trimming.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5D22553860900C340D1 /* String+Trimming.swift */; }; + FDFC4E1929F1F9A600992FB6 /* libsession-util.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = FDFC4E1829F1F9A600992FB6 /* libsession-util.xcframework */; }; FDFD645927F26C6800808CA1 /* Array+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5D12553860800C340D1 /* Array+Utilities.swift */; }; FDFD645D27F273F300808CA1 /* MockGeneralCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFD645C27F273F300808CA1 /* MockGeneralCache.swift */; }; FDFDE124282D04F20098B17F /* MediaDismissAnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFDE123282D04F20098B17F /* MediaDismissAnimationController.swift */; }; FDFDE126282D05380098B17F /* MediaInteractiveDismiss.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFDE125282D05380098B17F /* MediaInteractiveDismiss.swift */; }; FDFDE128282D05530098B17F /* MediaPresentationContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFDE127282D05530098B17F /* MediaPresentationContext.swift */; }; FDFDE12A282D056B0098B17F /* MediaZoomAnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFDE129282D056B0098B17F /* MediaZoomAnimationController.swift */; }; + FDFF61D729F2600300F95FB0 /* Identity+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFF61D629F2600300F95FB0 /* Identity+Utilities.swift */; }; FE43694493EC2E1E438EBEB3 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit_SessionMessagingKitTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 13D1714FDC4DAB121DA2C73A /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit_SessionMessagingKitTests.framework */; }; /* End PBXBuildFile section */ @@ -923,13 +924,6 @@ remoteGlobalIDString = 453518671FC635DD00210559; remoteInfo = SignalShareExtension; }; - 7B251C3827D82D9E001A6284 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = D221A080169C9E5E00537ABF /* Project object */; - proxyType = 1; - remoteGlobalIDString = C3C2A678255388CC00C340D1; - remoteInfo = SessionUtilitiesKit; - }; 7BC01A40241F40AB00BC7C55 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = D221A080169C9E5E00537ABF /* Project object */; @@ -1233,7 +1227,6 @@ 7B2561C12978B307005C086C /* MediaInfoVC+MediaInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MediaInfoVC+MediaInfoView.swift"; sourceTree = ""; }; 7B2561C329874851005C086C /* SessionCarouselView+Info.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionCarouselView+Info.swift"; sourceTree = ""; }; 7B2DB2AD26F1B0FF0035B509 /* si */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = si; path = si.lproj/Localizable.strings; sourceTree = ""; }; - 7B2E985729AC227C001792D7 /* UIContextualAction+Theming.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIContextualAction+Theming.swift"; sourceTree = ""; }; 7B3A392D2977791E002FE4AC /* MediaInfoVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaInfoVC.swift; sourceTree = ""; }; 7B3A392F297A3919002FE4AC /* MediaInfoVC+MediaPreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MediaInfoVC+MediaPreviewView.swift"; sourceTree = ""; }; 7B3A39312980D02B002FE4AC /* SessionCarouselView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionCarouselView.swift; sourceTree = ""; }; @@ -1252,7 +1245,6 @@ 7B81682728B310D50069F315 /* _007_HomeQueryOptimisationIndexes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _007_HomeQueryOptimisationIndexes.swift; sourceTree = ""; }; 7B81682928B6F1420069F315 /* ReactionResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionResponse.swift; sourceTree = ""; }; 7B81682B28B72F480069F315 /* PendingChange.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PendingChange.swift; sourceTree = ""; }; - 7B89FF4529C016E300C4C708 /* _012_AddFTSIfNeeded.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _012_AddFTSIfNeeded.swift; sourceTree = ""; }; 7B8C44C428B49DDA00FBE25F /* NewConversationVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewConversationVC.swift; sourceTree = ""; }; 7B8D5FC328332600008324D9 /* VisibleMessage+Reaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "VisibleMessage+Reaction.swift"; sourceTree = ""; }; 7B93D06927CF173D00811CB6 /* MessageRequestsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageRequestsViewController.swift; sourceTree = ""; }; @@ -1877,7 +1869,6 @@ FD87DCFD28B7582C00AF0F98 /* BlockedContactsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockedContactsViewModel.swift; sourceTree = ""; }; FD87DCFF28B820F200AF0F98 /* BlockedContactCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockedContactCell.swift; sourceTree = ""; }; FD87DD0328B8727D00AF0F98 /* Configuration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = ""; }; - FD8ECF7829340F7100C0D1BB /* libsession-util.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = "libsession-util.xcframework"; sourceTree = ""; }; FD8ECF7A29340FFD00C0D1BB /* SessionUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionUtil.swift; sourceTree = ""; }; FD8ECF7C2934293A00C0D1BB /* _013_SessionUtilChanges.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _013_SessionUtilChanges.swift; sourceTree = ""; }; FD8ECF7E2934298100C0D1BB /* ConfigDump.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigDump.swift; sourceTree = ""; }; @@ -2048,12 +2039,14 @@ FDF848F229413DB0007DCAE5 /* ImagePickerHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImagePickerHandler.swift; sourceTree = ""; }; FDF848F429413EEC007DCAE5 /* SessionCell+Styling.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SessionCell+Styling.swift"; sourceTree = ""; }; FDF848F629414477007DCAE5 /* CurrentUserPoller.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CurrentUserPoller.swift; sourceTree = ""; }; + FDFC4E1829F1F9A600992FB6 /* libsession-util.xcframework */ = {isa = PBXFileReference; explicitFileType = wrapper.xcframework; includeInIndex = 0; path = "libsession-util.xcframework"; sourceTree = BUILD_DIR; }; FDFD645A27F26D4600808CA1 /* Data+Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Data+Utilities.swift"; sourceTree = ""; }; FDFD645C27F273F300808CA1 /* MockGeneralCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockGeneralCache.swift; sourceTree = ""; }; FDFDE123282D04F20098B17F /* MediaDismissAnimationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaDismissAnimationController.swift; sourceTree = ""; }; FDFDE125282D05380098B17F /* MediaInteractiveDismiss.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaInteractiveDismiss.swift; sourceTree = ""; }; FDFDE127282D05530098B17F /* MediaPresentationContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPresentationContext.swift; sourceTree = ""; }; FDFDE129282D056B0098B17F /* MediaZoomAnimationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaZoomAnimationController.swift; sourceTree = ""; }; + FDFF61D629F2600300F95FB0 /* Identity+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Identity+Utilities.swift"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -2124,7 +2117,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - FD8ECF7929340F7200C0D1BB /* libsession-util.xcframework in Frameworks */, + FDFC4E1929F1F9A600992FB6 /* libsession-util.xcframework in Frameworks */, FDC4386C27B4E90300C60D73 /* SessionUtilitiesKit.framework in Frameworks */, C3C2A70B25539E1E00C340D1 /* SessionSnodeKit.framework in Frameworks */, BE25D9230CA2C3A40A9216EF /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit.framework in Frameworks */, @@ -3265,6 +3258,7 @@ FD772899284AF1BD0018502F /* Sodium+Utilities.swift */, C3A3A170256E1D25004D228D /* SSKReachabilityManager.swift */, C3ECBF7A257056B700EA7FCE /* Threading.swift */, + FDFF61D629F2600300F95FB0 /* Identity+Utilities.swift */, ); path = Utilities; sourceTree = ""; @@ -3343,9 +3337,9 @@ C352A2F325574B3300338F3E /* Jobs */, C3A7215C2558C0AC0043A11F /* File Server */, C3A721332558BDDF0043A11F /* Open Groups */, + FD8ECF7529340F4800C0D1BB /* SessionUtil */, FD3E0C82283B581F002A425C /* Shared Models */, C3BBE0B32554F0D30050F1E3 /* Utilities */, - FD8ECF7529340F4800C0D1BB /* LibSessionUtil */, FDC438C027BB4E6800C60D73 /* SMKDependencies.swift */, FD245C612850664300B966DD /* Configuration.swift */, ); @@ -3502,7 +3496,6 @@ D221A08C169C9E5E00537ABF /* Frameworks */, D221A08A169C9E5E00537ABF /* Products */, 2BADBA206E0B8D297E313FBA /* Pods */, - FD368A6629DE86A9000DBF1E /* Recovered References */, ); sourceTree = ""; }; @@ -3527,6 +3520,7 @@ D221A08C169C9E5E00537ABF /* Frameworks */ = { isa = PBXGroup; children = ( + FDFC4E1829F1F9A600992FB6 /* libsession-util.xcframework */, B8DE1FAF26C228780079C9CE /* SignalRingRTC.framework */, C35E8AA22485C72300ACB629 /* SwiftCSV.framework */, B847570023D568EB00759540 /* SignalServiceKit.framework */, @@ -3794,15 +3788,6 @@ path = Database; sourceTree = ""; }; - FD368A6629DE86A9000DBF1E /* Recovered References */ = { - isa = PBXGroup; - children = ( - 7B2E985729AC227C001792D7 /* UIContextualAction+Theming.swift */, - 7B89FF4529C016E300C4C708 /* _012_AddFTSIfNeeded.swift */, - ); - name = "Recovered References"; - sourceTree = ""; - }; FD37E9C428A1C701003AE748 /* Themes */ = { isa = PBXGroup; children = ( @@ -4095,17 +4080,16 @@ path = Types; sourceTree = ""; }; - FD8ECF7529340F4800C0D1BB /* LibSessionUtil */ = { + FD8ECF7529340F4800C0D1BB /* SessionUtil */ = { isa = PBXGroup; children = ( FD2B4B022949886900AB4848 /* Database */, FD8ECF8E29381FB200C0D1BB /* Config Handling */, FD432435299DEA1C008A0213 /* Utilities */, - FD8ECF7829340F7100C0D1BB /* libsession-util.xcframework */, FD8ECF882935AB7200C0D1BB /* SessionUtilError.swift */, FD8ECF7A29340FFD00C0D1BB /* SessionUtil.swift */, ); - path = LibSessionUtil; + path = SessionUtil; sourceTree = ""; }; FD8ECF802934385900C0D1BB /* LibSessionUtil */ = { @@ -4122,11 +4106,11 @@ FD8ECF8E29381FB200C0D1BB /* Config Handling */ = { isa = PBXGroup; children = ( - FD8ECF8F29381FC200C0D1BB /* SessionUtil+UserProfile.swift */, FD2B4AFC294688D000AB4848 /* SessionUtil+Contacts.swift */, FD43EE9E297E2EE0009C87C5 /* SessionUtil+ConvoInfoVolatile.swift */, - FD43EE9C297A5190009C87C5 /* SessionUtil+UserGroups.swift */, FDA1E83A29A5F2D500C5C3BD /* SessionUtil+Shared.swift */, + FD43EE9C297A5190009C87C5 /* SessionUtil+UserGroups.swift */, + FD8ECF8F29381FC200C0D1BB /* SessionUtil+UserProfile.swift */, ); path = "Config Handling"; sourceTree = ""; @@ -4620,6 +4604,7 @@ isa = PBXNativeTarget; buildConfigurationList = C3C2A6F925539DE700C340D1 /* Build configuration list for PBXNativeTarget "SessionMessagingKit" */; buildPhases = ( + FDFC4E1729F14F7A00992FB6 /* Validate pre-build actions */, 2014435DF351DF6C60122751 /* [CP] Check Pods Manifest.lock */, C3C2A6EB25539DE700C340D1 /* Headers */, C3C2A6EC25539DE700C340D1 /* Sources */, @@ -4629,7 +4614,6 @@ buildRules = ( ); dependencies = ( - 7B251C3927D82D9E001A6284 /* PBXTargetDependency */, ); name = SessionMessagingKit; productName = SessionMessagingKit; @@ -4734,7 +4718,7 @@ isa = PBXProject; attributes = { DefaultBuildSystemTypeForWorkspace = Original; - LastSwiftUpdateCheck = 1340; + LastSwiftUpdateCheck = 1430; LastTestingUpgradeCheck = 0600; LastUpgradeCheck = 1400; ORGANIZATIONNAME = "Rangeproof Pty Ltd"; @@ -5340,6 +5324,7 @@ }; FDE7214D287E50820093DF33 /* Lint Localizable.strings */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -5357,6 +5342,26 @@ shellScript = "\"${SRCROOT}/Scripts/LintLocalizableStrings.swift\"\n"; showEnvVarsInLog = 0; }; + FDFC4E1729F14F7A00992FB6 /* Validate pre-build actions */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Validate pre-build actions"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if [ -f \"${TARGET_BUILD_DIR}/libsession_util_error.log\" ]; then\n read -r line < \"${TARGET_BUILD_DIR}/libsession_util_error.log\"\n echo \"${line}\"\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -5534,8 +5539,8 @@ FDF848BC29405C5A007DCAE5 /* SnodeRecursiveResponse.swift in Sources */, FDF848C029405C5A007DCAE5 /* ONSResolveResponse.swift in Sources */, FD17D7A427F40F8100122BE0 /* _003_YDBToGRDBMigration.swift in Sources */, + FDFC4D9A29F0C51500992FB6 /* String+Trimming.swift in Sources */, FDF848C629405C5B007DCAE5 /* DeleteAllMessagesRequest.swift in Sources */, - C3C2A5DE2553860B00C340D1 /* String+Trimming.swift in Sources */, FDF848D429405C5B007DCAE5 /* DeleteAllBeforeResponse.swift in Sources */, FDF848D629405C5B007DCAE5 /* SnodeMessage.swift in Sources */, FDF848D129405C5B007DCAE5 /* SnodeSwarmItem.swift in Sources */, @@ -5551,7 +5556,6 @@ FDF848D229405C5B007DCAE5 /* LegacyGetMessagesRequest.swift in Sources */, FDF848CB29405C5B007DCAE5 /* SnodePoolResponse.swift in Sources */, FDF848C429405C5A007DCAE5 /* RevokeSubkeyResponse.swift in Sources */, - C3C2A5DE2553860B00C340D1 /* String+Trimming.swift in Sources */, FD26FA6D291DADAE005801D8 /* (null) in Sources */, FDF848E529405D6E007DCAE5 /* SnodeAPIError.swift in Sources */, FDF848D529405C5B007DCAE5 /* DeleteAllMessagesResponse.swift in Sources */, @@ -5740,6 +5744,7 @@ FD716E6628502EE200C96BF4 /* CurrentCallProtocol.swift in Sources */, FD8ECF9029381FC200C0D1BB /* SessionUtil+UserProfile.swift in Sources */, FD09B7E5288670BB00ED0B66 /* _008_EmojiReacts.swift in Sources */, + FDFF61D729F2600300F95FB0 /* Identity+Utilities.swift in Sources */, FDC4385F27B4C4A200C60D73 /* PinnedMessage.swift in Sources */, 7BFD1A8C2747150E00FB91B9 /* TurnServerInfo.swift in Sources */, B8BF43BA26CC95FB007828D1 /* WebRTC+Utilities.swift in Sources */, @@ -6188,11 +6193,6 @@ target = 453518671FC635DD00210559 /* SessionShareExtension */; targetProxy = 453518701FC635DD00210559 /* PBXContainerItemProxy */; }; - 7B251C3927D82D9E001A6284 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = C3C2A678255388CC00C340D1 /* SessionUtilitiesKit */; - targetProxy = 7B251C3827D82D9E001A6284 /* PBXContainerItemProxy */; - }; 7BC01A41241F40AB00BC7C55 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 7BC01A3A241F40AB00BC7C55 /* SessionNotificationServiceExtension */; @@ -7350,7 +7350,6 @@ ENABLE_BITCODE = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; - FRAMEWORK_SEARCH_PATHS = "\"$(SRCROOT)/SessionMessagingKit/LibSessionUtil\""; GCC_NO_COMMON_BLOCKS = YES; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", @@ -7391,7 +7390,6 @@ ); "OTHER_SWIFT_FLAGS[arch=*]" = "-D DEBUG"; SDKROOT = iphoneos; - SWIFT_INCLUDE_PATHS = "\"$(SRCROOT)/SessionMessagingKit/LibSessionUtil\""; SWIFT_VERSION = 4.0; VALIDATE_PRODUCT = YES; }; @@ -7427,7 +7425,6 @@ CODE_SIGN_IDENTITY = "iPhone Distribution"; ENABLE_BITCODE = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; - FRAMEWORK_SEARCH_PATHS = "\"$(SRCROOT)/SessionMessagingKit/LibSessionUtil\""; GCC_NO_COMMON_BLOCKS = YES; GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES; GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES; @@ -7465,7 +7462,6 @@ ); SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_INCLUDE_PATHS = "\"$(SRCROOT)/SessionMessagingKit/LibSessionUtil\""; SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 4.0; VALIDATE_PRODUCT = YES; diff --git a/Session.xcodeproj/xcshareddata/xcschemes/Session.xcscheme b/Session.xcodeproj/xcshareddata/xcschemes/Session.xcscheme index ea85c66b2..f2e5c8744 100644 --- a/Session.xcodeproj/xcshareddata/xcschemes/Session.xcscheme +++ b/Session.xcodeproj/xcshareddata/xcschemes/Session.xcscheme @@ -5,6 +5,24 @@ + + + + + + + + + + + version = "1.7"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + version = "1.7"> + + + + + + + + + + + version = "1.7"> + + + + + + + + + + Void) { AppReadiness.runNowOrWhenAppDidBecomeReady { - guard Identity.userExists() else { return } + guard Identity.userCompletedRequiredOnboarding() else { return } SessionApp.homeViewController.wrappedValue?.createNewConversation() completionHandler(true) @@ -662,7 +691,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD func syncConfigurationIfNeeded() { // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent - guard !Features.useSharedUtilForUserConfig else { return } + guard !SessionUtil.userConfigsEnabled else { return } let lastSync: Date = (UserDefaults.standard[.lastConfigurationSync] ?? .distantPast) diff --git a/Session/Notifications/SyncPushTokensJob.swift b/Session/Notifications/SyncPushTokensJob.swift index bd83701f8..4ac93c7a8 100644 --- a/Session/Notifications/SyncPushTokensJob.swift +++ b/Session/Notifications/SyncPushTokensJob.swift @@ -24,12 +24,7 @@ public enum SyncPushTokensJob: JobExecutor { // Don't run when inactive or not in main app or if the user doesn't exist yet guard (UserDefaults.sharedLokiProject?[.isMainAppActive]).defaulting(to: false), - Identity.userExists(), - // If we have no display name then the user will be asked to enter one (this - // can happen if the app crashed during onboarding which would leave the user - // in an invalid state with no display name - the user is likely going to be - // taken to the PN registration screen next which will re-trigger this job) - !Profile.fetchOrCreateCurrentUser().name.isEmpty + Identity.userCompletedRequiredOnboarding() else { deferred(job) // Don't need to do anything if it's not the main app return diff --git a/Session/Onboarding/Onboarding.swift b/Session/Onboarding/Onboarding.swift index 93047b95d..902143c9c 100644 --- a/Session/Onboarding/Onboarding.swift +++ b/Session/Onboarding/Onboarding.swift @@ -10,7 +10,7 @@ import SessionMessagingKit enum Onboarding { private static let profileNameRetrievalPublisher: Atomic> = { // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent - guard Features.useSharedUtilForUserConfig else { + guard SessionUtil.userConfigsEnabled else { return Atomic( Just(nil) .setFailureType(to: Error.self) @@ -39,7 +39,7 @@ enum Onboarding { ) .tryFlatMap { receivedMessageTypes -> AnyPublisher in // FIXME: Remove this entire 'tryFlatMap' once the updated user config has been released for long enough - guard !receivedMessageTypes.isEmpty else { + guard receivedMessageTypes.isEmpty else { return Just(()) .setFailureType(to: Error.self) .eraseToAnyPublisher() @@ -149,9 +149,19 @@ enum Onboarding { Contact.Columns.didApproveMe.set(to: true) ) - // Create the 'Note to Self' thread (not visible by default) + /// Create the 'Note to Self' thread (not visible by default) + /// + /// **Note:** We need to explicitly `updateAllAndConfig` the `shouldBeVisible` value to `false` + /// otherwise it won't actually get synced correctly try SessionThread .fetchOrCreate(db, id: x25519PublicKey, variant: .contact, shouldBeVisible: false) + + try SessionThread + .filter(id: x25519PublicKey) + .updateAllAndConfig( + db, + SessionThread.Columns.shouldBeVisible.set(to: false) + ) } // Set hasSyncedInitialConfiguration to true so that when we hit the diff --git a/SessionMessagingKit/Configuration.swift b/SessionMessagingKit/Configuration.swift index a9bc44b35..0be9fa4f0 100644 --- a/SessionMessagingKit/Configuration.swift +++ b/SessionMessagingKit/Configuration.swift @@ -32,6 +32,7 @@ public enum SNMessagingKit { // Just to make the external API nice _013_SessionUtilChanges.self, // Wait until the feature is turned on before doing the migration that generates // the config dump data + // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent (Features.useSharedUtilForUserConfig ? _014_GenerateInitialUserConfigDumps.self : (nil as Migration.Type?) diff --git a/SessionMessagingKit/Database/Migrations/_014_GenerateInitialUserConfigDumps.swift b/SessionMessagingKit/Database/Migrations/_014_GenerateInitialUserConfigDumps.swift index 0478650e8..703acf6a1 100644 --- a/SessionMessagingKit/Database/Migrations/_014_GenerateInitialUserConfigDumps.swift +++ b/SessionMessagingKit/Database/Migrations/_014_GenerateInitialUserConfigDumps.swift @@ -16,9 +16,12 @@ enum _014_GenerateInitialUserConfigDumps: Migration { static func migrate(_ db: Database) throws { // If we have no ed25519 key then there is no need to create cached dump data - guard let secretKey: [UInt8] = Identity.fetchUserEd25519KeyPair(db)?.secretKey else { return } + guard let secretKey: [UInt8] = Identity.fetchUserEd25519KeyPair(db)?.secretKey else { + Storage.update(progress: 1, for: self, in: target) // In case this is the last migration + return + } - // Load the initial config state if needed + // Create the initial config state let userPublicKey: String = getUserHexEncodedPublicKey(db) SessionUtil.loadState(db, userPublicKey: userPublicKey, ed25519SecretKey: secretKey) diff --git a/SessionMessagingKit/Database/Models/Profile.swift b/SessionMessagingKit/Database/Models/Profile.swift index 4b73b0d2d..59aa9fe73 100644 --- a/SessionMessagingKit/Database/Models/Profile.swift +++ b/SessionMessagingKit/Database/Models/Profile.swift @@ -229,25 +229,15 @@ public extension Profile { /// /// **Note:** This method intentionally does **not** save the newly created Profile, /// it will need to be explicitly saved after calling - static func fetchOrCreateCurrentUser() -> Profile { - var userPublicKey: String = "" - - let exisingProfile: Profile? = Storage.shared.read { db in - userPublicKey = getUserHexEncodedPublicKey(db) - - return try Profile.fetchOne(db, id: userPublicKey) - } - - return (exisingProfile ?? defaultFor(userPublicKey)) - } - - /// Fetches or creates a Profile for the current user - /// - /// **Note:** This method intentionally does **not** save the newly created Profile, - /// it will need to be explicitly saved after calling - static func fetchOrCreateCurrentUser(_ db: Database) -> Profile { + static func fetchOrCreateCurrentUser(_ db: Database? = nil) -> Profile { let userPublicKey: String = getUserHexEncodedPublicKey(db) + guard let db: Database = db else { + return Storage.shared + .read { db in fetchOrCreateCurrentUser(db) } + .defaulting(to: defaultFor(userPublicKey)) + } + return ( (try? Profile.fetchOne(db, id: userPublicKey)) ?? defaultFor(userPublicKey) diff --git a/SessionMessagingKit/Jobs/Types/ConfigurationSyncJob.swift b/SessionMessagingKit/Jobs/Types/ConfigurationSyncJob.swift index 40e36ec74..bd7e183e1 100644 --- a/SessionMessagingKit/Jobs/Types/ConfigurationSyncJob.swift +++ b/SessionMessagingKit/Jobs/Types/ConfigurationSyncJob.swift @@ -20,10 +20,10 @@ public enum ConfigurationSyncJob: JobExecutor { failure: @escaping (Job, Error?, Bool) -> (), deferred: @escaping (Job) -> () ) { - guard Features.useSharedUtilForUserConfig else { - success(job, true) - return - } + guard + SessionUtil.userConfigsEnabled, + Identity.userCompletedRequiredOnboarding() + else { return success(job, true) } // On startup it's possible for multiple ConfigSyncJob's to run at the same time (which is // redundant) so check if there is another job already running and, if so, defer this job @@ -175,14 +175,13 @@ public extension ConfigurationSyncJob { static func enqueue(_ db: Database, publicKey: String) { // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent - guard Features.useSharedUtilForUserConfig else { + guard SessionUtil.userConfigsEnabled else { // If we don't have a userKeyPair (or name) yet then there is no need to sync the // configuration as the user doesn't fully exist yet (this will get triggered on // the first launch of a fresh install due to the migrations getting run and a few // times during onboarding) guard - Identity.userExists(db), - !Profile.fetchOrCreateCurrentUser(db).name.isEmpty, + Identity.userCompletedRequiredOnboarding(db), let legacyConfigMessage: Message = try? ConfigurationMessage.getCurrent(db) else { return } @@ -232,13 +231,13 @@ public extension ConfigurationSyncJob { static func run() -> AnyPublisher { // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent - guard Features.useSharedUtilForUserConfig else { + guard SessionUtil.userConfigsEnabled else { return Storage.shared .writePublisher { db -> MessageSender.PreparedSendData in // If we don't have a userKeyPair yet then there is no need to sync the configuration // as the user doesn't exist yet (this will get triggered on the first launch of a // fresh install due to the migrations getting run) - guard Identity.userExists(db) else { throw StorageError.generic } + guard Identity.userCompletedRequiredOnboarding(db) else { throw StorageError.generic } let publicKey: String = getUserHexEncodedPublicKey(db) diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/Info.plist b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/Info.plist deleted file mode 100644 index 97310ed4d..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/Info.plist +++ /dev/null @@ -1,40 +0,0 @@ - - - - - AvailableLibraries - - - LibraryIdentifier - ios-arm64_x86_64-simulator - LibraryPath - libsession-util.a - SupportedArchitectures - - arm64 - x86_64 - - SupportedPlatform - ios - SupportedPlatformVariant - simulator - - - LibraryIdentifier - ios-arm64 - LibraryPath - libsession-util.a - SupportedArchitectures - - arm64 - - SupportedPlatform - ios - - - CFBundlePackageType - XFWK - XCFrameworkFormatVersion - 1.0 - - diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/ios-arm64/libsession-util.a b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/ios-arm64/libsession-util.a deleted file mode 100644 index a0903f5ef..000000000 Binary files a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/ios-arm64/libsession-util.a and /dev/null differ diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/ios-arm64_x86_64-simulator/libsession-util.a b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/ios-arm64_x86_64-simulator/libsession-util.a deleted file mode 100644 index 9a1e7ea9d..000000000 Binary files a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/ios-arm64_x86_64-simulator/libsession-util.a and /dev/null differ diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/module.modulemap b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/module.modulemap deleted file mode 100644 index 292f5b121..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/module.modulemap +++ /dev/null @@ -1,21 +0,0 @@ -module SessionUtil { - module capi { - header "session/version.h" - header "session/export.h" - header "session/config.h" - header "session/config/error.h" - header "session/config/community.h" - header "session/config/expiring.h" - header "session/config/user_groups.h" - header "session/config/convo_info_volatile.h" - header "session/config/notify.h" - header "session/config/user_profile.h" - header "session/config/util.h" - header "session/config/contacts.h" - header "session/config/encrypt.h" - header "session/config/base.h" - header "session/config/profile_pic.h" - header "session/xed25519.h" - export * - } -} diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/bt_merge.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/bt_merge.hpp deleted file mode 100644 index e5042a5d1..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/bt_merge.hpp +++ /dev/null @@ -1,58 +0,0 @@ -#pragma once -#include - -#include -#ifndef NDEBUG -#include -#endif - -namespace session::bt { - -using oxenc::bt_dict; -using oxenc::bt_list; - -/// Merges two bt dicts together: the returned dict includes all keys in a or b. Keys in *both* -/// dicts get their value from `a`, otherwise the value is that of the dict that contains the key. -bt_dict merge(const bt_dict& a, const bt_dict& b); - -/// Merges two ordered bt_lists together using a predicate to determine order. The input lists must -/// be sorted to begin with. `cmp` must be callable with a pair of `const bt_value&` arguments and -/// must return true if the first argument should be considered less than the second argument. By -/// default this skips elements from b that compare equal to a value of a, but you can include all -/// the duplicates by specifying the `duplicates` parameter as true. -template -bt_list merge_sorted(const bt_list& a, const bt_list& b, Compare cmp, bool duplicates = false) { - bt_list result; - auto it_a = a.begin(); - auto it_b = b.begin(); - - assert(std::is_sorted(it_a, a.end(), cmp)); - assert(std::is_sorted(it_b, b.end(), cmp)); - - if (duplicates) { - while (it_a != a.end() && it_b != b.end()) { - if (!cmp(*it_a, *it_b)) // *b <= *a - result.push_back(*it_b++); - else // *a < *b - result.push_back(*it_a++); - } - } else { - while (it_a != a.end() && it_b != b.end()) { - if (cmp(*it_b, *it_a)) // *b < *a - result.push_back(*it_b++); - else if (cmp(*it_a, *it_b)) // *a < *b - result.push_back(*it_a++); - else // *a == *b - ++it_b; // skip it - } - } - - if (it_a != a.end()) - result.insert(result.end(), it_a, a.end()); - else if (it_b != b.end()) - result.insert(result.end(), it_b, b.end()); - - return result; -} - -} // namespace session::bt diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config.h deleted file mode 100644 index eea54c1dd..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#include - -typedef int64_t seqno_t; - -#ifdef __cplusplus -} -#endif diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config.hpp deleted file mode 100644 index 450446155..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config.hpp +++ /dev/null @@ -1,352 +0,0 @@ -#pragma once - -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include "types.hpp" - -namespace session::config { - -// FIXME: for multi-message we encode to longer and then split it up -inline constexpr int MAX_MESSAGE_SIZE = 76800; // 76.8kB = Storage server's limit - -// Application data data types: -using scalar = std::variant; - -using set = std::set; -struct dict_value; -using dict = std::map; -using dict_variant = std::variant; -struct dict_value : dict_variant { - using dict_variant::dict_variant; - using dict_variant::operator=; -}; - -// Helpers for gcc-10 and earlier which don't like visiting a std::variant subtype: -constexpr inline dict_variant& unwrap(dict_value& v) { - return static_cast(v); -} -constexpr inline const dict_variant& unwrap(const dict_value& v) { - return static_cast(v); -} - -using hash_t = std::array; -using seqno_hash_t = std::pair; - -class MutableConfigMessage; - -/// Base type for all errors that can happen during config parsing -struct config_error : std::runtime_error { - using std::runtime_error::runtime_error; -}; -/// Type thrown for bad signatures (bad or missing signature). -struct signature_error : config_error { - using config_error::config_error; -}; -/// Type thrown for a missing signature when a signature is required. -struct missing_signature : signature_error { - using signature_error::signature_error; -}; -/// Type thrown for an unparseable config (e.g. keys with invalid types, or keys before "#" or after -/// "~"). -struct config_parse_error : config_error { - using config_error::config_error; -}; - -/// Class for a parsed, read-only config message; also serves as the base class of a -/// MutableConfigMessage which allows setting values. -class ConfigMessage { - public: - using lagged_diffs_t = std::map; - -#ifndef SESSION_TESTING_EXPOSE_INTERNALS - protected: -#endif - dict data_; - - // diff data for *this* message, parsed during construction. Subclasses may use this for - // managing their own diff in the `diff()` method. - oxenc::bt_dict diff_; - - // diffs of previous messages that are included in this message. - lagged_diffs_t lagged_diffs_; - - // Unknown top-level config keys which we preserve even though we don't understand what they - // mean. - oxenc::bt_dict unknown_; - - /// Seqno and hash of the message; we calculate this when loading. Subclasses put the hash here - /// (so that they can return a reference to it). - seqno_hash_t seqno_hash_{0, {0}}; - - bool verified_signature_ = false; - - // This will be set during construction from configs based on the merge result: - // -1 means we had to merge one or more configs together into a new merged config - // >= 0 indicates the index of the config we used if we did not merge (i.e. there was only one - // config, or there were multiple but one of them referenced all the others). - int unmerged_ = -1; - - public: - constexpr static int DEFAULT_DIFF_LAGS = 5; - - /// Verification function: this is passed the data that should have been signed and the 64-byte - /// signature. Should return true to accept the signature, false to reject it and skip the - /// message. It can also throw to abort message construction (that is: returning false skips - /// the message when loading multiple messages, but can still continue with other messages; - /// throwing aborts the entire construction). - using verify_callable = std::function; - - /// Signing function: this is passed the data to be signed and returns the 64-byte signature. - using sign_callable = std::function; - - ConfigMessage(); - ConfigMessage(const ConfigMessage&) = default; - ConfigMessage& operator=(const ConfigMessage&) = default; - ConfigMessage(ConfigMessage&&) = default; - ConfigMessage& operator=(ConfigMessage&&) = default; - - virtual ~ConfigMessage() = default; - - /// Initializes a config message by parsing a serialized message. Throws on any error. See the - /// vector version below for argument descriptions. - explicit ConfigMessage( - ustring_view serialized, - verify_callable verifier = nullptr, - sign_callable signer = nullptr, - int lag = DEFAULT_DIFF_LAGS, - bool signature_optional = false); - - /// Constructs a new ConfigMessage by loading and potentially merging multiple serialized - /// ConfigMessages together, according to the config conflict resolution rules. The result - /// of this call can either be one of the config messages directly (if one is found that - /// includes all the others), or can be a new config message that merges multiple configs - /// together. You can check `.merged()` to see which happened. - /// - /// This constructor always requires at least one valid config from the given inputs; if all are - /// empty, - /// - /// verifier - a signature verification function. If provided and not nullptr this will be - /// called to verify each signature in the provided messages: any that are missing a signature - /// or for which the verifier returns false will be dropped from consideration for merging. If - /// *all* messages fail verification an exception is raised. - /// - /// signer - a signature generation function. This is not used directly by the ConfigMessage, - /// but providing it will allow it to be passed automatically to any MutableConfigMessage - /// derived from this ConfigMessage. - /// - /// lag - the lag setting controlling the config merging rules. Any config message with lagged - /// diffs that exceeding this lag value will have those early lagged diffs dropping during - /// loading. - /// - /// signature_optional - if true then accept a message with no signature even when a verifier is - /// set, thus allowing unsigned messages (though messages with an invalid signature are still - /// not allowed). This option is ignored when verifier is not set. - /// - /// error_handler - if set then any config message parsing error will be passed to this function - /// for handling with the index of `configs` that failed and the error exception: the callback - /// typically warns and, if the overall construction should abort, rethrows the error. If this - /// function is omitted then the default skips (without failing) individual parse errors and - /// only aborts construction if *all* messages fail to parse. A simple handler such as - /// `[](size_t, const auto& e) { throw e; }` can be used to make any parse error of any message - /// fatal. - explicit ConfigMessage( - const std::vector& configs, - verify_callable verifier = nullptr, - sign_callable signer = nullptr, - int lag = DEFAULT_DIFF_LAGS, - bool signature_optional = false, - std::function error_handler = nullptr); - - /// Returns a read-only reference to the contained data. (To get a mutable config object use - /// MutableConfigMessage). - const dict& data() const { return data_; } - - /// The verify function; if loading a message with a signature and this is set then it will - /// be called to verify the signature of the message. Takes a pointer to the signing data, - /// the data length, and a pointer to the 64-byte signature. - verify_callable verifier; - - /// The signing function; this is not directly used by the non-mutable base class, but will be - /// propagated to mutable config messages that are derived e.g. by calling `.increment()`. This - /// is called when serializing a config message to add a signature. If it is nullptr then no - /// signature is added to the serialized data. - sign_callable signer; - - /// How many lagged config diffs that should be carried forward to resolve conflicts, - /// including this message. If 0 then config messages won't have any diffs and will not be - /// mergeable. - int lag = DEFAULT_DIFF_LAGS; - - /// The diff structure for changes in *this* config message. Subclasses that need to override - /// should populate into `diff_` and return a reference to it (internal code assumes `diff_` is - /// correct immediately after a call to this). - virtual const oxenc::bt_dict& diff(); - - /// Returns the seqno of this message - const seqno_t& seqno() const { return seqno_hash_.first; } - - /// Calculates the hash of the current message. For a ConfigMessage this is calculated when the - /// message is first loaded; for a MutableConfigMessage this serializes the current value to - /// properly compute the current hash. Subclasses must ensure that seqno_hash_.second is set to - /// the correct value when this is called (and typically return a reference to it). - virtual const hash_t& hash() { return seqno_hash_.second; } - - /// After loading multiple config files this flag indicates whether or not we had to produce a - /// new, merged configuration message (true) or did not need to merge (false). (For config - /// messages that were not loaded from serialized data this is always true). - bool merged() const { return unmerged_ == -1; } - - /// After loading multiple config files this field contains the index of the single config we - /// used if we didn't need to merge (that is: there was only one config or one config that - /// superceded all the others). If we had to merge (or this wasn't loaded from serialized - /// data), this will return -1. - int unmerged_index() const { return unmerged_; } - - /// Returns true if this message contained a valid, verified signature when it was parsed. - /// Returns false otherwise (e.g. not loaded from verification at all; loaded without a - /// verification function; or had no signature and a signature wasn't required). - bool verified_signature() const { return verified_signature_; } - - /// Constructs a new MutableConfigMessage from this config message with an incremented seqno. - /// The new config message's diff will reflect changes made after this construction. - virtual MutableConfigMessage increment() const; - - /// Serializes this config's data. Note that if the ConfigMessage was constructed from signed, - /// serialized input, this will only produce an exact copy of the original serialized input if - /// it uses the identical, deterministic signing function used to construct the original. - /// - /// The optional `enable_signing` argument can be specified as false to disable signing (this is - /// typically for a local serialization value that isn't being pushed to the server). Note that - /// signing is always disabled if there is no signing callback set, regardless of the value of - /// this argument. - virtual ustring serialize(bool enable_signing = true); - - protected: - ustring serialize_impl(const oxenc::bt_dict& diff, bool enable_signing = true); -}; - -// Constructor tag -struct increment_seqno_t {}; -struct retain_seqno_t {}; -inline constexpr increment_seqno_t increment_seqno{}; -inline constexpr retain_seqno_t retain_seqno{}; - -class MutableConfigMessage : public ConfigMessage { - protected: - dict orig_data_{data_}; - - friend class ConfigMessage; - - public: - MutableConfigMessage(const MutableConfigMessage&) = default; - MutableConfigMessage& operator=(const MutableConfigMessage&) = default; - MutableConfigMessage(MutableConfigMessage&&) = default; - MutableConfigMessage& operator=(MutableConfigMessage&&) = default; - - /// Constructs a new, empty config message. Takes various fields to pre-fill the various - /// properties during construction (these are for convenience and equivalent to setting them via - /// properties/methods after construction). - /// - /// seqno -- the message's seqno, default 0 - /// lags -- number of lags to keep (when deriving messages, e.g. via increment()) - /// signer -- if specified and not nullptr then this message will be signed when serialized - /// using the given signing function. If omitted no signing takes place. - explicit MutableConfigMessage( - seqno_t seqno = 0, int lag = DEFAULT_DIFF_LAGS, sign_callable signer = nullptr) { - this->lag = lag; - this->seqno(seqno); - this->signer = signer; - } - - /// Wraps the ConfigMessage constructor with the same arguments but always produces a - /// MutableConfigMessage. In particular this means that if the base constructor performed a - /// merge (and thus incremented seqno) then the config stays as is, but contained in a Mutable - /// message that can be changed. If it did *not* merge (i.e. the highest seqno message it found - /// did not conflict with any other messages) then this construction is equivalent to doing a - /// base load followed by a .increment() call. In other words: this constructor *always* gives - /// you an incremented seqno value from the highest valid input config message. - /// - /// This is almost equivalent to ConfigMessage{args...}.increment(), except that this - /// constructor only increments seqno once while the indirect version would increment twice in - /// the case of a required merge conflict resolution. - explicit MutableConfigMessage( - const std::vector& configs, - verify_callable verifier = nullptr, - sign_callable signer = nullptr, - int lag = DEFAULT_DIFF_LAGS, - bool signature_optional = false, - std::function error_handler = nullptr); - - /// Wrapper around the above that takes a single string view to load a single message, doesn't - /// take an error handler and instead always throws on parse errors (the above also throws for - /// an erroneous single message, but with a less specific "no valid config messages" error). - explicit MutableConfigMessage( - ustring_view config, - verify_callable verifier = nullptr, - sign_callable signer = nullptr, - int lag = DEFAULT_DIFF_LAGS, - bool signature_optional = false); - - /// Does the same as the base incrementing, but also records any diff info from the current - /// MutableConfigMessage. *this* object gets pruned and signed as part of this call. If the - /// sign argument is omitted/nullptr then the current object's `sign` callback gets copied into - /// the new object. After this call you typically do not want to further modify *this (because - /// any modifications will change the hash, making *this no longer a parent of the new object). - MutableConfigMessage increment() const override; - - /// Constructor that does the same thing as the `m.increment()` factory method. The second - /// value should be the literal `increment_seqno` value (to select this constructor). - explicit MutableConfigMessage(const ConfigMessage& m, const increment_seqno_t&); - - /// Constructor that moves a immutable message into a mutable one, retaining the current seqno. - /// This is typically used in situations where the ConfigMessage has had some implicit seqno - /// increment already (e.g. from merging) and we want it to become mutable without incrementing - /// the seqno again. The second value should be the literal `retain_seqno` value (to select - /// this constructor). - explicit MutableConfigMessage(ConfigMessage&& m, const retain_seqno_t&); - - using ConfigMessage::data; - /// Returns a mutable reference to the underlying config data. - dict& data() { return data_; } - - using ConfigMessage::seqno; - - /// Sets the seqno of the message to a specific value. You usually want to use `.increment()` - /// from an existing config message rather than manually adjusting the seqno. - void seqno(seqno_t new_seqno) { seqno_hash_.first = new_seqno; } - - /// Returns the current diff for this data relative to its original data. The data is pruned - /// implicitly by this call. - const oxenc::bt_dict& diff() override; - - /// Prunes empty dicts/sets from data. This is called automatically when serializing or - /// calculating a diff. Returns true if the data was actually changed, false if nothing needed - /// pruning. - bool prune(); - - /// Calculates the hash of the current message. Can optionally be given the already-serialized - /// value, if available; if empty/omitted, `serialize()` will be called to compute it. - const hash_t& hash() override; - - protected: - const hash_t& hash(ustring_view serialized); - void increment_impl(); -}; - -} // namespace session::config - -namespace oxenc::detail { - -template <> -struct bt_serialize : bt_serialize {}; - -} // namespace oxenc::detail diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/base.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/base.h deleted file mode 100644 index fd932b37e..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/base.h +++ /dev/null @@ -1,154 +0,0 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include -#include - -#include "../config.h" - -// Config object base type: this type holds the internal object and is initialized by the various -// config-dependent settings (e.g. config_user_profile_init) then passed to the various functions. -typedef struct config_object { - // Internal opaque object pointer; calling code should leave this alone. - void* internals; - // When an error occurs in the C API this string will be set to the specific error message. May - // be empty. - const char* last_error; - - // Sometimes used as the backing buffer for `last_error`. Should not be touched externally. - char _error_buf[256]; -} config_object; - -// Common functions callable on any config instance: - -/// Frees a config object created with one of the config-dependent ..._init functions (e.g. -/// user_profile_init). -void config_free(config_object* conf); - -typedef enum config_log_level { - LOG_LEVEL_DEBUG = 0, - LOG_LEVEL_INFO, - LOG_LEVEL_WARNING, - LOG_LEVEL_ERROR -} config_log_level; - -/// Sets a logging function; takes the log function pointer and a context pointer (which can be NULL -/// if not needed). The given function pointer will be invoked with one of the above values, a -/// null-terminated c string containing the log message, and the void* context object given when -/// setting the logger (this is for caller-specific state data and won't be touched). -/// -/// The logging function must have signature: -/// -/// void log(config_log_level lvl, const char* msg, void* ctx); -/// -/// Can be called with callback set to NULL to clear an existing logger. -/// -/// The config object itself has no log level: the caller should filter by level as needed. -void config_set_logger( - config_object* conf, void (*callback)(config_log_level, const char*, void*), void* ctx); - -/// Returns the numeric namespace in which config messages of this type should be stored. -int16_t config_storage_namespace(const config_object* conf); - -/// Merges the config object with one or more remotely obtained config strings. After this call the -/// config object may be unchanged, complete replaced, or updated and needing a push, depending on -/// the messages that are merged; the caller should check config_needs_push(). -/// -/// `msg_hashes` is an array of null-terminated C strings containing the hashes of the configs being -/// provided. -/// `configs` is an array of pointers to the start of the (binary) data. -/// `lengths` is an array of lengths of the binary data -/// `count` is the length of all three arrays. -int config_merge( - config_object* conf, - const char** msg_hashes, - const unsigned char** configs, - const size_t* lengths, - size_t count); - -/// Returns true if this config object contains updated data that has not yet been confirmed stored -/// on the server. -bool config_needs_push(const config_object* conf); - -/// Returned struct of config push data. -typedef struct config_push_data { - // The config seqno (to be provided later in `config_confirm_pushed`). - seqno_t seqno; - // The config message to push (binary data, not null-terminated). - unsigned char* config; - // The length of `config` - size_t config_len; - // Array of obsolete message hashes to delete; each element is a null-terminated C string - char** obsolete; - // length of `obsolete` - size_t obsolete_len; -} config_push_data; - -/// Obtains the configuration data that needs to be pushed to the server. -/// -/// Generally this call should be guarded by a call to `config_needs_push`, however it can be used -/// to re-obtain the current serialized config even if no push is needed (for example, if the client -/// wants to re-submit it after a network error). -/// -/// NB: The returned pointer belongs to the caller: that is, the caller *MUST* free() it when -/// done with it. -config_push_data* config_push(config_object* conf); - -/// Reports that data obtained from `config_push` has been successfully stored on the server with -/// message hash `msg_hash`. The seqno value is the one returned by the config_push call that -/// yielded the config data. -void config_confirm_pushed(config_object* conf, seqno_t seqno, const char* msg_hash); - -/// Returns a binary dump of the current state of the config object. This dump can be used to -/// resurrect the object at a later point (e.g. after a restart). Allocates a new buffer and sets -/// it in `out` and the length in `outlen`. Note that this is binary data, *not* a null-terminated -/// C string. -/// -/// NB: It is the caller's responsibility to `free()` the buffer when done with it. -/// -/// Immediately after this is called `config_needs_dump` will start returning true (until the -/// configuration is next modified). -void config_dump(config_object* conf, unsigned char** out, size_t* outlen); - -/// Returns true if something has changed since the last call to `dump()` that requires calling -/// and saving the `config_dump()` data again. -bool config_needs_dump(const config_object* conf); - -/// Struct containing a list of C strings. Typically where this is returned by this API it must be -/// freed (via `free()`) when done with it. -typedef struct config_string_list { - char** value; // array of null-terminated C strings - size_t len; // length of `value` -} config_string_list; - -/// Obtains the current active hashes. Note that this will be empty if the current hash is unknown -/// or not yet determined (for example, because the current state is dirty or because the most -/// recent push is still pending and we don't know the hash yet). -/// -/// The returned pointer belongs to the caller and must be freed via `free()` when done with it. -config_string_list* config_current_hashes(const config_object* conf); - -/// Config key management; see the corresponding method docs in base.hpp. All `key` arguments here -/// are 32-byte binary buffers (and since fixed-length, there is no keylen argument). -void config_add_key(config_object* conf, const unsigned char* key); -void config_add_key_low_prio(config_object* conf, const unsigned char* key); -int config_clear_keys(config_object* conf); -bool config_remove_key(config_object* conf, const unsigned char* key); -int config_key_count(const config_object* conf); -bool config_has_key(const config_object* conf, const unsigned char* key); -// Returns a pointer to the 32-byte binary key at position i. This is *not* null terminated (and is -// exactly 32 bytes long). `i < config_key_count(conf)` must be satisfied. Ownership of the data -// remains in the object (that is: the caller must not attempt to free it). -const unsigned char* config_key(const config_object* conf, size_t i); - -/// Returns the encryption domain C-str used to encrypt values for this config object. (This is -/// here only for debugging/testing). -const char* config_encryption_domain(const config_object* conf); - -#ifdef __cplusplus -} // extern "C" -#endif diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/base.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/base.hpp deleted file mode 100644 index 46b71c2f8..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/base.hpp +++ /dev/null @@ -1,650 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -#include "base.h" -#include "namespaces.hpp" - -namespace session::config { - -template -static constexpr bool is_one_of = (std::is_same_v || ...); - -/// True for a dict_value direct subtype, but not scalar sub-subtypes. -template -static constexpr bool is_dict_subtype = is_one_of; - -/// True for a dict_value or any of the types containable within a dict value -template -static constexpr bool is_dict_value = - is_dict_subtype || is_one_of; - -// Levels for the logging callback -enum class LogLevel { debug = 0, info, warning, error }; - -/// Our current config state -enum class ConfigState : int { - /// Clean means the config is confirmed stored on the server and we haven't changed anything. - Clean = 0, - - /// Dirty means we have local changes, and the changes haven't been serialized yet for sending - /// to the server. - Dirty = 1, - - /// Waiting is halfway in-between clean and dirty: the caller has serialized the data, but - /// hasn't yet reported back that the data has been stored, *and* we haven't made any changes - /// since the data was serialize. - Waiting = 2, -}; - -/// Base config type for client-side configs containing common functionality needed by all config -/// sub-types. -class ConfigBase { - private: - // The object (either base config message or MutableConfigMessage) that stores the current - // config message. Subclasses do not directly access this: instead they call `dirty()` if they - // intend to make changes, or the `set_config_field` wrapper. - std::unique_ptr _config; - - // Tracks our current state - ConfigState _state = ConfigState::Clean; - - static constexpr size_t KEY_SIZE = 32; - - // Contains the base key(s) we use to encrypt/decrypt messages. If non-empty, the .front() - // element will be used when encrypting a new message to push. When decrypting, we attempt each - // of them, starting with .front(), until decryption succeeds. - using Key = std::array; - Key* _keys = nullptr; - size_t _keys_size = 0; - size_t _keys_capacity = 0; - - // Contains the current active message hash, as fed into us in `confirm_pushed()`. Empty if we - // don't know it yet. When we dirty the config this value gets moved into `old_hashes_` to be - // removed by the next push. - std::string _curr_hash; - - // Contains obsolete known message hashes that are obsoleted by the most recent merge or push; - // these are returned (and cleared) when `push` is called. - std::unordered_set _old_hashes; - - protected: - // Constructs a base config by loading the data from a dump as produced by `dump()`. If the - // dump is nullopt then an empty base config is constructed with no config settings and seqno - // set to 0. - explicit ConfigBase(std::optional dump = std::nullopt); - - // Tracks whether we need to dump again; most mutating methods should set this to true (unless - // calling set_state, which sets to to true implicitly). - bool _needs_dump = false; - - // Sets the current state; this also sets _needs_dump to true. If transitioning to a dirty - // state and we know our current message hash, that hash gets added to `old_hashes_` to be - // deleted at the next push. - void set_state(ConfigState s); - - // Invokes the `logger` callback if set, does nothing if there is no logger. - void log(LogLevel lvl, std::string msg) { - if (logger) - logger(lvl, std::move(msg)); - } - - // Returns a reference to the current MutableConfigMessage. If the current message is not - // already dirty (i.e. Clean or Waiting) then calling this increments the seqno counter. - MutableConfigMessage& dirty(); - - public: - // class for proxying subfield access; this class should never be stored but only used - // ephemerally (most of its methods are rvalue-qualified). This lets constructs such as - // foo["abc"]["def"]["ghi"] = 12; - // work, auto-vivifying (or trampling, if not a dict) subdicts to reach the target. It also - // allows non-vivifying value retrieval via .string(), .integer(), etc. methods. - class DictFieldProxy { - private: - ConfigBase& _conf; - std::vector _inter_keys; - std::string _last_key; - - // See if we can find the key without needing to create anything, so that we can attempt to - // access values without mutating anything (which allows, among other things, for assigning - // of the existing value to not dirty anything). Returns nullptrs if the value or something - // along its path would need to be created, or has the wrong type; otherwise a const pointer - // to the key and the value. The templated type, if provided, can be one of the types a - // dict_value can hold to also check that the returned value has a particular type; if - // omitted you get back the dict_value pointer itself. If the field exists but is not the - // requested `T` type, you get back the key string pointer with a nullptr value. - template >> - std::pair get_clean_pair() const { - const config::dict* data = &_conf._config->data(); - // All but the last need to be dicts: - for (const auto& key : _inter_keys) { - auto it = data->find(key); - data = it != data->end() ? std::get_if(&it->second) : nullptr; - if (!data) - return {nullptr, nullptr}; - } - - const std::string* key; - const dict_value* val; - // The last can be any value type: - if (auto it = data->find(_last_key); it != data->end()) { - key = &it->first; - val = &it->second; - } else - return {nullptr, nullptr}; - - if constexpr (std::is_same_v) - return {key, val}; - else if constexpr (is_dict_subtype) { - return {key, std::get_if(val)}; - } else { // int64 or std::string, i.e. the config::scalar sub-types. - if (auto* scalar = std::get_if(val)) - return {key, std::get_if(scalar)}; - return {key, nullptr}; - } - } - - // Same as above but just gives back the value, not the key - template >> - const T* get_clean() const { - return get_clean_pair().second; - } - - // Returns a lvalue reference to the value, stomping its way through the dict as it goes to - // create subdicts as needed to reach the target value. If given a template type then we - // also cast the final dict_value variant into the given type (and replace if with a - // default-constructed value if it has the wrong type) then return a reference to that. - template >> - T& get_dirty() { - config::dict* data = &_conf.dirty().data(); - for (const auto& key : _inter_keys) { - auto& val = (*data)[key]; - data = std::get_if(&val); - if (!data) - data = &val.emplace(); - } - auto& val = (*data)[_last_key]; - - if constexpr (std::is_same_v) - return val; - else if constexpr (is_dict_subtype) { - if (auto* v = std::get_if(&val)) - return *v; - return val.emplace(); - } else { // int64 or std::string, i.e. the config::scalar sub-types. - if (auto* scalar = std::get_if(&val)) { - if (auto* v = std::get_if(scalar)) - return *v; - return scalar->emplace(); - } - return val.emplace().emplace(); - } - } - - template - void assign_if_changed(T value) { - // Try to avoiding dirtying the config if this assignment isn't changing anything - if (!_conf.is_dirty()) - if (auto current = get_clean(); current && *current == value) - return; - - get_dirty() = std::move(value); - } - - void insert_if_missing(config::scalar&& value) { - if (!_conf.is_dirty()) - if (auto current = get_clean(); current && current->count(value)) - return; - - get_dirty().insert(std::move(value)); - } - - void set_erase_impl(const config::scalar& value) { - if (!_conf.is_dirty()) - if (auto current = get_clean(); current && !current->count(value)) - return; - - config::dict* data = &_conf.dirty().data(); - - for (const auto& key : _inter_keys) { - auto it = data->find(key); - data = it != data->end() ? std::get_if(&it->second) : nullptr; - if (!data) - return; - } - - auto it = data->find(_last_key); - if (it == data->end()) - return; - auto& val = it->second; - if (auto* current = std::get_if(&val)) - current->erase(value); - else - val.emplace(); - } - - public: - DictFieldProxy(ConfigBase& b, std::string key) : _conf{b}, _last_key{std::move(key)} {} - - /// Descends into a dict, returning a copied proxy object for the path to the requested - /// field. Nothing is created by doing this unless you actually assign to a value. - DictFieldProxy operator[](std::string subkey) const& { - DictFieldProxy subfield{_conf, std::move(subkey)}; - subfield._inter_keys.reserve(_inter_keys.size() + 1); - subfield._inter_keys.insert( - subfield._inter_keys.end(), _inter_keys.begin(), _inter_keys.end()); - subfield._inter_keys.push_back(_last_key); - return subfield; - } - - // Same as above, but when called on an rvalue reference we just mutate the current proxy to - // the new dict path. - DictFieldProxy&& operator[](std::string subkey) && { - _inter_keys.push_back(std::move(_last_key)); - _last_key = std::move(subkey); - return std::move(*this); - } - - /// Returns a pointer to the (deepest level) key for this dict pair *if* a pair exists at - /// the given location, nullptr otherwise. This allows a caller to get a reference to the - /// actual key, rather than an ephemeral copy of the current key value. - const std::string* key() const { return get_clean_pair().first; } - - /// Returns a const pointer to the string if one exists at the given location, nullptr - /// otherwise. - const std::string* string() const { return get_clean(); } - - /// Returns the value as a ustring_view, if it exists and is a string; nullopt otherwise. - std::optional uview() const { - if (auto* s = get_clean()) - return ustring_view{reinterpret_cast(s->data()), s->size()}; - return std::nullopt; - } - - /// returns the value as a string_view or a fallback if the value doesn't exist (or isn't a - /// string). The returned view is directly into the value (or fallback) and so mustn't be - /// used beyond the validity of either. - std::string_view string_view_or(std::string_view fallback) const { - if (auto* s = string()) - return {*s}; - return fallback; - } - - /// Returns a copy of the value as a string, if it exists and is a string; returns - /// `fallback` otherwise. - std::string string_or(std::string fallback) const { - if (auto* s = string()) - return *s; - return fallback; - } - - /// Returns a const pointer to the integer if one exists at the given location, nullptr - /// otherwise. - const int64_t* integer() const { return get_clean(); } - - /// Returns the value as an integer or a fallback if the value doesn't exist (or isn't an - /// integer). - int64_t integer_or(int64_t fallback) const { - if (auto* i = integer()) - return *i; - return fallback; - } - - /// Returns a const pointer to the set if one exists at the given location, nullptr - /// otherwise. - const config::set* set() const { return get_clean(); } - /// Returns a const pointer to the dict if one exists at the given location, nullptr - /// otherwise. (You typically don't need to use this but can rather just use [] to descend - /// into the dict). - const config::dict* dict() const { return get_clean(); } - - /// Replaces the current value with the given string. This also auto-vivifies any - /// intermediate dicts needed to reach the given key, including replacing non-dict values if - /// they currently exist along the path. - void operator=(std::string&& value) { assign_if_changed(std::move(value)); } - /// Same as above, but takes a string_view for convenience (this makes a copy). - void operator=(std::string_view value) { *this = std::string{value}; } - /// Same as above, but takes a ustring_view - void operator=(ustring_view value) { - *this = std::string{reinterpret_cast(value.data()), value.size()}; - } - /// Replace the current value with the given integer. See above. - void operator=(int64_t value) { assign_if_changed(value); } - /// Replace the current value with the given set. See above. - void operator=(config::set value) { assign_if_changed(std::move(value)); } - /// Replace the current value with the given dict. See above. This often isn't needed - /// because of how other assignment operations work. - void operator=(config::dict value) { assign_if_changed(std::move(value)); } - - /// Returns true if there is a value at the current key. If a template type T is given, it - /// only returns true if that value also is a `T`. - template >> - bool exists() const { - return get_clean() != nullptr; - } - - // Alias for `exists()` - template - bool is() const { - return exists(); - } - - /// Removes the value at the current location, regardless of what it currently is. This - /// does nothing if the current location does not have a value. - void erase() { - if (!_conf.is_dirty() && !get_clean()) - return; - - config::dict* data = &_conf.dirty().data(); - for (const auto& key : _inter_keys) { - auto it = data->find(key); - data = it != data->end() ? std::get_if(&it->second) : nullptr; - if (!data) - return; - } - data->erase(_last_key); - } - - /// Adds a value to the set at the current location. If the current value is not a set or - /// does not exist then dicts will be created to reach it and a new set will be created. - void set_insert(std::string_view value) { - insert_if_missing(config::scalar{std::string{value}}); - } - void set_insert(int64_t value) { insert_if_missing(config::scalar{value}); } - - /// Removes a value from the set at the current location. If the current value does not - /// exist then nothing happens. If it does exist, but is not a set, it will be replaced - /// with an empty set. Otherwise the given value will be removed from the set, if present. - void set_erase(std::string_view value) { - set_erase_impl(config::scalar{std::string{value}}); - } - void set_erase(int64_t value) { set_erase_impl(scalar{value}); } - - /// Emplaces a value at the current location. As with assignment, this creates dicts as - /// needed along the keys to reach the target. The existing value (if present) is destroyed - /// to make room for the new one. - template < - typename T, - typename... Args, - typename = std::enable_if_t< - is_one_of>> - T& emplace(Args&&... args) { - if constexpr (is_one_of) - return get_dirty().emplace(std::forward(args)...); - - return get_dirty().emplace(std::forward(args)...); - } - }; - - /// Wrapper for the ConfigBase's root `data` field to provide data access. Only provides a [] - /// that gets you into a DictFieldProxy. - class DictFieldRoot { - ConfigBase& _conf; - DictFieldRoot(DictFieldRoot&&) = delete; - DictFieldRoot(const DictFieldRoot&) = delete; - DictFieldRoot& operator=(DictFieldRoot&&) = delete; - DictFieldRoot& operator=(const DictFieldRoot&) = delete; - - public: - DictFieldRoot(ConfigBase& b) : _conf{b} {} - - /// Access a dict element. This returns a proxy object for accessing the value, but does - /// *not* auto-vivify the path (unless/until you assign to it). - DictFieldProxy operator[](std::string key) const& { - return DictFieldProxy{_conf, std::move(key)}; - } - }; - - protected: - // Called when dumping to obtain any extra data that a subclass needs to store to reconstitute - // the object. The base implementation does nothing. The counterpart to this, - // `load_extra_data()`, is called when loading from a dump that has extra data; a subclass - // should either override both (if it needs to serialize extra data) or neither (if it needs no - // extra data). Internally this extra data (if non-empty) is stored in the "+" key of the dump. - virtual oxenc::bt_dict extra_data() const { return {}; } - - // Called when constructing from a dump that has extra data. The base implementation does - // nothing. - virtual void load_extra_data(oxenc::bt_dict extra) {} - - // Called to load an ed25519 key for encryption; this is meant for use by single-ownership - // config types, like UserProfile, but not shared config types (closed groups). - // - // Takes a binary string which is either the 32-byte seed, or 64-byte libsodium secret (which is - // just the seed and pubkey concatenated together), and then calls `key(...)` with the seed. - // Throws std::invalid_argument if given something that doesn't match the required input. - void load_key(ustring_view ed25519_secretkey); - - public: - virtual ~ConfigBase(); - - // Proxy class providing read and write access to the contained config data. - const DictFieldRoot data{*this}; - - // If set then we log things by calling this callback - std::function logger; - - // Accesses the storage namespace where this config type is to be stored/loaded from. See - // namespaces.hpp for the underlying integer values. - virtual Namespace storage_namespace() const = 0; - - /// Subclasses must override this to return a constant string that is unique per config type; - /// this value is used for domain separation in encryption. The string length must be between 1 - /// and 24 characters; use the class name (e.g. "UserProfile") unless you have something better - /// to use. This is rarely needed externally; it is public merely for testing purposes. - virtual const char* encryption_domain() const = 0; - - /// The zstd compression level to use for this type. Subclasses can override this if they have - /// some particular special compression level, or to disable compression entirely (by returning - /// std::nullopt). The default is zstd level 1. - virtual std::optional compression_level() const { return 1; } - - // How many config lags should be used for this object; default to 5. Implementing subclasses - // can override to return a different constant if desired. More lags require more "diff" - // storage in the config messages, but also allow for a higher tolerance of simultaneous message - // conflicts. - virtual int config_lags() const { return 5; } - - // This takes all of the messages pulled down from the server and does whatever is necessary to - // merge (or replace) the current values. - // - // Values are pairs of the message hash (as provided by the server) and the raw message body. - // - // After this call the caller should check `needs_push()` to see if the data on hand was updated - // and needs to be pushed to the server again (for example, because the data contained conflicts - // that required another update to resolve). - // - // Returns the number of the given config messages that were successfully parsed. - // - // Will throw on serious error (i.e. if neither the current nor any of the given configs are - // parseable). This should not happen (the current config, at least, should always be - // re-parseable). - virtual int merge(const std::vector>& configs); - - // Same as above but takes the values as ustring's as sometimes that is more convenient. - int merge(const std::vector>& configs); - - // Returns true if we are currently dirty (i.e. have made changes that haven't been serialized - // yet). - bool is_dirty() const { return _state == ConfigState::Dirty; } - - // Returns true if we are curently clean (i.e. our current config is stored on the server and - // unmodified). - bool is_clean() const { return _state == ConfigState::Clean; } - - // The current config hash(es); this can be empty if the current hash is unknown or the current - // state is not clean (i.e. a push is needed or pending). - std::vector current_hashes() const; - - // Returns true if this object contains updated data that has not yet been confirmed stored on - // the server. This will be true whenever `is_clean()` is false: that is, if we are currently - // "dirty" (i.e. have changes that haven't been pushed) or are still awaiting confirmation of - // storage of the most recent serialized push data. - virtual bool needs_push() const; - - // Returns a tuple of three elements: - // - the seqno value of the data - // - the data message to push to the server - // - a list of known message hashes that are obsoleted by this push. - // - // Additionally, if the internal state is currently dirty (i.e. there are unpushed changes), the - // internal state will be marked as awaiting-confirmation. Any further data changes made after - // this call will re-dirty the data (incrementing seqno and requiring another push). - // - // The client is expected to send a sequence request to the server that stores the message and - // deletes the hashes (if any). It is strongly recommended to use a sequence rather than a - // batch so that the deletions won't happen if the store fails for some reason. - // - // Upon successful completion of the store+deletion requests the client should call - // `confirm_pushed` with the seqno value to confirm that the message has been stored. - // - // Subclasses that need to perform pre-push tasks (such as pruning stale data) can override this - // to prune and then call the base method to perform the actual push generation. - virtual std::tuple> push(); - - // Should be called after the push is confirmed stored on the storage server swarm to let the - // object know the config message has been stored and, ideally, that the obsolete messages - // returned by `push()` are deleted. Once this is called `needs_push` will start returning - // false until something changes. Takes the seqno that was pushed so that the object can ensure - // that the latest version was pushed (i.e. in case there have been other changes since the - // `push()` call that returned this seqno). - // - // Ideally the caller should have both stored the returned message and deleted the given - // messages. The deletion step isn't critical (it is just cleanup) and callers should call this - // as long as the store succeeded even if there were errors in the deletions. - // - // It is safe to call this multiple times with the same seqno value, and with out-of-order - // seqnos (e.g. calling with seqno 122 after having called with 123; the duplicates and earlier - // ones will just be ignored). - virtual void confirm_pushed(seqno_t seqno, std::string msg_hash); - - // Returns a dump of the current state for storage in the database; this value would get passed - // into the constructor to reconstitute the object (including the push/not pushed status). This - // method is *not* virtual: if subclasses need to store extra data they should set it in the - // `subclass_data` field. - ustring dump(); - - // Returns true if something has changed since the last call to `dump()` that requires calling - // and saving the `dump()` data again. - virtual bool needs_dump() const { return _needs_dump; } - - // Encryption key methods. For classes that have a single, static key (such as user profile - // storage types) these methods typically don't need to be used: the subclass calls them - // automatically. - - // Adds an encryption/decryption key, without removing existing keys. They key must be exactly - // 32 bytes long. The newly added key becomes the highest priority key (unless the - // `high_priority` argument is set to false' see below): it will be used for encryption of - // config pushes after the call, and will be tried first when decrypting, followed by keys - // present (if any) before this call. If the given key is already present in the key list then - // this call moves it to the front of the list (if not already at the front). - // - // If the `high_priority` argument is specified and false, then the key is added to the *end* of - // the key list instead of the beginning: that is, it will not replace the current - // highest-priority key used for encryption, but will still be usable for decryption of new - // incoming messages (after trying keys present before the call). If the key already exists - // then nothing happens with `high_priority=false` (in particular, it is *not* repositioned, in - // contrast to high_priority=true behaviour). - // - // Will throw a std::invalid_argument if the key is not 32 bytes. - void add_key(ustring_view key, bool high_priority = true); - - // Clears all stored encryption/decryption keys. This is typically immediately followed with - // one or more `add_key` call to replace existing keys. Returns the number of keys removed. - int clear_keys(); - - // Removes the given encryption/decryption key, if present. Returns true if it was found and - // removed, false if it was not in the key list. - // - // The optional second argument removes the key only from position `from` or higher. It is - // mainly for internal use and is usually omitted. - bool remove_key(ustring_view key, size_t from = 0); - - // Returns a vector of encryption keys, in priority order (i.e. element 0 is the encryption key, - // and the first decryption key). - std::vector get_keys() const; - - // Returns the number of encryption keys. - int key_count() const; - - // Returns true if the given key is already in the keys list. - bool has_key(ustring_view key) const; - - // Accesses the key at position i (0 if omitted). There must be at least one key, and i must be - // less than key_count(). The key at position 0 is used for encryption; for decryption all keys - // are tried in order, starting from position 0. - ustring_view key(size_t i = 0) const { - assert(i < _keys_size); - return {_keys[i].data(), _keys[i].size()}; - } -}; - -// The C++ struct we hold opaquely inside the C internals struct. This is designed so that any -// internals has the same layout so that it doesn't matter whether we unbox to an -// internals or internals. -template < - typename ConfigT = ConfigBase, - std::enable_if_t, int> = 0> -struct internals final { - std::unique_ptr config; - std::string error; - - // Dereferencing falls through to the ConfigBase object - ConfigT* operator->() { - if constexpr (std::is_same_v) - return config.get(); - else { - auto* c = dynamic_cast(config.get()); - assert(c); - return c; - } - } - const ConfigT* operator->() const { - if constexpr (std::is_same_v) - return config.get(); - else { - auto* c = dynamic_cast(config.get()); - assert(c); - return c; - } - } - ConfigT& operator*() { return *operator->(); } - const ConfigT& operator*() const { return *operator->(); } -}; - -template , int> = 0> -inline internals& unbox(config_object* conf) { - return *static_cast*>(conf->internals); -} -template , int> = 0> -inline const internals& unbox(const config_object* conf) { - return *static_cast*>(conf->internals); -} - -// Sets an error message in the internals.error string and updates the last_error pointer in the -// outer (C) config_object struct to point at it. -void set_error(config_object* conf, std::string e); - -// Same as above, but gets the error string out of an exception and passed through a return value. -// Intended to simplify catch-and-return-error such as: -// try { -// whatever(); -// } catch (const std::exception& e) { -// return set_error(conf, LIB_SESSION_ERR_OHNOES, e); -// } -inline int set_error(config_object* conf, int errcode, const std::exception& e) { - set_error(conf, e.what()); - return errcode; -} - -// Copies a value contained in a string into a new malloced char buffer, returning the buffer and -// size via the two pointer arguments. -void copy_out(ustring_view data, unsigned char** out, size_t* outlen); - -} // namespace session::config diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/community.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/community.h deleted file mode 100644 index 9530af967..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/community.h +++ /dev/null @@ -1,48 +0,0 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include - -// Maximum string length of a community base URL -extern const size_t COMMUNITY_BASE_URL_MAX_LENGTH; - -// Maximum string length of a community room token -extern const size_t COMMUNITY_ROOM_MAX_LENGTH; - -// Maximum string length of a full URL as produced by the community_make_full_url() function. -// Unlike the above constants, this *includes* space for a NULL string terminator. -extern const size_t COMMUNITY_FULL_URL_MAX_LENGTH; - -// Parses a community URL. Writes the canonical base url, room token, and pubkey bytes into the -// given pointers. base_url must be at least BASE_URL_MAX_LENGTH+1; room must be at least -// ROOM_MAX_LENGTH+1; and pubkey must be (at least) 32 bytes. -// -// Returns true if the url was parsed successfully, false if parsing failed (i.e. an invalid URL). -bool community_parse_full_url( - const char* full_url, char* base_url, char* room_token, unsigned char* pubkey); - -// Similar to the above, but allows a URL to omit the pubkey. If no pubkey is found, `pubkey` is -// left unchanged and `has_pubkey` is set to false; otherwise `pubkey` is written and `has_pubkey` -// is set to true. `pubkey` may be set to NULL, in which case it is never written. `has_pubkey` -// may be NULL in which case it is not set (typically both pubkey arguments would be null for cases -// where you don't care at all about the pubkey). -bool community_parse_partial_url( - const char* full_url, - char* base_url, - char* room_token, - unsigned char* pubkey, - bool* has_pubkey); - -// Produces a standard full URL from a given base_url (c string), room token (c string), and pubkey -// (fixed-length 32 byte buffer). The full URL is written to `full_url`, which must be at least -// COMMUNITY_FULL_URL_MAX_LENGTH in size. -void community_make_full_url( - const char* base_url, const char* room, const unsigned char* pubkey, char* full_url); - -#ifdef __cplusplus -} -#endif diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/community.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/community.hpp deleted file mode 100644 index 04cc8ac8d..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/community.hpp +++ /dev/null @@ -1,254 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include -#include -#include -#include - -namespace session::config { - -/// Base class for types representing a community; this base type handles the url/room/pubkey that -/// such a type need. Generally a class inherits from this to extend with the local -/// community-related values. -struct community { - - // 267 = len('https://') + 253 (max valid DNS name length) + len(':XXXXX') - static constexpr size_t BASE_URL_MAX_LENGTH = 267; - static constexpr size_t ROOM_MAX_LENGTH = 64; - - community() = default; - - // Constructs an empty community struct from url, room, and pubkey. `base_url` will be - // normalized if not already. pubkey is 32 bytes. - community(std::string_view base_url, std::string_view room, ustring_view pubkey); - - // Same as above, but takes pubkey as an encoded (hex or base32z or base64) string. - community(std::string_view base_url, std::string_view room, std::string_view pubkey_encoded); - - // Takes a combined room URL (e.g. https://whatever.com/r/Room?public_key=01234....), either - // new style (with /r/) or old style (without /r/). Note that the URL gets canonicalized so - // the resulting `base_url()` and `room()` values may not be exactly equal to what is given. - // - // See also `parse_full_url` which does the same thing but returns it in pieces rather than - // constructing a new `community` object. - explicit community(std::string_view full_url); - - // Replaces the baseurl/room/pubkey of this object from a URL. This parses the URL, then stores - // the values as if passed to set_base_url/set_room/set_pubkey. - // - // The base URL will be normalized; the room name will be case-preserving (but see `set_room` - // for info on limitations on "case-preserving", particularly for volatile configs); and the - // embedded pubkey must be encoded in one of hex, base32z, or base64. - void set_full_url(std::string_view full_url); - - // Replaces the base_url of this object. Note that changing the URL and then giving it to `set` - // will end up inserting a *new* record but not removing the *old* one (you need to erase first - // to do that). - void set_base_url(std::string_view new_url); - - // Changes the room token. This stores (or updates) the name as given as the localized room, - // and separately stores the normalized (lower-case) token. Note that the localized name does - // not persist across a push or dump in some config contexts (such as volatile room info). If - // the new room given here changes more than just case (i.e. if the normalized room token - // changes) then a call to `set` will end up inserting a *new* record but not removing the *old* - // one (you need to erase first to do that). - void set_room(std::string_view room); - - // Updates the pubkey of this community (typically this is not called directly but rather - // via `set_server` or during construction). Throws std::invalid_argument if the given - // pubkey does not look like a valid pubkey. The std::string_view version takes the pubkey - // as any of hex/base64/base32z. - // - // NOTE: the pubkey of all communities with the same URLs are stored in common, so changing - // one community pubkey (and storing) will affect all communities using the same community - // base URL. - void set_pubkey(ustring_view pubkey); - void set_pubkey(std::string_view pubkey); - - // Accesses the base url (i.e. not including room or pubkey). Always lower-case/normalized. - const std::string& base_url() const { return base_url_; } - - // Accesses the room token; this is case-preserving, where possible. In some contexts, however, - // such as volatile info, the case is not preserved and this will always return the normalized - // (lower-case) form rather than the preferred form. - const std::string& room() const { return localized_room_ ? *localized_room_ : room_; } - - // Accesses the normalized room token, i.e. always lower-case. - const std::string& room_norm() const { return room_; } - - const ustring& pubkey() const { return pubkey_; } // Accesses the server pubkey (32 bytes). - std::string pubkey_hex() const; // Accesses the server pubkey as hex (64 hex digits). - std::string pubkey_b32z() const; // Accesses the server pubkey as base32z (52 alphanumeric - // digits) - std::string pubkey_b64() const; // Accesses the server pubkey as unpadded base64 (43 from - // alphanumeric, '+', and '/'). - - // Constructs and returns the full URL for this room. See below. - std::string full_url() const; - - // Constructs and returns the full URL for a given base, room, and pubkey. Currently this - // returns it in a Session-compatibility form (https://server.com/RoomName?public_key=....), but - // future versions are expected to change to use (https://server.com/r/RoomName?public_key=...), - // which this library also accepts. - static std::string full_url( - std::string_view base_url, std::string_view room, ustring_view pubkey); - - // Takes a base URL as input and returns it in canonical form. This involves doing things - // like lower casing it and removing redundant ports (e.g. :80 when using http://). Throws - // std::invalid_argument if given an invalid base URL. - static std::string canonical_url(std::string_view url); - - // Takes a room token and returns it in canonical form (i.e. lower-cased). Throws - // std::invalid_argument if given an invalid room token (e.g. too long, or containing token - // other than a-z, 0-9, -, _). - static std::string canonical_room(std::string_view room); - - // Same as above, but modifies the argument in-place instead of returning a modified - // copy. - static void canonicalize_url(std::string& url); - static void canonicalize_room(std::string& room); - - // Takes a full room URL, splits it up into canonical url (see above), room, and server - // pubkey. We take both the deprecated form (e.g. - // https://example.com/SomeRoom?public_key=...) and new form - // (https://example.com/r/SomeRoom?public_key=...). The public_key is typically specified - // in hex (64 digits), but we also accept base64 (43 chars or 44 with padding) and base32z - // (52 chars) encodings (for slightly shorter URLs). - // - // The returned URL is normalized (lower-cased, and cleaned up). - // - // The returned room name is *not* normalized, that is, it preserve case. - // - // Throw std::invalid_argument if anything in the URL is unparseable or invalid. - static std::tuple parse_full_url(std::string_view full_url); - - // Takes a full or partial room URL (partial here meaning missing the ?public_key=...) and - // splits it up into canonical url, room, and (if present) pubkey. - static std::tuple> parse_partial_url( - std::string_view url); - - protected: - // The canonical base url and room (i.e. lower-cased, URL cleaned up): - std::string base_url_, room_; - // The localized token of this room, that is, with case preserved (so `room_` could be - // `someroom` and this could `SomeRoom`). Omitted if not available. - std::optional localized_room_; - // server pubkey - ustring pubkey_; - - // Construction without a pubkey for when pubkey isn't known yet but will be set shortly - // after constructing (or when isn't needed, such as when deleting). - community(std::string_view base_url, std::string_view room); -}; - -struct comm_iterator_helper { - - comm_iterator_helper(dict::const_iterator it_server, dict::const_iterator end_server) : - it_server{std::move(it_server)}, end_server{std::move(end_server)} {} - - std::optional it_server, end_server, it_room, end_room; - - bool operator==(const comm_iterator_helper& other) const { - return it_server == other.it_server && it_room == other.it_room; - } - - void next_server() { - ++*it_server; - it_room.reset(); - end_room.reset(); - } - - bool done() const { return !it_server || *it_server == *end_server; } - - template - bool load(std::shared_ptr& val) { - while (it_server) { - if (*it_server == *end_server) { - it_server.reset(); - end_server.reset(); - return false; - } - - auto& [base_url, server_info] = **it_server; - auto* server_info_dict = std::get_if(&server_info); - if (!server_info_dict) { - next_server(); - continue; - } - - const std::string* pubkey_raw = nullptr; - if (auto pubkey_it = server_info_dict->find("#"); pubkey_it != server_info_dict->end()) - if (auto* pk_sc = std::get_if(&pubkey_it->second)) - pubkey_raw = std::get_if(pk_sc); - - if (!pubkey_raw) { - next_server(); - continue; - } - - ustring_view pubkey{ - reinterpret_cast(pubkey_raw->data()), pubkey_raw->size()}; - - if (!it_room) { - if (auto rit = server_info_dict->find("R"); - rit != server_info_dict->end() && std::holds_alternative(rit->second)) { - auto& rooms_dict = std::get(rit->second); - it_room = rooms_dict.begin(); - end_room = rooms_dict.end(); - } else { - next_server(); - continue; - } - } - - while (it_room) { - if (*it_room == *end_room) { - it_room.reset(); - end_room.reset(); - break; - } - - auto& [room, data] = **it_room; - auto* data_dict = std::get_if(&data); - if (!data_dict) { - ++*it_room; - continue; - } - - val = std::make_shared(Comm{}); - auto& og = std::get(*val); - try { - og.set_base_url(base_url); - og.set_room(room); // Will be replaced with "n" in the `.load` below - og.set_pubkey(pubkey); - og.load(*data_dict); - } catch (const std::exception& e) { - ++*it_room; - continue; - } - return true; - } - - ++*it_server; - } - - return false; - } - - bool advance() { - if (it_room) { - ++*it_room; - return true; - } - if (it_server) { - ++*it_server; - return true; - } - return false; - } -}; - -} // namespace session::config diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/contacts.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/contacts.h deleted file mode 100644 index f93d55857..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/contacts.h +++ /dev/null @@ -1,160 +0,0 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#include "base.h" -#include "expiring.h" -#include "notify.h" -#include "profile_pic.h" -#include "util.h" - -// Maximum length of a contact name/nickname, in bytes (not including the null terminator). -extern const size_t CONTACT_MAX_NAME_LENGTH; - -typedef struct contacts_contact { - char session_id[67]; // in hex; 66 hex chars + null terminator. - - // These two will be 0-length strings when unset: - char name[101]; - char nickname[101]; - user_profile_pic profile_pic; - - bool approved; - bool approved_me; - bool blocked; - - int priority; - CONVO_NOTIFY_MODE notifications; - int64_t mute_until; - - CONVO_EXPIRATION_MODE exp_mode; - int exp_seconds; - - int64_t created; // unix timestamp (seconds) - -} contacts_contact; - -/// Constructs a contacts config object and sets a pointer to it in `conf`. -/// -/// \param ed25519_secretkey must be the 32-byte secret key seed value. (You can also pass the -/// pointer to the beginning of the 64-byte value libsodium calls the "secret key" as the first 32 -/// bytes of that are the seed). This field cannot be null. -/// -/// \param dump - if non-NULL this restores the state from the dumped byte string produced by a past -/// instantiation's call to `dump()`. To construct a new, empty object this should be NULL. -/// -/// \param dumplen - the length of `dump` when restoring from a dump, or 0 when `dump` is NULL. -/// -/// \param error - the pointer to a buffer in which we will write an error string if an error -/// occurs; error messages are discarded if this is given as NULL. If non-NULL this must be a -/// buffer of at least 256 bytes. -/// -/// Returns 0 on success; returns a non-zero error code and write the exception message as a -/// C-string into `error` (if not NULL) on failure. -/// -/// When done with the object the `config_object` must be destroyed by passing the pointer to -/// config_free() (in `session/config/base.h`). -int contacts_init( - config_object** conf, - const unsigned char* ed25519_secretkey, - const unsigned char* dump, - size_t dumplen, - char* error) __attribute__((warn_unused_result)); - -/// Fills `contact` with the contact info given a session ID (specified as a null-terminated hex -/// string), if the contact exists, and returns true. If the contact does not exist then `contact` -/// is left unchanged and false is returned. -bool contacts_get(config_object* conf, contacts_contact* contact, const char* session_id) - __attribute__((warn_unused_result)); - -/// Same as the above except that when the contact does not exist, this sets all the contact fields -/// to defaults and loads it with the given session_id. -/// -/// Returns true as long as it is given a valid session_id. A false return is considered an error, -/// and means the session_id was not a valid session_id. -/// -/// This is the method that should usually be used to create or update a contact, followed by -/// setting fields in the contact, and then giving it to contacts_set(). -bool contacts_get_or_construct( - config_object* conf, contacts_contact* contact, const char* session_id) - __attribute__((warn_unused_result)); - -/// Adds or updates a contact from the given contact info struct. -void contacts_set(config_object* conf, const contacts_contact* contact); - -// NB: wrappers for set_name, set_nickname, etc. C++ methods are deliberately omitted as they would -// save very little in actual calling code. The procedure for updating a single field without them -// is simple enough; for example to update `approved` and leave everything else unchanged: -// -// contacts_contact c; -// if (contacts_get_or_construct(conf, &c, some_session_id)) { -// const char* new_nickname = "Joe"; -// c.approved = new_nickname; -// contacts_set_or_create(conf, &c); -// } else { -// // some_session_id was invalid! -// } - -/// Erases a contact from the contact list. session_id is in hex. Returns true if the contact was -/// found and removed, false if the contact was not present. You must not call this during -/// iteration; see details below. -bool contacts_erase(config_object* conf, const char* session_id); - -/// Returns the number of contacts. -size_t contacts_size(const config_object* conf); - -/// Functions for iterating through the entire contact list, in sorted order. Intended use is: -/// -/// contacts_contact c; -/// contacts_iterator *it = contacts_iterator_new(contacts); -/// for (; !contacts_iterator_done(it, &c); contacts_iterator_advance(it)) { -/// // c.session_id, c.nickname, etc. are loaded -/// } -/// contacts_iterator_free(it); -/// -/// It is permitted to modify records (e.g. with a call to `contacts_set`) and add records while -/// iterating. -/// -/// If you need to remove while iterating then usage is slightly different: you must advance the -/// iteration by calling either contacts_iterator_advance if not deleting, or -/// contacts_iterator_erase to erase and advance. Usage looks like this: -/// -/// contacts_contact c; -/// contacts_iterator *it = contacts_iterator_new(contacts); -/// while (!contacts_iterator_done(it, &c)) { -/// // c.session_id, c.nickname, etc. are loaded -/// -/// bool should_delete = /* ... */; -/// -/// if (should_delete) -/// contacts_iterator_erase(it); -/// else -/// contacts_iterator_advance(it); -/// } -/// contacts_iterator_free(it); -/// -/// - -typedef struct contacts_iterator { - void* _internals; -} contacts_iterator; - -// Starts a new iterator. -contacts_iterator* contacts_iterator_new(const config_object* conf); -// Frees an iterator once no longer needed. -void contacts_iterator_free(contacts_iterator* it); - -// Returns true if iteration has reached the end. Otherwise `c` is populated and false is returned. -bool contacts_iterator_done(contacts_iterator* it, contacts_contact* c); - -// Advances the iterator. -void contacts_iterator_advance(contacts_iterator* it); - -// Erases the current contact while advancing the iterator to the next contact in the iteration. -void contacts_iterator_erase(config_object* conf, contacts_iterator* it); - -#ifdef __cplusplus -} // extern "C" -#endif diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/contacts.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/contacts.hpp deleted file mode 100644 index e0212dbdc..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/contacts.hpp +++ /dev/null @@ -1,231 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -#include "base.hpp" -#include "expiring.hpp" -#include "namespaces.hpp" -#include "notify.hpp" -#include "profile_pic.hpp" - -extern "C" struct contacts_contact; - -using namespace std::literals; - -namespace session::config { - -/// keys used in this config, either currently or in the past (so that we don't reuse): -/// -/// c - dict of contacts; within this dict each key is the session pubkey (binary, 33 bytes) and -/// value is a dict containing keys: -/// -/// n - contact name (string). This is always serialized, even if empty (but empty indicates -/// no name) so that we always have at least one key set (required to keep the dict value -/// alive as empty dicts get pruned). -/// N - contact nickname (string) -/// p - profile url (string) -/// q - profile decryption key (binary) -/// a - 1 if approved, omitted otherwise (int) -/// A - 1 if remote has approved me, omitted otherwise (int) -/// b - 1 if contact is blocked, omitted otherwise -/// @ - notification setting (int). Omitted = use default setting; 1 = all; 2 = disabled. -/// ! - mute timestamp: if this is set then notifications are to be muted until the given unix -/// timestamp (seconds, not milliseconds). -/// + - the conversation priority; -1 means hidden; omitted means not pinned; otherwise an -/// integer value >0, where a higher priority means the conversation is meant to appear -/// earlier in the pinned conversation list. -/// e - Disappearing messages expiration type. Omitted if disappearing messages are not enabled -/// for the conversation with this contact; 1 for delete-after-send, and 2 for -/// delete-after-read. -/// E - Disappearing message timer, in seconds. Omitted when `e` is omitted. -/// j - Unix timestamp (seconds) when the contact was created ("j" to match user_groups -/// equivalent "j"oined field). Omitted if 0. - -/// Struct containing contact info. -struct contact_info { - static constexpr size_t MAX_NAME_LENGTH = 100; - - std::string session_id; // in hex - std::string name; - std::string nickname; - profile_pic profile_picture; - bool approved = false; - bool approved_me = false; - bool blocked = false; - int priority = 0; // If >0 then this message is pinned; higher values mean higher priority - // (i.e. pinned earlier in the pinned list). If negative then this - // conversation is hidden. Otherwise (0) this is a regular, unpinned - // conversation. - notify_mode notifications = notify_mode::defaulted; - int64_t mute_until = 0; // If non-zero, disable notifications until the given unix timestamp - // (overriding whatever the current `notifications` value is until the - // timestamp expires). - expiration_mode exp_mode = expiration_mode::none; // The expiry time; none if not expiring. - std::chrono::seconds exp_timer{0}; // The expiration timer (in seconds) - int64_t created = 0; // Unix timestamp when this contact was added - - explicit contact_info(std::string sid); - - // Internal ctor/method for C API implementations: - contact_info(const struct contacts_contact& c); // From c struct - void into(contacts_contact& c) const; // Into c struct - - // Sets a name or nickname; this is exactly the same as assigning to .name/.nickname directly, - // except that we throw an exception if the given name is longer than MAX_NAME_LENGTH. - void set_name(std::string name); - void set_nickname(std::string nickname); - - private: - friend class Contacts; - void load(const dict& info_dict); -}; - -class Contacts : public ConfigBase { - - public: - // No default constructor - Contacts() = delete; - - /// Constructs a contact list from existing data (stored from `dump()`) and the user's secret - /// key for generating the data encryption key. To construct a blank list (i.e. with no - /// pre-existing dumped data to load) pass `std::nullopt` as the second argument. - /// - /// \param ed25519_secretkey - contains the libsodium secret key used to encrypt/decrypt the - /// data when pushing/pulling from the swarm. This can either be the full 64-byte value (which - /// is technically the 32-byte seed followed by the 32-byte pubkey), or just the 32-byte seed of - /// the secret key. - /// - /// \param dumped - either `std::nullopt` to construct a new, empty object; or binary state data - /// that was previously dumped from an instance of this class by calling `dump()`. - Contacts(ustring_view ed25519_secretkey, std::optional dumped); - - Namespace storage_namespace() const override { return Namespace::Contacts; } - - const char* encryption_domain() const override { return "Contacts"; } - - /// Looks up and returns a contact by session ID (hex). Returns nullopt if the session ID was - /// not found, otherwise returns a filled out `contact_info`. - std::optional get(std::string_view pubkey_hex) const; - - /// Similar to get(), but if the session ID does not exist this returns a filled-out - /// contact_info containing the session_id (all other fields will be empty/defaulted). This is - /// intended to be combined with `set` to set-or-create a record. - /// - /// NB: calling this does *not* add the session id to the contact list when called: that - /// requires also calling `set` with this value. - contact_info get_or_construct(std::string_view pubkey_hex) const; - - /// Sets or updates multiple contact info values at once with the given info. The usual use is - /// to access the current info, change anything desired, then pass it back into set_contact, - /// e.g.: - /// - /// auto c = contacts.get_or_construct(pubkey); - /// c.name = "Session User 42"; - /// c.nickname = "BFF"; - /// contacts.set(c); - void set(const contact_info& contact); - - /// Alternative to `set()` for setting a single field. (If setting multiple fields at once you - /// should use `set()` instead). - void set_name(std::string_view session_id, std::string name); - void set_nickname(std::string_view session_id, std::string nickname); - void set_profile_pic(std::string_view session_id, profile_pic pic); - void set_approved(std::string_view session_id, bool approved); - void set_approved_me(std::string_view session_id, bool approved_me); - void set_blocked(std::string_view session_id, bool blocked); - void set_priority(std::string_view session_id, int priority); - void set_notifications(std::string_view session_id, notify_mode notifications); - void set_expiry( - std::string_view session_id, - expiration_mode exp_mode, - std::chrono::seconds expiration_timer = 0min); - void set_created(std::string_view session_id, int64_t timestamp); - - /// Removes a contact, if present. Returns true if it was found and removed, false otherwise. - /// Note that this removes all fields related to a contact, even fields we do not know about. - bool erase(std::string_view session_id); - - struct iterator; - - /// This works like erase, but takes an iterator to the contact to remove. The element is - /// removed and the iterator to the next element after the removed one is returned. This is - /// intended for use where elements are to be removed during iteration: see below for an - /// example. - iterator erase(iterator it); - - /// Returns the number of contacts. - size_t size() const; - - /// Returns true if the contact list is empty. - bool empty() const { return size() == 0; } - - /// Iterators for iterating through all contacts. Typically you access this implicit via a for - /// loop over the `Contacts` object: - /// - /// for (auto& contact : contacts) { - /// // use contact.session_id, contact.name, etc. - /// } - /// - /// This iterates in sorted order through the session_ids. - /// - /// It is permitted to modify and add records while iterating (e.g. by modifying `contact` and - /// then calling set()). - /// - /// If you need to erase the current contact during iteration then care is required: you need to - /// advance the iterator via the iterator version of erase when erasing an element rather than - /// incrementing it regularly. For example: - /// - /// for (auto it = contacts.begin(); it != contacts.end(); ) { - /// if (should_remove(*it)) - /// it = contacts.erase(it); - /// else - /// ++it; - /// } - /// - /// Alternatively, you can use the first version with two loops: the first loop through all - /// contacts doesn't erase but just builds a vector of IDs to erase, then the second loops - /// through that vector calling `erase()` for each one. - /// - iterator begin() const { return iterator{data["c"].dict()}; } - iterator end() const { return iterator{nullptr}; } - - using iterator_category = std::input_iterator_tag; - using value_type = contact_info; - using reference = value_type&; - using pointer = value_type*; - using difference_type = std::ptrdiff_t; - - struct iterator { - private: - std::shared_ptr _val; - dict::const_iterator _it; - const dict* _contacts; - void _load_info(); - iterator(const dict* contacts) : _contacts{contacts} { - if (_contacts) { - _it = _contacts->begin(); - _load_info(); - } - } - friend class Contacts; - - public: - bool operator==(const iterator& other) const; - bool operator!=(const iterator& other) const { return !(*this == other); } - bool done() const; // Equivalent to comparing against the end iterator - contact_info& operator*() const { return *_val; } - contact_info* operator->() const { return _val.get(); } - iterator& operator++(); - iterator operator++(int) { - auto copy{*this}; - ++*this; - return copy; - } - }; -}; - -} // namespace session::config diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/convo_info_volatile.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/convo_info_volatile.h deleted file mode 100644 index 8d141b801..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/convo_info_volatile.h +++ /dev/null @@ -1,229 +0,0 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#include "base.h" -#include "profile_pic.h" - -typedef struct convo_info_volatile_1to1 { - char session_id[67]; // in hex; 66 hex chars + null terminator. - - int64_t last_read; // milliseconds since unix epoch - bool unread; // true if the conversation is explicitly marked unread -} convo_info_volatile_1to1; - -typedef struct convo_info_volatile_community { - char base_url[268]; // null-terminated (max length 267), normalized (i.e. always lower-case, - // only has port if non-default, has trailing / removed) - char room[65]; // null-terminated (max length 64), normalized (always lower-case) - unsigned char pubkey[32]; // 32 bytes (not terminated, can contain nulls) - - int64_t last_read; // ms since unix epoch - bool unread; // true if marked unread -} convo_info_volatile_community; - -typedef struct convo_info_volatile_legacy_group { - char group_id[67]; // in hex; 66 hex chars + null terminator. Looks just like a Session ID, - // though isn't really one. - - int64_t last_read; // ms since unix epoch - bool unread; // true if marked unread -} convo_info_volatile_legacy_group; - -/// Constructs a conversations config object and sets a pointer to it in `conf`. -/// -/// \param ed25519_secretkey must be the 32-byte secret key seed value. (You can also pass the -/// pointer to the beginning of the 64-byte value libsodium calls the "secret key" as the first 32 -/// bytes of that are the seed). This field cannot be null. -/// -/// \param dump - if non-NULL this restores the state from the dumped byte string produced by a past -/// instantiation's call to `dump()`. To construct a new, empty object this should be NULL. -/// -/// \param dumplen - the length of `dump` when restoring from a dump, or 0 when `dump` is NULL. -/// -/// \param error - the pointer to a buffer in which we will write an error string if an error -/// occurs; error messages are discarded if this is given as NULL. If non-NULL this must be a -/// buffer of at least 256 bytes. -/// -/// Returns 0 on success; returns a non-zero error code and write the exception message as a -/// C-string into `error` (if not NULL) on failure. -/// -/// When done with the object the `config_object` must be destroyed by passing the pointer to -/// config_free() (in `session/config/base.h`). -int convo_info_volatile_init( - config_object** conf, - const unsigned char* ed25519_secretkey, - const unsigned char* dump, - size_t dumplen, - char* error) __attribute__((warn_unused_result)); - -/// Fills `convo` with the conversation info given a session ID (specified as a null-terminated hex -/// string), if the conversation exists, and returns true. If the conversation does not exist then -/// `convo` is left unchanged and false is returned. If an error occurs, false is returned and -/// `conf->last_error` will be set to non-NULL containing the error string (if no error occurs, such -/// as in the case where the conversation merely doesn't exist, `last_error` will be set to NULL). -bool convo_info_volatile_get_1to1( - config_object* conf, convo_info_volatile_1to1* convo, const char* session_id) - __attribute__((warn_unused_result)); - -/// Same as the above except that when the conversation does not exist, this sets all the convo -/// fields to defaults and loads it with the given session_id. -/// -/// Returns true as long as it is given a valid session_id. A false return is considered an error, -/// and means the session_id was not a valid session_id. In such a case `conf->last_error` will be -/// set to an error string. -/// -/// This is the method that should usually be used to create or update a conversation, followed by -/// setting fields in the convo, and then giving it to convo_info_volatile_set(). -bool convo_info_volatile_get_or_construct_1to1( - config_object* conf, convo_info_volatile_1to1* convo, const char* session_id) - __attribute__((warn_unused_result)); - -/// community versions of the 1-to-1 functions: -/// -/// Gets a community convo info. `base_url` and `room` are null-terminated c strings; pubkey is -/// 32 bytes. base_url and room will always be lower-cased (if not already). -/// -/// Error handling works the same as the 1-to-1 version. -bool convo_info_volatile_get_community( - config_object* conf, - convo_info_volatile_community* comm, - const char* base_url, - const char* room) __attribute__((warn_unused_result)); -bool convo_info_volatile_get_or_construct_community( - config_object* conf, - convo_info_volatile_community* convo, - const char* base_url, - const char* room, - unsigned const char* pubkey) __attribute__((warn_unused_result)); - -/// Fills `convo` with the conversation info given a legacy group ID (specified as a null-terminated -/// hex string), if the conversation exists, and returns true. If the conversation does not exist -/// then `convo` is left unchanged and false is returned. On error, false is returned and the error -/// is set in conf->last_error (on non-error, last_error is cleared). -bool convo_info_volatile_get_legacy_group( - config_object* conf, convo_info_volatile_legacy_group* convo, const char* id) - __attribute__((warn_unused_result)); - -/// Same as the above except that when the conversation does not exist, this sets all the convo -/// fields to defaults and loads it with the given id. -/// -/// Returns true as long as it is given a valid legacy group id (i.e. same format as a session id). -/// A false return is considered an error, and means the id was not a valid session id; an error -/// string will be set in `conf->last_error`. -/// -/// This is the method that should usually be used to create or update a conversation, followed by -/// setting fields in the convo, and then giving it to convo_info_volatile_set(). -bool convo_info_volatile_get_or_construct_legacy_group( - config_object* conf, convo_info_volatile_legacy_group* convo, const char* id) - __attribute__((warn_unused_result)); - -/// Adds or updates a conversation from the given convo info -void convo_info_volatile_set_1to1(config_object* conf, const convo_info_volatile_1to1* convo); -void convo_info_volatile_set_community( - config_object* conf, const convo_info_volatile_community* convo); -void convo_info_volatile_set_legacy_group( - config_object* conf, const convo_info_volatile_legacy_group* convo); - -/// Erases a conversation from the conversation list. Returns true if the conversation was found -/// and removed, false if the conversation was not present. You must not call this during -/// iteration; see details below. -bool convo_info_volatile_erase_1to1(config_object* conf, const char* session_id); -bool convo_info_volatile_erase_community( - config_object* conf, const char* base_url, const char* room); -bool convo_info_volatile_erase_legacy_group(config_object* conf, const char* group_id); - -/// Returns the number of conversations. -size_t convo_info_volatile_size(const config_object* conf); -/// Returns the number of conversations of the specific type. -size_t convo_info_volatile_size_1to1(const config_object* conf); -size_t convo_info_volatile_size_communities(const config_object* conf); -size_t convo_info_volatile_size_legacy_groups(const config_object* conf); - -/// Functions for iterating through the entire conversation list. Intended use is: -/// -/// convo_info_volatile_1to1 c1; -/// convo_info_volatile_community c2; -/// convo_info_volatile_legacy_group c3; -/// convo_info_volatile_iterator *it = convo_info_volatile_iterator_new(my_convos); -/// for (; !convo_info_volatile_iterator_done(it); convo_info_volatile_iterator_advance(it)) { -/// if (convo_info_volatile_it_is_1to1(it, &c1)) { -/// // use c1.whatever -/// } else if (convo_info_volatile_it_is_community(it, &c2)) { -/// // use c2.whatever -/// } else if (convo_info_volatile_it_is_legacy_group(it, &c3)) { -/// // use c3.whatever -/// } -/// } -/// convo_info_volatile_iterator_free(it); -/// -/// It is permitted to modify records (e.g. with a call to one of the `convo_info_volatile_set_*` -/// functions) and add records while iterating. -/// -/// If you need to remove while iterating then usage is slightly different: you must advance the -/// iteration by calling either convo_info_volatile_iterator_advance if not deleting, or -/// convo_info_volatile_iterator_erase to erase and advance. Usage looks like this: -/// -/// convo_info_volatile_1to1 c1; -/// convo_info_volatile_iterator *it = convo_info_volatile_iterator_new(my_convos); -/// while (!convo_info_volatile_iterator_done(it)) { -/// if (convo_it_is_1to1(it, &c1)) { -/// bool should_delete = /* ... */; -/// if (should_delete) -/// convo_info_volatile_iterator_erase(it); -/// else -/// convo_info_volatile_iterator_advance(it); -/// } else { -/// convo_info_volatile_iterator_advance(it); -/// } -/// } -/// convo_info_volatile_iterator_free(it); -/// - -typedef struct convo_info_volatile_iterator convo_info_volatile_iterator; - -// Starts a new iterator that iterates over all conversations. -convo_info_volatile_iterator* convo_info_volatile_iterator_new(const config_object* conf); - -// The same as `convo_info_volatile_iterator_new` except that this iterates *only* over one type of -// conversation. You still need to use `convo_info_volatile_it_is_1to1` (or the alternatives) to -// load the data in each pass of the loop. (You can, however, safely ignore the bool return value -// of the `it_is_whatever` function: it will always be true for the particular type being iterated -// over). -convo_info_volatile_iterator* convo_info_volatile_iterator_new_1to1(const config_object* conf); -convo_info_volatile_iterator* convo_info_volatile_iterator_new_communities( - const config_object* conf); -convo_info_volatile_iterator* convo_info_volatile_iterator_new_legacy_groups( - const config_object* conf); - -// Frees an iterator once no longer needed. -void convo_info_volatile_iterator_free(convo_info_volatile_iterator* it); - -// Returns true if iteration has reached the end. -bool convo_info_volatile_iterator_done(convo_info_volatile_iterator* it); - -// Advances the iterator. -void convo_info_volatile_iterator_advance(convo_info_volatile_iterator* it); - -// If the current iterator record is a 1-to-1 conversation this sets the details into `c` and -// returns true. Otherwise it returns false. -bool convo_info_volatile_it_is_1to1(convo_info_volatile_iterator* it, convo_info_volatile_1to1* c); - -// If the current iterator record is a community conversation this sets the details into `c` and -// returns true. Otherwise it returns false. -bool convo_info_volatile_it_is_community( - convo_info_volatile_iterator* it, convo_info_volatile_community* c); - -// If the current iterator record is a legacy group conversation this sets the details into `c` and -// returns true. Otherwise it returns false. -bool convo_info_volatile_it_is_legacy_group( - convo_info_volatile_iterator* it, convo_info_volatile_legacy_group* c); - -// Erases the current convo while advancing the iterator to the next convo in the iteration. -void convo_info_volatile_iterator_erase(config_object* conf, convo_info_volatile_iterator* it); - -#ifdef __cplusplus -} // extern "C" -#endif diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/convo_info_volatile.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/convo_info_volatile.hpp deleted file mode 100644 index 149fdcbce..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/convo_info_volatile.hpp +++ /dev/null @@ -1,347 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -#include "base.hpp" -#include "community.hpp" - -using namespace std::literals; - -extern "C" { -struct convo_info_volatile_1to1; -struct convo_info_volatile_community; -struct convo_info_volatile_legacy_group; -} - -namespace session::config { - -class ConvoInfoVolatile; - -/// keys used in this config, either currently or in the past (so that we don't reuse): -/// -/// Note that this is a high-frequency object, intended only for properties that change frequently ( -/// (currently just the read timestamp for each conversation). -/// -/// 1 - dict of one-to-one conversations. Each key is the Session ID of the contact (in hex). -/// Values are dicts with keys: -/// r - the unix timestamp (in integer milliseconds) of the last-read message. Always -/// included, but will be 0 if no messages are read. -/// u - will be present and set to 1 if this conversation is specifically marked unread. -/// -/// o - community conversations. This is a nested dict where the outer keys are the BASE_URL of the -/// community and the outer value is a dict containing: -/// - `#` -- the 32-byte server pubkey -/// - `R` -- dict of rooms on the server; each key is the lower-case room name, value is a dict -/// containing keys: -/// r - the unix timestamp (in integer milliseconds) of the last-read message. Always -/// included, but will be 0 if no messages are read. -/// u - will be present and set to 1 if this conversation is specifically marked unread. -/// -/// C - legacy group conversations (aka closed groups). The key is the group identifier (which -/// looks indistinguishable from a Session ID, but isn't really a proper Session ID). Values -/// are dicts with keys: -/// r - the unix timestamp (integer milliseconds) of the last-read message. Always included, -/// but will be 0 if no messages are read. -/// u - will be present and set to 1 if this conversation is specifically marked unread. -/// -/// c - reserved for future tracking of new group conversations. - -namespace convo { - - struct base { - int64_t last_read = 0; - bool unread = false; - - protected: - void load(const dict& info_dict); - }; - - struct one_to_one : base { - std::string session_id; // in hex - - // Constructs an empty one_to_one from a session_id. Session ID can be either bytes (33) or - // hex (66). - explicit one_to_one(std::string&& session_id); - explicit one_to_one(std::string_view session_id); - - // Internal ctor/method for C API implementations: - one_to_one(const struct convo_info_volatile_1to1& c); // From c struct - void into(convo_info_volatile_1to1& c) const; // Into c struct - - friend class session::config::ConvoInfoVolatile; - }; - - struct community : config::community, base { - - using config::community::community; - - // Internal ctor/method for C API implementations: - community(const convo_info_volatile_community& c); // From c struct - void into(convo_info_volatile_community& c) const; // Into c struct - - friend class session::config::ConvoInfoVolatile; - friend struct session::config::comm_iterator_helper; - }; - - struct legacy_group : base { - std::string id; // in hex, indistinguishable from a Session ID - - // Constructs an empty legacy_group from a quasi-session_id - explicit legacy_group(std::string&& group_id); - explicit legacy_group(std::string_view group_id); - - // Internal ctor/method for C API implementations: - legacy_group(const struct convo_info_volatile_legacy_group& c); // From c struct - void into(convo_info_volatile_legacy_group& c) const; // Into c struct - - private: - friend class session::config::ConvoInfoVolatile; - }; - - using any = std::variant; -} // namespace convo - -class ConvoInfoVolatile : public ConfigBase { - - public: - // No default constructor - ConvoInfoVolatile() = delete; - - /// Constructs a conversation list from existing data (stored from `dump()`) and the user's - /// secret key for generating the data encryption key. To construct a blank list (i.e. with no - /// pre-existing dumped data to load) pass `std::nullopt` as the second argument. - /// - /// \param ed25519_secretkey - contains the libsodium secret key used to encrypt/decrypt the - /// data when pushing/pulling from the swarm. This can either be the full 64-byte value (which - /// is technically the 32-byte seed followed by the 32-byte pubkey), or just the 32-byte seed of - /// the secret key. - /// - /// \param dumped - either `std::nullopt` to construct a new, empty object; or binary state data - /// that was previously dumped from an instance of this class by calling `dump()`. - ConvoInfoVolatile(ustring_view ed25519_secretkey, std::optional dumped); - - Namespace storage_namespace() const override { return Namespace::ConvoInfoVolatile; } - - const char* encryption_domain() const override { return "ConvoInfoVolatile"; } - - /// Our pruning ages. We ignore added conversations that are more than PRUNE_LOW before now, - /// and we active remove (when doing a new push) any conversations that are more than PRUNE_HIGH - /// before now. Clients can mostly ignore these and just add all conversations; the class just - /// transparently ignores (or removes) pruned values. - static constexpr auto PRUNE_LOW = 30 * 24h; - static constexpr auto PRUNE_HIGH = 45 * 24h; - - /// Overrides push() to prune stale last-read values before we do the push. - std::tuple> push() override; - - /// Looks up and returns a contact by session ID (hex). Returns nullopt if the session ID was - /// not found, otherwise returns a filled out `convo::one_to_one`. - std::optional get_1to1(std::string_view session_id) const; - - /// Looks up and returns a community conversation. Takes the base URL and room name (case - /// insensitive). Retuns nullopt if the community was not found, otherwise a filled out - /// `convo::community`. - std::optional get_community( - std::string_view base_url, std::string_view room) const; - - /// Shortcut for calling community::parse_partial_url then calling the above with the base url - /// and room. The URL is not required to contain the pubkey (if present it will be ignored). - std::optional get_community(std::string_view partial_url) const; - - /// Looks up and returns a legacy group conversation by ID. The ID looks like a hex Session ID, - /// but isn't really a Session ID. Returns nullopt if there is no record of the group - /// conversation. - std::optional get_legacy_group(std::string_view pubkey_hex) const; - - /// These are the same as the above methods (without "_or_construct" in the name), except that - /// when the conversation doesn't exist a new one is created, prefilled with the pubkey/url/etc. - convo::one_to_one get_or_construct_1to1(std::string_view session_id) const; - convo::legacy_group get_or_construct_legacy_group(std::string_view pubkey_hex) const; - - /// This is similar to get_community, except that it also takes the pubkey; the community is - /// looked up by the url & room; if not found, it is constructed using room, url, and pubkey; if - /// it *is* found, then it will always have the *input* pubkey, not the stored pubkey - /// (effectively the provided pubkey replaces the stored one in the returned object; this is not - /// applied to storage, however, unless/until the instance is given to `set()`). - /// - /// Note, however, that when modifying an object like this the update is *only* applied to the - /// returned object; like other fields, it is not updated in the internal state unless/until - /// that community instance is passed to `set()`. - convo::community get_or_construct_community( - std::string_view base_url, std::string_view room, std::string_view pubkey_hex) const; - convo::community get_or_construct_community( - std::string_view base_url, std::string_view room, ustring_view pubkey) const; - - // Shortcut for calling community::parse_full_url then calling the above - convo::community get_or_construct_community(std::string_view full_url) const; - - /// Inserts or replaces existing conversation info. For example, to update a 1-to-1 - /// conversation last read time you would do: - /// - /// auto info = conversations.get_or_construct_1to1(some_session_id); - /// info.last_read = new_unix_timestamp; - /// conversations.set(info); - /// - void set(const convo::one_to_one& c); - void set(const convo::legacy_group& c); - void set(const convo::community& c); - - void set(const convo::any& c); // Variant which can be any of the above - - protected: - void set_base(const convo::base& c, DictFieldProxy& info); - - // Drills into the nested dicts to access community details; if the second argument is - // non-nullptr then it will be set to the community's pubkey, if it exists. - DictFieldProxy community_field( - const convo::community& og, ustring_view* get_pubkey = nullptr) const; - - public: - /// Removes a one-to-one conversation. Returns true if found and removed, false if not present. - bool erase_1to1(std::string_view pubkey); - - /// Removes a community conversation record. Returns true if found and removed, false if not - /// present. Arguments are the same as `get_community`. - bool erase_community(std::string_view base_url, std::string_view room); - - /// Removes a legacy group conversation. Returns true if found and removed, false if not - /// present. - bool erase_legacy_group(std::string_view pubkey_hex); - - /// Removes a conversation taking the convo::whatever record (rather than the pubkey/url). - bool erase(const convo::one_to_one& c); - bool erase(const convo::community& c); - bool erase(const convo::legacy_group& c); - - bool erase(const convo::any& c); // Variant of any of them - - struct iterator; - - /// This works like erase, but takes an iterator to the conversation to remove. The element is - /// removed and the iterator to the next element after the removed one is returned. This is - /// intended for use where elements are to be removed during iteration: see below for an - /// example. - iterator erase(iterator it); - - /// Returns the number of conversations (of any type). - size_t size() const; - - /// Returns the number of 1-to-1, community, and legacy group conversations, respectively. - size_t size_1to1() const; - size_t size_communities() const; - size_t size_legacy_groups() const; - - /// Returns true if the conversation list is empty. - bool empty() const { return size() == 0; } - - /// Iterators for iterating through all conversations. Typically you access this implicit via a - /// for loop over the `ConvoInfoVolatile` object: - /// - /// for (auto& convo : conversations) { - /// if (auto* dm = std::get_if(&convo)) { - /// // use dm->session_id, dm->last_read, etc. - /// } else if (auto* og = std::get_if(&convo)) { - /// // use og->base_url, og->room, om->last_read, etc. - /// } else if (auto* lcg = std::get_if(&convo)) { - /// // use lcg->id, lcg->last_read - /// } - /// } - /// - /// This iterates through all conversations in sorted order (sorted first by convo type, then by - /// id within the type). - /// - /// It is permitted to modify and add records while iterating (e.g. by modifying one of the - /// `dm`/`og`/`lcg` and then calling set()). - /// - /// If you need to erase the current conversation during iteration then care is required: you - /// need to advance the iterator via the iterator version of erase when erasing an element - /// rather than incrementing it regularly. For example: - /// - /// for (auto it = conversations.begin(); it != conversations.end(); ) { - /// if (should_remove(*it)) - /// it = converations.erase(it); - /// else - /// ++it; - /// } - /// - /// Alternatively, you can use the first version with two loops: the first loop through all - /// converations doesn't erase but just builds a vector of IDs to erase, then the second loops - /// through that vector calling `erase_1to1()`/`erase_community()`/`erase_legacy_group()` for - /// each one. - /// - iterator begin() const { return iterator{data}; } - iterator end() const { return iterator{}; } - - template - struct subtype_iterator; - - /// Returns an iterator that iterates only through one type of conversations - subtype_iterator begin_1to1() const { return {data}; } - subtype_iterator begin_communities() const { return {data}; } - subtype_iterator begin_legacy_groups() const { return {data}; } - - using iterator_category = std::input_iterator_tag; - using value_type = std::variant; - using reference = value_type&; - using pointer = value_type*; - using difference_type = std::ptrdiff_t; - - struct iterator { - protected: - std::shared_ptr _val; - std::optional _it_11, _end_11, _it_lgroup, _end_lgroup; - std::optional _it_comm; - void _load_val(); - iterator() = default; // Constructs an end tombstone - explicit iterator( - const DictFieldRoot& data, - bool oneto1 = true, - bool communities = true, - bool legacy_groups = true); - friend class ConvoInfoVolatile; - - public: - bool operator==(const iterator& other) const; - bool operator!=(const iterator& other) const { return !(*this == other); } - bool done() const; // Equivalent to comparing against the end iterator - convo::any& operator*() const { return *_val; } - convo::any* operator->() const { return _val.get(); } - iterator& operator++(); - iterator operator++(int) { - auto copy{*this}; - ++*this; - return copy; - } - }; - - template - struct subtype_iterator : iterator { - protected: - subtype_iterator(const DictFieldRoot& data) : - iterator( - data, - std::is_same_v, - std::is_same_v, - std::is_same_v) {} - friend class ConvoInfoVolatile; - - public: - ConvoType& operator*() const { return std::get(*_val); } - ConvoType* operator->() const { return &std::get(*_val); } - subtype_iterator& operator++() { - iterator::operator++(); - return *this; - } - subtype_iterator operator++(int) { - auto copy{*this}; - ++*this; - return copy; - } - }; -}; - -} // namespace session::config diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/encrypt.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/encrypt.h deleted file mode 100644 index b29848929..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/encrypt.h +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#include - -/// Wrapper around session::config::encrypt. message and key_base are binary: message has the -/// length provided, key_base must be exactly 32 bytes. domain is a c string. Returns a newly -/// allocated buffer containing the encrypted data, and sets the data's length into -/// `ciphertext_size`. It is the caller's responsibility to `free()` the returned buffer! -/// -/// Returns nullptr on error. -unsigned char* config_encrypt( - const unsigned char* message, - size_t mlen, - const unsigned char* key_base, - const char* domain, - size_t* ciphertext_size); - -/// Works just like config_encrypt, but in reverse. -unsigned char* config_decrypt( - const unsigned char* ciphertext, - size_t clen, - const unsigned char* key_base, - const char* domain, - size_t* plaintext_size); - -/// Returns the amount of padding needed for a plaintext of size s with encryption overhead -/// `overhead`. -size_t config_padded_size(size_t s, size_t overhead); - -#ifdef __cplusplus -} -#endif diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/encrypt.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/encrypt.hpp deleted file mode 100644 index 75c9f9aff..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/encrypt.hpp +++ /dev/null @@ -1,69 +0,0 @@ -#pragma once - -#include - -#include "../types.hpp" - -namespace session::config { - -/// Encrypts a config message using XChaCha20-Poly1305, using a blake2b keyed hash of the message -/// for the nonce (rather than pure random) so that different clients will encrypt the same data to -/// the same encrypted value (thus allowing for server-side deduplication of identical messages). -/// -/// `key_base` must be 32 bytes. This value is a fixed key that all clients that might receive this -/// message can calculate independently (for instance a value derived from a secret key, or a shared -/// random key). This key will be hashed with the message size and domain suffix (see below) to -/// determine the actual encryption key. -/// -/// `domain` is a short string (1-24 chars) used for the keyed hash. Typically this is the type of -/// config, e.g. "closed-group" or "contacts". The full key will be -/// "session-config-encrypted-message-[domain]". This value is also used for the encrypted key (see -/// above). -/// -/// The returned result will consist of encrypted data with authentication tag and appended nonce, -/// suitable for being passed to decrypt() to authenticate and decrypt. -/// -/// Throw std::invalid_argument on bad input (i.e. from invalid key_base or domain). -ustring encrypt(ustring_view message, ustring_view key_base, std::string_view domain); - -/// Same as above, but modifies `message` in place. `message` gets encrypted plus has the extra -/// data and nonce appended. -void encrypt_inplace(ustring& message, ustring_view key_base, std::string_view domain); - -/// Constant amount of extra bytes required to be appended when encrypting. -constexpr size_t ENCRYPT_DATA_OVERHEAD = 40; // ABYTES + NPUBBYTES - -/// Thrown if decrypt() fails. -struct decrypt_error : std::runtime_error { - using std::runtime_error::runtime_error; -}; - -/// Takes a value produced by `encrypt()` and decrypts it. `key_base` and `domain` must be the same -/// given to encrypt or else decryption fails. Upon decryption failure a `decrypt_error` exception -/// is thrown. -ustring decrypt(ustring_view ciphertext, ustring_view key_base, std::string_view domain); - -/// Same as above, but does in in-place. The string gets shortend to the plaintext after this call. -void decrypt_inplace(ustring& ciphertext, ustring_view key_base, std::string_view domain); - -/// Returns the target size of the message with padding, assuming an additional `overhead` bytes of -/// overhead (e.g. from encrypt() overhead) will be appended. Will always return a value >= s + -/// overhead. -/// -/// Padding increments we use: 256 byte increments up to 5120; 1024 byte increments up to 20480, -/// 2048 increments up to 40960, then 5120 from there up. -inline constexpr size_t padded_size(size_t s, size_t overhead = ENCRYPT_DATA_OVERHEAD) { - size_t s2 = s + overhead; - size_t chunk = s2 < 5120 ? 256 : s2 < 20480 ? 1024 : s2 < 40960 ? 2048 : 5120; - return (s2 + chunk - 1) / chunk * chunk - overhead; -} - -/// Inserts null byte padding to the beginning of a message to make the final message size granular. -/// See the above function for the sizes. -/// -/// \param data - the data; this is modified in place. -/// \param overhead - encryption overhead to account for to reach the desired padded size. The -/// default, if omitted, is the space used by the `encrypt()` function defined above. -void pad_message(ustring& data, size_t overhead = ENCRYPT_DATA_OVERHEAD); - -} // namespace session::config diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/error.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/error.h deleted file mode 100644 index 598a1539a..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/error.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -enum config_error { - /// Value returned for no error - SESSION_ERR_NONE = 0, - /// Error indicating that initialization failed because the dumped data being loaded is invalid. - SESSION_ERR_INVALID_DUMP = 1, - /// Error indicated a bad value, e.g. if trying to set something invalid in a config field. - SESSION_ERR_BAD_VALUE = 2, -}; - -// Returns a generic string for a given integer error code as returned by some functions. Depending -// on the call, a more details error string may be available in the config_object's `last_error` -// field. -const char* config_errstr(int err); - -#ifdef __cplusplus -} // extern "C" -#endif diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/expiring.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/expiring.h deleted file mode 100644 index c98653f13..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/expiring.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -typedef enum CONVO_EXPIRATION_MODE { - CONVO_EXPIRATION_NONE = 0, - CONVO_EXPIRATION_AFTER_SEND = 1, - CONVO_EXPIRATION_AFTER_READ = 2, -} CONVO_EXPIRATION_MODE; diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/expiring.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/expiring.hpp deleted file mode 100644 index 4c040b740..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/expiring.hpp +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once -#include - -namespace session::config { - -enum class expiration_mode : int8_t { none = 0, after_send = 1, after_read = 2 }; - -} diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/namespaces.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/namespaces.hpp deleted file mode 100644 index 394617c0c..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/namespaces.hpp +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -#include - -namespace session::config { - -enum class Namespace : std::int16_t { - UserProfile = 2, - Contacts = 3, - ConvoInfoVolatile = 4, - UserGroups = 5, -}; - -} // namespace session::config diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/notify.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/notify.h deleted file mode 100644 index 6dc825bf7..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/notify.h +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once - -typedef enum CONVO_NOTIFY_MODE { - CONVO_NOTIFY_DEFAULT = 0, - CONVO_NOTIFY_ALL = 1, - CONVO_NOTIFY_DISABLED = 2, - CONVO_NOTIFY_MENTIONS_ONLY = 3, -} CONVO_NOTIFY_MODE; diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/notify.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/notify.hpp deleted file mode 100644 index 5de1f5eec..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/notify.hpp +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once - -namespace session::config { - -enum class notify_mode { - defaulted = 0, - all = 1, - disabled = 2, - mentions_only = 3, // Only for groups; for DMs this becomes `all` -}; - -} diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/profile_pic.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/profile_pic.h deleted file mode 100644 index 204c87318..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/profile_pic.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#include - -// Maximum length of the profile pic URL (not including the null terminator) -extern const size_t PROFILE_PIC_MAX_URL_LENGTH; - -typedef struct user_profile_pic { - // Null-terminated C string containing the uploaded URL of the pic. Will be length 0 if there - // is no profile pic. - char url[224]; - // The profile pic decryption key, in bytes. This is a byte buffer of length 32, *not* a - // null-terminated C string. This is only valid when there is a url (i.e. url has strlen > 0). - unsigned char key[32]; -} user_profile_pic; - -#ifdef __cplusplus -} -#endif diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/profile_pic.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/profile_pic.hpp deleted file mode 100644 index d450d074f..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/profile_pic.hpp +++ /dev/null @@ -1,57 +0,0 @@ -#pragma once - -#include - -#include "session/types.hpp" - -namespace session::config { - -// Profile pic info. -struct profile_pic { - static constexpr size_t MAX_URL_LENGTH = 223; - - std::string url; - ustring key; - - static void check_key(ustring_view key) { - if (!(key.empty() || key.size() == 32)) - throw std::invalid_argument{"Invalid profile pic key: 32 bytes required"}; - } - - // Default constructor, makes an empty profile pic - profile_pic() = default; - - // Constructs from a URL and key. Key must be empty or 32 bytes. - profile_pic(std::string_view url, ustring_view key) : url{url}, key{key} { - check_key(this->key); - } - - // Constructs from a string/ustring pair moved into the constructor - profile_pic(std::string&& url, ustring&& key) : url{std::move(url)}, key{std::move(key)} { - check_key(this->key); - } - - // Returns true if either url or key are empty (or invalid) - bool empty() const { return url.empty() || key.size() != 32; } - - // Clears the current url/key, if set. This is just a shortcut for calling `.clear()` on each - // of them. - void clear() { - url.clear(); - key.clear(); - } - - // The object in boolean context is true if url and key are both set, i.e. the opposite of - // `empty()`. - explicit operator bool() const { return !empty(); } - - // Sets and validates the key. The key can be empty, or 32 bytes. This is almost the same as - // just setting `.key` directly, except that it will throw if the provided key is invalid (i.e. - // neither empty nor 32 bytes). - void set_key(ustring new_key) { - check_key(new_key); - key = std::move(new_key); - } -}; - -} // namespace session::config diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_groups.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_groups.h deleted file mode 100644 index 5a2451d5f..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_groups.h +++ /dev/null @@ -1,271 +0,0 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#include "base.h" -#include "notify.h" -#include "util.h" - -// Maximum length of a group name, in bytes -extern const size_t GROUP_NAME_MAX_LENGTH; - -/// Struct holding legacy group info; this struct owns allocated memory and *must* be freed via -/// either `ugroups_legacy_group_free()` or `user_groups_set_free_legacy_group()` when finished with -/// it. -typedef struct ugroups_legacy_group_info { - char session_id[67]; // in hex; 66 hex chars + null terminator. - - char name[101]; // Null-terminated C string (human-readable). Max length is 100 (plus 1 for - // null). Will always be set (even if an empty string). - - bool have_enc_keys; // Will be true if we have an encryption keypair, false if not. - unsigned char enc_pubkey[32]; // If `have_enc_keys`, this is the 32-byte pubkey (no NULL - // terminator). - unsigned char enc_seckey[32]; // If `have_enc_keys`, this is the 32-byte secret key (no NULL - // terminator). - - int64_t disappearing_timer; // Minutes. 0 == disabled. - int priority; // pinned message priority; 0 = unpinned, negative = hidden, positive = pinned - // (with higher meaning pinned higher). - int64_t joined_at; // unix timestamp when joined (or re-joined) - CONVO_NOTIFY_MODE notifications; // When the user wants notifications - int64_t mute_until; // Mute notifications until this timestamp (overrides `notifications` - // setting until the timestamp) - - // For members use the ugroups_legacy_group_members and associated calls. - - void* _internal; // Internal storage, do not touch. -} ugroups_legacy_group_info; - -typedef struct ugroups_community_info { - char base_url[268]; // null-terminated (max length 267), normalized (i.e. always lower-case, - // only has port if non-default, has trailing / removed) - char room[65]; // null-terminated (max length 64); this is case-preserving (i.e. can be - // "SomeRoom" instead of "someroom". Note this is different from volatile - // info (that one is always forced lower-cased). - unsigned char pubkey[32]; // 32 bytes (not terminated, can contain nulls) - - int priority; // pinned message priority; 0 = unpinned, negative = hidden, positive = pinned - // (with higher meaning pinned higher). - int64_t joined_at; // unix timestamp when joined (or re-joined) - CONVO_NOTIFY_MODE notifications; // When the user wants notifications - int64_t mute_until; // Mute notifications until this timestamp (overrides `notifications` - // setting until the timestamp) -} ugroups_community_info; - -int user_groups_init( - config_object** conf, - const unsigned char* ed25519_secretkey, - const unsigned char* dump, - size_t dumplen, - char* error) __attribute__((warn_unused_result)); - -/// Gets community conversation info into `comm`, if the community info was found. `base_url` and -/// `room` are null-terminated c strings; pubkey is 32 bytes. base_url will be -/// normalized/lower-cased; room is case-insensitive for the lookup: note that this may well return -/// a community info with a different room capitalization than the one provided to the call. -/// -/// Returns true if the community was found and `comm` populated; false otherwise. A false return -/// can either be because it didn't exist (`conf->last_error` will be NULL) or because of some error -/// (`last_error` will be set to an error string). -bool user_groups_get_community( - config_object* conf, - ugroups_community_info* comm, - const char* base_url, - const char* room) __attribute__((warn_unused_result)); - -/// Like the above, but if the community was not found, this constructs one that can be inserted. -/// `base_url` will be normalized in the returned object. `room` is a case-insensitive lookup key -/// for the room token. Note that it has subtle handling w.r.t its case: if an existing room is -/// found, you get back a record with the found case (which could differ in case from what you -/// provided). If you want to override to what you provided regardless of what is there you should -/// immediately set the name of the returned object to the case you prefer. If a *new* record is -/// constructed, however, it will match the room token case as given here. -/// -/// Note that this is all different from convo_info_volatile, which always forces the room token to -/// lower-case (because it does not preserve the case). -/// -/// Returns false (and sets `conf->last_error`) on error. -bool user_groups_get_or_construct_community( - config_object* conf, - ugroups_community_info* comm, - const char* base_url, - const char* room, - unsigned const char* pubkey) __attribute__((warn_unused_result)); - -/// Returns a ugroups_legacy_group_info pointer containing the conversation info for a given legacy -/// group ID (specified as a null-terminated hex string), if the conversation exists. If the -/// conversation does not exist, returns NULL. Sets conf->last_error on error. -/// -/// The returned pointer *must* be freed either by calling `ugroups_legacy_group_free()` when done -/// with it, or by passing it to `user_groups_set_free_legacy_group()`. -ugroups_legacy_group_info* user_groups_get_legacy_group(config_object* conf, const char* id) - __attribute__((warn_unused_result)); - -/// Same as the above except that when the conversation does not exist, this sets all the group -/// fields to defaults and loads it with the given id. -/// -/// Returns a ugroups_legacy_group_info as long as it is given a valid legacy group id (i.e. same -/// format as a session id); it will return NULL only if the given id is invalid (and so the caller -/// needs to either pre-validate the id, or post-validate the return value). -/// -/// The returned pointer *must* be freed either by calling `ugroups_legacy_group_free()` when done -/// with it, or by passing it to `user_groups_set_free_legacy_group()`. -/// -/// This is the method that should usually be used to create or update a conversation, followed by -/// setting fields in the group, and then giving it to user_groups_set(). -/// -/// On error, this returns NULL and sets `conf->last_error`. -ugroups_legacy_group_info* user_groups_get_or_construct_legacy_group( - config_object* conf, const char* id) __attribute__((warn_unused_result)); - -/// Properly frees memory associated with a ugroups_legacy_group_info pointer (as returned by -/// get_legacy_group/get_or_construct_legacy_group). -void ugroups_legacy_group_free(ugroups_legacy_group_info* group); - -/// Adds or updates a community conversation from the given group info -void user_groups_set_community(config_object* conf, const ugroups_community_info* group); - -/// Adds or updates a legacy group conversation from the into. This version of the method should -/// only be used when you explicitly want the `group` to remain valid; if the set is the last thing -/// you need to do with it (which is common) it is more efficient to call the freeing version, -/// below. -void user_groups_set_legacy_group(config_object* conf, const ugroups_legacy_group_info* group); - -/// Same as above, except that this also frees the pointer for you, which is commonly what is wanted -/// when updating fields. This is equivalent to, but more efficient than, setting and then freeing. -void user_groups_set_free_legacy_group(config_object* conf, ugroups_legacy_group_info* group); - -/// Erases a conversation from the conversation list. Returns true if the conversation was found -/// and removed, false if the conversation was not present. You must not call this during -/// iteration; see details below. -bool user_groups_erase_community(config_object* conf, const char* base_url, const char* room); -bool user_groups_erase_legacy_group(config_object* conf, const char* group_id); - -typedef struct ugroups_legacy_members_iterator ugroups_legacy_members_iterator; - -/// Group member iteration; this lets you walk through the full group member list. Example usage: -/// -/// const char* session_id; -/// bool admin; -/// ugroups_legacy_members_iterator* it = ugroups_legacy_members_begin(legacy_info); -/// while (ugroups_legacy_members_next(it, &session_id, &admin)) { -/// if (admin) -/// printf("ADMIN: %s", session_id); -/// } -/// ugroups_legacy_members_free(it); -/// -ugroups_legacy_members_iterator* ugroups_legacy_members_begin(ugroups_legacy_group_info* group); -bool ugroups_legacy_members_next( - ugroups_legacy_members_iterator* it, const char** session_id, bool* admin); -void ugroups_legacy_members_free(ugroups_legacy_members_iterator* it); - -/// This erases the group member at the current iteration location during a member iteration, -/// allowing iteration to continue. -/// -/// Example: -/// -/// while (ugroups_legacy_members_next(it, &sid, &admin)) { -/// if (should_remove(sid)) -/// ugroups_legacy_members_erase(it); -/// } -void ugroups_legacy_members_erase(ugroups_legacy_members_iterator* it); - -/// Adds a member (by session id and admin status) to this group. Returns true if the member was -/// inserted or had the admin status changed, false if the member already existed with the given -/// status, or if the session_id is not valid. -bool ugroups_legacy_member_add( - ugroups_legacy_group_info* group, const char* session_id, bool admin); - -/// Removes a member (including admins) from the group given the member's session id. This is not -/// safe to use on the current member during member iteration; for that see the above method -/// instead. Returns true if the session id was found and removed, false if not found. -bool ugroups_legacy_member_remove(ugroups_legacy_group_info* group, const char* session_id); - -/// Accesses the number of members in the group. The overall number is returned (both admins and -/// non-admins); if the given variables are not NULL, they will be populated with the individual -/// counts of members/admins. -size_t ugroups_legacy_members_count( - const ugroups_legacy_group_info* group, size_t* members, size_t* admins); - -/// Returns the number of conversations. -size_t user_groups_size(const config_object* conf); -/// Returns the number of conversations of the specific type. -size_t user_groups_size_communities(const config_object* conf); -size_t user_groups_size_legacy_groups(const config_object* conf); - -/// Functions for iterating through the entire conversation list. Intended use is: -/// -/// ugroups_community_info c2; -/// ugroups_legacy_group_info c3; -/// user_groups_iterator *it = user_groups_iterator_new(my_groups); -/// for (; !user_groups_iterator_done(it); user_groups_iterator_advance(it)) { -/// if (user_groups_it_is_community(it, &c2)) { -/// // use c2.whatever -/// } else if (user_groups_it_is_legacy_group(it, &c3)) { -/// // use c3.whatever -/// } -/// } -/// user_groups_iterator_free(it); -/// -/// It is permitted to modify records (e.g. with a call to one of the `user_groups_set_*` -/// functions) and add records while iterating. -/// -/// If you need to remove while iterating then usage is slightly different: you must advance the -/// iteration by calling either user_groups_iterator_advance if not deleting, or -/// user_groups_iterator_erase to erase and advance. Usage looks like this: -/// -/// ugroups_community_info comm; -/// ugroups_iterator *it = ugroups_iterator_new(my_groups); -/// while (!user_groups_iterator_done(it)) { -/// if (user_groups_it_is_community(it, &comm)) { -/// bool should_delete = /* ... */; -/// if (should_delete) -/// user_groups_iterator_erase(it); -/// else -/// user_groups_iterator_advance(it); -/// } else { -/// user_groups_iterator_advance(it); -/// } -/// } -/// user_groups_iterator_free(it); -/// - -typedef struct user_groups_iterator user_groups_iterator; - -// Starts a new iterator that iterates over all conversations. -user_groups_iterator* user_groups_iterator_new(const config_object* conf); - -// The same as `user_groups_iterator_new` except that this iterates *only* over one type of -// conversation. You still need to use `user_groups_it_is_community` (or the alternatives) -// to load the data in each pass of the loop. (You can, however, safely ignore the bool return -// value of the `it_is_whatever` function: it will always be true for the particular type being -// iterated over). -user_groups_iterator* user_groups_iterator_new_communities(const config_object* conf); -user_groups_iterator* user_groups_iterator_new_legacy_groups(const config_object* conf); - -// Frees an iterator once no longer needed. -void user_groups_iterator_free(user_groups_iterator* it); - -// Returns true if iteration has reached the end. -bool user_groups_iterator_done(user_groups_iterator* it); - -// Advances the iterator. -void user_groups_iterator_advance(user_groups_iterator* it); - -// If the current iterator record is a community conversation this sets the details into `c` and -// returns true. Otherwise it returns false. -bool user_groups_it_is_community(user_groups_iterator* it, ugroups_community_info* c); - -// If the current iterator record is a legacy group conversation this sets the details into -// `c` and returns true. Otherwise it returns false. -bool user_groups_it_is_legacy_group(user_groups_iterator* it, ugroups_legacy_group_info* c); - -// Erases the current group while advancing the iterator to the next group in the iteration. -void user_groups_iterator_erase(config_object* conf, user_groups_iterator* it); - -#ifdef __cplusplus -} // extern "C" -#endif diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_groups.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_groups.hpp deleted file mode 100644 index 9ab69117e..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_groups.hpp +++ /dev/null @@ -1,366 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -#include "base.hpp" -#include "community.hpp" -#include "namespaces.hpp" -#include "notify.hpp" - -extern "C" { -struct ugroups_legacy_group_info; -struct ugroups_community_info; -} - -namespace session::config { - -/// keys used in this config, either currently or in the past (so that we don't reuse): -/// -/// C - dict of legacy groups; within this dict each key is the group pubkey (binary, 33 bytes) and -/// value is a dict containing keys: -/// -/// n - name (string). Always set, even if empty. -/// k - encryption public key (32 bytes). Optional. -/// K - encryption secret key (32 bytes). Optional. -/// m - set of member session ids (each 33 bytes). -/// a - set of admin session ids (each 33 bytes). -/// E - disappearing messages duration, in seconds, > 0. Omitted if disappearing messages is -/// disabled. (Note that legacy groups only support expire after-read) -/// @ - notification setting (int). Omitted = use default setting; 1 = all, 2 = disabled, 3 = -/// mentions-only. -/// ! - mute timestamp: if set then don't show notifications for this contact's messages until -/// this unix timestamp (i.e. overriding the current notification setting until the given -/// time). -/// + - the conversation priority, for pinned/hidden messages. Integer. Omitted means not -/// pinned; -1 means hidden, and a positive value is a pinned message for which higher -/// priority values means the conversation is meant to appear earlier in the pinned -/// conversation list. -/// j - joined at unix timestamp. Omitted if 0. -/// -/// o - dict of communities (AKA open groups); within this dict (which deliberately has the same -/// layout as convo_info_volatile) each key is the SOGS base URL (in canonical form), and value -/// is a dict of: -/// -/// # - server pubkey -/// R - dict of rooms on the server. Each key is the *lower-case* room name; each value is: -/// n - the room name as is commonly used, i.e. with possible capitalization (if -/// appropriate). For instance, a room name SudokuSolvers would be "sudokusolvers" in -/// the outer key, with the capitalization variation in use ("SudokuSolvers") in this -/// key. This key is *always* present (to keep the room dict non-empty). -/// @ - notification setting (see above). -/// ! - mute timestamp (see above). -/// + - the conversation priority, for pinned messages. Omitted means not pinned; -1 means -/// hidden; otherwise an integer value >0, where a higher priority means the -/// conversation is meant to appear earlier in the pinned conversation list. -/// j - joined at unix timestamp. Omitted if 0. -/// -/// c - reserved for future storage of new-style group info. - -/// Common base type with fields shared by all the groups -struct base_group_info { - int priority = 0; // The priority; 0 means unpinned, -1 means hidden, positive means - // pinned higher (i.e. higher priority conversations come first). - int64_t joined_at = 0; // unix timestamp (seconds) when the group was joined (or re-joined) - notify_mode notifications = notify_mode::defaulted; // When the user wants notifications - int64_t mute_until = 0; // unix timestamp (seconds) until which notifications are disabled - - protected: - void load(const dict& info_dict); -}; - -/// Struct containing legacy group info (aka "closed groups"). -struct legacy_group_info : base_group_info { - static constexpr size_t NAME_MAX_LENGTH = 100; // in bytes; name will be truncated if exceeded - - std::string session_id; // The legacy group "session id" (33 bytes). - std::string name; // human-readable; this should normally always be set, but in theory could be - // set to an empty string. - ustring enc_pubkey; // bytes (32 or empty) - ustring enc_seckey; // bytes (32 or empty) - std::chrono::seconds disappearing_timer{0}; // 0 == disabled. - - /// Constructs a new legacy group info from an id (which must look like a session_id). Throws - /// if id is invalid. - explicit legacy_group_info(std::string sid); - - // Accesses the session ids (in hex) of members of this group. The key is the hex session_id; - // the value indicates whether the member is an admin (true) or not (false). - const std::map& members() const { return members_; } - - // Returns a pair of the number of admins, and regular members of this group. (If all you want - // is the overall number just use `.members().size()` instead). - std::pair counts() const; - - // Adds a member (by session id and admin status) to this group. Returns true if the member was - // inserted or changed admin status, false if the member already existed. Throws - // std::invalid_argument if the given session id is invalid. - bool insert(std::string session_id, bool admin); - - // Removes a member (by session id) from this group. Returns true if the member was - // removed, false if the member was not present. - bool erase(const std::string& session_id); - - // Internal ctor/method for C API implementations: - legacy_group_info(const struct ugroups_legacy_group_info& c); // From c struct - legacy_group_info(struct ugroups_legacy_group_info&& c); // From c struct - void into(struct ugroups_legacy_group_info& c) const&; // Copy into c struct - void into(struct ugroups_legacy_group_info& c) &&; // Move into c struct - - private: - // session_id => (is admin) - std::map members_; - - friend class UserGroups; - - // Private implementations of the to/from C struct methods - struct impl_t {}; - static constexpr inline impl_t impl{}; - legacy_group_info(const struct ugroups_legacy_group_info& c, impl_t); - void into(struct ugroups_legacy_group_info& c, impl_t) const; - - void load(const dict& info_dict); -}; - -/// Community (aka open group) info -struct community_info : base_group_info, community { - // Note that *changing* url/room/pubkey and then doing a set inserts a new room under the given - // url/room/pubkey, it does *not* update an existing room. - - // See community_base (comm_base.hpp) for common constructors - using community::community; - - // Internal ctor/method for C API implementations: - community_info(const struct ugroups_community_info& c); // From c struct - void into(ugroups_community_info& c) const; // Into c struct - - private: - void load(const dict& info_dict); - - friend class UserGroups; - friend class comm_iterator_helper; -}; - -using any_group_info = std::variant; - -class UserGroups : public ConfigBase { - - public: - // No default constructor - UserGroups() = delete; - - /// Constructs a user group list from existing data (stored from `dump()`) and the user's - /// secret key for generating the data encryption key. To construct a blank list (i.e. with no - /// pre-existing dumped data to load) pass `std::nullopt` as the second argument. - /// - /// \param ed25519_secretkey - contains the libsodium secret key used to encrypt/decrypt the - /// data when pushing/pulling from the swarm. This can either be the full 64-byte value (which - /// is technically the 32-byte seed followed by the 32-byte pubkey), or just the 32-byte seed of - /// the secret key. - /// - /// \param dumped - either `std::nullopt` to construct a new, empty object; or binary state data - /// that was previously dumped from an instance of this class by calling `dump()`. - UserGroups(ustring_view ed25519_secretkey, std::optional dumped); - - Namespace storage_namespace() const override { return Namespace::UserGroups; } - - const char* encryption_domain() const override { return "UserGroups"; } - - /// Looks up and returns a community (aka open group) conversation. Takes the base URL and room - /// token (case insensitive). Retuns nullopt if the open group was not found, otherwise a - /// filled out `community_info`. Note that the `room` argument here is case-insensitive, but - /// the returned value will be the room as stored in the object (i.e. it may have a different - /// case from the requested `room` value). - std::optional get_community( - std::string_view base_url, std::string_view room) const; - - /// Looks up a community from a full URL. It is permitted for the URL to omit the pubkey (it - /// is not used or needed by this call). - std::optional get_community(std::string_view partial_url) const; - - /// Looks up and returns a legacy group by group ID (hex, looks like a Session ID). Returns - /// nullopt if the group was not found, otherwise returns a filled out `legacy_group_info`. - std::optional get_legacy_group(std::string_view pubkey_hex) const; - - /// Same as `get_community`, except if the community isn't found a new blank one is created for - /// you, prefilled with the url/room/pubkey. - /// - /// Note that `room` and `pubkey` have special handling: - /// - `room` is case-insensitive for the lookup: if a matching room is found then the returned - /// value reflects the room case of the existing record, which is not necessarily the same as - /// the `room` argument given here (to force a case change, set it within the returned - /// object). - /// - `pubkey` is not used to find an existing community, but if the community found has a - /// *different* pubkey from the one given then the returned record has its pubkey updated in - /// the return instance (note that this changed value is not committed to storage, however, - /// until the instance is passed to `set()`). For the string_view version the pubkey is - /// accepted as hex, base32z, or base64. - community_info get_or_construct_community( - std::string_view base_url, - std::string_view room, - std::string_view pubkey_encoded) const; - community_info get_or_construct_community( - std::string_view base_url, std::string_view room, ustring_view pubkey) const; - /// Shortcut to pass the url through community::parse_full_url, then call the above. - community_info get_or_construct_community(std::string_view full_url) const; - - /// Gets or constructs a blank legacy_group_info for the given group id. - legacy_group_info get_or_construct_legacy_group(std::string_view pubkey_hex) const; - - /// Inserts or replaces existing group info. For example, to update the info for a community - /// you would do: - /// - /// auto info = conversations.get_or_construct_community(some_session_id); - /// info.last_read = new_unix_timestamp; - /// conversations.set(info); - /// - void set(const community_info& info); - void set(const legacy_group_info& info); - /// Takes a variant of either group type to set: - void set(const any_group_info& info); - - protected: - // Drills into the nested dicts to access open group details - DictFieldProxy community_field( - const community_info& og, ustring_view* get_pubkey = nullptr) const; - - void set_base(const base_group_info& bg, DictFieldProxy& info) const; - - public: - /// Removes a community group. Returns true if found and removed, false if not present. - /// Arguments are the same as `get_community`. - bool erase_community(std::string_view base_url, std::string_view room); - - /// Removes a legacy group conversation. Returns true if found and removed, false if not - /// present. - bool erase_legacy_group(std::string_view pubkey_hex); - - /// Removes a conversation taking the community_info or legacy_group_info instance (rather than - /// the pubkey/url) for convenience. - bool erase(const community_info& g); - bool erase(const legacy_group_info& c); - bool erase(const any_group_info& info); - - struct iterator; - - /// This works like erase, but takes an iterator to the group to remove. The element is removed - /// and the iterator to the next element after the removed one is returned. This is intended - /// for use where elements are to be removed during iteration: see below for an example. - iterator erase(iterator it); - - /// Returns the number of groups (of any type). - size_t size() const; - - /// Returns the number of communities - size_t size_communities() const; - - /// Returns the number of legacy groups - size_t size_legacy_groups() const; - - /// Returns true if the group list is empty. - bool empty() const { return size() == 0; } - - /// Iterators for iterating through all groups. Typically you access this implicit via a - /// for loop over the `UserGroups` object: - /// - /// for (auto& group : usergroups) { - /// if (auto* comm = std::get_if(&group)) { - /// // use comm->name, comm->priority, etc. - /// } else if (auto* lg = std::get_if(&convo)) { - /// // use lg->session_id, lg->priority, etc. - /// } - /// } - /// - /// This iterates through all groups in sorted order (sorted first by convo type, then by - /// id within the type). - /// - /// It is permitted to modify and add records while iterating (e.g. by modifying one of the - /// `comm`/`lg` objects and then calling set()). - /// - /// If you need to erase the current conversation during iteration then care is required: you - /// need to advance the iterator via the iterator version of erase when erasing an element - /// rather than incrementing it regularly. For example: - /// - /// for (auto it = conversations.begin(); it != conversations.end(); ) { - /// if (should_remove(*it)) - /// it = converations.erase(it); - /// else - /// ++it; - /// } - /// - /// Alternatively, you can use the first version with two loops: the first loop through all - /// converations doesn't erase but just builds a vector of IDs to erase, then the second loops - /// through that vector calling `erase_1to1()`/`erase_open()`/`erase_legacy_group()` for each - /// one. - /// - iterator begin() const { return iterator{data}; } - iterator end() const { return iterator{}; } - - template - struct subtype_iterator; - - /// Returns an iterator that iterates only through one type of conversations. (The regular - /// `.end()` iterator is valid for testing the end of these iterations). - subtype_iterator begin_communities() const { return {data}; } - subtype_iterator begin_legacy_groups() const { return {data}; } - - using iterator_category = std::input_iterator_tag; - using value_type = std::variant; - using reference = value_type&; - using pointer = value_type*; - using difference_type = std::ptrdiff_t; - - struct iterator { - protected: - std::shared_ptr _val; - std::optional _it_comm; - std::optional _it_legacy, _end_legacy; - void _load_val(); - iterator() = default; // Constructs an end tombstone - explicit iterator( - const DictFieldRoot& data, bool communities = true, bool legacy_closed = true); - friend class UserGroups; - - public: - bool operator==(const iterator& other) const; - bool operator!=(const iterator& other) const { return !(*this == other); } - bool done() const; // Equivalent to comparing against the end iterator - any_group_info& operator*() const { return *_val; } - any_group_info* operator->() const { return _val.get(); } - iterator& operator++(); - iterator operator++(int) { - auto copy{*this}; - ++*this; - return copy; - } - }; - - template - struct subtype_iterator : iterator { - protected: - subtype_iterator(const DictFieldRoot& data) : - iterator( - data, - std::is_same_v, - std::is_same_v) {} - friend class UserGroups; - - public: - GroupType& operator*() const { return std::get(*_val); } - GroupType* operator->() const { return &std::get(*_val); } - subtype_iterator& operator++() { - iterator::operator++(); - return *this; - } - subtype_iterator operator++(int) { - auto copy{*this}; - ++*this; - return copy; - } - }; -}; - -} // namespace session::config diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.h deleted file mode 100644 index b7fb66842..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.h +++ /dev/null @@ -1,63 +0,0 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#include "base.h" -#include "profile_pic.h" - -/// Constructs a user profile config object and sets a pointer to it in `conf`. -/// -/// \param ed25519_secretkey must be the 32-byte secret key seed value. (You can also pass the -/// pointer to the beginning of the 64-byte value libsodium calls the "secret key" as the first 32 -/// bytes of that are the seed). This field cannot be null. -/// -/// \param dump - if non-NULL this restores the state from the dumped byte string produced by a past -/// instantiation's call to `dump()`. To construct a new, empty profile this should be NULL. -/// -/// \param dumplen - the length of `dump` when restoring from a dump, or 0 when `dump` is NULL. -/// -/// \param error - the pointer to a buffer in which we will write an error string if an error -/// occurs; error messages are discarded if this is given as NULL. If non-NULL this must be a -/// buffer of at least 256 bytes. -/// -/// Returns 0 on success; returns a non-zero error code and write the exception message as a -/// C-string into `error` (if not NULL) on failure. -/// -/// When done with the object the `config_object` must be destroyed by passing the pointer to -/// config_free() (in `session/config/base.h`). -int user_profile_init( - config_object** conf, - const unsigned char* ed25519_secretkey, - const unsigned char* dump, - size_t dumplen, - char* error) __attribute__((warn_unused_result)); - -/// Returns a pointer to the currently-set name (null-terminated), or NULL if there is no name at -/// all. Should be copied right away as the pointer may not remain valid beyond other API calls. -const char* user_profile_get_name(const config_object* conf); - -/// Sets the user profile name to the null-terminated C string. Returns 0 on success, non-zero on -/// error (and sets the config_object's error string). -int user_profile_set_name(config_object* conf, const char* name); - -// Obtains the current profile pic. The pointers in the returned struct will be NULL if a profile -// pic is not currently set, and otherwise should be copied right away (they will not be valid -// beyond other API calls on this config object). -user_profile_pic user_profile_get_pic(const config_object* conf); - -// Sets a user profile -int user_profile_set_pic(config_object* conf, user_profile_pic pic); - -// Gets the current note-to-self priority level. Will be negative for hidden, 0 for unpinned, and > -// 0 for pinned (with higher value = higher priority). -int user_profile_get_nts_priority(const config_object* conf); - -// Sets the current note-to-self priority level. Set to -1 for hidden; 0 for unpinned, and > 0 for -// higher priority in the conversation list. -void user_profile_set_nts_priority(config_object* conf, int priority); - -#ifdef __cplusplus -} // extern "C" -#endif diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.hpp deleted file mode 100644 index 2ce3ca697..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.hpp +++ /dev/null @@ -1,67 +0,0 @@ -#pragma once - -#include -#include - -#include "base.hpp" -#include "namespaces.hpp" -#include "profile_pic.hpp" - -namespace session::config { - -/// keys used in this config, either currently or in the past (so that we don't reuse): -/// -/// n - user profile name -/// p - user profile url -/// q - user profile decryption key (binary) -/// + - the priority value for the "Note to Self" pseudo-conversation (higher = higher in the -/// conversation list). Omitted when 0. -1 means hidden. - -class UserProfile final : public ConfigBase { - - public: - // No default constructor - UserProfile() = delete; - - /// Constructs a user profile from existing data (stored from `dump()`) and the user's secret - /// key for generating the data encryption key. To construct a blank profile (i.e. with no - /// pre-existing dumped data to load) pass `std::nullopt` as the second argument. - /// - /// \param ed25519_secretkey - contains the libsodium secret key used to encrypt/decrypt the - /// data when pushing/pulling from the swarm. This can either be the full 64-byte value (which - /// is technically the 32-byte seed followed by the 32-byte pubkey), or just the 32-byte seed of - /// the secret key. - /// - /// \param dumped - either `std::nullopt` to construct a new, empty object; or binary state data - /// that was previously dumped from an instance of this class by calling `dump()`. - UserProfile(ustring_view ed25519_secretkey, std::optional dumped); - - Namespace storage_namespace() const override { return Namespace::UserProfile; } - - const char* encryption_domain() const override { return "UserProfile"; } - - /// Returns the user profile name, or std::nullopt if there is no profile name set. - std::optional get_name() const; - - /// Sets the user profile name; if given an empty string then the name is removed. - void set_name(std::string_view new_name); - - /// Gets the user's current profile pic URL and decryption key. The returned object will - /// evaluate as false if the URL and/or key are not set. - profile_pic get_profile_pic() const; - - /// Sets the user's current profile pic to a new URL and decryption key. Clears both if either - /// one is empty. - void set_profile_pic(std::string_view url, ustring_view key); - void set_profile_pic(profile_pic pic); - - /// Gets the Note-to-self conversation priority. Negative means hidden; 0 means unpinned; - /// higher means higher priority (i.e. hidden in the convo list). - int get_nts_priority() const; - - /// Sets the Note-to-self conversation priority. -1 for hidden, 0 for unpinned, higher for - /// pinned higher. - void set_nts_priority(int priority); -}; - -} // namespace session::config diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/util.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/util.h deleted file mode 100644 index 6ae2c890b..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/util.h +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#include - -/// Returns true if session_id has the right form (66 hex digits). This is a quick check, not a -/// robust one: it does not check the leading byte prefix, nor the cryptographic properties of the -/// pubkey for actual validity. -bool session_id_is_valid(const char* session_id); - -#ifdef __cplusplus -} -#endif diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/export.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/export.h deleted file mode 100644 index ab307f6f2..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/export.h +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once - -#if defined(_WIN32) || defined(WIN32) -#define LIBSESSION_EXPORT __declspec(dllexport) -#else -#define LIBSESSION_EXPORT __attribute__((visibility("default"))) -#endif -#define LIBSESSION_C_API extern "C" LIBSESSION_EXPORT diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/fields.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/fields.hpp deleted file mode 100644 index 6ca71a245..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/fields.hpp +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -namespace session { - -using namespace std::literals; - -/// An uploaded file is its URL + decryption key -struct Uploaded { - std::string url; - std::string key; -}; - -/// A conversation disappearing messages setting -struct Disappearing { - /// The possible modes of a disappearing messages setting. - enum class Mode : int { None = 0, AfterSend = 1, AfterRead = 2 }; - - /// The mode itself - Mode mode = Mode::None; - - /// The timer value; this is only used when mode is not None. - std::chrono::seconds timer = 0s; -}; - -/// A Session ID: an x25519 pubkey, with a 05 identifying prefix. On the wire we send just the -/// 32-byte pubkey value (i.e. not hex, without the prefix). -struct SessionID { - /// The fixed session netid, 0x05 - static constexpr unsigned char netid = 0x05; - - /// The raw x25519 pubkey, as bytes - std::array pubkey; - - /// Returns the full pubkey in hex, including the netid prefix. - std::string hex() const; -}; - -} // namespace session diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/types.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/types.hpp deleted file mode 100644 index d63fe470e..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/types.hpp +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -#include -#include -#include - -namespace session { - -using ustring = std::basic_string; -using ustring_view = std::basic_string_view; - -namespace config { - - using seqno_t = std::int64_t; - -} // namespace config - -} // namespace session diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/util.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/util.hpp deleted file mode 100644 index 6cfa77e2b..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/util.hpp +++ /dev/null @@ -1,44 +0,0 @@ -#pragma once -#include "types.hpp" - -namespace session { - -// Helper function to go to/from char pointers to unsigned char pointers: -inline const unsigned char* to_unsigned(const char* x) { - return reinterpret_cast(x); -} -inline unsigned char* to_unsigned(char* x) { - return reinterpret_cast(x); -} -inline const char* from_unsigned(const unsigned char* x) { - return reinterpret_cast(x); -} -inline char* from_unsigned(unsigned char* x) { - return reinterpret_cast(x); -} -// Helper function to switch between string_view and ustring_view -inline ustring_view to_unsigned_sv(std::string_view v) { - return {to_unsigned(v.data()), v.size()}; -} -inline std::string_view from_unsigned_sv(ustring_view v) { - return {from_unsigned(v.data()), v.size()}; -} - -/// Returns true if the first string is equal to the second string, compared case-insensitively. -inline bool string_iequal(std::string_view s1, std::string_view s2) { - return std::equal(s1.begin(), s1.end(), s2.begin(), s2.end(), [](char a, char b) { - return std::tolower(static_cast(a)) == - std::tolower(static_cast(b)); - }); -} - -// C++20 starts_/ends_with backport -inline constexpr bool starts_with(std::string_view str, std::string_view prefix) { - return str.size() >= prefix.size() && str.substr(prefix.size()) == prefix; -} - -inline constexpr bool end_with(std::string_view str, std::string_view suffix) { - return str.size() >= suffix.size() && str.substr(str.size() - suffix.size()) == suffix; -} - -} // namespace session diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/version.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/version.h deleted file mode 100644 index 5574fdecc..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/version.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifdef __cplusplus -extern "C" { -#endif - -#include - -/// libsession-util version triplet (major, minor, patch) -extern const uint16_t LIBSESSION_UTIL_VERSION[3]; - -/// Printable full libsession-util name and version string, such as `libsession-util v0.1.2-release` -/// for a tagged release or `libsession-util v0.1.2-7f144eb5` for an untagged build. -extern const char* LIBSESSION_UTIL_VERSION_FULL; - -/// Just the version component as a string, e.g. `v0.1.2-release`. -extern const char* LIBSESSION_UTIL_VERSION_STR; - -#ifdef __cplusplus -} // extern "C" -#endif diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/xed25519.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/xed25519.h deleted file mode 100644 index 5348dafa0..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/xed25519.h +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -/// XEd25519-signed a message given a curve25519 privkey and message. Writes the 64-byte signature -/// to `sig` on success and returns 0. Returns non-zero on failure. -__attribute__((warn_unused_result)) int session_xed25519_sign( - unsigned char* signature /* 64 byte buffer */, - const unsigned char* curve25519_privkey /* 32 bytes */, - const unsigned char* msg, - const unsigned int msg_len); - -/// Verifies an XEd25519-signed message given a 64-byte signature, 32-byte curve25519 pubkey, and -/// message. Returns 0 if the signature verifies successfully, non-zero on failure. -__attribute__((warn_unused_result)) int session_xed25519_verify( - const unsigned char* signature /* 64 bytes */, - const unsigned char* pubkey /* 32-bytes */, - const unsigned char* msg, - const unsigned int msg_len); - -/// Given a curve25519 pubkey, this writes the associated XEd25519-derived Ed25519 pubkey into -/// ed25519_pubkey. Note, however, that there are *two* possible Ed25519 pubkeys that could result -/// in a given curve25519 pubkey: this always returns the positive value. You can get the other -/// possibility (the negative) by flipping the sign bit, i.e. `returned_pubkey[31] |= 0x80`. -/// Returns 0 on success, non-0 on failure. -__attribute__((warn_unused_result)) int session_xed25519_pubkey( - unsigned char* ed25519_pubkey /* 32-byte output buffer */, - const unsigned char* curve25519_pubkey /* 32 bytes */); - -#ifdef __cplusplus -} -#endif diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/xed25519.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/xed25519.hpp deleted file mode 100644 index 9889113c2..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/xed25519.hpp +++ /dev/null @@ -1,38 +0,0 @@ -#pragma once -#include -#include -#include - -namespace session::xed25519 { - -using ustring_view = std::basic_string_view; - -/// XEd25519-signs a message given the curve25519 privkey and message. -std::array sign( - ustring_view curve25519_privkey /* 32 bytes */, ustring_view msg); - -/// "Softer" version that takes and returns strings of regular chars -std::string sign(std::string_view curve25519_privkey /* 32 bytes */, std::string_view msg); - -/// Verifies a curve25519 message allegedly signed by the given curve25519 pubkey -[[nodiscard]] bool verify( - ustring_view signature /* 64 bytes */, - ustring_view curve25519_pubkey /* 32 bytes */, - ustring_view msg); - -/// "Softer" version that takes strings of regular chars -[[nodiscard]] bool verify( - std::string_view signature /* 64 bytes */, - std::string_view curve25519_pubkey /* 32 bytes */, - std::string_view msg); - -/// Given a curve25519 pubkey, this returns the associated XEd25519-derived Ed25519 pubkey. Note, -/// however, that there are *two* possible Ed25519 pubkeys that could result in a given curve25519 -/// pubkey: this always returns the positive value. You can get the other possibility (the -/// negative) by flipping the sign bit, i.e. `returned_pubkey[31] |= 0x80`. -std::array pubkey(ustring_view curve25519_pubkey); - -/// "Softer" version that takes/returns strings of regular chars -std::string pubkey(std::string_view curve25519_pubkey); - -} // namespace session::xed25519 diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift index cb7a9675e..d2814eeea 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift @@ -8,7 +8,8 @@ import SessionUtilitiesKit extension MessageReceiver { internal static func handleLegacyConfigurationMessage(_ db: Database, message: ConfigurationMessage) throws { - guard !Features.useSharedUtilForUserConfig else { + // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent + guard !SessionUtil.userConfigsEnabled else { TopBannerController.show(warning: .outdatedUserConfig) return } diff --git a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Contacts.swift b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Contacts.swift similarity index 100% rename from SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Contacts.swift rename to SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Contacts.swift diff --git a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift similarity index 99% rename from SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift rename to SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift index 1a834a168..ff4fb4eac 100644 --- a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift +++ b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift @@ -260,7 +260,7 @@ internal extension SessionUtil { } convo_info_volatile_set_community(conf, &community) - case .group: return // TODO: Need to add when the type is added to the lib + case .group: return } } } @@ -419,7 +419,7 @@ public extension SessionUtil { return (convoCommunity.last_read > timestampMs) - case .group: return false // TODO: Need to add when the type is added to the lib + case .group: return false } } .defaulting(to: false) // If we don't have a config then just assume it's unread diff --git a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Shared.swift b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Shared.swift similarity index 97% rename from SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Shared.swift rename to SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Shared.swift index d2d03a8e3..93e494262 100644 --- a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Shared.swift +++ b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Shared.swift @@ -42,11 +42,7 @@ internal extension SessionUtil { change: (UnsafeMutablePointer?) throws -> () ) throws { // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent - guard Features.useSharedUtilForUserConfig else { return } - - // If we haven't completed the required migrations then do nothing (assume that - // this is called from a migration change and we won't miss a change) - guard SessionUtil.requiredMigrationsCompleted(db) else { return } + guard SessionUtil.userConfigsEnabled(db, ignoreRequirementsForRunningMigrations: true) else { return } // Since we are doing direct memory manipulation we are using an `Atomic` // type which has blocking access in it's `mutate` closure @@ -307,7 +303,7 @@ public extension SessionUtil { threadVariant: SessionThread.Variant ) -> Bool { // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent - guard Features.useSharedUtilForUserConfig else { return true } + guard SessionUtil.userConfigsEnabled else { return true } let configVariant: ConfigDump.Variant = { switch threadVariant { diff --git a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserGroups.swift b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+UserGroups.swift similarity index 99% rename from SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserGroups.swift rename to SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+UserGroups.swift index edeb4a472..2958b133c 100644 --- a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserGroups.swift +++ b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+UserGroups.swift @@ -354,7 +354,7 @@ internal extension SessionUtil { } // MARK: -- Handle Group Changes - // TODO: Add this + } fileprivate static func memberInfo(in legacyGroup: UnsafeMutablePointer) -> [String: Bool] { @@ -708,6 +708,7 @@ public extension SessionUtil { static func remove(_ db: Database, groupIds: [String]) throws { guard !groupIds.isEmpty else { return } + } } diff --git a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserProfile.swift b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+UserProfile.swift similarity index 100% rename from SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserProfile.swift rename to SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+UserProfile.swift diff --git a/SessionMessagingKit/LibSessionUtil/Database/QueryInterfaceRequest+Utilities.swift b/SessionMessagingKit/SessionUtil/Database/QueryInterfaceRequest+Utilities.swift similarity index 97% rename from SessionMessagingKit/LibSessionUtil/Database/QueryInterfaceRequest+Utilities.swift rename to SessionMessagingKit/SessionUtil/Database/QueryInterfaceRequest+Utilities.swift index dbfeb8645..a8be039fe 100644 --- a/SessionMessagingKit/LibSessionUtil/Database/QueryInterfaceRequest+Utilities.swift +++ b/SessionMessagingKit/SessionUtil/Database/QueryInterfaceRequest+Utilities.swift @@ -93,7 +93,7 @@ public extension QueryInterfaceRequest where RowDecoder: FetchableRecord & Table // Then check if any of the changes could affect the config guard // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent - Features.useSharedUtilForUserConfig, + SessionUtil.userConfigsEnabled(db, ignoreRequirementsForRunningMigrations: true), SessionUtil.assignmentsRequireConfigUpdate(assignments) else { return updatedData } diff --git a/SessionMessagingKit/LibSessionUtil/SessionUtil.swift b/SessionMessagingKit/SessionUtil/SessionUtil.swift similarity index 90% rename from SessionMessagingKit/LibSessionUtil/SessionUtil.swift rename to SessionMessagingKit/SessionUtil/SessionUtil.swift index d20e50253..16c7cf5a8 100644 --- a/SessionMessagingKit/LibSessionUtil/SessionUtil.swift +++ b/SessionMessagingKit/SessionUtil/SessionUtil.swift @@ -50,7 +50,7 @@ public enum SessionUtil { /// loaded yet (eg. fresh install) public static var needsSync: Bool { // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent - guard Features.useSharedUtilForUserConfig else { return false } + guard SessionUtil.userConfigsEnabled else { return false } return configStore .wrappedValue @@ -63,16 +63,44 @@ public enum SessionUtil { public static var libSessionVersion: String { String(cString: LIBSESSION_UTIL_VERSION_STR) } - private static var hasCompletedRequiredMigrations: Bool = false + private static let requiredMigrationsCompleted: Atomic = Atomic(false) + private static let requiredMigrationIdentifiers: Set = [ + TargetMigrations.Identifier.messagingKit.key(with: _013_SessionUtilChanges.self), + TargetMigrations.Identifier.messagingKit.key(with: _014_GenerateInitialUserConfigDumps.self) + ] - internal static func requiredMigrationsCompleted(_ db: Database) -> Bool { - guard !hasCompletedRequiredMigrations else { return true } + public static var userConfigsEnabled: Bool { + Features.useSharedUtilForUserConfig && + requiredMigrationsCompleted.wrappedValue + } + + internal static func userConfigsEnabled( + _ db: Database, + ignoreRequirementsForRunningMigrations: Bool + ) -> Bool { + // First check if we are enabled regardless of what we want to ignore + guard + Features.useSharedUtilForUserConfig, + !requiredMigrationsCompleted.wrappedValue, + !refreshingUserConfigsEnabled(db), + ignoreRequirementsForRunningMigrations, + let currentlyRunningMigration: (identifier: TargetMigrations.Identifier, migration: Migration.Type) = Storage.shared.currentlyRunningMigration + else { return true } + + let nonIgnoredMigrationIdentifiers: Set = SessionUtil.requiredMigrationIdentifiers + .removing(currentlyRunningMigration.identifier.key(with: currentlyRunningMigration.migration)) return Storage.appliedMigrationIdentifiers(db) - .isSuperset(of: [ - _013_SessionUtilChanges.identifier, - _014_GenerateInitialUserConfigDumps.identifier - ]) + .isSuperset(of: nonIgnoredMigrationIdentifiers) + } + + @discardableResult public static func refreshingUserConfigsEnabled(_ db: Database) -> Bool { + let result: Bool = Storage.appliedMigrationIdentifiers(db) + .isSuperset(of: SessionUtil.requiredMigrationIdentifiers) + + requiredMigrationsCompleted.mutate { $0 = result } + + return result } internal static func lastError(_ conf: UnsafeMutablePointer?) -> String { @@ -86,9 +114,6 @@ public enum SessionUtil { userPublicKey: String, ed25519SecretKey: [UInt8]? ) { - // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent - guard Features.useSharedUtilForUserConfig else { return } - // Ensure we have the ed25519 key and that we haven't already loaded the state before // we continue guard @@ -104,6 +129,9 @@ public enum SessionUtil { return } + // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent + guard SessionUtil.userConfigsEnabled(db, ignoreRequirementsForRunningMigrations: true) else { return } + // Retrieve the existing dumps from the database let existingDumps: Set = ((try? ConfigDump.fetchSet(db)) ?? []) let existingDumpVariants: Set = existingDumps @@ -300,7 +328,7 @@ public enum SessionUtil { public static func configHashes(for publicKey: String) -> [String] { // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent - guard Features.useSharedUtilForUserConfig else { return [] } + guard SessionUtil.userConfigsEnabled else { return [] } return Storage.shared .read { db -> [String] in @@ -347,7 +375,7 @@ public enum SessionUtil { publicKey: String ) throws { // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent - guard Features.useSharedUtilForUserConfig else { return } + guard SessionUtil.userConfigsEnabled else { return } guard !messages.isEmpty else { return } guard !publicKey.isEmpty else { throw MessageReceiverError.noThread } diff --git a/SessionMessagingKit/LibSessionUtil/SessionUtilError.swift b/SessionMessagingKit/SessionUtil/SessionUtilError.swift similarity index 100% rename from SessionMessagingKit/LibSessionUtil/SessionUtilError.swift rename to SessionMessagingKit/SessionUtil/SessionUtilError.swift diff --git a/SessionMessagingKit/LibSessionUtil/Utilities/TypeConversion+Utilities.swift b/SessionMessagingKit/SessionUtil/Utilities/TypeConversion+Utilities.swift similarity index 100% rename from SessionMessagingKit/LibSessionUtil/Utilities/TypeConversion+Utilities.swift rename to SessionMessagingKit/SessionUtil/Utilities/TypeConversion+Utilities.swift diff --git a/SessionMessagingKit/Utilities/AppReadiness.h b/SessionMessagingKit/Utilities/AppReadiness.h index 617892b00..2196cb68c 100755 --- a/SessionMessagingKit/Utilities/AppReadiness.h +++ b/SessionMessagingKit/Utilities/AppReadiness.h @@ -15,7 +15,8 @@ typedef void (^AppReadyBlock)(void); // This method can be called on any thread. + (BOOL)isAppReady; -// This method should only be called on the main thread. +// These methods should only be called on the main thread. ++ (void)invalidate; + (void)setAppIsReady; // If the app is ready, the block is called immediately; diff --git a/SessionMessagingKit/Utilities/AppReadiness.m b/SessionMessagingKit/Utilities/AppReadiness.m index 2d1ef8176..6698a777c 100755 --- a/SessionMessagingKit/Utilities/AppReadiness.m +++ b/SessionMessagingKit/Utilities/AppReadiness.m @@ -103,6 +103,16 @@ NS_ASSUME_NONNULL_BEGIN [self.appDidBecomeReadyBlocks addObject:block]; } ++ (void)invalidate +{ + [self.sharedManager invalidate]; +} + +- (void)invalidate +{ + self.isAppReady = NO; +} + + (void)setAppIsReady { [self.sharedManager setAppIsReady]; diff --git a/SessionMessagingKit/Utilities/Identity+Utilities.swift b/SessionMessagingKit/Utilities/Identity+Utilities.swift new file mode 100644 index 000000000..ca4d6e22b --- /dev/null +++ b/SessionMessagingKit/Utilities/Identity+Utilities.swift @@ -0,0 +1,19 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import GRDB +import SessionUtilitiesKit + +public extension Identity { + /// The user actually exists very early on during the onboarding process but there are also a few cases + /// where we want to know that the user is in a valid state (ie. has completed the proper onboarding + /// process), this value indicates that state + /// + /// One case which can happen is if the app crashed during onboarding the user can be left in an invalid + /// state (ie. with no display name) - the user would be asked to enter one on a subsequent launch to + /// resolve the invalid state + static func userCompletedRequiredOnboarding(_ db: Database? = nil) -> Bool { + Identity.userExists(db) && + !Profile.fetchOrCreateCurrentUser(db).name.isEmpty + } +} diff --git a/SessionMessagingKit/Utilities/ProfileManager.swift b/SessionMessagingKit/Utilities/ProfileManager.swift index 315896a46..14f748f9a 100644 --- a/SessionMessagingKit/Utilities/ProfileManager.swift +++ b/SessionMessagingKit/Utilities/ProfileManager.swift @@ -593,7 +593,7 @@ public struct ProfileManager { ) } // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent - else if !Features.useSharedUtilForUserConfig { + else if !SessionUtil.userConfigsEnabled { // If we have a contact record for the profile (ie. it's a synced profile) then // should should send an updated config message, otherwise we should just update // the local state (the shared util has this logic build in to it's handling) diff --git a/SessionUtilitiesKit/Database/Storage.swift b/SessionUtilitiesKit/Database/Storage.swift index f2095bd52..4993be3f7 100644 --- a/SessionUtilitiesKit/Database/Storage.swift +++ b/SessionUtilitiesKit/Database/Storage.swift @@ -22,9 +22,15 @@ open class Storage { return true } + private let migrationsCompleted: Atomic = Atomic(false) + internal let internalCurrentlyRunningMigration: Atomic<(identifier: TargetMigrations.Identifier, migration: Migration.Type)?> = Atomic(nil) + public static let shared: Storage = Storage() public private(set) var isValid: Bool = false - public private(set) var hasCompletedMigrations: Bool = false + public var hasCompletedMigrations: Bool { migrationsCompleted.wrappedValue } + public var currentlyRunningMigration: (identifier: TargetMigrations.Identifier, migration: Migration.Type)? { + internalCurrentlyRunningMigration.wrappedValue + } public static let defaultPublisherScheduler: ValueObservationScheduler = .async(onQueue: .main) fileprivate var dbWriter: DatabaseWriter? @@ -186,7 +192,7 @@ open class Storage { // Store the logic to run when the migration completes let migrationCompleted: (Swift.Result) -> () = { [weak self] result in - self?.hasCompletedMigrations = true + self?.migrationsCompleted.mutate { $0 = true } self?.migrationProgressUpdater = nil SUKLegacy.clearLegacyDatabaseInstance() @@ -197,6 +203,12 @@ open class Storage { onComplete(result, needsConfigSync) } + // Update the 'migrationsCompleted' state (since we not support running migrations when + // returning from the background it's possible for this flag to transition back to false) + if unperformedMigrations.isEmpty { + self.migrationsCompleted.mutate { $0 = false } + } + // Note: The non-async migration should only be used for unit tests guard async else { do { try self.migrator?.migrate(dbWriter) } @@ -303,7 +315,7 @@ open class Storage { try? SUKLegacy.deleteLegacyDatabaseFilesAndKey() Storage.shared.isValid = false - Storage.shared.hasCompletedMigrations = false + Storage.shared.migrationsCompleted.mutate { $0 = false } Storage.shared.dbWriter = nil self.deleteDatabaseFiles() diff --git a/SessionUtilitiesKit/Database/Types/Migration.swift b/SessionUtilitiesKit/Database/Types/Migration.swift index 761525ea0..b0d87d187 100644 --- a/SessionUtilitiesKit/Database/Types/Migration.swift +++ b/SessionUtilitiesKit/Database/Types/Migration.swift @@ -16,7 +16,12 @@ public extension Migration { static func loggedMigrate(_ targetIdentifier: TargetMigrations.Identifier) -> ((_ db: Database) throws -> ()) { return { (db: Database) in SNLogNotTests("[Migration Info] Starting \(targetIdentifier.key(with: self))") - try migrate(db) + Storage.shared.internalCurrentlyRunningMigration.mutate { $0 = (targetIdentifier, self) } + do { try migrate(db) } + catch { + Storage.shared.internalCurrentlyRunningMigration.mutate { $0 = nil } + throw error + } SNLogNotTests("[Migration Info] Completed \(targetIdentifier.key(with: self))") } } diff --git a/SignalUtilitiesKit/Utilities/AppSetup.swift b/SignalUtilitiesKit/Utilities/AppSetup.swift index 641e7726a..9e89ed175 100644 --- a/SignalUtilitiesKit/Utilities/AppSetup.swift +++ b/SignalUtilitiesKit/Utilities/AppSetup.swift @@ -7,16 +7,16 @@ import SessionUtilitiesKit import SessionUIKit public enum AppSetup { - private static var hasRun: Bool = false + private static let hasRun: Atomic = Atomic(false) public static func setupEnvironment( appSpecificBlock: @escaping () -> (), migrationProgressChanged: ((CGFloat, TimeInterval) -> ())? = nil, migrationsCompletion: @escaping (Result, Bool) -> () ) { - guard !AppSetup.hasRun else { return } + guard !AppSetup.hasRun.wrappedValue else { return } - AppSetup.hasRun = true + AppSetup.hasRun.mutate { $0 = true } var backgroundTask: OWSBackgroundTask? = OWSBackgroundTask(labelStr: #function) @@ -84,6 +84,12 @@ public enum AppSetup { ) } + // Refresh the migration state for 'SessionUtil' so it's logic can start running + // correctly when called (doing this here instead of automatically via the + // `SessionUtil.userConfigsEnabled` property to avoid having to use the correct + // method when calling within a database read/write closure) + Storage.shared.read { db in SessionUtil.refreshingUserConfigsEnabled(db) } + DispatchQueue.main.async { migrationsCompletion(result, (needsConfigSync || SessionUtil.needsSync))