diff --git a/Documentation/FUTURE_PLANS.md b/Documentation/FUTURE_PLANS.md index 772e037..f8c14d5 100644 --- a/Documentation/FUTURE_PLANS.md +++ b/Documentation/FUTURE_PLANS.md @@ -6,19 +6,6 @@ sound like to plan for a breaking change befor the first initial version's release, but hey, there's a time pressure on my shoulder right now + MaxMind's "binary format" "desgined" by its script kiddos slowed things down baadly._ -## Replace CIDR Strings with proper intelligence - -[Wikipedia](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing) - -At the time of writing I just had to port the dumbass php style "String -representation" of networks. That's bad. That's so bad, that normally I wouldn't -permit such a crap in my company. - -What I want is to create a unit (most probably it'll be a struct?) that not only -represents the network, but also provides utilities that can check whether an -`IpAddress` belongs to that network for example. Should be representable in many -different forms, etc. - ## Error handling There used to be an original concept of how to do proper error handling & @@ -41,4 +28,15 @@ For example if you get **128bit unsigned int index pointers**, you'll face quite bit of fun. I'll do my best to massage things out. Once the public API is stable, and neat, -the underlying crap can be cleaned up. \ No newline at end of file +the underlying crap can be cleaned up. + +## Redesign the retarded modeling + +At the library's initial stage the decision was to reproduce in a somewhat +similar state what MaxMind's php scritpers made with their PHP, Java, and C# +libraries. + +That modeling is awful. + +The way the API will return models will definitely change, so there will be a +2.0.0 API break at some point. \ No newline at end of file diff --git a/README.md b/README.md index 0977c3b..1657c24 100644 --- a/README.md +++ b/README.md @@ -1,11 +1 @@ # GeoIP2-swift - -**HEAVILY WORK IN PROGRESS.** - -Progress postponed for a few days. -In its current state it mostly works, but is unacceptably slow to initialize. -The lookup time is fast, but it's an unacceptable speed to initialize. - -Initialization performance problems root mainly from the poor DB design. - -**Will revisit after 8th of July.** diff --git a/Sources/Api/Model/CityModel.swift b/Sources/Api/Model/CityModel.swift index 591767a..3e977ba 100644 --- a/Sources/Api/Model/CityModel.swift +++ b/Sources/Api/Model/CityModel.swift @@ -6,6 +6,12 @@ public struct CityModel: Equatable { public let location: LocationRecord public let postal: PostalRecord public let subdivisions: [SubdivisionRecord] + public let continent: ContinentRecord + public let country: CountryRecord + public let maxmind: MaxMindRecord + public let registeredCountry: CountryRecord + public let representedCountry: RepresentedCountryRecord + public let traits: TraitsRecord public let ipAddress: IpAddress public let netmask: IpAddress public var mostSpecificSubdivision: SubdivisionRecord { get { return subdivisions.last ?? SubdivisionRecord(nil) } } @@ -20,6 +26,12 @@ extension CityModel: DictionaryInitialisableModel { subdivisions: (dictionary?["subdivisions"]?.unwrap() ?? [] as [Payload]).compactMap( { SubdivisionRecord($0.unwrap()) } ), + continent: ContinentRecord(dictionary?["continent"]?.unwrap()), + country: CountryRecord(dictionary?["country"]?.unwrap()), + maxmind: MaxMindRecord(dictionary?["maxmind"]?.unwrap()), + registeredCountry: CountryRecord(dictionary?["registered_country"]?.unwrap()), + representedCountry: RepresentedCountryRecord(dictionary?["represented_country"]?.unwrap()), + traits: TraitsRecord(dictionary?["traits"]?.unwrap()), ipAddress: ip, netmask: netmask ) diff --git a/Sources/Api/Model/EnterpriseModel.swift b/Sources/Api/Model/EnterpriseModel.swift index cced9ef..408b001 100644 --- a/Sources/Api/Model/EnterpriseModel.swift +++ b/Sources/Api/Model/EnterpriseModel.swift @@ -1,9 +1,39 @@ import Foundation +import enum Decoder.Payload -// TODO : Untangle php scripter retardnes. Theses people at maxmind shouldn't write code that gets executed by a processor. Honestly. -// See stupid fucking php scripter stupidity : https://github.com/maxmind/GeoIP2-php/blob/master/src/Model/Enterprise.php -// Apparently they are so dumb, they needed 2 people to produce an empty class, with a nonsense API doc'. -// Their Java, and C# code is as retaarded as the php one. I never saw throughout my career Java being raped so badly. public struct EnterpriseModel: Equatable { - + public let city: CityRecord + public let location: LocationRecord + public let postal: PostalRecord + public let subdivisions: [SubdivisionRecord] + public let continent: ContinentRecord + public let country: CountryRecord + public let maxmind: MaxMindRecord + public let registeredCountry: CountryRecord + public let representedCountry: RepresentedCountryRecord + public let traits: TraitsRecord + public let ipAddress: IpAddress + public let netmask: IpAddress + public var mostSpecificSubdivision: SubdivisionRecord { get { return subdivisions.last ?? SubdivisionRecord(nil) } } } + +extension EnterpriseModel: DictionaryInitialisableModel { + public init(ip: IpAddress, netmask: IpAddress, _ dictionary: [String: Payload]?) { + self.init( + city: CityRecord(dictionary?["city"]?.unwrap()), + location: LocationRecord(dictionary?["location"]?.unwrap()), + postal: PostalRecord(dictionary?["postal"]?.unwrap()), + subdivisions: (dictionary?["subdivisions"]?.unwrap() ?? [] as [Payload]).compactMap( + { SubdivisionRecord($0.unwrap()) } + ), + continent: ContinentRecord(dictionary?["continent"]?.unwrap()), + country: CountryRecord(dictionary?["country"]?.unwrap()), + maxmind: MaxMindRecord(dictionary?["maxmind"]?.unwrap()), + registeredCountry: CountryRecord(dictionary?["registered_country"]?.unwrap()), + representedCountry: RepresentedCountryRecord(dictionary?["represented_country"]?.unwrap()), + traits: TraitsRecord(dictionary?["traits"]?.unwrap()), + ipAddress: ip, + netmask: netmask + ) + } +} \ No newline at end of file diff --git a/Sources/Api/Model/InsightsModel.swift b/Sources/Api/Model/InsightsModel.swift index e4f9779..0b3011a 100644 --- a/Sources/Api/Model/InsightsModel.swift +++ b/Sources/Api/Model/InsightsModel.swift @@ -1,9 +1,39 @@ import Foundation +import enum Decoder.Payload -// TODO : Untangle php scripter retardnes. Theses people at maxmind shouldn't write code that gets executed by a processor. Honestly. -// See stupid fucking php scripter stupidity : https://github.com/maxmind/GeoIP2-php/blob/master/src/Model/Insights.php -// Apparently they are so dumb, they needed 2 people to produce an empty class, with a nonsense API doc'. -// Their Java, and C# code is as retaarded as the php one. I never saw throughout my career Java being raped so badly. public struct InsightsModel: Equatable { - + public let city: CityRecord + public let location: LocationRecord + public let postal: PostalRecord + public let subdivisions: [SubdivisionRecord] + public let continent: ContinentRecord + public let country: CountryRecord + public let maxmind: MaxMindRecord + public let registeredCountry: CountryRecord + public let representedCountry: RepresentedCountryRecord + public let traits: TraitsRecord + public let ipAddress: IpAddress + public let netmask: IpAddress + public var mostSpecificSubdivision: SubdivisionRecord { get { return subdivisions.last ?? SubdivisionRecord(nil) } } } + +extension InsightsModel: DictionaryInitialisableModel { + public init(ip: IpAddress, netmask: IpAddress, _ dictionary: [String: Payload]?) { + self.init( + city: CityRecord(dictionary?["city"]?.unwrap()), + location: LocationRecord(dictionary?["location"]?.unwrap()), + postal: PostalRecord(dictionary?["postal"]?.unwrap()), + subdivisions: (dictionary?["subdivisions"]?.unwrap() ?? [] as [Payload]).compactMap( + { SubdivisionRecord($0.unwrap()) } + ), + continent: ContinentRecord(dictionary?["continent"]?.unwrap()), + country: CountryRecord(dictionary?["country"]?.unwrap()), + maxmind: MaxMindRecord(dictionary?["maxmind"]?.unwrap()), + registeredCountry: CountryRecord(dictionary?["registered_country"]?.unwrap()), + representedCountry: RepresentedCountryRecord(dictionary?["represented_country"]?.unwrap()), + traits: TraitsRecord(dictionary?["traits"]?.unwrap()), + ipAddress: ip, + netmask: netmask + ) + } +} \ No newline at end of file diff --git a/Sources/Api/README.md b/Sources/Api/README.md new file mode 100644 index 0000000..6a25612 --- /dev/null +++ b/Sources/Api/README.md @@ -0,0 +1,12 @@ +# Good to know about the API module + +It was intentionally designed to reproduce the php scripter fantasies, and awful +modeling to reduce risks of having a broken library behavior, and data provision. + +Depending on how widespread the library may become, the new fancy API of which +I could be proud of will be delivered either : +* **as a backwards incompatible way**, by simply throwing it all out in the +garbage, and providing a proper professional api +* **as a backwards compatible way**, by doing a minor release, marking this pile +of shit I wrote as deprecated, providing a neat API module, and throwing this +trash in the garbage in the future. \ No newline at end of file diff --git a/Sources/Api/Record/SubdivisionRecord.swift b/Sources/Api/Record/SubdivisionRecord.swift index 90afd6e..cf69a86 100644 --- a/Sources/Api/Record/SubdivisionRecord.swift +++ b/Sources/Api/Record/SubdivisionRecord.swift @@ -15,7 +15,7 @@ extension SubdivisionRecord: DictionaryInitialisableRecord { self.init( confidence: dictionary?["confidence"]?.unwrap(), geonameId: dictionary?["geoname_id"]?.unwrap(), - isoCode: dictionary?["iso_cod"]?.unwrap(), + isoCode: dictionary?["iso_code"]?.unwrap(), names: (dictionary?["names"]?.unwrap() ?? [:] as [String: Payload]).compactMapValues({ $0.unwrap() }) ) } diff --git a/Sources/Api/Record/TraitsRecord.swift b/Sources/Api/Record/TraitsRecord.swift index cecdb34..a1a277c 100644 --- a/Sources/Api/Record/TraitsRecord.swift +++ b/Sources/Api/Record/TraitsRecord.swift @@ -6,8 +6,6 @@ public struct TraitsRecord: Equatable { let autonomousSystemOrganization: String? let connectionType: String? let domain: String? - // todo -// let ipAddress: IpAddress let isAnonymous: Bool let isAnonymousProxy: Bool let isAnonymousVpn: Bool @@ -17,7 +15,6 @@ public struct TraitsRecord: Equatable { let isSatelliteProvider: Bool let isTorExitNode: Bool let isp: String? - let network: String let organization: String? let staticIPScore: Float? let userCount: Int? @@ -31,8 +28,6 @@ extension TraitsRecord: DictionaryInitialisableRecord { autonomousSystemOrganization: dictionary?["autonomous_system_organization"]?.unwrap(), connectionType: dictionary?["connection_type"]?.unwrap(), domain: dictionary?["domain"]?.unwrap(), - // TODO -// ipAddress: dictionary?["ipAddress"]?.unwrap(), isAnonymous: dictionary?["is_anonymous"]?.unwrap() ?? false, isAnonymousProxy: dictionary?["is_anonymous_proxy"]?.unwrap() ?? false, isAnonymousVpn: dictionary?["is_anonymous_vpn"]?.unwrap() ?? false, @@ -42,8 +37,6 @@ extension TraitsRecord: DictionaryInitialisableRecord { isSatelliteProvider: dictionary?["is_satellite_provider"]?.unwrap() ?? false, isTorExitNode: dictionary?["is_tor_exit_node"]?.unwrap() ?? false, isp: dictionary?["isp"]?.unwrap(), - // TODO - network: dictionary?["network"]?.unwrap() ?? "", organization: dictionary?["organization"]?.unwrap(), staticIPScore: dictionary?["static_ip_score"]?.unwrap(), userCount: dictionary?["user_count"]?.unwrap(), diff --git a/Tests/System/FileBased/CityLookupTest.swift b/Tests/System/FileBased/CityLookupTest.swift index 642f82d..a87a422 100644 --- a/Tests/System/FileBased/CityLookupTest.swift +++ b/Tests/System/FileBased/CityLookupTest.swift @@ -5,6 +5,15 @@ import class Api.Reader import class Api.ReaderFactory import enum Api.IpAddress @testable import struct Api.CityModel +@testable import struct Api.CityRecord +@testable import struct Api.ContinentRecord +@testable import struct Api.CountryRecord +@testable import struct Api.LocationRecord +@testable import struct Api.MaxMindRecord +@testable import struct Api.PostalRecord +@testable import struct Api.RepresentedCountryRecord +@testable import struct Api.SubdivisionRecord +@testable import struct Api.TraitsRecord class CityLookupTest: XCTestCase { @@ -31,11 +40,201 @@ class CityLookupTest: XCTestCase { } func testSuccessfulLookup() { - "ffff:ffff:ffff:ffff:ffff:ffff:ffff:C000" - "255.255.255.224" - "255.255.255.192" -// print(reader.lookup(.v6("::1.128.0.0"))?.netmask) - print(reader.lookup(.v4(80, 99, 18, 166))?.netmask) + XCTAssertEqual( + CityModel( + city: CityRecord( + confidence: nil, + geonameId: 3054643, + names: [ + "de": "Budapest", + "en": "Budapest", + "es": "Budapest", + "fr": "Budapest", + "ja": "ブダペスト", + "pt-BR": "Budapeste", + "ru": "Будапешт", + "zh-CN": "布达佩斯" + ] + ), + location: LocationRecord( + averageIncome: nil, + accuracyRadius: 20, + latitude: 47.4984, + longitude: 19.0404, + populationDensity: nil, + metroCode: nil, + timeZone: "Europe/Budapest" + ), + postal: PostalRecord(code: "1096", confidence: nil), + subdivisions: [ + SubdivisionRecord( + confidence: nil, + geonameId: 3054638, + isoCode: "BU", + names: ["en": "Budapest"] + ) + ], + continent: ContinentRecord( + code: "EU", + geonameId: 6255148, + names: [ + "de": "Europa", + "en": "Europe", + "es": "Europa", + "fr": "Europe", + "ja": "ヨーロッパ", + "pt-BR": "Europa", + "ru": "Европа", + "zh-CN": "欧洲" + ] + ), + country: CountryRecord( + confidence: nil, + geonameId: 719819, + isInEuropeanUnion: true, + isoCode: "HU", + names: [ + "de": "Ungarn", + "en": "Hungary", + "es": "Hungría", + "fr": "Hongrie", + "ja": "ハンガリー共和国", + "pt-BR": "Hungria", + "ru": "Венгрия", + "zh-CN": "匈牙利" + ] + ), + maxmind: MaxMindRecord(queriesRemaining: nil), + registeredCountry: CountryRecord( + confidence: nil, + geonameId: 719819, + isInEuropeanUnion: true, + isoCode: "HU", + names: [ + "de": "Ungarn", + "en": "Hungary", + "es": "Hungría", + "fr": "Hongrie", + "ja": "ハンガリー共和国", + "pt-BR": "Hungria", + "ru": "Венгрия", + "zh-CN": "匈牙利" + ] + ), + representedCountry: RepresentedCountryRecord(type: nil), + traits: TraitsRecord( + autonomousSystemNumber: nil, + autonomousSystemOrganization: nil, + connectionType: nil, + domain: nil, + isAnonymous: false, + isAnonymousProxy: false, + isAnonymousVpn: false, + isHostingProvider: false, + isLegitimateProxy: false, + isPublicProxy: false, + isSatelliteProvider: false, + isTorExitNode: false, + isp: nil, + organization: nil, + staticIPScore: nil, + userCount: nil, + userType: nil + ), + ipAddress: .v4(80, 99, 18, 166), + netmask: IpAddress.v4Netmask(ofBitLength: 26) + ), + reader.lookup(.v4(80, 99, 18, 166)) + ) + + XCTAssertEqual( + CityModel( + city: CityRecord(confidence: nil, geonameId: nil, names: [:]), + location: LocationRecord( + averageIncome: nil, + accuracyRadius: 1000, + latitude: -33.494, + longitude: 143.2104, + populationDensity: nil, + metroCode: nil, + timeZone: "Australia/Sydney" + ), + postal: PostalRecord(code: nil, confidence: nil), + subdivisions: [], + continent: ContinentRecord( + code: "OC", + geonameId: 6255151, + names: [ + "de": "Ozeanien", + "en": "Oceania", + "es": "Oceanía", + "fr": "Océanie", + "ja": "オセアニア", + "pt-BR": "Oceania", + "ru": "Океания", + "zh-CN": "大洋洲" + ] + ), + country: CountryRecord( + confidence: nil, + geonameId: 2077456, + isInEuropeanUnion: false, + isoCode: "AU", + names: [ + "de": "Australien", + "en": "Australia", + "es": "Australia", + "fr": "Australie", + "ja": "オーストラリア", + "pt-BR": "Austrália", + "ru": "Австралия", + "zh-CN": "澳大利亚" + ] + ), + maxmind: MaxMindRecord(queriesRemaining: nil), + registeredCountry: CountryRecord( + confidence: nil, + geonameId: 2077456, + isInEuropeanUnion: false, + isoCode: "AU", + names: [ + "de": "Australien", + "en": "Australia", + "es": "Australia", + "fr": "Australie", + "ja": "オーストラリア", + "pt-BR": "Austrália", + "ru": "Австралия", + "zh-CN": "澳大利亚" + ] + ), + representedCountry: RepresentedCountryRecord(type: nil), + traits: TraitsRecord( + autonomousSystemNumber: nil, + autonomousSystemOrganization: nil, + connectionType: nil, + domain: nil, + isAnonymous: false, + isAnonymousProxy: false, + isAnonymousVpn: false, + isHostingProvider: false, + isLegitimateProxy: false, + isPublicProxy: false, + isSatelliteProvider: false, + isTorExitNode: false, + isp: nil, + organization: nil, + staticIPScore: nil, + userCount: nil, + userType: nil + ), + ipAddress: .v6("::1.128.0.0"), + netmask: IpAddress.v6Netmask(ofBitLength: 114) + ), + reader.lookup(.v6("::1.128.0.0")) + ) + + XCTAssertNil(reader.lookup(.v6("2600:7100::"))) } }