diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 71d3a6fe3..2daeba859 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -305,6 +305,7 @@ B8FF8DAF25C0D00F004D1F22 /* SessionUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A679255388CC00C340D1 /* SessionUtilitiesKit.framework */; }; B8FF8E6225C10DA5004D1F22 /* GeoLite2-Country-Blocks-IPv4 in Resources */ = {isa = PBXBuildFile; fileRef = B8FF8E6125C10DA5004D1F22 /* GeoLite2-Country-Blocks-IPv4 */; }; B8FF8E7425C10FC3004D1F22 /* GeoLite2-Country-Locations-English in Resources */ = {isa = PBXBuildFile; fileRef = B8FF8E7325C10FC3004D1F22 /* GeoLite2-Country-Locations-English */; }; + B8FF8EA625C11FEF004D1F22 /* IPv4.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8FF8EA525C11FEF004D1F22 /* IPv4.swift */; }; B90418E6183E9DD40038554A /* DateUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = B90418E5183E9DD40038554A /* DateUtil.m */; }; B9EB5ABD1884C002007CBB57 /* MessageUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B9EB5ABC1884C002007CBB57 /* MessageUI.framework */; }; C300A5B22554AF9800555489 /* VisibleMessage+Profile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C300A5B12554AF9800555489 /* VisibleMessage+Profile.swift */; }; @@ -1347,6 +1348,7 @@ B8D8F1EF256621180092EF10 /* MessageSender+Convenience.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "MessageSender+Convenience.swift"; path = "../../SignalUtilitiesKit/Messaging/Sending & Receiving/MessageSender+Convenience.swift"; sourceTree = ""; }; B8FF8E6125C10DA5004D1F22 /* GeoLite2-Country-Blocks-IPv4 */ = {isa = PBXFileReference; lastKnownFileType = file.bplist; name = "GeoLite2-Country-Blocks-IPv4"; path = "Countries/GeoLite2-Country-Blocks-IPv4"; sourceTree = ""; }; B8FF8E7325C10FC3004D1F22 /* GeoLite2-Country-Locations-English */ = {isa = PBXFileReference; lastKnownFileType = file.bplist; name = "GeoLite2-Country-Locations-English"; path = "Countries/GeoLite2-Country-Locations-English"; sourceTree = ""; }; + B8FF8EA525C11FEF004D1F22 /* IPv4.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IPv4.swift; sourceTree = ""; }; B90418E4183E9DD40038554A /* DateUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DateUtil.h; sourceTree = ""; }; B90418E5183E9DD40038554A /* DateUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DateUtil.m; sourceTree = ""; }; B9EB5ABC1884C002007CBB57 /* MessageUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MessageUI.framework; path = System/Library/Frameworks/MessageUI.framework; sourceTree = SDKROOT; }; @@ -2264,6 +2266,7 @@ children = ( C33FDB68255A580F00E217F9 /* ContentProxy.swift */, C3C2A5BC255385EE00C340D1 /* HTTP.swift */, + B8FF8EA525C11FEF004D1F22 /* IPv4.swift */, C3C2A5D92553860B00C340D1 /* JSON.swift */, C33FDAF2255A580500E217F9 /* ProxiedContentDownloader.swift */, C352A3A42557B5F000338F3E /* TSRequest.h */, @@ -4838,6 +4841,7 @@ B8856D23256F116B001CE70E /* Weak.swift in Sources */, C32C5A48256DB8F0003C73A2 /* BuildConfiguration.swift in Sources */, C300A60D2554B31900555489 /* Logging.swift in Sources */, + B8FF8EA625C11FEF004D1F22 /* IPv4.swift in Sources */, C3D9E35525675EE10040E4F3 /* MIMETypeUtil.m in Sources */, C3A7219A2558C1660043A11F /* AnyPromise+Conversion.swift in Sources */, C300A6322554B6D100555489 /* NSDate+Timestamp.mm in Sources */, diff --git a/Session/Meta/Countries/GeoLite2-Country-Blocks-IPv4 b/Session/Meta/Countries/GeoLite2-Country-Blocks-IPv4 index 6a2d66cce..fa1eece06 100644 Binary files a/Session/Meta/Countries/GeoLite2-Country-Blocks-IPv4 and b/Session/Meta/Countries/GeoLite2-Country-Blocks-IPv4 differ diff --git a/Session/Utilities/IP2Country.swift b/Session/Utilities/IP2Country.swift index da8a52836..c7ca65a23 100644 --- a/Session/Utilities/IP2Country.swift +++ b/Session/Utilities/IP2Country.swift @@ -2,10 +2,18 @@ final class IP2Country { var countryNamesCache: [String:String] = [:] - private lazy var ipv4Table: [String:[String]] = { + private static let workQueue = DispatchQueue(label: "IP2Country.workQueue", qos: .utility) // It's important that this is a serial queue + static var isInitialized = false + + // MARK: Tables + /// This table has two columns: the "network" column and the "registered_country_geoname_id" column. The network column contains the **lower** bound of an IP + /// range and the "registered_country_geoname_id" column contains the ID of the country corresponding to that range. We look up an IP by finding the first index in the + /// network column where the value is greater than the IP we're looking up (converted to an integer). The IP we're looking up must then be in the range **before** that + /// range. + private lazy var ipv4Table: [String:[Int]] = { let url = Bundle.main.url(forResource: "GeoLite2-Country-Blocks-IPv4", withExtension: nil)! let data = try! Data(contentsOf: url) - return try! NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as! [String:[String]] + return try! NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as! [String:[Int]] }() private lazy var countryNamesTable: [String:[String]] = { @@ -14,10 +22,6 @@ final class IP2Country { return try! NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as! [String:[String]] }() - private static let workQueue = DispatchQueue(label: "IP2Country.workQueue", qos: .utility) // It's important that this is a serial queue - - static var isInitialized = false - // MARK: Lifecycle static let shared = IP2Country() @@ -28,29 +32,17 @@ final class IP2Country { deinit { NotificationCenter.default.removeObserver(self) } - + // MARK: Implementation private func cacheCountry(for ip: String) -> String { - var truncatedIP = ip - func getCountryInternal() -> String { - if let country = countryNamesCache[ip] { return country } - if let ipv4TableIndex = ipv4Table["network"]!.firstIndex(where: { $0.starts(with: truncatedIP) }) { - let countryID = ipv4Table["registered_country_geoname_id"]![ipv4TableIndex] - if let countryNamesTableIndex = countryNamesTable["geoname_id"]!.firstIndex(of: countryID) { - let country = countryNamesTable["country_name"]![countryNamesTableIndex] - countryNamesCache[ip] = country - return country - } - } - if truncatedIP.contains(".") && !truncatedIP.hasSuffix(".") { // The fuzziest we want to go is xxx.x - truncatedIP.removeLast() - if truncatedIP.hasSuffix(".") { truncatedIP.removeLast() } - return getCountryInternal() - } else { - return "Unknown Country" - } - } - return getCountryInternal() + if let result = countryNamesCache[ip] { return result } + let ipAsInt = IPv4.toInt(ip) + guard let ipv4TableIndex = given(ipv4Table["network"]!.firstIndex(where: { $0 > ipAsInt }), { $0 - 1 }) else { return "Unknown Country" } // Relies on the array being sorted + let countryID = ipv4Table["registered_country_geoname_id"]![ipv4TableIndex] + guard let countryNamesTableIndex = countryNamesTable["geoname_id"]!.firstIndex(of: String(countryID)) else { return "Unknown Country" } + let result = countryNamesTable["country_name"]![countryNamesTableIndex] + countryNamesCache[ip] = result + return result } @objc func populateCacheIfNeededAsync() { diff --git a/SessionUtilitiesKit/Networking/IPv4.swift b/SessionUtilitiesKit/Networking/IPv4.swift new file mode 100644 index 000000000..c3f6ffe18 --- /dev/null +++ b/SessionUtilitiesKit/Networking/IPv4.swift @@ -0,0 +1,12 @@ + +public enum IPv4 { + + public static func toInt(_ ip: String) -> Int { + let octets: [Int] = ip.split(separator: ".").map { Int($0)! } + var result: Int = 0 + for i in stride(from: 3, through: 0, by: -1) { + result += octets[ 3 - i ] << (i * 8) + } + return result + } +}