field_ios

In-the-field app for Radroots on iOS
git clone https://radroots.dev/git/field_ios.git
Log | Files | Refs | README | LICENSE

TeraPublicationTargets.swift (4469B)


      1 import Foundation
      2 import TeraKitBindings
      3 
      4 enum TeraPublicationTargetPolicy: Sendable, Equatable {
      5   case any, all, quorum(UInt16), required([String])
      6 }
      7 
      8 struct TeraPublicationTarget: Sendable, Equatable, Identifiable {
      9   let id: String
     10   let endpoint: String
     11   let attempted: Bool
     12   let accepted: Bool
     13   let delivered: Bool
     14   let rejected: Bool
     15   let uncertain: Bool
     16   let readBackObservedAtUnixMilliseconds: UInt64?
     17 
     18   var summary: String {
     19     if delivered {
     20       return "Delivery confirmed by this relay."
     21     }
     22     if accepted {
     23       return "Accepted by this relay."
     24     }
     25     if uncertain {
     26       return "Delivery outcome is uncertain."
     27     }
     28     if rejected {
     29       return "A refusal from this relay is recorded."
     30     }
     31     return attempted ? "Attempt recorded; acceptance is unconfirmed." : "No attempt is recorded for this relay."
     32   }
     33 }
     34 
     35 struct TeraPublicationTargets: Sendable, Equatable {
     36   let requiresDelivery: Bool
     37   let policy: TeraPublicationTargetPolicy
     38   let targets: [TeraPublicationTarget]
     39   let readBackAvailable: Bool
     40   let readBackComplete: Bool
     41 
     42   var policySummary: String {
     43     let level = requiresDelivery ? "delivery confirmation" : "acceptance"
     44     switch policy {
     45     case .any: return "Saved policy: \(level) from any one saved relay."
     46     case .all: return "Saved policy: \(level) from every saved relay."
     47     case let .quorum(count): return "Saved policy: \(level) from \(count) saved relays."
     48     case let .required(ids): return "Saved policy: \(level) from \(ids.count) specifically required relays."
     49     }
     50   }
     51 
     52   func isRequired(_ id: String) -> Bool {
     53     if case let .required(ids) = policy {
     54       return ids.contains(id)
     55     }
     56     return policy == .all
     57   }
     58 
     59   func follows(_ previous: Self) -> Bool {
     60     guard policy == previous.policy, requiresDelivery == previous.requiresDelivery,
     61           targets.map(\.id) == previous.targets.map(\.id) else { return false }
     62     return zip(targets, previous.targets).allSatisfy { current, old in
     63       current.endpoint == old.endpoint && (!old.attempted || current.attempted)
     64         && (!old.accepted || current.accepted) && (!old.delivered || current.delivered)
     65         && (!old.rejected || current.rejected)
     66     }
     67   }
     68 
     69   static func decode(_ value: FfiPublicationTargetDetails) throws -> Self {
     70     guard (1 ... 64).contains(value.targets.count), value.readBackAvailable || !value.readBackComplete
     71     else { throw TeraGeneratedSubmission.mismatch() }
     72     let targets = try value.targets.map { wire in
     73       guard fingerprint(wire.fingerprint), !wire.endpoint.isEmpty, wire.endpoint.utf8.count <= 2048,
     74             !wire.endpoint.unicodeScalars.contains(where: CharacterSet.controlCharacters.contains),
     75             !wire.accepted || wire.attempted, !wire.delivered || wire.accepted,
     76             !wire.rejected || wire.attempted,
     77             wire.readBackObservedAtUnixMs.map({ $0 > 0 && $0 <= UInt64(Int64.max) }) != false,
     78             value.readBackAvailable || wire.readBackObservedAtUnixMs == nil
     79       else { throw TeraGeneratedSubmission.mismatch() }
     80       return TeraPublicationTarget(id: wire.fingerprint, endpoint: wire.endpoint, attempted: wire.attempted,
     81                                    accepted: wire.accepted, delivered: wire.delivered, rejected: wire.rejected,
     82                                    uncertain: wire.uncertain, readBackObservedAtUnixMilliseconds: wire.readBackObservedAtUnixMs)
     83     }
     84     let ids = Set(targets.map(\.id))
     85     guard ids.count == targets.count else { throw TeraGeneratedSubmission.mismatch() }
     86     let policy: TeraPublicationTargetPolicy
     87     switch value.policy {
     88     case .any: policy = .any
     89     case .all: policy = .all
     90     case let .quorum(threshold):
     91       guard threshold > 0, threshold <= targets.count else { throw TeraGeneratedSubmission.mismatch() }
     92       policy = .quorum(threshold)
     93     case let .required(required):
     94       guard !required.isEmpty, required.count <= targets.count, Set(required).count == required.count,
     95             required.allSatisfy(ids.contains) else { throw TeraGeneratedSubmission.mismatch() }
     96       policy = .required(required)
     97     }
     98     return Self(requiresDelivery: value.requiresDelivery, policy: policy, targets: targets,
     99                 readBackAvailable: value.readBackAvailable, readBackComplete: value.readBackComplete)
    100   }
    101 
    102   private static func fingerprint(_ value: String) -> Bool {
    103     value.utf8.count == 64 && value.utf8.allSatisfy { (48 ... 57).contains($0) || (97 ... 102).contains($0) }
    104   }
    105 }