Sources/OTelCommon/DeviceUtils.swift (272 lines of code) (raw):

// // Copyright 2023 aliyun-sls Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import Foundation #if canImport(CoreTelephony) && os(iOS) && !targetEnvironment(macCatalyst) import CoreTelephony #endif #if canImport(UIKit) import UIKit #elseif canImport(AppKit) import AppKit #endif public class DeviceUtils: NSObject { @objc public static func getDeviceModelIdentifier() -> String { var systemInfo = utsname() uname(&systemInfo) let machineMirror = Mirror(reflecting: systemInfo.machine) let identifier = machineMirror.children.reduce("") { identifier, element in guard let value = element.value as? Int8, value != 0 else { return identifier } return identifier + String(UnicodeScalar(UInt8(value))) } return identifier } @objc public static func getDeviceModel() -> String { let modelIdentifier = DeviceUtils.getDeviceModelIdentifier() switch modelIdentifier { case "iPhone3,1" : return "iPhone 4" case "iPhone3,2" : return "iPhone 4" case "iPhone3,3" : return "iPhone 4" case "iPhone4,1" : return "iPhone 4S" case "iPhone5,1" : return "iPhone 5" case "iPhone5,2" : return "iPhone 5 (GSM+CDMA)" case "iPhone5,3" : return "iPhone 5c (GSM)" case "iPhone5,4" : return "iPhone 5c (GSM+CDMA)" case "iPhone6,1" : return "iPhone 5s (GSM)" case "iPhone6,2" : return "iPhone 5s (GSM+CDMA)" case "iPhone7,1" : return "iPhone 6 Plus" case "iPhone7,2" : return "iPhone 6" case "iPhone8,1" : return "iPhone 6s" case "iPhone8,2" : return "iPhone 6s Plus" case "iPhone8,4" : return "iPhone SE" case "iPhone9,1" : return "iPhone 7" case "iPhone9,2" : return "iPhone 7 Plus" case "iPhone9,3" : return "iPhone 7" case "iPhone9,4" : return "iPhone 7 Plus" case "iPhone10,1" : return "iPhone_8" case "iPhone10,4" : return "iPhone_8" case "iPhone10,2" : return "iPhone_8_Plus" case "iPhone10,5" : return "iPhone_8_Plus" case "iPhone10,3" : return "iPhone X" case "iPhone10,6" : return "iPhone X" case "iPhone11,8" : return "iPhone XR" case "iPhone11,2" : return "iPhone XS" case "iPhone11,6" : return "iPhone XS Max" case "iPhone11,4" : return "iPhone XS Max" case "iPhone12,1" : return "iPhone 11" case "iPhone12,3" : return "iPhone 11 Pro" case "iPhone12,5" : return "iPhone 11 Pro Max" case "iPhone12,8" : return "iPhone SE2" case "iPhone13,1" : return "iPhone 12 mini" case "iPhone13,2" : return "iPhone 12" case "iPhone13,3" : return "iPhone 12 Pro" case "iPhone13,4" : return "iPhone 12 Pro Max" case "iPhone14,4" : return "iPhone 13 mini" case "iPhone14,5" : return "iPhone 13" case "iPhone14,2" : return "iPhone 13 Pro" case "iPhone14,3" : return "iPhone 13 Pro Max" case "iPod1,1" : return "iPod Touch 1G" case "iPod2,1" : return "iPod Touch 2G" case "iPod3,1" : return "iPod Touch 3G" case "iPod4,1" : return "iPod Touch 4G" case "iPod5,1" : return "iPod Touch (5 Gen)" case "iPod7,1" : return "iPod Touch (6 Gen)" case "iPad1,1" : return "iPad" case "iPad1,2" : return "iPad 3G" case "iPad2,1" : return "iPad 2 (WiFi)" case "iPad2,2" : return "iPad 2" case "iPad2,3" : return "iPad 2 (CDMA)" case "iPad2,4" : return "iPad 2" case "iPad3,1" : return "iPad 3 (WiFi)" case "iPad3,2" : return "iPad 3 (GSM+CDMA)" case "iPad3,3" : return "iPad 3" case "iPad3,4" : return "iPad 4 (WiFi)" case "iPad3,5" : return "iPad 4" case "iPad3,6" : return "iPad 4 (GSM+CDMA)" case "iPad6,11" : return "iPad 5" case "iPad6,12" : return "iPad 5" case "iPad7,5" : return "iPad 6" case "iPad7,6" : return "iPad 6" case "iPad7,11" : return "iPad 7" case "iPad7,12" : return "iPad 7" case "iPad11,6" : return "iPad 8" case "iPad12,1" : return "iPad 9" case "iPad12,2" : return "iPad 9" case "iPad4,1" : return "iPad Air (WiFi)" case "iPad4,2" : return "iPad Air (Cellular)" case "iPad5,3" : return "iPad Air 2" case "iPad5,4" : return "iPad Air 2" case "iPad11,3" : return "iPad Air 3" case "iPad11,4" : return "iPad Air 3" case "iPad13,1" : return "iPad Air 4" case "iPad13,2" : return "iPad Air 4" case "iPad2,5" : return "iPad Mini (WiFi)" case "iPad2,6" : return "iPad Mini" case "iPad2,7" : return "iPad Mini (GSM+CDMA)" case "iPad4,4" : return "iPad Mini 2 (WiFi)" case "iPad4,5" : return "iPad Mini 2 (Cellular)" case "iPad4,6" : return "iPad Mini 2" case "iPad4,7" : return "iPad Mini 3" case "iPad4,8" : return "iPad Mini 3" case "iPad4,9" : return "iPad Mini 3" case "iPad5,1" : return "iPad Mini 4 (WiFi)" case "iPad5,2" : return "iPad Mini 4 (LTE)" case "iPad11,1" : return "iPad Mini 5" case "iPad11,2" : return "iPad Mini 5" case "iPad14,1" : return "iPad Mini 6" case "iPad14,2" : return "iPad Mini 6" case "iPad6,3" : return "iPad Pro 9.7" case "iPad6,4" : return "iPad Pro 9.7" case "iPad7,3" : return "iPad Pro 10.5" case "iPad7,4" : return "iPad Pro 10.5" case "iPad8,1" : return "iPad Pro 11" case "iPad8,2" : return "iPad Pro 11" case "iPad8,3" : return "iPad Pro 11" case "iPad8,4" : return "iPad Pro 11" case "iPad8,9" : return "iPad Pro 11 2" case "iPad8,10" : return "iPad Pro 11 2" case "iPad13,4" : return "iPad Pro 11 3" case "iPad13,5" : return "iPad Pro 11 3" case "iPad13,6" : return "iPad Pro 11 3" case "iPad13,7" : return "iPad Pro 11 3" case "iPad6,7" : return "iPad Pro 12.9" case "iPad6,8" : return "iPad Pro 12.9" case "iPad7,1" : return "iPad Pro 12.9 2" case "iPad7,2" : return "iPad Pro 12.9 2" case "iPad8,5" : return "iPad Pro 12.9 3" case "iPad8,6" : return "iPad Pro 12.9 3" case "iPad8,7" : return "iPad Pro 12.9 3" case "iPad8,8" : return "iPad Pro 12.9 3" case "iPad8,11" : return "iPad Pro 12.9 4" case "iPad8,12" : return "iPad Pro 12.9 4" case "iPad13,8" : return "iPad Pro 12.9 5" case "iPad13,9" : return "iPad Pro 12.9 5" case "iPad13,10" : return "iPad Pro 12.9 5" case "iPad13,11" : return "iPad Pro 12.9 5" case "AppleTV1,1" : return "Apple TV 1" case "AppleTV2,1" : return "Apple TV 2" case "AppleTV3,1" : return "Apple TV 3" case "AppleTV3,2" : return "Apple TV 3" case "AppleTV5,3" : return "Apple TV 4" case "AppleTV6,2" : return "Apple TV 4K" case "AppleTV11,1" : return "Apple TV 4K 2" case "i386" : return "Simulator" case "x86_64" : return "Simulator" default: return modelIdentifier } } @objc public static func isJailBreak() -> Bool { // 获取越狱文件路径 let cydiaPath = "/Applications/Cydia.app" let aptPath = "/private/var/lib/apt/" if FileManager.default.fileExists(atPath: cydiaPath) { return true } if FileManager.default.fileExists(atPath: aptPath) { return true } // 可能存在hook了NSFileManager方法,此处用底层C stat去检测 var stat_info = stat() if stat("/Library/MobileSubstrate/MobileSubstrate.dylib", &stat_info) == 0 { return true } if stat("/Applications/Cydia.app", &stat_info) == 0 { return true } if stat("/var/lib/cydia/", &stat_info) == 0 { return true } if stat("/var/cache/apt", &stat_info) == 0 { return true } // 通常,越狱机的输出结果会包含字符串:Library/MobileSubstrate/MobileSubstrate.dylib。 // 攻击者给MobileSubstrate改名,原理都是通过DYLD_INSERT_LIBRARIES注入动态库。那么可以检测当前程序运行的环境变量 if let _ = getenv("DYLD_INSERT_LIBRARIES") { return true } return false } @objc public static func getResolution() -> String { #if canImport(UIKit) let size = UIScreen.main.bounds.size let scale = UIScreen.main.scale return "\((Int)(size.width * scale))x\((Int)(size.height * scale))" #elseif canImport(AppKit) let screen = NSScreen.main let description = screen?.deviceDescription let size = (description?[NSDeviceDescriptionKey.size] as? NSValue)?.sizeValue ?? CGSize.zero return "\((Int)(size.width))x\((Int)(size.height))" #else return "0x0" #endif } @objc public static func getCPUArch() -> String { var cpu = "" var size = MemoryLayout<cpu_type_t>.size var type: cpu_type_t = 0 sysctlbyname("hw.cputype", &type, &size, nil, 0) size = MemoryLayout<cpu_subtype_t>.size var subtype: cpu_subtype_t = 0 sysctlbyname("hw.cpusubtype", &subtype, &size, nil, 0) if type == CPU_TYPE_X86_64 { cpu = "x86_64" } else if type == CPU_TYPE_X86 { cpu = "x86" } else if type == CPU_TYPE_ARM { cpu = "ARM" switch subtype { case CPU_SUBTYPE_ARM_V6: cpu += "v6" case CPU_SUBTYPE_ARM_V7: cpu += "v7" case CPU_SUBTYPE_ARM_V8: cpu += "v8" default: break } } else if type == CPU_TYPE_ARM64 { cpu = "ARM64" } return cpu } @objc public static func getNetworkType() -> String { guard let reachability = try? Reachability() else { return "No Connection" } if reachability.connection == .wifi { return "Wi-Fi" } #if canImport(CoreTelephony) && os(iOS) && !targetEnvironment(macCatalyst) let networkInfo = CTTelephonyNetworkInfo() if let currentRadio = currentRadioAccessTechnology(networkInfo: networkInfo) { switch currentRadio { case CTRadioAccessTechnologyGPRS, CTRadioAccessTechnologyEdge, CTRadioAccessTechnologyCDMA1x: return "2G" case CTRadioAccessTechnologyWCDMA, CTRadioAccessTechnologyHSDPA, CTRadioAccessTechnologyHSUPA, CTRadioAccessTechnologyCDMAEVDORev0, CTRadioAccessTechnologyCDMAEVDORevA, CTRadioAccessTechnologyCDMAEVDORevB, CTRadioAccessTechnologyeHRPD: return "3G" case CTRadioAccessTechnologyLTE: return "4G" default: return "Unknown" } } #endif return "No Connection" } #if canImport(CoreTelephony) && os(iOS) && !targetEnvironment(macCatalyst) private static func currentRadioAccessTechnology(networkInfo: CTTelephonyNetworkInfo) -> String? { if #available(iOS 12.0, *) { return networkInfo.serviceCurrentRadioAccessTechnology?.values.first } else { return networkInfo.currentRadioAccessTechnology } } #endif }