Files
PCA_Phobos/ios/Shared/PhobosProfile.swift

102 lines
4.8 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import Foundation
/// Parsed phobos:// payload from the PCA Phobos panel:
/// [Interface] + [Peer] (WireGuard) + [instance] (wg-obfuscator) + "# pca-tcp = <port>".
struct PhobosProfile {
let raw: String
init(_ raw: String) { self.raw = raw }
/// phobos://<urlsafe base64>#name
static func fromLink(_ link: String) -> (profile: PhobosProfile, name: String)? {
var s = link.trimmingCharacters(in: .whitespacesAndNewlines)
guard s.hasPrefix("phobos://") else { return nil }
s.removeFirst("phobos://".count)
var name = "Phobos"
if let hash = s.firstIndex(of: "#") {
name = String(s[s.index(after: hash)...]).removingPercentEncoding ?? name
s = String(s[..<hash])
}
var b64 = s.replacingOccurrences(of: "-", with: "+").replacingOccurrences(of: "_", with: "/")
while b64.count % 4 != 0 { b64 += "=" }
guard let data = Data(base64Encoded: b64), let text = String(data: data, encoding: .utf8),
text.contains("[Interface]"), text.contains("[instance]") else { return nil }
return (PhobosProfile(text), name)
}
/// Lines of a section, without "= none" placeholders and comments.
func lines(_ header: String) -> [(key: String, line: String)] {
var inSection = false
var out: [(String, String)] = []
for rawLine in raw.split(separator: "\n") {
let line = rawLine.trimmingCharacters(in: .whitespaces)
if line.hasPrefix("[") && line.hasSuffix("]") { inSection = line.lowercased() == header.lowercased(); continue }
guard inSection, !line.hasPrefix("#"), let eq = line.firstIndex(of: "=") else { continue }
let value = line[line.index(after: eq)...].trimmingCharacters(in: .whitespaces)
if value.lowercased() == "none" { continue }
out.append((line[..<eq].trimmingCharacters(in: .whitespaces).lowercased(), line))
}
return out
}
private func value(_ header: String, _ key: String) -> String? {
lines(header).first { $0.key == key }.map { line in
line.line[line.line.index(after: line.line.firstIndex(of: "=")!)...].trimmingCharacters(in: .whitespaces)
}
}
var target: String? { value("[instance]", "target") }
var targetHost: String? { target.map { String($0[..<($0.lastIndex(of: ":") ?? $0.endIndex)]) } }
var targetPort: Int? { target.flatMap { $0.split(separator: ":").last.flatMap { Int($0) } } }
/// "# pca-tcp = 443" written by the panel when the server has the TCP fallback.
var tcpPort: Int? {
for rawLine in raw.split(separator: "\n") {
let line = rawLine.trimmingCharacters(in: .whitespaces)
if line.hasPrefix("#"), line.contains("pca-tcp"), let eq = line.firstIndex(of: "=") {
return Int(line[line.index(after: eq)...].trimmingCharacters(in: .whitespaces))
}
}
return nil
}
/// "# pca-ws = wss://…" — the user's Keenetic (KeenDNS cloud subdomain -> wsgw), channel «Облако».
var wsURL: String? {
for rawLine in raw.split(separator: "\n") {
let line = rawLine.trimmingCharacters(in: .whitespaces)
if line.hasPrefix("#"), line.contains("pca-ws"), let eq = line.firstIndex(of: "=") {
return line[line.index(after: eq)...].trimmingCharacters(in: .whitespaces)
}
}
return nil
}
func obfuscatorConfig(listen: Int, target: String) -> String {
var out = "[instance]\nsource-if = 127.0.0.1\nsource-lport = \(listen)\ntarget = \(target)\n"
for item in lines("[instance]") where !["source-if", "source-lport", "target", "verbose"].contains(item.key) {
out += item.line + "\n"
}
return out + "verbose = INFO\n"
}
func wireGuardConfig(endpoint: String, allowedIPs: String?) -> String {
var out = "[Interface]\n"
for item in lines("[Interface]") where !item.key.hasSuffix("applications") && item.key != "mtu" { out += item.line + "\n" }
// ponytail: obfuscator masking adds bytes; the panel's default 1420 stalls on mobile networks,
// so 1420/missing -> 1280, any other MTU in the link wins (set per client in the panel)
let linkMTU = lines("[Interface]").first { $0.key == "mtu" }
.flatMap { Int($0.line.split(separator: "=").last?.trimmingCharacters(in: .whitespaces) ?? "") }
let mtu = (linkMTU == nil || linkMTU == 1420) ? 1280 : linkMTU!
out += "MTU = \(mtu)\n"
out += "\n[Peer]\n"
for item in lines("[Peer]") {
switch item.key {
case "endpoint": out += "Endpoint = \(endpoint)\n"
case "allowedips" where allowedIPs != nil: out += "AllowedIPs = \(allowedIPs!)\n"
default: out += item.line + "\n"
}
}
return out
}
}