Remote error handling and byte alignment resolved

- All AddressBookClient operations have been updated to return a remote store result
- Undefined Behaviour resolved for byte load operation
- Typos fixed
This commit is contained in:
Lukas Korba 2024-11-15 11:54:29 +01:00
parent 4459ab5211
commit 206ce8262a
7 changed files with 113 additions and 59 deletions

View File

@ -218,8 +218,23 @@ extension AddressBookClient {
return nil
}
return bytes.withUnsafeBytes {
$0.load(as: Int.self).bigEndian
return bytes.withUnsafeBytes { ptr -> Int in
// Check if the pointer is properly aligned for Int
if ptr.baseAddress?.alignedUp(toMultipleOf: MemoryLayout<Int>.alignment) == ptr.baseAddress {
// Memory is already aligned
return ptr.load(as: Int.self).bigEndian
} else {
// Handle unaligned memory
var value: Int = 0
withUnsafeMutableBytes(of: &value) { valuePtr in
// Copy bytes manually to handle unaligned access
for i in 0..<Swift.min(ptr.count, MemoryLayout<Int>.size) {
valuePtr[i] = ptr[i]
}
}
return value.bigEndian
}
}
}

View File

@ -17,8 +17,8 @@ extension DependencyValues {
@DependencyClient
public struct AddressBookClient {
public let allLocalContacts: () throws -> AddressBookContacts
public let syncContacts: (AddressBookContacts?) async throws -> AddressBookContacts
public let storeContact: (Contact) throws -> (contacts: AddressBookContacts, storeResult: StoreResult)
public let deleteContact: (Contact) throws -> AddressBookContacts
public let allLocalContacts: () throws -> (contacts: AddressBookContacts, remoteStoreResult: RemoteStoreResult?)
public let syncContacts: (AddressBookContacts?) async throws -> (contacts: AddressBookContacts, remoteStoreResult: RemoteStoreResult)
public let storeContact: (Contact) throws -> (contacts: AddressBookContacts, remoteStoreResult: RemoteStoreResult)
public let deleteContact: (Contact) throws -> (contacts: AddressBookContacts, remoteStoreResult: RemoteStoreResult?)
}

View File

@ -22,9 +22,10 @@ extension AddressBookClient: DependencyKey {
static let int64Size = MemoryLayout<Int64>.size
}
public enum StoreResult: Equatable {
public enum RemoteStoreResult: Equatable {
case failure
case notAttempted
case success
case remoteFailed
}
public enum AddressBookClientError: Error {
@ -48,10 +49,10 @@ extension AddressBookClient: DependencyKey {
allLocalContacts: {
// return latest known contacts or load ones for the first time
guard latestKnownContacts == nil else {
return latestKnownContacts ?? .empty
return (latestKnownContacts ?? .empty, nil)
}
// contacts haven't been loaded from the locale storage yet, do it
// contacts haven't been loaded from the local storage yet, do it
do {
guard let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
throw AddressBookClientError.documentsFolder
@ -63,7 +64,7 @@ extension AddressBookClient: DependencyKey {
if let contactsData = try? Data(contentsOf: encryptedFileURL) {
let contacts = try AddressBookClient.contactsFrom(encryptedData: contactsData)
// file exists and was successfuly decrypted;
// file exists and was successfully decrypted and parsed;
// try to find the unencrypted file and delete it
let unencryptedFileURL = documentsDirectory.appendingPathComponent(Constants.unencryptedFilename)
if FileManager.default.fileExists(atPath: unencryptedFileURL.path) {
@ -71,20 +72,26 @@ extension AddressBookClient: DependencyKey {
}
latestKnownContacts = contacts
return contacts
return (contacts, nil)
} else {
// Fallback to the unencrypted file check and resolution
let unencryptedFileURL = documentsDirectory.appendingPathComponent(Constants.unencryptedFilename)
if let contactsData = try? Data(contentsOf: unencryptedFileURL) {
// Unencrypted file exists; ensure data are parsed, re-saved as encrypted, and file deteled
let contacts = try AddressBookClient.contactsFrom(contactsData)
// Unencrypted file exists; ensure data are parsed, re-saved as encrypted, and the original file deleted.
var contacts = try AddressBookClient.contactsFrom(contactsData)
// try to encrypt and store the data
var remoteStoreResult: RemoteStoreResult
do {
try AddressBookClient.storeContacts(contacts, remoteStorage: remoteStorage, remoteStore: false)
remoteStoreResult = try AddressBookClient.storeContacts(contacts, remoteStorage: remoteStorage, remoteStore: false)
let result = try syncContacts(contacts: contacts, remoteStorage: remoteStorage, storeAfterSync: true)
remoteStoreResult = result.remoteStoreResult
contacts = result.contacts
} catch {
// the store of the new file failed, skip the file remove
// the store of the new file failed locally, skip the file remove
latestKnownContacts = contacts
throw error
}
@ -92,9 +99,9 @@ extension AddressBookClient: DependencyKey {
try? FileManager.default.removeItem(at: unencryptedFileURL)
latestKnownContacts = contacts
return contacts
return (contacts, remoteStoreResult)
} else {
return .empty
return (.empty, nil)
}
}
} catch {
@ -104,16 +111,17 @@ extension AddressBookClient: DependencyKey {
syncContacts: {
let abContacts = $0 ?? latestKnownContacts ?? AddressBookContacts.empty
let syncedContacts = try syncContacts(contacts: abContacts, remoteStorage: remoteStorage)
let result = try syncContacts(contacts: abContacts, remoteStorage: remoteStorage)
latestKnownContacts = syncedContacts
latestKnownContacts = result.contacts
return syncedContacts
return result
},
storeContact: {
let abContacts = latestKnownContacts ?? AddressBookContacts.empty
var syncedContacts = try syncContacts(contacts: abContacts, remoteStorage: remoteStorage, storeAfterSync: false)
let result = try syncContacts(contacts: abContacts, remoteStorage: remoteStorage, storeAfterSync: false)
var syncedContacts = result.contacts
// if already exists, remove it
if syncedContacts.contacts.contains($0) {
@ -122,31 +130,32 @@ extension AddressBookClient: DependencyKey {
syncedContacts.contacts.append($0)
let storeResult = try storeContacts(syncedContacts, remoteStorage: remoteStorage)
let remoteStoreResult = try storeContacts(syncedContacts, remoteStorage: remoteStorage)
// update the latest known contacts
latestKnownContacts = syncedContacts
return (syncedContacts, storeResult)
return (syncedContacts, remoteStoreResult)
},
deleteContact: {
let abContacts = latestKnownContacts ?? AddressBookContacts.empty
var syncedContacts = try syncContacts(contacts: abContacts, remoteStorage: remoteStorage, storeAfterSync: false)
let result = try syncContacts(contacts: abContacts, remoteStorage: remoteStorage, storeAfterSync: false)
var syncedContacts = result.contacts
// if it doesn't exist, do nothing
guard syncedContacts.contacts.contains($0) else {
return syncedContacts
return (syncedContacts, nil)
}
syncedContacts.contacts.remove($0)
try storeContacts(syncedContacts, remoteStorage: remoteStorage)
let remoteStoreResult = try storeContacts(syncedContacts, remoteStorage: remoteStorage)
// update the latest known contacts
latestKnownContacts = syncedContacts
return syncedContacts
return (syncedContacts, remoteStoreResult)
}
)
}
@ -155,7 +164,7 @@ extension AddressBookClient: DependencyKey {
contacts: AddressBookContacts,
remoteStorage: RemoteStorageClient,
storeAfterSync: Bool = true
) throws -> AddressBookContacts {
) throws -> (contacts: AddressBookContacts, remoteStoreResult: RemoteStoreResult) {
// Ensure remote contacts are prepared
var remoteContacts: AddressBookContacts = .empty
var storeData = true
@ -207,43 +216,49 @@ extension AddressBookClient: DependencyKey {
}
}
var remoteStoreResult = RemoteStoreResult.notAttempted
if storeAfterSync {
try storeContacts(syncedContacts, remoteStorage: remoteStorage, remoteStore: storeData)
remoteStoreResult = try storeContacts(syncedContacts, remoteStorage: remoteStorage, remoteStore: storeData)
}
return syncedContacts
return (syncedContacts, remoteStoreResult)
}
@discardableResult private static func storeContacts(
private static func storeContacts(
_ abContacts: AddressBookContacts,
remoteStorage: RemoteStorageClient,
remoteStore: Bool = true
) throws -> StoreResult {
) throws -> RemoteStoreResult {
// encrypt data
let encryptedContacts = try AddressBookClient.encryptContacts(abContacts)
let filenameForEncryptedFile = try AddressBookClient.filenameForEncryptedFile()
// store encrypted data to the local storage
guard let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
throw AddressBookClientError.documentsFolder
}
let filenameForEncryptedFile = try AddressBookClient.filenameForEncryptedFile()
let fileURL = documentsDirectory.appendingPathComponent(filenameForEncryptedFile)
try encryptedContacts.write(to: fileURL)
var storeResult = StoreResult.success
// store encrypted data to the remote storage
var isRemoteSuccess = remoteStore
if remoteStore {
do {
try remoteStorage.storeAddressBookContacts(encryptedContacts, filenameForEncryptedFile)
} catch {
storeResult = .remoteFailed
isRemoteSuccess = false
}
}
return storeResult
switch (remoteStore, isRemoteSuccess) {
case (true, true): return .success
case (true, false): return .failure
case (false, true): return .notAttempted
case (false, false): return .notAttempted
}
}
private static func filenameForEncryptedFile() throws -> String {

View File

@ -204,13 +204,17 @@ public struct AddressBook {
}
if let contact {
do {
let contacts = try addressBook.deleteContact(contact)
let result = try addressBook.deleteContact(contact)
let abContacts = result.contacts
if let remoteStoreResult = result.remoteStoreResult, remoteStoreResult == .failure {
// TODO: [#1408] error handling https://github.com/Electric-Coin-Company/zashi-ios/issues/1408
}
return .concatenate(
.send(.fetchedABContacts(contacts, false)),
.send(.fetchedABContacts(abContacts, false)),
.send(.updateDestination(nil))
)
} catch {
// TODO: [#1408] erro handling https://github.com/Electric-Coin-Company/zashi-ios/issues/1408
// TODO: [#1408] error handling https://github.com/Electric-Coin-Company/zashi-ios/issues/1408
}
}
return .none
@ -240,15 +244,15 @@ public struct AddressBook {
do {
let result = try addressBook.storeContact(Contact(address: state.address, name: state.name))
let abContacts = result.contacts
if result.storeResult == .remoteFailed {
// TODO: [#1408] erro handling https://github.com/Electric-Coin-Company/zashi-ios/issues/1408
if result.remoteStoreResult == .failure {
// TODO: [#1408] error handling https://github.com/Electric-Coin-Company/zashi-ios/issues/1408
}
return .concatenate(
.send(.fetchedABContacts(abContacts, false)),
.send(.contactStoreSuccess)
)
} catch {
// TODO: [#1408] erro handling https://github.com/Electric-Coin-Company/zashi-ios/issues/1408
// TODO: [#1408] error handling https://github.com/Electric-Coin-Company/zashi-ios/issues/1408
return .send(.updateDestination(nil))
}
@ -261,10 +265,14 @@ public struct AddressBook {
case .fetchABContactsRequested:
do {
let abContacts = try addressBook.allLocalContacts()
let result = try addressBook.allLocalContacts()
let abContacts = result.contacts
if let remoteStoreResult = result.remoteStoreResult, remoteStoreResult == .failure {
// TODO: [#1408] error handling https://github.com/Electric-Coin-Company/zashi-ios/issues/1408
}
return .send(.fetchedABContacts(abContacts, true))
} catch {
// TODO: [#1408] erro handling https://github.com/Electric-Coin-Company/zashi-ios/issues/1408
// TODO: [#1408] error handling https://github.com/Electric-Coin-Company/zashi-ios/issues/1408
return .none
}
@ -273,10 +281,14 @@ public struct AddressBook {
if requestToSync {
return .run { send in
do {
let syncedContacts = try await addressBook.syncContacts(abContacts)
let result = try await addressBook.syncContacts(abContacts)
let syncedContacts = result.contacts
if result.remoteStoreResult == .failure {
// TODO: [#1408] error handling https://github.com/Electric-Coin-Company/zashi-ios/issues/1408
}
await send(.fetchedABContacts(syncedContacts, false))
} catch {
// TODO: [#1408] erro handling https://github.com/Electric-Coin-Company/zashi-ios/issues/1408
// TODO: [#1408] error handling https://github.com/Electric-Coin-Company/zashi-ios/issues/1408
}
}
} else {

View File

@ -179,10 +179,14 @@ public struct SendConfirmation {
state.canSendMail = MFMailComposeViewController.canSendMail()
state.alias = nil
do {
let abContacts = try addressBook.allLocalContacts()
let result = try addressBook.allLocalContacts()
let abContacts = result.contacts
if let remoteStoreResult = result.remoteStoreResult, remoteStoreResult == .failure {
// TODO: [#1408] error handling https://github.com/Electric-Coin-Company/zashi-ios/issues/1408
}
return .send(.fetchedABContacts(abContacts))
} catch {
// TODO: [#1408] erro handling https://github.com/Electric-Coin-Company/zashi-ios/issues/1408
// TODO: [#1408] error handling https://github.com/Electric-Coin-Company/zashi-ios/issues/1408
return .none
}

View File

@ -247,13 +247,17 @@ public struct SendFlow {
case .onAppear:
state.memoState.charLimit = zcashSDKEnvironment.memoCharLimit
do {
let abContacts = try addressBook.allLocalContacts()
let result = try addressBook.allLocalContacts()
let abContacts = result.contacts
if let remoteStoreResult = result.remoteStoreResult, remoteStoreResult == .failure {
// TODO: [#1408] error handling https://github.com/Electric-Coin-Company/zashi-ios/issues/1408
}
return .merge(
.send(.exchangeRateSetupChanged),
.send(.fetchedABContacts(abContacts))
)
} catch {
// TODO: [#1408] erro handling https://github.com/Electric-Coin-Company/zashi-ios/issues/1408
// TODO: [#1408] error handling https://github.com/Electric-Coin-Company/zashi-ios/issues/1408
return .send(.exchangeRateSetupChanged)
}

View File

@ -72,10 +72,14 @@ public struct TransactionList {
case .onAppear:
state.requiredTransactionConfirmations = zcashSDKEnvironment.requiredTransactionConfirmations
do {
let abContacts = try addressBook.allLocalContacts()
let result = try addressBook.allLocalContacts()
let abContacts = result.contacts
if let remoteStoreResult = result.remoteStoreResult, remoteStoreResult == .failure {
// TODO: [#1408] error handling https://github.com/Electric-Coin-Company/zashi-ios/issues/1408
}
state.addressBookContacts = abContacts
} catch {
// TODO: [#1408] erro handling https://github.com/Electric-Coin-Company/zashi-ios/issues/1408
// TODO: [#1408] error handling https://github.com/Electric-Coin-Company/zashi-ios/issues/1408
}
return .merge(