TeraConfigurationStore.swift (28904B)
1 import CryptoKit 2 import Darwin 3 import Foundation 4 import RadrootsKit 5 import Security 6 7 enum TeraAppNetworkProfile: String, Codable, Sendable, Equatable { 8 case publicNetwork = "public" 9 case simulator 10 case device 11 12 var runtimeValue: TeraRuntimeNetworkProfile { 13 switch self { 14 case .publicNetwork: .publicNetwork 15 case .simulator: .simulator 16 case .device: .device 17 } 18 } 19 } 20 21 struct TeraAppConfiguration: Sendable, Equatable { 22 let profile: TeraAppNetworkProfile 23 let writableRelays: [String] 24 let blossom: TeraBlossomEndpointConfiguration? 25 let keychainServicePrefix: String 26 let bundleIdentifier: String 27 let appMetadata: TeraRuntimeAppMetadata 28 let generation: UInt64 29 let activationState: TeraConfigurationActivationState 30 let previousBlossomConfigFingerprint: String? 31 } 32 33 enum TeraConfigurationActivationState: String, Codable, Sendable, Equatable { 34 case current 35 case reconfigurationRequired = "reconfiguration_required" 36 } 37 38 struct TeraConfigurationBootstrap: Sendable, Equatable { 39 let runtimeMode: String 40 let relayURLs: [String] 41 let blossomOrigins: [String] 42 let keychainServicePrefix: String 43 let bundleIdentifier: String 44 let appMetadata: TeraRuntimeAppMetadata 45 } 46 47 struct TeraSourceGeneration: Codable, Sendable, Equatable { 48 let schemaVersion: UInt16 49 let generationHex: String 50 let createdAtUnixMilliseconds: UInt64 51 } 52 53 enum TeraConfigurationError: Error, Sendable, Equatable { 54 case missing(String) 55 case invalid(String) 56 case corruptStoredConfiguration 57 case corruptSourceGeneration 58 case persistenceFailed 59 } 60 61 extension TeraConfigurationError: LocalizedError { 62 var errorDescription: String? { 63 switch self { 64 case .missing: 65 "Required Tera configuration is missing." 66 case .invalid: 67 "Tera network configuration is invalid." 68 case .corruptStoredConfiguration: 69 "Stored Tera settings are corrupt and require recovery." 70 case .corruptSourceGeneration: 71 "Stored Tera local-state identity is corrupt and requires recovery." 72 case .persistenceFailed: 73 "Tera could not persist its local configuration." 74 } 75 } 76 } 77 78 actor TeraConfigurationStore { 79 static let maximumStoredConfigurationBytes = 64 * 1024 80 81 private struct StoredConfigurationV3: Codable, Equatable { 82 static let format = "radroots_ios_configuration_v3" 83 84 let format: String 85 let profile: TeraAppNetworkProfile 86 let writableRelays: [String] 87 let blossom: TeraBlossomEndpointConfiguration? 88 let generation: UInt64? 89 let activationState: TeraConfigurationActivationState? 90 let bootstrapFingerprint: String? 91 let canonicalBlossomConfigFingerprint: String? 92 let previousBlossomConfigFingerprint: String? 93 } 94 95 private struct BootstrapNetworkIdentity: Codable { 96 let profile: TeraAppNetworkProfile 97 let relayURLs: [String] 98 let blossomOrigins: [String] 99 } 100 101 private struct StoredConfigurationV2: Codable { 102 static let format = "radroots_ios_configuration_v2" 103 104 let format: String 105 let profile: TeraAppNetworkProfile 106 let writableRelays: [String] 107 let blossomOrigins: [String] 108 } 109 110 private struct LegacyRelaySettings: Codable { 111 static let format = "radroots_field_ios_relay_settings_v1" 112 113 let format: String 114 let relays: [String] 115 } 116 117 private static let configurationFile = RadrootsFileReference( 118 scope: .data, 119 relativePath: "settings/radroots_configuration_v3.json" 120 ) 121 private static let v2ConfigurationFile = RadrootsFileReference( 122 scope: .data, 123 relativePath: "settings/radroots_configuration_v2.json" 124 ) 125 private static let legacyRelayFile = RadrootsFileReference( 126 scope: .data, 127 relativePath: "settings/relay_settings.json" 128 ) 129 private static let sourceGenerationFile = RadrootsFileReference( 130 scope: .data, 131 relativePath: "state/source_generation_v1.json" 132 ) 133 134 private let bootstrap: TeraConfigurationBootstrap 135 private let fileAccess: RadrootsAppleFileAccess 136 private let clock: TeraClock 137 138 init( 139 bootstrap: TeraConfigurationBootstrap, 140 roots: RadrootsAppleFileRoots, 141 clock: TeraClock = .system 142 ) { 143 self.bootstrap = bootstrap 144 fileAccess = RadrootsAppleFileAccess(roots: roots) 145 self.clock = clock 146 } 147 148 func load() throws -> TeraAppConfiguration { 149 let profile = try Self.profile(for: bootstrap.runtimeMode) 150 let bootstrapFingerprint = try Self.bootstrapFingerprint(bootstrap, profile: profile) 151 let selected: StoredConfigurationV3 152 if let stored = try readStoredConfiguration() { 153 guard stored.format == StoredConfigurationV3.format else { 154 throw TeraConfigurationError.corruptStoredConfiguration 155 } 156 let upgraded = try Self.upgradeStoredConfiguration( 157 stored, 158 bootstrapFingerprint: bootstrapFingerprint 159 ) 160 if upgraded.profile == profile, 161 Self.blossomMatchesProfile(upgraded.blossom, profile: profile), 162 upgraded.bootstrapFingerprint == bootstrapFingerprint 163 { 164 selected = upgraded 165 if selected != stored { 166 try persist(selected) 167 } 168 } else { 169 selected = try Self.bootstrapConfiguration( 170 bootstrap, 171 profile: profile, 172 generation: Self.nextGeneration(after: upgraded.generation ?? 0), 173 activationState: .reconfigurationRequired, 174 bootstrapFingerprint: bootstrapFingerprint, 175 previousBlossomConfigFingerprint: upgraded.canonicalBlossomConfigFingerprint 176 ?? upgraded.previousBlossomConfigFingerprint 177 ) 178 try persist(selected) 179 } 180 } else if let stored = try readV2Configuration() { 181 guard stored.format == StoredConfigurationV2.format else { 182 throw TeraConfigurationError.corruptStoredConfiguration 183 } 184 selected = if stored.profile == profile { 185 StoredConfigurationV3( 186 format: StoredConfigurationV3.format, 187 profile: profile, 188 writableRelays: stored.writableRelays, 189 blossom: Self.blossomConfiguration( 190 origins: stored.blossomOrigins, 191 profile: profile 192 ), 193 generation: 1, 194 activationState: .current, 195 bootstrapFingerprint: bootstrapFingerprint, 196 canonicalBlossomConfigFingerprint: nil, 197 previousBlossomConfigFingerprint: nil 198 ) 199 } else { 200 Self.bootstrapConfiguration( 201 bootstrap, 202 profile: profile, 203 generation: 1, 204 activationState: .reconfigurationRequired, 205 bootstrapFingerprint: bootstrapFingerprint 206 ) 207 } 208 try persist(selected) 209 } else if let legacy = try readLegacyConfiguration() { 210 guard legacy.format == LegacyRelaySettings.format else { 211 throw TeraConfigurationError.corruptStoredConfiguration 212 } 213 selected = StoredConfigurationV3( 214 format: StoredConfigurationV3.format, 215 profile: profile, 216 writableRelays: legacy.relays, 217 blossom: Self.blossomConfiguration( 218 origins: bootstrap.blossomOrigins, 219 profile: profile 220 ), 221 generation: 1, 222 activationState: .current, 223 bootstrapFingerprint: bootstrapFingerprint, 224 canonicalBlossomConfigFingerprint: nil, 225 previousBlossomConfigFingerprint: nil 226 ) 227 try persist(selected) 228 } else { 229 selected = Self.bootstrapConfiguration( 230 bootstrap, 231 profile: profile, 232 generation: 1, 233 activationState: .current, 234 bootstrapFingerprint: bootstrapFingerprint 235 ) 236 try persist(selected) 237 } 238 239 let relays = try TeraNetworkValidator.relays( 240 selected.writableRelays, 241 profile: profile 242 ) 243 guard !bootstrap.keychainServicePrefix.isEmpty, 244 !bootstrap.bundleIdentifier.isEmpty 245 else { 246 throw TeraConfigurationError.missing("identity") 247 } 248 return TeraAppConfiguration( 249 profile: profile, 250 writableRelays: relays, 251 blossom: selected.blossom, 252 keychainServicePrefix: bootstrap.keychainServicePrefix, 253 bundleIdentifier: bootstrap.bundleIdentifier, 254 appMetadata: bootstrap.appMetadata, 255 generation: selected.generation ?? 1, 256 activationState: selected.activationState ?? .current, 257 previousBlossomConfigFingerprint: selected.previousBlossomConfigFingerprint 258 ) 259 } 260 261 func confirmCanonicalBlossomConfiguration( 262 _ canonical: TeraBlossomConfigurationStatus?, 263 expectedGeneration: UInt64 264 ) throws { 265 guard let stored = try readStoredConfiguration(), 266 stored.format == StoredConfigurationV3.format, 267 stored.generation == expectedGeneration, 268 let bootstrapFingerprint = stored.bootstrapFingerprint 269 else { 270 throw TeraConfigurationError.persistenceFailed 271 } 272 let canonicalBlossom: TeraBlossomEndpointConfiguration? 273 let canonicalFingerprint: String? 274 switch (stored.blossom, canonical) { 275 case (nil, nil): 276 canonicalBlossom = nil 277 canonicalFingerprint = nil 278 case let (.some(selected), .some(canonical)): 279 guard let hostKind = TeraBlossomHostKind(rawValue: canonical.hostKind), 280 let endpointAuthority = TeraBlossomEndpointAuthority( 281 runtimeValue: canonical.endpointAuthority 282 ), 283 hostKind == selected.hostKind, 284 endpointAuthority == selected.endpointAuthority, 285 Self.isFingerprint(canonical.configFingerprint) 286 else { 287 throw TeraConfigurationError.invalid("canonical_blossom_configuration") 288 } 289 canonicalBlossom = TeraBlossomEndpointConfiguration( 290 hostKind: hostKind, 291 endpointAuthority: endpointAuthority, 292 primaryOrigin: canonical.primaryOrigin, 293 fallbackOrigins: canonical.fallbackOrigins 294 ) 295 canonicalFingerprint = canonical.configFingerprint 296 default: 297 throw TeraConfigurationError.invalid("canonical_blossom_configuration") 298 } 299 try persist( 300 StoredConfigurationV3( 301 format: StoredConfigurationV3.format, 302 profile: stored.profile, 303 writableRelays: stored.writableRelays, 304 blossom: canonicalBlossom, 305 generation: expectedGeneration, 306 activationState: .current, 307 bootstrapFingerprint: bootstrapFingerprint, 308 canonicalBlossomConfigFingerprint: canonicalFingerprint, 309 previousBlossomConfigFingerprint: nil 310 ) 311 ) 312 } 313 314 func confirmBootstrapActivation(expectedGeneration: UInt64) throws { 315 guard let stored = try readStoredConfiguration(), 316 stored.format == StoredConfigurationV3.format, 317 stored.generation == expectedGeneration, 318 let bootstrapFingerprint = stored.bootstrapFingerprint 319 else { 320 throw TeraConfigurationError.persistenceFailed 321 } 322 try persist( 323 StoredConfigurationV3( 324 format: stored.format, 325 profile: stored.profile, 326 writableRelays: stored.writableRelays, 327 blossom: stored.blossom, 328 generation: expectedGeneration, 329 activationState: .current, 330 bootstrapFingerprint: bootstrapFingerprint, 331 canonicalBlossomConfigFingerprint: nil, 332 previousBlossomConfigFingerprint: nil 333 ) 334 ) 335 } 336 337 func sourceGeneration() throws -> TeraSourceGeneration { 338 if let data = try read(Self.sourceGenerationFile) { 339 guard let value = try? JSONDecoder().decode(TeraSourceGeneration.self, from: data), 340 value.schemaVersion == 1, 341 value.generationHex.count == 64, 342 value.generationHex.allSatisfy(\.isHexDigit), 343 value.generationHex == value.generationHex.lowercased(), 344 value.createdAtUnixMilliseconds > 0 345 else { 346 throw TeraConfigurationError.corruptSourceGeneration 347 } 348 return value 349 } 350 var bytes = [UInt8](repeating: 0, count: 32) 351 guard SecRandomCopyBytes(kSecRandomDefault, bytes.count, &bytes) == errSecSuccess else { 352 throw TeraConfigurationError.persistenceFailed 353 } 354 let createdAtUnixMilliseconds: UInt64 355 do { 356 createdAtUnixMilliseconds = try clock.unixMilliseconds(requirePositive: true) 357 } catch { 358 throw TeraConfigurationError.persistenceFailed 359 } 360 let value = TeraSourceGeneration( 361 schemaVersion: 1, 362 generationHex: bytes.map { String(format: "%02x", $0) }.joined(), 363 createdAtUnixMilliseconds: createdAtUnixMilliseconds 364 ) 365 do { 366 let data = try JSONEncoder.tera.encode(value) 367 try fileAccess.write(.inline(data), to: Self.sourceGenerationFile) 368 return value 369 } catch let error as TeraConfigurationError { 370 throw error 371 } catch { 372 throw TeraConfigurationError.persistenceFailed 373 } 374 } 375 376 private func readStoredConfiguration() throws -> StoredConfigurationV3? { 377 guard let data = try read(Self.configurationFile) else { return nil } 378 guard let stored = try? JSONDecoder().decode(StoredConfigurationV3.self, from: data) else { 379 throw TeraConfigurationError.corruptStoredConfiguration 380 } 381 return stored 382 } 383 384 private func readV2Configuration() throws -> StoredConfigurationV2? { 385 guard let data = try read(Self.v2ConfigurationFile) else { return nil } 386 guard let stored = try? JSONDecoder().decode(StoredConfigurationV2.self, from: data) else { 387 throw TeraConfigurationError.corruptStoredConfiguration 388 } 389 return stored 390 } 391 392 private func readLegacyConfiguration() throws -> LegacyRelaySettings? { 393 guard let data = try read(Self.legacyRelayFile) else { return nil } 394 guard let legacy = try? JSONDecoder().decode(LegacyRelaySettings.self, from: data) else { 395 throw TeraConfigurationError.corruptStoredConfiguration 396 } 397 return legacy 398 } 399 400 private func read(_ file: RadrootsFileReference) throws -> Data? { 401 do { 402 guard case let .inline(data) = try fileAccess.read( 403 file, 404 mode: .inline(maxBytes: Self.maximumStoredConfigurationBytes) 405 ) else { 406 throw TeraConfigurationError.persistenceFailed 407 } 408 return data 409 } catch RadrootsAppleFileError.notFound { 410 return nil 411 } catch let error as TeraConfigurationError { 412 throw error 413 } catch { 414 throw TeraConfigurationError.persistenceFailed 415 } 416 } 417 418 private func persist(_ configuration: StoredConfigurationV3) throws { 419 do { 420 let data = try JSONEncoder.tera.encode(configuration) 421 try fileAccess.write(.inline(data), to: Self.configurationFile) 422 } catch { 423 throw TeraConfigurationError.persistenceFailed 424 } 425 } 426 427 private static func nextGeneration(after generation: UInt64) throws -> UInt64 { 428 do { 429 return try TeraCheckedStateTransition.nextGeneration(after: generation) 430 } catch { 431 throw TeraConfigurationError.corruptStoredConfiguration 432 } 433 } 434 435 private static func profile(for runtimeMode: String) throws -> TeraAppNetworkProfile { 436 switch runtimeMode.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() { 437 case "production": .publicNetwork 438 case "localhost-dev", "simulator": .simulator 439 case "device-development", "device": .device 440 default: throw TeraConfigurationError.invalid("runtime_mode") 441 } 442 } 443 444 private static func bootstrapConfiguration( 445 _ bootstrap: TeraConfigurationBootstrap, 446 profile: TeraAppNetworkProfile, 447 generation: UInt64, 448 activationState: TeraConfigurationActivationState, 449 bootstrapFingerprint: String, 450 previousBlossomConfigFingerprint: String? = nil 451 ) -> StoredConfigurationV3 { 452 StoredConfigurationV3( 453 format: StoredConfigurationV3.format, 454 profile: profile, 455 writableRelays: bootstrap.relayURLs, 456 blossom: blossomConfiguration(origins: bootstrap.blossomOrigins, profile: profile), 457 generation: generation, 458 activationState: activationState, 459 bootstrapFingerprint: bootstrapFingerprint, 460 canonicalBlossomConfigFingerprint: nil, 461 previousBlossomConfigFingerprint: previousBlossomConfigFingerprint 462 ) 463 } 464 465 private static func upgradeStoredConfiguration( 466 _ stored: StoredConfigurationV3, 467 bootstrapFingerprint: String 468 ) throws -> StoredConfigurationV3 { 469 if let generation = stored.generation, 470 let activationState = stored.activationState, 471 let persistedBootstrapFingerprint = stored.bootstrapFingerprint 472 { 473 guard generation > 0, 474 isFingerprint(persistedBootstrapFingerprint), 475 stored.canonicalBlossomConfigFingerprint.map(isFingerprint) ?? true, 476 stored.previousBlossomConfigFingerprint.map(isFingerprint) ?? true 477 else { 478 throw TeraConfigurationError.corruptStoredConfiguration 479 } 480 return StoredConfigurationV3( 481 format: stored.format, 482 profile: stored.profile, 483 writableRelays: stored.writableRelays, 484 blossom: stored.blossom, 485 generation: generation, 486 activationState: activationState, 487 bootstrapFingerprint: persistedBootstrapFingerprint, 488 canonicalBlossomConfigFingerprint: stored.canonicalBlossomConfigFingerprint, 489 previousBlossomConfigFingerprint: stored.previousBlossomConfigFingerprint 490 ) 491 } 492 guard stored.generation == nil, 493 stored.activationState == nil, 494 stored.bootstrapFingerprint == nil, 495 stored.canonicalBlossomConfigFingerprint == nil, 496 stored.previousBlossomConfigFingerprint == nil 497 else { 498 throw TeraConfigurationError.corruptStoredConfiguration 499 } 500 return StoredConfigurationV3( 501 format: stored.format, 502 profile: stored.profile, 503 writableRelays: stored.writableRelays, 504 blossom: stored.blossom, 505 generation: 1, 506 activationState: .current, 507 bootstrapFingerprint: bootstrapFingerprint, 508 canonicalBlossomConfigFingerprint: nil, 509 previousBlossomConfigFingerprint: nil 510 ) 511 } 512 513 private static func bootstrapFingerprint( 514 _ bootstrap: TeraConfigurationBootstrap, 515 profile: TeraAppNetworkProfile 516 ) throws -> String { 517 do { 518 let data = try JSONEncoder.tera.encode( 519 BootstrapNetworkIdentity( 520 profile: profile, 521 relayURLs: bootstrap.relayURLs, 522 blossomOrigins: bootstrap.blossomOrigins 523 ) 524 ) 525 return SHA256.hash(data: data).map { String(format: "%02x", $0) }.joined() 526 } catch { 527 throw TeraConfigurationError.invalid("bootstrap_network_identity") 528 } 529 } 530 531 private static func isFingerprint(_ value: String) -> Bool { 532 value.count == 64 533 && value == value.lowercased() 534 && value.allSatisfy(\.isHexDigit) 535 } 536 537 private static func blossomConfiguration( 538 origins: [String], 539 profile: TeraAppNetworkProfile 540 ) -> TeraBlossomEndpointConfiguration? { 541 guard let primary = origins.first else { return nil } 542 let identity = blossomIdentity(profile: profile) 543 return TeraBlossomEndpointConfiguration( 544 hostKind: identity.hostKind, 545 endpointAuthority: identity.endpointAuthority, 546 primaryOrigin: primary, 547 fallbackOrigins: Array(origins.dropFirst()) 548 ) 549 } 550 551 private static func blossomMatchesProfile( 552 _ configuration: TeraBlossomEndpointConfiguration?, 553 profile: TeraAppNetworkProfile 554 ) -> Bool { 555 guard let configuration else { return true } 556 let identity = blossomIdentity(profile: profile) 557 return configuration.hostKind == identity.hostKind 558 && configuration.endpointAuthority == identity.endpointAuthority 559 } 560 561 private static func blossomIdentity( 562 profile: TeraAppNetworkProfile 563 ) -> ( 564 hostKind: TeraBlossomHostKind, 565 endpointAuthority: TeraBlossomEndpointAuthority 566 ) { 567 switch profile { 568 case .publicNetwork: 569 (.physicalDevice, .publicWebPKI) 570 case .simulator: 571 (.simulator, .loopbackDevelopment) 572 case .device: 573 (.physicalDevice, .privateNetworkDevelopment) 574 } 575 } 576 } 577 578 enum TeraNetworkValidator { 579 static let canonicalRelay = "wss://radroots.org" 580 581 static func relays( 582 _ values: [String], 583 profile: TeraAppNetworkProfile 584 ) throws -> [String] { 585 var output: [String] = [] 586 var seen = Set<String>() 587 for raw in values { 588 let canonical = try relay(raw, profile: profile) 589 if profile != .simulator, canonical == canonicalRelay { 590 continue 591 } 592 guard seen.insert(canonical).inserted else { 593 throw TeraConfigurationError.invalid("duplicate_relay") 594 } 595 output.append(canonical) 596 } 597 if profile != .publicNetwork, output.isEmpty { 598 throw TeraConfigurationError.invalid("empty_relay_set") 599 } 600 let totalCount = output.count + (profile == .simulator ? 0 : 1) 601 guard totalCount <= 64 else { 602 throw TeraConfigurationError.invalid("too_many_relays") 603 } 604 return output 605 } 606 607 private static func relay( 608 _ raw: String, 609 profile: TeraAppNetworkProfile 610 ) throws -> String { 611 guard raw == raw.trimmingCharacters(in: .whitespacesAndNewlines), 612 raw.utf8.count <= 2048, 613 !raw.contains(where: { $0.isASCII && ($0.isWhitespace || $0.asciiValue ?? 32 < 32) }), 614 !raw.contains("?"), 615 !raw.contains("#"), 616 !raw.contains("\\"), 617 let components = URLComponents(string: raw), 618 components.user == nil, 619 components.password == nil, 620 let scheme = components.scheme?.lowercased(), 621 let parsedHost = components.host?.lowercased(), 622 components.port != 0, 623 components.path.isEmpty || components.path == "/" 624 else { 625 throw TeraConfigurationError.invalid("relay_url") 626 } 627 let host = parsedHost.hasPrefix("[") && parsedHost.hasSuffix("]") 628 ? String(parsedHost.dropFirst().dropLast()) 629 : parsedHost 630 let schemeAllowed = scheme == "wss" 631 || (scheme == "ws" && (profile == .simulator || profile == .device)) 632 guard schemeAllowed, hostAllowed(host, profile: profile) else { 633 throw TeraConfigurationError.invalid("relay_policy") 634 } 635 var canonical = "\(scheme)://\(hostForURL(host))" 636 if let port = components.port, 637 !((scheme == "wss" && port == 443) || (scheme == "ws" && port == 80)) 638 { 639 canonical += ":\(port)" 640 } 641 return canonical 642 } 643 644 private static func hostForURL(_ host: String) -> String { 645 host.contains(":") ? "[\(host)]" : host 646 } 647 648 private static func hostAllowed( 649 _ host: String, 650 profile: TeraAppNetworkProfile 651 ) -> Bool { 652 if profile == .simulator { 653 return host == "localhost" || host == "127.0.0.1" || host == "::1" 654 } 655 if host == "localhost" || host.hasSuffix(".localhost") { 656 return false 657 } 658 if let ipv4 = IPv4Address(host) { 659 return profile == .publicNetwork ? ipv4.isPublic 660 : profile == .device ? ipv4.isPrivateLAN : ipv4.isTrustedDevice 661 } 662 if let ipv6 = IPv6Address(host) { 663 return profile == .publicNetwork ? ipv6.isPublic 664 : profile == .device ? ipv6.isPrivateLAN : ipv6.isTrustedDevice 665 } 666 if profile == .device { 667 return false 668 } 669 let normalized = host.trimmingCharacters(in: CharacterSet(charactersIn: ".")) 670 return normalized.contains(".") 671 && !normalized.hasSuffix(".local") 672 && !normalized.hasSuffix(".home.arpa") 673 } 674 } 675 676 private struct IPv4Address { 677 private var address = in_addr() 678 679 init?(_ value: String) { 680 guard inet_pton(AF_INET, value, &address) == 1 else { return nil } 681 } 682 683 var isTrustedDevice: Bool { 684 var address = address 685 let octets = withUnsafeBytes(of: &address.s_addr) { Array($0) } 686 return octets[0] != 0 && octets[0] != 127 && !(224 ... 239).contains(octets[0]) 687 && octets != [255, 255, 255, 255] 688 } 689 690 var isPublic: Bool { 691 var address = address 692 let octets = withUnsafeBytes(of: &address.s_addr) { Array($0) } 693 guard isTrustedDevice else { return false } 694 return !(octets[0] == 10 695 || octets[0] == 169 && octets[1] == 254 696 || octets[0] == 172 && (16 ... 31).contains(octets[1]) 697 || octets[0] == 192 && octets[1] == 168 698 || octets[0] == 100 && (64 ... 127).contains(octets[1]) 699 || octets[0] == 192 && octets[1] == 0 && octets[2] == 0 700 || octets[0] == 192 && octets[1] == 0 && octets[2] == 2 701 || octets[0] == 192 && octets[1] == 88 && octets[2] == 99 702 || octets[0] == 198 && (octets[1] == 18 || octets[1] == 19) 703 || octets[0] == 198 && octets[1] == 51 && octets[2] == 100 704 || octets[0] == 203 && octets[1] == 0 && octets[2] == 113 705 || octets[0] >= 240) 706 } 707 708 var isPrivateLAN: Bool { 709 var address = address 710 let octets = withUnsafeBytes(of: &address.s_addr) { Array($0) } 711 return octets[0] == 10 712 || octets[0] == 172 && (16 ... 31).contains(octets[1]) 713 || octets[0] == 192 && octets[1] == 168 714 } 715 } 716 717 private struct IPv6Address { 718 private var address = in6_addr() 719 720 init?(_ value: String) { 721 guard inet_pton(AF_INET6, value, &address) == 1 else { return nil } 722 } 723 724 var isTrustedDevice: Bool { 725 var address = address 726 let bytes = withUnsafeBytes(of: &address) { Array($0) } 727 let allZero = bytes.allSatisfy { $0 == 0 } 728 let loopback = bytes.dropLast().allSatisfy { $0 == 0 } && bytes.last == 1 729 let multicast = bytes.first == 0xFF 730 return !allZero && !loopback && !multicast 731 } 732 733 var isPublic: Bool { 734 var address = address 735 let bytes = withUnsafeBytes(of: &address) { Array($0) } 736 guard isTrustedDevice, bytes.count == 16 else { return false } 737 let segment0 = UInt16(bytes[0]) << 8 | UInt16(bytes[1]) 738 let segment1 = UInt16(bytes[2]) << 8 | UInt16(bytes[3]) 739 return segment0 & 0xE000 == 0x2000 740 && !(segment0 == 0x2001 && segment1 <= 0x01FF) 741 && !(segment0 == 0x2001 && segment1 == 0x0DB8) 742 && segment0 != 0x2002 743 && !(segment0 == 0x3FFF && segment1 & 0xF000 == 0) 744 } 745 746 var isPrivateLAN: Bool { 747 var address = address 748 let bytes = withUnsafeBytes(of: &address) { Array($0) } 749 return bytes.count == 16 && bytes[0] & 0xFE == 0xFC 750 } 751 } 752 753 private extension JSONEncoder { 754 static var tera: JSONEncoder { 755 let encoder = JSONEncoder() 756 encoder.outputFormatting = [.prettyPrinted, .sortedKeys] 757 return encoder 758 } 759 }