diff --git a/decoded/bruteforce/results.json b/decoded/bruteforce/results.json index 246ae8c..f7b64b3 100644 --- a/decoded/bruteforce/results.json +++ b/decoded/bruteforce/results.json @@ -1,5 +1,5 @@ { - "elapsed_sec": 61.97, + "elapsed_sec": 46.262, "pad_matches": [], "confirmed": [] } diff --git a/ios/DESCracker.xcodeproj/project.pbxproj b/ios/DESCracker.xcodeproj/project.pbxproj new file mode 100644 index 0000000..a11e5ae --- /dev/null +++ b/ios/DESCracker.xcodeproj/project.pbxproj @@ -0,0 +1,431 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 56; + objects = { + +/* Begin PBXBuildFile section */ + A40000000000000000000001 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = A20000000000000000000001 /* main.m */; }; + A40000000000000000000002 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = A20000000000000000000003 /* AppDelegate.m */; }; + A40000000000000000000003 /* PSCSlaveViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = A20000000000000000000007 /* PSCSlaveViewController.m */; }; + A40000000000000000000004 /* PSCProtocol.m in Sources */ = {isa = PBXBuildFile; fileRef = A20000000000000000000009 /* PSCProtocol.m */; }; + A40000000000000000000005 /* PSCSlaveClient.m in Sources */ = {isa = PBXBuildFile; fileRef = A20000000000000000000011 /* PSCSlaveClient.m */; }; + A40000000000000000000006 /* PSCMetalEngine.m in Sources */ = {isa = PBXBuildFile; fileRef = A20000000000000000000014 /* PSCMetalEngine.m */; }; + A40000000000000000000007 /* PSCCpuEngine.m in Sources */ = {isa = PBXBuildFile; fileRef = A20000000000000000000016 /* PSCCpuEngine.m */; }; + A40000000000000000000008 /* des.c in Sources */ = {isa = PBXBuildFile; fileRef = A20000000000000000000017 /* des.c */; }; + A40000000000000000000009 /* des_bruteforce.metal in Sources */ = {isa = PBXBuildFile; fileRef = A20000000000000000000019 /* des_bruteforce.metal */; }; + A40000000000000000000010 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A20000000000000000000005 /* Assets.xcassets */; }; + A40000000000000000000011 /* Metal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A20000000000000000000020 /* Metal.framework */; }; + A40000000000000000000012 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A20000000000000000000021 /* QuartzCore.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + A10000000000000000000003 /* DES Cracker.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "DES Cracker.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + A20000000000000000000001 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + A20000000000000000000002 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + A20000000000000000000003 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + A20000000000000000000004 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + A20000000000000000000005 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + A20000000000000000000006 /* PSCSlaveViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PSCSlaveViewController.h; sourceTree = ""; }; + A20000000000000000000007 /* PSCSlaveViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PSCSlaveViewController.m; sourceTree = ""; }; + A20000000000000000000008 /* PSCProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PSCProtocol.h; sourceTree = ""; }; + A20000000000000000000009 /* PSCProtocol.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PSCProtocol.m; sourceTree = ""; }; + A20000000000000000000010 /* PSCSlaveClient.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PSCSlaveClient.h; sourceTree = ""; }; + A20000000000000000000011 /* PSCSlaveClient.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PSCSlaveClient.m; sourceTree = ""; }; + A20000000000000000000012 /* PSCBruteEngine.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PSCBruteEngine.h; sourceTree = ""; }; + A20000000000000000000013 /* PSCMetalEngine.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PSCMetalEngine.h; sourceTree = ""; }; + A20000000000000000000014 /* PSCMetalEngine.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PSCMetalEngine.m; sourceTree = ""; }; + A20000000000000000000015 /* PSCCpuEngine.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PSCCpuEngine.h; sourceTree = ""; }; + A20000000000000000000016 /* PSCCpuEngine.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PSCCpuEngine.m; sourceTree = ""; }; + A20000000000000000000017 /* des.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = des.c; path = ../native/common/des.c; sourceTree = SOURCE_ROOT; }; + A20000000000000000000018 /* des.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = des.h; path = ../native/common/des.h; sourceTree = SOURCE_ROOT; }; + A20000000000000000000019 /* des_bruteforce.metal */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.metal; name = des_bruteforce.metal; path = ../native/metal/des_bruteforce.metal; sourceTree = SOURCE_ROOT; }; + A20000000000000000000020 /* Metal.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Metal.framework; path = System/Library/Frameworks/Metal.framework; sourceTree = SDKROOT; }; + A20000000000000000000021 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + A10000000000000000000005 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + A40000000000000000000011 /* Metal.framework in Frameworks */, + A40000000000000000000012 /* QuartzCore.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + A30000000000000000000001 /* DESCracker */ = { + isa = PBXGroup; + children = ( + A20000000000000000000001 /* main.m */, + A20000000000000000000002 /* AppDelegate.h */, + A20000000000000000000003 /* AppDelegate.m */, + A20000000000000000000004 /* Info.plist */, + A20000000000000000000005 /* Assets.xcassets */, + A30000000000000000000002 /* UI */, + A30000000000000000000003 /* Network */, + A30000000000000000000004 /* Compute */, + A30000000000000000000005 /* Native */, + ); + path = DESCracker; + sourceTree = ""; + }; + A30000000000000000000002 /* UI */ = { + isa = PBXGroup; + children = ( + A20000000000000000000006 /* PSCSlaveViewController.h */, + A20000000000000000000007 /* PSCSlaveViewController.m */, + ); + path = UI; + sourceTree = ""; + }; + A30000000000000000000003 /* Network */ = { + isa = PBXGroup; + children = ( + A20000000000000000000008 /* PSCProtocol.h */, + A20000000000000000000009 /* PSCProtocol.m */, + A20000000000000000000010 /* PSCSlaveClient.h */, + A20000000000000000000011 /* PSCSlaveClient.m */, + ); + path = Network; + sourceTree = ""; + }; + A30000000000000000000004 /* Compute */ = { + isa = PBXGroup; + children = ( + A20000000000000000000012 /* PSCBruteEngine.h */, + A20000000000000000000013 /* PSCMetalEngine.h */, + A20000000000000000000014 /* PSCMetalEngine.m */, + A20000000000000000000015 /* PSCCpuEngine.h */, + A20000000000000000000016 /* PSCCpuEngine.m */, + ); + path = Compute; + sourceTree = ""; + }; + A30000000000000000000005 /* Native */ = { + isa = PBXGroup; + children = ( + A20000000000000000000017 /* des.c */, + A20000000000000000000018 /* des.h */, + A20000000000000000000019 /* des_bruteforce.metal */, + ); + name = Native; + sourceTree = ""; + }; + A30000000000000000000006 /* Products */ = { + isa = PBXGroup; + children = ( + A10000000000000000000003 /* DES Cracker.app */, + ); + name = Products; + sourceTree = ""; + }; + A30000000000000000000007 /* Frameworks */ = { + isa = PBXGroup; + children = ( + A20000000000000000000020 /* Metal.framework */, + A20000000000000000000021 /* QuartzCore.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + A30000000000000000000000 /* Root */ = { + isa = PBXGroup; + children = ( + A30000000000000000000001 /* DESCracker */, + A30000000000000000000007 /* Frameworks */, + A30000000000000000000006 /* Products */, + ); + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + A10000000000000000000002 /* DESCracker */ = { + isa = PBXNativeTarget; + buildConfigurationList = A50000000000000000000004 /* Build configuration list for PBXNativeTarget "DESCracker" */; + buildPhases = ( + A10000000000000000000004 /* Sources */, + A10000000000000000000005 /* Frameworks */, + A10000000000000000000006 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = DESCracker; + productName = "DES Cracker"; + productReference = A10000000000000000000003 /* DES Cracker.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + A10000000000000000000001 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastUpgradeCheck = 1600; + TargetAttributes = { + A10000000000000000000002 = { + CreatedOnToolsVersion = 16.0; + }; + }; + }; + buildConfigurationList = A50000000000000000000001 /* Build configuration list for PBXProject "DESCracker" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = A30000000000000000000000 /* Root */; + productRefGroup = A30000000000000000000006 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + A10000000000000000000002 /* DESCracker */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + A10000000000000000000006 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A40000000000000000000010 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + A10000000000000000000004 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A40000000000000000000001 /* main.m in Sources */, + A40000000000000000000002 /* AppDelegate.m in Sources */, + A40000000000000000000003 /* PSCSlaveViewController.m in Sources */, + A40000000000000000000004 /* PSCProtocol.m in Sources */, + A40000000000000000000005 /* PSCSlaveClient.m in Sources */, + A40000000000000000000006 /* PSCMetalEngine.m in Sources */, + A40000000000000000000007 /* PSCCpuEngine.m in Sources */, + A40000000000000000000008 /* des.c in Sources */, + A40000000000000000000009 /* des_bruteforce.metal in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + A50000000000000000000002 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + A50000000000000000000003 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + A50000000000000000000005 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + ENABLE_PREVIEWS = NO; + GENERATE_INFOPLIST_FILE = NO; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "$(SRCROOT)/DESCracker", + "$(SRCROOT)/DESCracker/UI", + "$(SRCROOT)/DESCracker/Network", + "$(SRCROOT)/DESCracker/Compute", + "$(SRCROOT)/../native/common", + ); + INFOPLIST_FILE = DESCracker/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "DES Cracker"; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.pectusscan.DESCracker; + PRODUCT_NAME = "DES Cracker"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SWIFT_EMIT_LOC_STRINGS = NO; + TARGETED_DEVICE_FAMILY = "1,2"; + USER_HEADER_SEARCH_PATHS = ( + "$(SRCROOT)/DESCracker/UI", + "$(SRCROOT)/DESCracker/Network", + "$(SRCROOT)/DESCracker/Compute", + "$(SRCROOT)/../native/common", + ); + }; + name = Debug; + }; + A50000000000000000000006 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + ENABLE_PREVIEWS = NO; + GENERATE_INFOPLIST_FILE = NO; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "$(SRCROOT)/DESCracker", + "$(SRCROOT)/DESCracker/UI", + "$(SRCROOT)/DESCracker/Network", + "$(SRCROOT)/DESCracker/Compute", + "$(SRCROOT)/../native/common", + ); + INFOPLIST_FILE = DESCracker/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "DES Cracker"; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.pectusscan.DESCracker; + PRODUCT_NAME = "DES Cracker"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SWIFT_EMIT_LOC_STRINGS = NO; + TARGETED_DEVICE_FAMILY = "1,2"; + USER_HEADER_SEARCH_PATHS = ( + "$(SRCROOT)/DESCracker/UI", + "$(SRCROOT)/DESCracker/Network", + "$(SRCROOT)/DESCracker/Compute", + "$(SRCROOT)/../native/common", + ); + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + A50000000000000000000001 /* Build configuration list for PBXProject "DESCracker" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A50000000000000000000002 /* Debug */, + A50000000000000000000003 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + A50000000000000000000004 /* Build configuration list for PBXNativeTarget "DESCracker" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A50000000000000000000005 /* Debug */, + A50000000000000000000006 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = A10000000000000000000001 /* Project object */; +} diff --git a/ios/DESCracker.xcodeproj/xcshareddata/xcschemes/DES Cracker.xcscheme b/ios/DESCracker.xcodeproj/xcshareddata/xcschemes/DES Cracker.xcscheme new file mode 100644 index 0000000..977cfdf --- /dev/null +++ b/ios/DESCracker.xcodeproj/xcshareddata/xcschemes/DES Cracker.xcscheme @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/DESCracker/AppDelegate.h b/ios/DESCracker/AppDelegate.h new file mode 100644 index 0000000..39df806 --- /dev/null +++ b/ios/DESCracker/AppDelegate.h @@ -0,0 +1,5 @@ +#import + +@interface AppDelegate : UIResponder +@property (nonatomic, strong) UIWindow *window; +@end diff --git a/ios/DESCracker/AppDelegate.m b/ios/DESCracker/AppDelegate.m new file mode 100644 index 0000000..2f93bd0 --- /dev/null +++ b/ios/DESCracker/AppDelegate.m @@ -0,0 +1,14 @@ +#import "AppDelegate.h" +#import "PSCSlaveViewController.h" + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + self.window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds]; + self.window.backgroundColor = [UIColor colorWithRed:0x10 / 255.0 green:0x15 / 255.0 blue:0x1c / 255.0 alpha:1]; + self.window.rootViewController = [[PSCSlaveViewController alloc] init]; + [self.window makeKeyAndVisible]; + return YES; +} + +@end diff --git a/ios/DESCracker/Assets.xcassets/AccentColor.colorset/Contents.json b/ios/DESCracker/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..b36b80f --- /dev/null +++ b/ios/DESCracker/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.831", + "green" : "0.918", + "red" : "0.369" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios/DESCracker/Assets.xcassets/AppIcon.appiconset/AppIcon.png b/ios/DESCracker/Assets.xcassets/AppIcon.appiconset/AppIcon.png new file mode 100644 index 0000000..b6201b9 Binary files /dev/null and b/ios/DESCracker/Assets.xcassets/AppIcon.appiconset/AppIcon.png differ diff --git a/ios/DESCracker/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/DESCracker/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..cefcc87 --- /dev/null +++ b/ios/DESCracker/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,14 @@ +{ + "images" : [ + { + "filename" : "AppIcon.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios/DESCracker/Assets.xcassets/Contents.json b/ios/DESCracker/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/ios/DESCracker/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios/DESCracker/Assets.xcassets/LaunchBackground.colorset/Contents.json b/ios/DESCracker/Assets.xcassets/LaunchBackground.colorset/Contents.json new file mode 100644 index 0000000..9f82c3f --- /dev/null +++ b/ios/DESCracker/Assets.xcassets/LaunchBackground.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.110", + "green" : "0.082", + "red" : "0.063" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios/DESCracker/Compute/PSCBruteEngine.h b/ios/DESCracker/Compute/PSCBruteEngine.h new file mode 100644 index 0000000..4162e9c --- /dev/null +++ b/ios/DESCracker/Compute/PSCBruteEngine.h @@ -0,0 +1,20 @@ +#import + +@class PSCAssignBlock; + +NS_ASSUME_NONNULL_BEGIN + +typedef void (^PSCBruteProgressBlock)(uint64_t done, uint64_t count, double rate); +typedef void (^PSCBruteHitBlock)(uint64_t index, uint64_t key, uint64_t plain); +typedef void (^PSCBruteFinishBlock)(NSInteger hits, NSTimeInterval elapsed, BOOL cancelled, NSError *_Nullable error); + +@protocol PSCBruteEngine +- (void)runAssign:(PSCAssignBlock *)block + onProgress:(nullable PSCBruteProgressBlock)onProgress + onHit:(nullable PSCBruteHitBlock)onHit + completion:(nullable PSCBruteFinishBlock)completion; +- (void)cancel; +@end + +NS_ASSUME_NONNULL_END + diff --git a/ios/DESCracker/Compute/PSCCpuEngine.h b/ios/DESCracker/Compute/PSCCpuEngine.h new file mode 100644 index 0000000..48dc8f7 --- /dev/null +++ b/ios/DESCracker/Compute/PSCCpuEngine.h @@ -0,0 +1,7 @@ +#import +#import "PSCBruteEngine.h" + +@interface PSCCpuEngine : NSObject +@property (nonatomic, assign, readonly) NSUInteger coreCount; +- (BOOL)prepare:(NSError **)error; +@end diff --git a/ios/DESCracker/Compute/PSCCpuEngine.m b/ios/DESCracker/Compute/PSCCpuEngine.m new file mode 100644 index 0000000..7a1fb6f --- /dev/null +++ b/ios/DESCracker/Compute/PSCCpuEngine.m @@ -0,0 +1,155 @@ +#import "PSCCpuEngine.h" +#import "PSCProtocol.h" +#import "des.h" +#import +#import +#import + +@interface PSCCpuEngine () { + atomic_bool _cancelled; + atomic_ullong _generation; +} +@property (nonatomic, strong) dispatch_queue_t workQueue; +@end + +@implementation PSCCpuEngine + +- (instancetype)init { + self = [super init]; + if (self) { + NSUInteger n = NSProcessInfo.processInfo.processorCount; + _coreCount = n > 0 ? n : 1; + _workQueue = dispatch_queue_create("com.descracker.cpu", DISPATCH_QUEUE_SERIAL); + atomic_init(&_cancelled, false); + atomic_init(&_generation, 0); + } + return self; +} + +- (BOOL)prepare:(NSError **)error { + if (!des_selftest()) { + if (error) { + *error = [NSError errorWithDomain:@"DESCracker" code:6 userInfo:@{ + NSLocalizedDescriptionKey : @"CPU DES self-test failed" + }]; + } + return NO; + } + return YES; +} + +- (void)cancel { + atomic_store(&_cancelled, true); + atomic_fetch_add(&_generation, 1); +} + +- (void)runAssign:(PSCAssignBlock *)block + onProgress:(PSCBruteProgressBlock)onProgress + onHit:(PSCBruteHitBlock)onHit + completion:(PSCBruteFinishBlock)completion { + dispatch_async(self.workQueue, ^{ + atomic_store(&self->_cancelled, false); + uint64_t generation = atomic_fetch_add(&self->_generation, 1) + 1; + NSTimeInterval t0 = CACurrentMediaTime(); + + uint32_t key_len = block.keyLen; + NSData *charsetData = [block.charset dataUsingEncoding:NSUTF8StringEncoding] ?: [NSData data]; + const uint8_t *cs = charsetData.bytes; + uint32_t clen = (uint32_t)charsetData.length; + uint32_t pad_byte = block.padByte; + uint64_t target = block.target; + uint64_t start = block.start; + uint64_t count = block.count; + NSUInteger workers = block.cpuWorkers ? (NSUInteger)MAX(1, block.cpuWorkers.integerValue) : self.coreCount; + if (workers == 0) { + workers = 1; + } + + NSMutableData *fillData = [NSMutableData dataWithLength:sizeof(uint64_t) * 8]; + uint64_t *fills = fillData.mutableBytes; + uint32_t nfills = 0; + for (NSNumber *item in block.fills) { + if (nfills >= 8) { + break; + } + fills[nfills++] = item.unsignedLongLongValue; + } + + __block atomic_ullong done; + atomic_init(&done, 0); + __block NSInteger hits = 0; + __block atomic_bool cancelledFlag; + atomic_init(&cancelledFlag, false); + NSLock *hitLock = [[NSLock alloc] init]; + + dispatch_queue_t pool = dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0); + dispatch_group_t group = dispatch_group_create(); + uint64_t chunk = count / workers; + uint64_t rem = count % workers; + uint64_t cursor = start; + + for (NSUInteger w = 0; w < workers; w++) { + uint64_t n = chunk + (w < rem ? 1 : 0); + if (n == 0) { + continue; + } + uint64_t wstart = cursor; + uint64_t wcount = n; + cursor += n; + dispatch_group_async(group, pool, ^{ + uint64_t local = 0; + for (uint64_t i = 0; i < wcount; i++) { + if ((local & 0x3FFF) == 0 && + (atomic_load(&self->_cancelled) || atomic_load(&self->_generation) != generation)) { + atomic_store(&cancelledFlag, true); + break; + } + uint64_t index = wstart + i; + uint64_t key = make_key(index, key_len, clen, pad_byte, cs); + uint64_t sk[16]; + des_key_schedule(key, sk); + uint64_t pt = des_crypt(target, sk, 1); + if (is_fill(pt, fills, nfills)) { + [hitLock lock]; + hits += 1; + [hitLock unlock]; + if (onHit) { + onHit(index, key, pt); + } + } + local++; + if ((local & 0x3FFF) == 0) { + uint64_t soFar = atomic_fetch_add(&done, 0x4000) + 0x4000; + double elapsed = CACurrentMediaTime() - t0; + if (elapsed < 1e-6) { + elapsed = 1e-6; + } + if (onProgress) { + onProgress(soFar > count ? count : soFar, count, soFar / elapsed); + } + local = 0; + } + } + if (local) { + atomic_fetch_add(&done, local); + } + }); + } + dispatch_group_wait(group, DISPATCH_TIME_FOREVER); + + BOOL cancelled = atomic_load(&cancelledFlag) || atomic_load(&self->_cancelled) || + atomic_load(&self->_generation) != generation; + if (!cancelled && onProgress) { + double elapsed = CACurrentMediaTime() - t0; + if (elapsed < 1e-6) { + elapsed = 1e-6; + } + onProgress(count, count, count / elapsed); + } + if (completion) { + completion(hits, CACurrentMediaTime() - t0, cancelled, nil); + } + }); +} + +@end diff --git a/ios/DESCracker/Compute/PSCMetalEngine.h b/ios/DESCracker/Compute/PSCMetalEngine.h new file mode 100644 index 0000000..c63ea02 --- /dev/null +++ b/ios/DESCracker/Compute/PSCMetalEngine.h @@ -0,0 +1,9 @@ +#import +#import "PSCBruteEngine.h" + +@interface PSCMetalEngine : NSObject +@property (nonatomic, readonly) BOOL available; +@property (nonatomic, copy, readonly) NSString *gpuName; +@property (nonatomic, assign, readonly) uint64_t memoryBytes; +- (BOOL)prepare:(NSError **)error; +@end diff --git a/ios/DESCracker/Compute/PSCMetalEngine.m b/ios/DESCracker/Compute/PSCMetalEngine.m new file mode 100644 index 0000000..31ec959 --- /dev/null +++ b/ios/DESCracker/Compute/PSCMetalEngine.m @@ -0,0 +1,266 @@ +#import "PSCMetalEngine.h" +#import "PSCProtocol.h" +#import +#import +#import +#import + +enum { kPSCMaxHits = 256, kPSCThreadsPerGroup = 256, kPSCParamsSize = 104, kPSCDefaultBatch = 1048576 }; + +typedef struct { + uint64_t index; + uint64_t key; + uint64_t plain; +} PSCHit; + +@interface PSCMetalEngine () { + atomic_bool _cancelled; +} +@property (nonatomic, strong) id device; +@property (nonatomic, strong) id queue; +@property (nonatomic, strong) id brutePipeline; +@property (nonatomic, strong) dispatch_queue_t workQueue; +@property (nonatomic, copy) NSString *activeJobId; +@end + +@implementation PSCMetalEngine + +- (instancetype)init { + self = [super init]; + if (self) { + _device = MTLCreateSystemDefaultDevice(); + _workQueue = dispatch_queue_create("com.descracker.metal", DISPATCH_QUEUE_SERIAL); + atomic_init(&_cancelled, false); + if (_device) { + _gpuName = [_device.name copy] ?: @"Metal GPU"; + if ([_device respondsToSelector:@selector(recommendedMaxWorkingSetSize)]) { + _memoryBytes = (uint64_t)_device.recommendedMaxWorkingSetSize; + } + } else { + _gpuName = @"Metal GPU"; + } + } + return self; +} + +- (BOOL)available { + return self.device != nil && self.brutePipeline != nil; +} + +static void PSCWriteParams(void *dst, uint64_t start, uint32_t key_len, uint32_t charset_len, uint32_t pad_byte, + uint32_t fill_count, uint32_t batch_count, uint64_t target, const uint64_t *fills, + int nfills) { + uint8_t *p = dst; + memcpy(p, &start, 8); + p += 8; + memcpy(p, &key_len, 4); + p += 4; + memcpy(p, &charset_len, 4); + p += 4; + memcpy(p, &pad_byte, 4); + p += 4; + memcpy(p, &fill_count, 4); + p += 4; + memcpy(p, &batch_count, 4); + p += 4; + uint32_t z = 0; + memcpy(p, &z, 4); + p += 4; + memcpy(p, &target, 8); + p += 8; + for (int i = 0; i < 8; i++) { + uint64_t f = i < nfills ? fills[i] : 0; + memcpy(p, &f, 8); + p += 8; + } +} + +- (uint32_t)currentBatchSize { + NSProcessInfoThermalState thermal = NSProcessInfo.processInfo.thermalState; + if (thermal >= NSProcessInfoThermalStateCritical) { + return 262144; + } + if (thermal >= NSProcessInfoThermalStateSerious) { + return 524288; + } + return kPSCDefaultBatch; +} + +- (BOOL)prepare:(NSError **)error { + if (!self.device) { + if (error) { + *error = [NSError errorWithDomain:@"DESCracker" code:1 userInfo:@{ + NSLocalizedDescriptionKey : @"No Metal GPU" + }]; + } + return NO; + } + NSError *err = nil; + id lib = [self.device newDefaultLibrary]; + if (!lib) { + if (error) { + *error = [NSError errorWithDomain:@"DESCracker" code:2 userInfo:@{ + NSLocalizedDescriptionKey : @"Failed to load Metal library" + }]; + } + return NO; + } + id known = [lib newFunctionWithName:@"des_known_answer"]; + id brute = [lib newFunctionWithName:@"des_brute"]; + if (!known || !brute) { + if (error) { + *error = [NSError errorWithDomain:@"DESCracker" code:3 userInfo:@{ + NSLocalizedDescriptionKey : @"Metal kernels missing (des_known_answer / des_brute)" + }]; + } + return NO; + } + id knownPipe = [self.device newComputePipelineStateWithFunction:known error:&err]; + if (!knownPipe) { + if (error) { + *error = err; + } + return NO; + } + self.brutePipeline = [self.device newComputePipelineStateWithFunction:brute error:&err]; + if (!self.brutePipeline) { + if (error) { + *error = err; + } + return NO; + } + self.queue = [self.device newCommandQueue]; + id out = [self.device newBufferWithLength:16 options:MTLResourceStorageModeShared]; + id cmd = [self.queue commandBuffer]; + id enc = [cmd computeCommandEncoder]; + [enc setComputePipelineState:knownPipe]; + [enc setBuffer:out offset:0 atIndex:0]; + [enc dispatchThreads:MTLSizeMake(1, 1, 1) threadsPerThreadgroup:MTLSizeMake(1, 1, 1)]; + [enc endEncoding]; + [cmd commit]; + [cmd waitUntilCompleted]; + uint64_t *words = out.contents; + uint64_t expected = 0x85E813540F0AB405ULL; + uint64_t plain = 0x0123456789ABCDEFULL; + if (words[0] != expected || words[1] != plain) { + if (error) { + *error = [NSError errorWithDomain:@"DESCracker" code:4 userInfo:@{ + NSLocalizedDescriptionKey : [NSString stringWithFormat:@"Metal DES self-test failed ct=%016llx", + (unsigned long long)words[0]] + }]; + } + self.brutePipeline = nil; + return NO; + } + return YES; +} + +- (void)cancel { + atomic_store(&_cancelled, true); + self.activeJobId = nil; +} + +- (void)runAssign:(PSCAssignBlock *)block + onProgress:(PSCBruteProgressBlock)onProgress + onHit:(PSCBruteHitBlock)onHit + completion:(PSCBruteFinishBlock)completion { + dispatch_async(self.workQueue, ^{ + atomic_store(&self->_cancelled, false); + self.activeJobId = block.jobId; + NSString *jobId = [block.jobId copy]; + NSTimeInterval t0 = CACurrentMediaTime(); + NSInteger hits = 0; + NSError *runError = nil; + + if (!self.available) { + if (completion) { + completion(0, 0, NO, [NSError errorWithDomain:@"DESCracker" code:5 userInfo:@{ + NSLocalizedDescriptionKey : @"Metal engine is not ready" + }]); + } + return; + } + + const char *cs = block.charset.UTF8String ?: ""; + uint32_t charset_len = (uint32_t)strlen(cs); + uint64_t fills[8] = {0}; + int nfills = 0; + for (NSNumber *item in block.fills) { + if (nfills >= 8) { + break; + } + fills[nfills++] = item.unsignedLongLongValue; + } + + id paramsBuf = [self.device newBufferWithLength:kPSCParamsSize options:MTLResourceStorageModeShared]; + id charsetBuf = [self.device newBufferWithBytes:cs length:MAX(charset_len, 1) + options:MTLResourceStorageModeShared]; + id hitsBuf = [self.device newBufferWithLength:kPSCMaxHits * sizeof(PSCHit) + options:MTLResourceStorageModeShared]; + id countBuf = [self.device newBufferWithLength:4 options:MTLResourceStorageModeShared]; + + uint64_t done = 0; + uint64_t count = block.count; + BOOL cancelled = NO; + while (done < count) { + if (atomic_load(&self->_cancelled) || ![self.activeJobId isEqualToString:jobId]) { + cancelled = YES; + break; + } + uint32_t batch = [self currentBatchSize]; + uint64_t remaining = count - done; + uint64_t n = batch < remaining ? batch : remaining; + *(uint32_t *)countBuf.contents = 0; + PSCWriteParams(paramsBuf.contents, block.start + done, block.keyLen, charset_len, block.padByte, + (uint32_t)nfills, (uint32_t)n, block.target, fills, nfills); + + id cmd = [self.queue commandBuffer]; + id enc = [cmd computeCommandEncoder]; + [enc setComputePipelineState:self.brutePipeline]; + [enc setBuffer:paramsBuf offset:0 atIndex:0]; + [enc setBuffer:charsetBuf offset:0 atIndex:1]; + [enc setBuffer:hitsBuf offset:0 atIndex:2]; + [enc setBuffer:countBuf offset:0 atIndex:3]; + NSUInteger groups = (NSUInteger)((n + kPSCThreadsPerGroup - 1) / kPSCThreadsPerGroup); + [enc dispatchThreadgroups:MTLSizeMake(groups, 1, 1) + threadsPerThreadgroup:MTLSizeMake(kPSCThreadsPerGroup, 1, 1)]; + [enc endEncoding]; + [cmd commit]; + [cmd waitUntilCompleted]; + if (cmd.error) { + runError = cmd.error; + break; + } + + uint32_t hitn = *(uint32_t *)countBuf.contents; + if (hitn > kPSCMaxHits) { + hitn = kPSCMaxHits; + } + PSCHit *outHits = hitsBuf.contents; + for (uint32_t i = 0; i < hitn; i++) { + hits += 1; + if (onHit) { + onHit(outHits[i].index, outHits[i].key, outHits[i].plain); + } + } + done += n; + double elapsed = CACurrentMediaTime() - t0; + if (elapsed < 1e-6) { + elapsed = 1e-6; + } + if (onProgress) { + onProgress(done, count, done / elapsed); + } + } + + if ([self.activeJobId isEqualToString:jobId]) { + self.activeJobId = nil; + } + NSTimeInterval elapsed = CACurrentMediaTime() - t0; + if (completion) { + completion(hits, elapsed, cancelled, runError); + } + }); +} + +@end diff --git a/ios/DESCracker/Info.plist b/ios/DESCracker/Info.plist new file mode 100644 index 0000000..39a3d4c --- /dev/null +++ b/ios/DESCracker/Info.plist @@ -0,0 +1,57 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + DES Cracker + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + DES Cracker + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + NSAllowsLocalNetworking + + + NSLocalNetworkUsageDescription + DES Cracker connects to the desktop master on your local network to receive search work. + UILaunchScreen + + UIColorName + LaunchBackground + + UIStatusBarStyle + UIStatusBarStyleLightContent + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIUserInterfaceStyle + Dark + + diff --git a/ios/DESCracker/Network/PSCProtocol.h b/ios/DESCracker/Network/PSCProtocol.h new file mode 100644 index 0000000..e52af1e --- /dev/null +++ b/ios/DESCracker/Network/PSCProtocol.h @@ -0,0 +1,57 @@ +#import +#include + +NS_ASSUME_NONNULL_BEGIN + +static const NSInteger kPSCProtocolVersion = 2; +static const NSInteger kPSCDefaultPort = 9876; +static const NSTimeInterval kPSCProgressInterval = 0.5; +static const NSTimeInterval kPSCPingInterval = 5.0; + +NSString *PSCFormatDec64(uint64_t value); +NSString *PSCFormatHex64(uint64_t value); +NSString *PSCFormatCount(uint64_t value); +NSString *PSCFormatRate(double rate); +uint64_t PSCParseDec64(id value); +uint64_t PSCParseHex64(NSString *hex); + +@interface PSCComputeDevice : NSObject +@property (nonatomic, copy) NSString *key; +@property (nonatomic, assign) NSInteger deviceId; +@property (nonatomic, copy) NSString *name; +@property (nonatomic, copy) NSString *type; +@property (nonatomic, strong, nullable) NSNumber *cores; +@property (nonatomic, strong, nullable) NSNumber *memory; +- (NSDictionary *)JSONObject; +@end + +@interface PSCAssignBlock : NSObject +@property (nonatomic, copy) NSString *jobId; +@property (nonatomic, copy) NSString *deviceKey; +@property (nonatomic, assign) uint64_t start; +@property (nonatomic, assign) uint64_t count; +@property (nonatomic, assign) uint32_t keyLen; +@property (nonatomic, copy) NSString *charset; +@property (nonatomic, assign) uint32_t padByte; +@property (nonatomic, assign) uint64_t target; +@property (nonatomic, copy) NSArray *fills; +@property (nonatomic, strong, nullable) NSNumber *cpuWorkers; ++ (nullable instancetype)fromJSON:(NSDictionary *)json; +@end + +@interface PSCWorkerSnapshot : NSObject +@property (nonatomic, copy) NSString *deviceKey; +@property (nonatomic, copy) NSString *deviceName; +@property (nonatomic, copy) NSString *deviceType; +@property (nonatomic, copy) NSString *current; +@property (nonatomic, assign) double pct; +@property (nonatomic, assign) NSInteger completedBlocks; +@property (nonatomic, assign) double rate; +@property (nonatomic, assign) BOOL busy; +@end + +NSDictionary *_Nullable PSCDecodeMessage(NSString *raw); +NSString *_Nullable PSCEncodeMessage(NSDictionary *msg); + +NS_ASSUME_NONNULL_END + diff --git a/ios/DESCracker/Network/PSCProtocol.m b/ios/DESCracker/Network/PSCProtocol.m new file mode 100644 index 0000000..429d2b0 --- /dev/null +++ b/ios/DESCracker/Network/PSCProtocol.m @@ -0,0 +1,146 @@ +#import "PSCProtocol.h" +#import +#import + +NSString *PSCFormatDec64(uint64_t value) { + return [NSString stringWithFormat:@"%llu", (unsigned long long)value]; +} + +NSString *PSCFormatHex64(uint64_t value) { + return [NSString stringWithFormat:@"%016llx", (unsigned long long)value]; +} + +NSString *PSCFormatCount(uint64_t value) { + double n = (double)value; + if (n >= 1e9) { + return [NSString stringWithFormat:@"%.1fB", n / 1e9]; + } + if (n >= 1e6) { + return [NSString stringWithFormat:@"%.1fM", n / 1e6]; + } + if (n >= 1e3) { + return [NSString stringWithFormat:@"%.1fk", n / 1e3]; + } + return [NSString stringWithFormat:@"%.0f", n]; +} + +NSString *PSCFormatRate(double rate) { + if (rate <= 0 || !isfinite(rate)) { + return @"—"; + } + return [NSString stringWithFormat:@"%@/s", PSCFormatCount((uint64_t)llround(rate))]; +} + +uint64_t PSCParseDec64(id value) { + if ([value isKindOfClass:[NSString class]]) { + return strtoull([(NSString *)value UTF8String], NULL, 10); + } + if ([value isKindOfClass:[NSNumber class]]) { + return [(NSNumber *)value unsignedLongLongValue]; + } + return 0; +} + +uint64_t PSCParseHex64(NSString *hex) { + if (hex.length == 0) { + return 0; + } + const char *p = hex.UTF8String; + if (p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) { + p += 2; + } + return strtoull(p, NULL, 16); +} + +@implementation PSCComputeDevice + +- (NSDictionary *)JSONObject { + NSMutableDictionary *json = [@{ + @"key" : self.key ?: @"", + @"id" : @(self.deviceId), + @"name" : self.name ?: @"", + @"type" : self.type ?: @"cpu", + } mutableCopy]; + if (self.cores) { + json[@"cores"] = self.cores; + } + if (self.memory) { + json[@"memory"] = self.memory; + } + return json; +} + +@end + +@implementation PSCAssignBlock + ++ (instancetype)fromJSON:(NSDictionary *)json { + if (![json isKindOfClass:[NSDictionary class]]) { + return nil; + } + PSCAssignBlock *block = [[PSCAssignBlock alloc] init]; + block.jobId = [json[@"jobId"] isKindOfClass:[NSString class]] ? json[@"jobId"] : @""; + block.deviceKey = [json[@"deviceKey"] isKindOfClass:[NSString class]] ? json[@"deviceKey"] : @""; + block.start = PSCParseDec64(json[@"start"]); + block.count = PSCParseDec64(json[@"count"]); + block.keyLen = (uint32_t)PSCParseDec64(json[@"keyLen"]); + block.charset = [json[@"charset"] isKindOfClass:[NSString class]] ? json[@"charset"] : @""; + block.padByte = (uint32_t)(PSCParseDec64(json[@"padByte"]) & 0xFF); + id target = json[@"target"]; + block.target = [target isKindOfClass:[NSString class]] ? PSCParseHex64(target) : PSCParseDec64(target); + NSMutableArray *fills = [NSMutableArray array]; + id fillsVal = json[@"fills"]; + if ([fillsVal isKindOfClass:[NSArray class]]) { + for (id item in (NSArray *)fillsVal) { + if (fills.count >= 8) { + break; + } + uint64_t f = [item isKindOfClass:[NSString class]] ? PSCParseHex64(item) : PSCParseDec64(item); + [fills addObject:@(f)]; + } + } + block.fills = fills; + id workers = json[@"cpuWorkers"]; + if ([workers isKindOfClass:[NSNumber class]]) { + block.cpuWorkers = workers; + } + if (block.jobId.length == 0 || block.count == 0 || block.keyLen == 0 || block.charset.length == 0) { + return nil; + } + return block; +} + +@end + +@implementation PSCWorkerSnapshot +@end + +NSDictionary *PSCDecodeMessage(NSString *raw) { + if (raw.length == 0) { + return nil; + } + NSData *data = [raw dataUsingEncoding:NSUTF8StringEncoding]; + if (!data) { + return nil; + } + id parsed = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; + if (![parsed isKindOfClass:[NSDictionary class]]) { + return nil; + } + NSDictionary *msg = parsed; + if (![msg[@"type"] isKindOfClass:[NSString class]]) { + return nil; + } + return msg; +} + +NSString *PSCEncodeMessage(NSDictionary *msg) { + if (!msg) { + return nil; + } + NSData *data = [NSJSONSerialization dataWithJSONObject:msg options:0 error:nil]; + if (!data) { + return nil; + } + return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; +} diff --git a/ios/DESCracker/Network/PSCSlaveClient.h b/ios/DESCracker/Network/PSCSlaveClient.h new file mode 100644 index 0000000..9db6a8e --- /dev/null +++ b/ios/DESCracker/Network/PSCSlaveClient.h @@ -0,0 +1,25 @@ +#import +#import "PSCProtocol.h" + +@class PSCSlaveClient; + +@protocol PSCSlaveClientDelegate +- (void)slaveClient:(PSCSlaveClient *)client didChangeStatus:(NSString *)status connected:(BOOL)connected; +- (void)slaveClient:(PSCSlaveClient *)client didLog:(NSString *)line; +- (void)slaveClient:(PSCSlaveClient *)client didUpdateWorkers:(NSArray *)workers; +- (void)slaveClient:(PSCSlaveClient *)client didHitJob:(NSString *)jobId index:(NSString *)index keyHex:(NSString *)keyHex; +@end + +@interface PSCSlaveClient : NSObject +@property (nonatomic, weak) id delegate; +@property (nonatomic, readonly, getter=isConnected) BOOL connected; +@property (nonatomic, assign) NSUInteger cpuWorkers; +@property (nonatomic, readonly) BOOL metalAvailable; +@property (nonatomic, copy, readonly) NSString *metalName; +@property (nonatomic, assign, readonly) uint64_t metalMemory; +@property (nonatomic, assign, readonly) NSUInteger cpuCores; +- (BOOL)prepareEngines:(NSError **)error; +- (void)setDevices:(NSArray *)devices; +- (void)connectToHost:(NSString *)host port:(NSInteger)port; +- (void)disconnect; +@end diff --git a/ios/DESCracker/Network/PSCSlaveClient.m b/ios/DESCracker/Network/PSCSlaveClient.m new file mode 100644 index 0000000..348f01d --- /dev/null +++ b/ios/DESCracker/Network/PSCSlaveClient.m @@ -0,0 +1,604 @@ +#import "PSCSlaveClient.h" +#import "PSCCpuEngine.h" +#import "PSCMetalEngine.h" + +@interface PSCDeviceJob : NSObject +@property (nonatomic, copy) NSString *jobId; +@property (nonatomic, assign) uint64_t done; +@property (nonatomic, assign) uint64_t count; +@property (nonatomic, assign) double rate; +@property (nonatomic, copy) void (^kill)(void); +@end + +@implementation PSCDeviceJob +@end + +@interface PSCRateStats : NSObject +@property (nonatomic, assign) uint64_t keysDone; +@property (nonatomic, assign) NSTimeInterval activeMs; +@property (nonatomic, strong) NSDate *busyStarted; +@end + +@implementation PSCRateStats +@end + +@interface PSCProgressGate : NSObject +@property (nonatomic, assign) NSTimeInterval lastSent; +@property (nonatomic, assign) BOOL timerPending; +@property (nonatomic, copy) NSDictionary *payload; +@end + +@implementation PSCProgressGate +@end + +@interface PSCSlaveClient () +@property (nonatomic, copy) NSArray *devices; +@property (nonatomic, strong) NSURLSession *session; +@property (nonatomic, strong) NSURLSessionWebSocketTask *task; +@property (nonatomic, strong) NSTimer *pingTimer; +@property (nonatomic, strong) NSMutableDictionary *jobs; +@property (nonatomic, strong) NSMutableDictionary *completedBlocks; +@property (nonatomic, strong) NSMutableDictionary *progressGates; +@property (nonatomic, strong) NSMutableDictionary *rateStats; +@property (nonatomic, strong) PSCMetalEngine *metal; +@property (nonatomic, strong) PSCCpuEngine *cpu; +@property (nonatomic, strong) dispatch_queue_t syncQueue; +@property (nonatomic, copy) NSString *connectURL; +@end + +@implementation PSCSlaveClient + +- (instancetype)init { + self = [super init]; + if (self) { + _devices = @[]; + _jobs = [NSMutableDictionary dictionary]; + _completedBlocks = [NSMutableDictionary dictionary]; + _progressGates = [NSMutableDictionary dictionary]; + _rateStats = [NSMutableDictionary dictionary]; + _cpuWorkers = MAX(1, NSProcessInfo.processInfo.processorCount); + _metal = [[PSCMetalEngine alloc] init]; + _cpu = [[PSCCpuEngine alloc] init]; + _syncQueue = dispatch_queue_create("com.descracker.slave", DISPATCH_QUEUE_SERIAL); + NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; + config.waitsForConnectivity = NO; + _session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil]; + } + return self; +} + +- (BOOL)isConnected { + return self.task != nil && self.task.state == NSURLSessionTaskStateRunning; +} + +- (BOOL)metalAvailable { + return self.metal.available; +} + +- (NSString *)metalName { + return self.metal.gpuName; +} + +- (uint64_t)metalMemory { + return self.metal.memoryBytes; +} + +- (NSUInteger)cpuCores { + return self.cpu.coreCount; +} + +- (BOOL)prepareEngines:(NSError **)error { + NSError *metalErr = nil; + BOOL metalOK = [self.metal prepare:&metalErr]; + NSError *cpuErr = nil; + BOOL cpuOK = [self.cpu prepare:&cpuErr]; + if (!metalOK && error) { + *error = metalErr; + } + if (!cpuOK) { + if (error && !metalErr) { + *error = cpuErr; + } + return NO; + } + return metalOK || cpuOK; +} + +- (void)setDevices:(NSArray *)devices { + dispatch_async(self.syncQueue, ^{ + self.devices = [devices copy] ?: @[]; + for (PSCComputeDevice *device in self.devices) { + if (!self.completedBlocks[device.key]) { + self.completedBlocks[device.key] = @0; + } + } + [self emitWorkers]; + }); +} + +- (void)connectToHost:(NSString *)host port:(NSInteger)port { + [self disconnect]; + NSString *urlString = [NSString stringWithFormat:@"ws://%@:%ld", host, (long)port]; + self.connectURL = urlString; + [self notifyStatus:[NSString stringWithFormat:@"Connecting to %@…", urlString] connected:NO]; + NSURL *url = [NSURL URLWithString:urlString]; + if (!url) { + [self log:[NSString stringWithFormat:@"Invalid URL %@", urlString]]; + [self notifyStatus:@"Disconnected" connected:NO]; + return; + } + self.task = [self.session webSocketTaskWithURL:url]; + [self.task resume]; +} + +- (void)disconnect { + dispatch_sync(self.syncQueue, ^{ + [self clearPing]; + [self killAll]; + }); + NSURLSessionWebSocketTask *task = self.task; + self.task = nil; + if (task) { + [task cancelWithCloseCode:NSURLSessionWebSocketCloseCodeNormalClosure reason:nil]; + } + dispatch_async(self.syncQueue, ^{ + [self emitWorkers]; + }); +} + +- (void)URLSession:(NSURLSession *)session + webSocketTask:(NSURLSessionWebSocketTask *)webSocketTask + didOpenWithProtocol:(NSString *)protocol { + (void)session; + (void)protocol; + if (webSocketTask != self.task) { + return; + } + dispatch_async(self.syncQueue, ^{ + NSMutableArray *deviceJSON = [NSMutableArray array]; + for (PSCComputeDevice *device in self.devices) { + [deviceJSON addObject:[device JSONObject]]; + } + NSString *hostname = NSProcessInfo.processInfo.hostName ?: @"iOS"; + [self send:@{ + @"type" : @"hello", + @"hostname" : hostname, + @"platform" : @"ios arm64", + @"devices" : deviceJSON, + }]; + NSString *exposed = self.devices.count == 0 ? @"no workers" : [[self.devices valueForKey:@"name"] componentsJoinedByString:@", "]; + [self notifyStatus:[NSString stringWithFormat:@"Connected to %@", self.connectURL] connected:YES]; + [self log:[NSString stringWithFormat:@"Hello sent (protocol %ld) — exposing %@", (long)kPSCProtocolVersion, exposed]]; + [self emitWorkers]; + dispatch_async(dispatch_get_main_queue(), ^{ + [self.pingTimer invalidate]; + __weak typeof(self) weakSelf = self; + self.pingTimer = [NSTimer scheduledTimerWithTimeInterval:kPSCPingInterval + repeats:YES + block:^(__unused NSTimer *timer) { + [weakSelf send:@{@"type" : @"ping"}]; + }]; + }); + [self listen]; + }); +} + +- (void)URLSession:(NSURLSession *)session + task:(NSURLSessionTask *)task +didCompleteWithError:(NSError *)error { + (void)session; + if (task != self.task && self.task != nil) { + return; + } + dispatch_async(self.syncQueue, ^{ + [self clearPing]; + [self killAll]; + if (self.task == task) { + self.task = nil; + } + if (error && error.code != NSURLErrorCancelled) { + [self log:[NSString stringWithFormat:@"WebSocket error: %@", error.localizedDescription]]; + } + [self notifyStatus:@"Disconnected" connected:NO]; + [self emitWorkers]; + }); +} + +- (void)listen { + NSURLSessionWebSocketTask *task = self.task; + if (!task) { + return; + } + __weak typeof(self) weakSelf = self; + [task receiveMessageWithCompletionHandler:^(NSURLSessionWebSocketMessage *message, NSError *error) { + PSCSlaveClient *strong = weakSelf; + if (!strong || task != strong.task) { + return; + } + if (error) { + return; + } + if (message.type == NSURLSessionWebSocketMessageTypeString && message.string) { + NSDictionary *msg = PSCDecodeMessage(message.string); + if (msg) { + [strong onServer:msg]; + } + } + [strong listen]; + }]; +} + +- (void)onServer:(NSDictionary *)msg { + NSString *type = msg[@"type"]; + if ([type isEqualToString:@"hello_ack"]) { + [self log:[NSString stringWithFormat:@"Master ack v%@", msg[@"protocolVersion"] ?: @"?"]]; + return; + } + if ([type isEqualToString:@"assign"]) { + PSCAssignBlock *block = [PSCAssignBlock fromJSON:msg]; + if (!block) { + [self log:@"Ignored malformed assign"]; + return; + } + [self runAssign:block]; + return; + } + if ([type isEqualToString:@"cancel"]) { + dispatch_async(self.syncQueue, ^{ + [self killAll]; + [self log:@"Cancel received"]; + [self notifyStatus:@"Idle (cancelled)" connected:YES]; + [self emitWorkers]; + }); + return; + } + if ([type isEqualToString:@"shutdown"]) { + dispatch_async(dispatch_get_main_queue(), ^{ + [self disconnect]; + }); + } +} + +- (id)engineForDevice:(PSCComputeDevice *)device { + if ([device.type isEqualToString:@"metal"]) { + return self.metal; + } + return self.cpu; +} + +- (PSCComputeDevice *)deviceForKey:(NSString *)key { + for (PSCComputeDevice *device in self.devices) { + if ([device.key isEqualToString:key]) { + return device; + } + } + for (PSCComputeDevice *device in self.devices) { + if ([device.type isEqualToString:@"metal"]) { + return device; + } + } + return self.devices.firstObject; +} + +- (void)runAssign:(PSCAssignBlock *)block { + dispatch_async(self.syncQueue, ^{ + PSCComputeDevice *matched = nil; + for (PSCComputeDevice *device in self.devices) { + if ([device.key isEqualToString:block.deviceKey]) { + matched = device; + break; + } + } + PSCComputeDevice *device = matched ?: [self deviceForKey:block.deviceKey]; + if (!device) { + [self send:@{@"type" : @"error", @"jobId" : block.jobId, @"message" : @"no compute device selected"}]; + return; + } + if (!matched) { + [self log:[NSString stringWithFormat:@"No device %@; using %@", block.deviceKey, device.name]]; + } + PSCDeviceJob *existing = self.jobs[device.key]; + if (existing.kill) { + existing.kill(); + } + [self clearProgressGate:device.key]; + [self log:[NSString stringWithFormat:@"Assigned %@ len=%u pad=0x%02x start=%llu count=%llu", device.name, + block.keyLen, block.padByte, (unsigned long long)block.start, + (unsigned long long)block.count]]; + NSDate *started = [NSDate date]; + __block NSInteger hitCount = 0; + id engine = [self engineForDevice:device]; + NSString *deviceKey = device.key; + NSString *jobId = block.jobId; + + PSCDeviceJob *job = [[PSCDeviceJob alloc] init]; + job.jobId = jobId; + job.done = 0; + job.count = block.count; + job.rate = 0; + __weak typeof(self) weakSelf = self; + job.kill = ^{ + [engine cancel]; + }; + self.jobs[deviceKey] = job; + [self beginRate:deviceKey]; + [self updateStatus]; + [self emitWorkers]; + + PSCBruteProgressBlock onProgress = ^(uint64_t done, uint64_t count, double rate) { + dispatch_async(weakSelf.syncQueue, ^{ + PSCDeviceJob *live = weakSelf.jobs[deviceKey]; + if (live && [live.jobId isEqualToString:jobId]) { + live.done = done; + live.count = count; + live.rate = rate; + } + [weakSelf sendProgressJob:jobId deviceKey:deviceKey done:done count:count rate:rate]; + [weakSelf updateStatus]; + [weakSelf emitWorkers]; + }); + }; + PSCBruteHitBlock onHit = ^(uint64_t index, uint64_t key, uint64_t plain) { + dispatch_async(weakSelf.syncQueue, ^{ + hitCount += 1; + NSString *indexS = PSCFormatDec64(index); + NSString *keyHex = PSCFormatHex64(key); + NSString *plainHex = PSCFormatHex64(plain); + [weakSelf send:@{ + @"type" : @"hit", + @"jobId" : jobId, + @"index" : indexS, + @"keyHex" : keyHex, + @"plainHex" : plainHex, + }]; + dispatch_async(dispatch_get_main_queue(), ^{ + [weakSelf.delegate slaveClient:weakSelf didHitJob:jobId index:indexS keyHex:keyHex]; + }); + }); + }; + [engine runAssign:block + onProgress:onProgress + onHit:onHit + completion:^(NSInteger hits, NSTimeInterval elapsed, BOOL cancelled, NSError *error) { + (void)hits; + dispatch_async(weakSelf.syncQueue, ^{ + PSCDeviceJob *still = weakSelf.jobs[deviceKey]; + BOOL same = still && [still.jobId isEqualToString:jobId]; + if (cancelled) { + if (same) { + [weakSelf endRate:deviceKey keys:still.done]; + [weakSelf.jobs removeObjectForKey:deviceKey]; + [weakSelf clearProgressGate:deviceKey]; + } + [weakSelf updateStatus]; + [weakSelf emitWorkers]; + return; + } + if (error) { + if (same) { + [weakSelf endRate:deviceKey keys:still.done]; + [weakSelf.jobs removeObjectForKey:deviceKey]; + [weakSelf clearProgressGate:deviceKey]; + } + [weakSelf send:@{ + @"type" : @"error", + @"jobId" : jobId, + @"message" : error.localizedDescription ?: @"brute failed", + }]; + [weakSelf log:error.localizedDescription]; + [weakSelf updateStatus]; + [weakSelf emitWorkers]; + return; + } + if (same) { + [weakSelf.jobs removeObjectForKey:deviceKey]; + [weakSelf endRate:deviceKey keys:block.count]; + NSInteger completed = weakSelf.completedBlocks[deviceKey].integerValue + 1; + weakSelf.completedBlocks[deviceKey] = @(completed); + [weakSelf clearProgressGate:deviceKey]; + [weakSelf send:@{ + @"type" : @"block_complete", + @"jobId" : jobId, + @"deviceKey" : deviceKey, + @"hits" : @(hitCount), + @"elapsed" : @(elapsed > 0 ? elapsed : ([NSDate date].timeIntervalSince1970 - started.timeIntervalSince1970)), + }]; + } + [weakSelf updateStatus]; + [weakSelf emitWorkers]; + }); + }]; + }); +} + +- (void)send:(NSDictionary *)msg { + NSString *json = PSCEncodeMessage(msg); + NSURLSessionWebSocketTask *task = self.task; + if (!json || !task || task.state != NSURLSessionTaskStateRunning) { + return; + } + NSURLSessionWebSocketMessage *message = [[NSURLSessionWebSocketMessage alloc] initWithString:json]; + [task sendMessage:message completionHandler:^(NSError *error) { + if (error) { + [self log:[NSString stringWithFormat:@"Send failed: %@", error.localizedDescription]]; + } + }]; +} + +- (PSCRateStats *)rateFor:(NSString *)deviceKey { + PSCRateStats *stats = self.rateStats[deviceKey]; + if (!stats) { + stats = [[PSCRateStats alloc] init]; + self.rateStats[deviceKey] = stats; + } + return stats; +} + +- (void)beginRate:(NSString *)deviceKey { + PSCRateStats *stats = [self rateFor:deviceKey]; + if (!stats.busyStarted) { + stats.busyStarted = [NSDate date]; + } +} + +- (void)endRate:(NSString *)deviceKey keys:(uint64_t)keys { + PSCRateStats *stats = [self rateFor:deviceKey]; + if (stats.busyStarted) { + stats.activeMs += [[NSDate date] timeIntervalSinceDate:stats.busyStarted] * 1000.0; + stats.busyStarted = nil; + } + if (keys > 0) { + stats.keysDone += keys; + } +} + +- (double)averageRate:(NSString *)deviceKey liveDone:(uint64_t)liveDone { + PSCRateStats *stats = [self rateFor:deviceKey]; + NSTimeInterval liveMs = stats.busyStarted ? [[NSDate date] timeIntervalSinceDate:stats.busyStarted] * 1000.0 : 0; + double sec = (stats.activeMs + liveMs) / 1000.0; + uint64_t keys = stats.keysDone + liveDone; + if (sec <= 0 || keys == 0) { + return 0; + } + return keys / sec; +} + +- (void)killAll { + NSArray *keys = self.jobs.allKeys; + for (NSString *deviceKey in keys) { + PSCDeviceJob *job = self.jobs[deviceKey]; + [self endRate:deviceKey keys:job.done]; + if (job.kill) { + job.kill(); + } + [self clearProgressGate:deviceKey]; + } + [self.jobs removeAllObjects]; +} + +- (void)clearPing { + dispatch_async(dispatch_get_main_queue(), ^{ + [self.pingTimer invalidate]; + self.pingTimer = nil; + }); +} + +- (PSCProgressGate *)progressGate:(NSString *)deviceKey { + PSCProgressGate *gate = self.progressGates[deviceKey]; + if (!gate) { + gate = [[PSCProgressGate alloc] init]; + self.progressGates[deviceKey] = gate; + } + return gate; +} + +- (void)sendProgressJob:(NSString *)jobId + deviceKey:(NSString *)deviceKey + done:(uint64_t)done + count:(uint64_t)count + rate:(double)rate { + PSCProgressGate *gate = [self progressGate:deviceKey]; + gate.payload = @{ + @"type" : @"progress", + @"jobId" : jobId, + @"deviceKey" : deviceKey, + @"done" : PSCFormatDec64(done), + @"count" : PSCFormatDec64(count), + @"rate" : @(rate), + }; + NSTimeInterval wait = kPSCProgressInterval - ([NSDate date].timeIntervalSince1970 - gate.lastSent); + if (wait <= 0) { + [self flushProgress:deviceKey]; + return; + } + if (!gate.timerPending) { + gate.timerPending = YES; + NSString *key = [deviceKey copy]; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(wait * NSEC_PER_SEC)), self.syncQueue, ^{ + PSCProgressGate *live = self.progressGates[key]; + live.timerPending = NO; + [self flushProgress:key]; + }); + } +} + +- (void)flushProgress:(NSString *)deviceKey { + PSCProgressGate *gate = self.progressGates[deviceKey]; + if (!gate.payload) { + return; + } + NSDictionary *payload = gate.payload; + PSCDeviceJob *live = self.jobs[deviceKey]; + if (!live || ![live.jobId isEqualToString:payload[@"jobId"]]) { + gate.payload = nil; + return; + } + gate.payload = nil; + gate.lastSent = [NSDate date].timeIntervalSince1970; + [self send:payload]; +} + +- (void)clearProgressGate:(NSString *)deviceKey { + PSCProgressGate *gate = self.progressGates[deviceKey]; + if (!gate) { + return; + } + gate.timerPending = NO; + gate.payload = nil; +} + +- (void)emitWorkers { + NSMutableArray *rows = [NSMutableArray array]; + for (PSCComputeDevice *device in self.devices) { + PSCDeviceJob *live = self.jobs[device.key]; + uint64_t done = live.done; + uint64_t count = live.count; + double pct = live && count > 0 ? (double)done * 100.0 / (double)count : 0; + PSCWorkerSnapshot *row = [[PSCWorkerSnapshot alloc] init]; + row.deviceKey = device.key; + row.deviceName = device.name; + row.deviceType = device.type; + row.current = live ? [NSString stringWithFormat:@"%@ / %@", PSCFormatCount(done), PSCFormatCount(count)] : @"idle"; + row.pct = pct; + row.completedBlocks = self.completedBlocks[device.key].integerValue; + row.rate = live.rate; + row.busy = live != nil; + [rows addObject:row]; + } + NSArray *snapshot = [rows copy]; + dispatch_async(dispatch_get_main_queue(), ^{ + [self.delegate slaveClient:self didUpdateWorkers:snapshot]; + }); +} + +- (void)updateStatus { + NSArray *busy = self.jobs.allValues; + BOOL connected = self.task != nil; + if (busy.count == 0) { + [self notifyStatus:connected ? @"Idle" : @"Disconnected" connected:connected]; + return; + } + if (busy.count == 1) { + PSCDeviceJob *job = busy.firstObject; + NSString *shortId = job.jobId.length > 8 ? [job.jobId substringToIndex:8] : job.jobId; + [self notifyStatus:[NSString stringWithFormat:@"Working %@ %llu/%llu", shortId, (unsigned long long)job.done, + (unsigned long long)job.count] + connected:YES]; + return; + } + [self notifyStatus:[NSString stringWithFormat:@"%lu workers assigned", (unsigned long)busy.count] connected:YES]; +} + +- (void)notifyStatus:(NSString *)status connected:(BOOL)connected { + dispatch_async(dispatch_get_main_queue(), ^{ + [self.delegate slaveClient:self didChangeStatus:status connected:connected]; + }); +} + +- (void)log:(NSString *)line { + dispatch_async(dispatch_get_main_queue(), ^{ + [self.delegate slaveClient:self didLog:line]; + }); +} + +@end diff --git a/ios/DESCracker/UI/PSCSlaveViewController.h b/ios/DESCracker/UI/PSCSlaveViewController.h new file mode 100644 index 0000000..74564f3 --- /dev/null +++ b/ios/DESCracker/UI/PSCSlaveViewController.h @@ -0,0 +1,5 @@ +#import +#import "PSCSlaveClient.h" + +@interface PSCSlaveViewController : UIViewController +@end diff --git a/ios/DESCracker/UI/PSCSlaveViewController.m b/ios/DESCracker/UI/PSCSlaveViewController.m new file mode 100644 index 0000000..ee8913a --- /dev/null +++ b/ios/DESCracker/UI/PSCSlaveViewController.m @@ -0,0 +1,619 @@ +#import "PSCSlaveViewController.h" + +static NSString *const kPSCHostKey = @"psc.masterHost"; +static NSString *const kPSCPortKey = @"psc.masterPort"; +static NSString *const kPSCMetalKey = @"psc.metalOn"; +static NSString *const kPSCCpuKey = @"psc.cpuOn"; +static NSString *const kPSCWorkersKey = @"psc.cpuWorkers"; + +static UIColor *PSCColorBG(void) { + return [UIColor colorWithRed:0x10 / 255.0 green:0x15 / 255.0 blue:0x1c / 255.0 alpha:1]; +} +static UIColor *PSCColorCard(void) { + return [UIColor colorWithRed:0x17 / 255.0 green:0x1e / 255.0 blue:0x28 / 255.0 alpha:1]; +} +static UIColor *PSCColorBorder(void) { + return [UIColor colorWithRed:0x2b / 255.0 green:0x35 / 255.0 blue:0x44 / 255.0 alpha:1]; +} +static UIColor *PSCColorText(void) { + return [UIColor colorWithRed:0xe8 / 255.0 green:0xee / 255.0 blue:0xf4 / 255.0 alpha:1]; +} +static UIColor *PSCColorMuted(void) { + return [UIColor colorWithRed:0x8b / 255.0 green:0x9a / 255.0 blue:0xab / 255.0 alpha:1]; +} +static UIColor *PSCColorTeal(void) { + return [UIColor colorWithRed:0x5e / 255.0 green:0xea / 255.0 blue:0xd4 / 255.0 alpha:1]; +} +static UIColor *PSCColorField(void) { + return [UIColor colorWithRed:0x0c / 255.0 green:0x11 / 255.0 blue:0x18 / 255.0 alpha:1]; +} + +@interface PSCWorkCard : UIView +@property (nonatomic, strong) UILabel *nameLabel; +@property (nonatomic, strong) UILabel *metaLabel; +@property (nonatomic, strong) UIProgressView *progress; +@property (nonatomic, copy) NSString *deviceKey; +@end + +@implementation PSCWorkCard +@end + +@interface PSCSlaveViewController () +@property (nonatomic, strong) PSCSlaveClient *client; +@property (nonatomic, strong) UIScrollView *scroll; +@property (nonatomic, strong) UIStackView *stack; +@property (nonatomic, strong) UILabel *statusPill; +@property (nonatomic, strong) UITextField *hostField; +@property (nonatomic, strong) UITextField *portField; +@property (nonatomic, strong) UIButton *connectBtn; +@property (nonatomic, strong) UIButton *disconnectBtn; +@property (nonatomic, strong) UISwitch *metalSwitch; +@property (nonatomic, strong) UISwitch *cpuSwitch; +@property (nonatomic, strong) UILabel *metalNameLabel; +@property (nonatomic, strong) UILabel *cpuNameLabel; +@property (nonatomic, strong) UILabel *workersValue; +@property (nonatomic, strong) UIStepper *workersStepper; +@property (nonatomic, strong) UIStackView *workStack; +@property (nonatomic, strong) UITextView *logView; +@property (nonatomic, strong) NSMutableString *logText; +@property (nonatomic, assign) BOOL metalReady; +@end + +@implementation PSCSlaveViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + self.view.backgroundColor = PSCColorBG(); + self.logText = [NSMutableString string]; + self.client = [[PSCSlaveClient alloc] init]; + self.client.delegate = self; + + [self buildUI]; + [self loadDefaults]; + [self applyConnectionEnabled:YES]; + + NSError *err = nil; + BOOL ok = [self.client prepareEngines:&err]; + self.metalReady = self.client.metalAvailable; + self.metalNameLabel.text = self.metalReady ? self.client.metalName : @"Metal GPU unavailable"; + self.metalSwitch.enabled = self.metalReady; + if (!self.metalReady) { + self.metalSwitch.on = NO; + } + unsigned cores = (unsigned)self.client.cpuCores; + self.cpuNameLabel.text = [NSString stringWithFormat:@"CPU (%u cores)", cores]; + self.workersStepper.minimumValue = 1; + self.workersStepper.maximumValue = MAX(1, cores); + if (self.workersStepper.value < 1 || self.workersStepper.value > cores) { + self.workersStepper.value = cores; + } + self.workersValue.text = [NSString stringWithFormat:@"%.0f", self.workersStepper.value]; + self.client.cpuWorkers = (NSUInteger)self.workersStepper.value; + + if (ok) { + [self appendLog:self.metalReady ? @"Metal and CPU self-tests passed." : @"CPU self-test passed. Metal unavailable."]; + } else { + [self appendLog:[NSString stringWithFormat:@"Engine setup: %@", err.localizedDescription ?: @"failed"]]; + } + [self rebuildDevices]; + [self rebuildWorkCards:@[]]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(appDidBackground) + name:UIApplicationDidEnterBackgroundNotification + object:nil]; +} + +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +- (void)appDidBackground { + if (self.client.isConnected) { + [self.client disconnect]; + [self applyConnectionEnabled:YES]; + [self appendLog:@"Disconnected (app backgrounded)."]; + } +} + +- (UILabel *)caption:(NSString *)text { + UILabel *label = [[UILabel alloc] init]; + label.text = text.uppercaseString; + label.textColor = PSCColorMuted(); + label.font = [UIFont systemFontOfSize:11 weight:UIFontWeightBold]; + return label; +} + +- (UIView *)cardWithTitle:(NSString *)title arranged:(NSArray *)views { + UIStackView *inner = [[UIStackView alloc] init]; + inner.axis = UILayoutConstraintAxisVertical; + inner.spacing = 10; + UILabel *titleLabel = [[UILabel alloc] init]; + titleLabel.text = title; + titleLabel.textColor = PSCColorText(); + titleLabel.font = [UIFont systemFontOfSize:15 weight:UIFontWeightSemibold]; + [inner addArrangedSubview:titleLabel]; + for (UIView *view in views) { + [inner addArrangedSubview:view]; + } + UIView *card = [[UIView alloc] init]; + card.backgroundColor = PSCColorCard(); + card.layer.cornerRadius = 12; + card.layer.borderWidth = 1; + card.layer.borderColor = PSCColorBorder().CGColor; + inner.translatesAutoresizingMaskIntoConstraints = NO; + [card addSubview:inner]; + [NSLayoutConstraint activateConstraints:@[ + [inner.topAnchor constraintEqualToAnchor:card.topAnchor constant:14], + [inner.leadingAnchor constraintEqualToAnchor:card.leadingAnchor constant:14], + [inner.trailingAnchor constraintEqualToAnchor:card.trailingAnchor constant:-14], + [inner.bottomAnchor constraintEqualToAnchor:card.bottomAnchor constant:-14], + ]]; + return card; +} + +- (UITextField *)fieldWithPlaceholder:(NSString *)placeholder keyboard:(UIKeyboardType)keyboard { + UITextField *field = [[UITextField alloc] init]; + field.placeholder = placeholder; + field.textColor = PSCColorText(); + field.keyboardType = keyboard; + field.autocapitalizationType = UITextAutocapitalizationTypeNone; + field.autocorrectionType = UITextAutocorrectionTypeNo; + field.spellCheckingType = UITextSpellCheckingTypeNo; + field.delegate = self; + field.returnKeyType = UIReturnKeyDone; + field.font = [UIFont monospacedSystemFontOfSize:15 weight:UIFontWeightRegular]; + field.backgroundColor = PSCColorField(); + field.layer.cornerRadius = 8; + field.layer.borderWidth = 1; + field.layer.borderColor = PSCColorBorder().CGColor; + field.leftView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 1)]; + field.leftViewMode = UITextFieldViewModeAlways; + field.rightView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 1)]; + field.rightViewMode = UITextFieldViewModeAlways; + field.attributedPlaceholder = [[NSAttributedString alloc] + initWithString:placeholder + attributes:@{NSForegroundColorAttributeName : PSCColorMuted()}]; + [field.heightAnchor constraintEqualToConstant:40].active = YES; + return field; +} + +- (UIButton *)buttonTitle:(NSString *)title primary:(BOOL)primary action:(SEL)action { + UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem]; + [btn setTitle:title forState:UIControlStateNormal]; + btn.titleLabel.font = [UIFont systemFontOfSize:15 weight:UIFontWeightSemibold]; + btn.layer.cornerRadius = 8; + [btn.heightAnchor constraintEqualToConstant:40].active = YES; + if (primary) { + btn.backgroundColor = PSCColorTeal(); + [btn setTitleColor:PSCColorBG() forState:UIControlStateNormal]; + } else { + btn.backgroundColor = PSCColorField(); + btn.layer.borderWidth = 1; + btn.layer.borderColor = PSCColorBorder().CGColor; + [btn setTitleColor:PSCColorText() forState:UIControlStateNormal]; + } + [btn addTarget:self action:action forControlEvents:UIControlEventTouchUpInside]; + return btn; +} + +- (UIView *)toggleRowSwitch:(UISwitch *)toggle name:(UILabel *)name { + UIStackView *row = [[UIStackView alloc] init]; + row.axis = UILayoutConstraintAxisHorizontal; + row.alignment = UIStackViewAlignmentCenter; + row.spacing = 10; + name.textColor = PSCColorText(); + name.font = [UIFont systemFontOfSize:14 weight:UIFontWeightMedium]; + name.numberOfLines = 2; + toggle.onTintColor = PSCColorTeal(); + [row addArrangedSubview:name]; + [row addArrangedSubview:toggle]; + [name setContentHuggingPriority:UILayoutPriorityDefaultLow forAxis:UILayoutConstraintAxisHorizontal]; + [toggle setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal]; + return row; +} + +- (void)buildUI { + self.scroll = [[UIScrollView alloc] init]; + self.scroll.translatesAutoresizingMaskIntoConstraints = NO; + self.scroll.alwaysBounceVertical = YES; + self.scroll.keyboardDismissMode = UIScrollViewKeyboardDismissModeOnDrag; + [self.view addSubview:self.scroll]; + + self.stack = [[UIStackView alloc] init]; + self.stack.axis = UILayoutConstraintAxisVertical; + self.stack.spacing = 16; + self.stack.translatesAutoresizingMaskIntoConstraints = NO; + [self.scroll addSubview:self.stack]; + + UILayoutGuide *safe = self.view.safeAreaLayoutGuide; + [NSLayoutConstraint activateConstraints:@[ + [self.scroll.topAnchor constraintEqualToAnchor:safe.topAnchor], + [self.scroll.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor], + [self.scroll.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor], + [self.scroll.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor], + [self.stack.topAnchor constraintEqualToAnchor:self.scroll.contentLayoutGuide.topAnchor constant:16], + [self.stack.leadingAnchor constraintEqualToAnchor:self.scroll.frameLayoutGuide.leadingAnchor constant:16], + [self.stack.trailingAnchor constraintEqualToAnchor:self.scroll.frameLayoutGuide.trailingAnchor constant:-16], + [self.stack.bottomAnchor constraintEqualToAnchor:self.scroll.contentLayoutGuide.bottomAnchor constant:-24], + ]]; + + UIStackView *header = [[UIStackView alloc] init]; + header.axis = UILayoutConstraintAxisHorizontal; + header.alignment = UIStackViewAlignmentCenter; + header.spacing = 12; + + UIStackView *brand = [[UIStackView alloc] init]; + brand.axis = UILayoutConstraintAxisVertical; + brand.spacing = 2; + UILabel *title = [[UILabel alloc] init]; + title.text = @"DES Cracker"; + title.textColor = PSCColorTeal(); + title.font = [UIFont systemFontOfSize:22 weight:UIFontWeightBold]; + UILabel *sub = [[UILabel alloc] init]; + sub.text = @"LAN WORKER"; + sub.textColor = PSCColorMuted(); + sub.font = [UIFont systemFontOfSize:10 weight:UIFontWeightBold]; + [brand addArrangedSubview:title]; + [brand addArrangedSubview:sub]; + + self.statusPill = [[UILabel alloc] init]; + self.statusPill.text = @"Offline"; + self.statusPill.textAlignment = NSTextAlignmentCenter; + self.statusPill.textColor = PSCColorMuted(); + self.statusPill.font = [UIFont systemFontOfSize:12 weight:UIFontWeightSemibold]; + self.statusPill.backgroundColor = PSCColorField(); + self.statusPill.layer.cornerRadius = 12; + self.statusPill.layer.masksToBounds = YES; + self.statusPill.layer.borderWidth = 1; + self.statusPill.layer.borderColor = PSCColorBorder().CGColor; + [self.statusPill.widthAnchor constraintGreaterThanOrEqualToConstant:96].active = YES; + [self.statusPill.heightAnchor constraintEqualToConstant:28].active = YES; + [header addArrangedSubview:brand]; + [header addArrangedSubview:self.statusPill]; + [brand setContentHuggingPriority:UILayoutPriorityDefaultLow forAxis:UILayoutConstraintAxisHorizontal]; + + self.hostField = [self fieldWithPlaceholder:@"192.168.1.10" keyboard:UIKeyboardTypeNumbersAndPunctuation]; + self.portField = [self fieldWithPlaceholder:@"9876" keyboard:UIKeyboardTypeNumberPad]; + [self.portField.widthAnchor constraintEqualToConstant:88].active = YES; + UIStackView *hostRow = [[UIStackView alloc] init]; + hostRow.axis = UILayoutConstraintAxisHorizontal; + hostRow.spacing = 8; + UIStackView *hostCol = [[UIStackView alloc] init]; + hostCol.axis = UILayoutConstraintAxisVertical; + hostCol.spacing = 4; + [hostCol addArrangedSubview:[self caption:@"Master host"]]; + [hostCol addArrangedSubview:self.hostField]; + UIStackView *portCol = [[UIStackView alloc] init]; + portCol.axis = UILayoutConstraintAxisVertical; + portCol.spacing = 4; + [portCol addArrangedSubview:[self caption:@"Port"]]; + [portCol addArrangedSubview:self.portField]; + [hostRow addArrangedSubview:hostCol]; + [hostRow addArrangedSubview:portCol]; + + self.connectBtn = [self buttonTitle:@"Connect" primary:YES action:@selector(connectTapped)]; + self.disconnectBtn = [self buttonTitle:@"Disconnect" primary:NO action:@selector(disconnectTapped)]; + UIStackView *btnRow = [[UIStackView alloc] init]; + btnRow.axis = UILayoutConstraintAxisHorizontal; + btnRow.spacing = 8; + btnRow.distribution = UIStackViewDistributionFillEqually; + [btnRow addArrangedSubview:self.connectBtn]; + [btnRow addArrangedSubview:self.disconnectBtn]; + + UIView *connectCard = [self cardWithTitle:@"Connection" arranged:@[ hostRow, btnRow ]]; + + self.metalSwitch = [[UISwitch alloc] init]; + self.metalSwitch.on = YES; + [self.metalSwitch addTarget:self action:@selector(devicesChanged) forControlEvents:UIControlEventValueChanged]; + self.metalNameLabel = [[UILabel alloc] init]; + self.metalNameLabel.text = @"Metal GPU"; + UIView *metalRow = [self toggleRowSwitch:self.metalSwitch name:self.metalNameLabel]; + + self.cpuSwitch = [[UISwitch alloc] init]; + self.cpuSwitch.on = YES; + [self.cpuSwitch addTarget:self action:@selector(devicesChanged) forControlEvents:UIControlEventValueChanged]; + self.cpuNameLabel = [[UILabel alloc] init]; + self.cpuNameLabel.text = @"CPU"; + UIView *cpuRow = [self toggleRowSwitch:self.cpuSwitch name:self.cpuNameLabel]; + + self.workersStepper = [[UIStepper alloc] init]; + self.workersStepper.minimumValue = 1; + self.workersStepper.maximumValue = 16; + self.workersStepper.stepValue = 1; + self.workersStepper.value = 4; + [self.workersStepper addTarget:self action:@selector(workersChanged) forControlEvents:UIControlEventValueChanged]; + self.workersValue = [[UILabel alloc] init]; + self.workersValue.textColor = PSCColorText(); + self.workersValue.font = [UIFont monospacedDigitSystemFontOfSize:14 weight:UIFontWeightMedium]; + UIStackView *workerRow = [[UIStackView alloc] init]; + workerRow.axis = UILayoutConstraintAxisHorizontal; + workerRow.alignment = UIStackViewAlignmentCenter; + workerRow.spacing = 8; + UILabel *workersCaption = [[UILabel alloc] init]; + workersCaption.text = @"CPU workers"; + workersCaption.textColor = PSCColorMuted(); + workersCaption.font = [UIFont systemFontOfSize:13 weight:UIFontWeightMedium]; + [workerRow addArrangedSubview:workersCaption]; + [workerRow addArrangedSubview:[[UIView alloc] init]]; + [workerRow addArrangedSubview:self.workersValue]; + [workerRow addArrangedSubview:self.workersStepper]; + + UILabel *hint = [[UILabel alloc] init]; + hint.text = @"Block size is set on the master. This device only exposes workers."; + hint.textColor = PSCColorMuted(); + hint.font = [UIFont systemFontOfSize:12]; + hint.numberOfLines = 0; + + UIView *deviceCard = [self cardWithTitle:@"Devices" arranged:@[ metalRow, cpuRow, workerRow, hint ]]; + + self.workStack = [[UIStackView alloc] init]; + self.workStack.axis = UILayoutConstraintAxisVertical; + self.workStack.spacing = 10; + UIView *workCard = [self cardWithTitle:@"Assigned work" arranged:@[ self.workStack ]]; + + self.logView = [[UITextView alloc] init]; + self.logView.editable = NO; + self.logView.backgroundColor = PSCColorField(); + self.logView.textColor = PSCColorText(); + self.logView.font = [UIFont monospacedSystemFontOfSize:11 weight:UIFontWeightRegular]; + self.logView.layer.cornerRadius = 8; + self.logView.layer.borderWidth = 1; + self.logView.layer.borderColor = PSCColorBorder().CGColor; + self.logView.textContainerInset = UIEdgeInsetsMake(8, 6, 8, 6); + [self.logView.heightAnchor constraintEqualToConstant:180].active = YES; + UIView *logCard = [self cardWithTitle:@"Log" arranged:@[ self.logView ]]; + + [self.stack addArrangedSubview:header]; + [self.stack addArrangedSubview:connectCard]; + [self.stack addArrangedSubview:deviceCard]; + [self.stack addArrangedSubview:workCard]; + [self.stack addArrangedSubview:logCard]; +} + +- (void)loadDefaults { + NSUserDefaults *defaults = NSUserDefaults.standardUserDefaults; + NSString *host = [defaults stringForKey:kPSCHostKey]; + self.hostField.text = host.length ? host : @""; + NSInteger port = [defaults integerForKey:kPSCPortKey]; + self.portField.text = port > 0 ? [NSString stringWithFormat:@"%ld", (long)port] : @"9876"; + if ([defaults objectForKey:kPSCMetalKey] != nil) { + self.metalSwitch.on = [defaults boolForKey:kPSCMetalKey]; + } + if ([defaults objectForKey:kPSCCpuKey] != nil) { + self.cpuSwitch.on = [defaults boolForKey:kPSCCpuKey]; + } + NSInteger workers = [defaults integerForKey:kPSCWorkersKey]; + if (workers > 0) { + self.workersStepper.value = workers; + } +} + +- (void)saveDefaults { + NSUserDefaults *defaults = NSUserDefaults.standardUserDefaults; + [defaults setObject:self.hostField.text ?: @"" forKey:kPSCHostKey]; + [defaults setInteger:self.portField.text.integerValue forKey:kPSCPortKey]; + [defaults setBool:self.metalSwitch.on forKey:kPSCMetalKey]; + [defaults setBool:self.cpuSwitch.on forKey:kPSCCpuKey]; + [defaults setInteger:(NSInteger)self.workersStepper.value forKey:kPSCWorkersKey]; +} + +- (void)applyConnectionEnabled:(BOOL)canConnect { + self.connectBtn.enabled = canConnect; + self.connectBtn.alpha = canConnect ? 1 : 0.45; + self.disconnectBtn.enabled = !canConnect; + self.disconnectBtn.alpha = canConnect ? 0.45 : 1; + self.hostField.enabled = canConnect; + self.portField.enabled = canConnect; +} + +- (NSArray *)selectedDevices { + NSMutableArray *devices = [NSMutableArray array]; + if (self.metalSwitch.on && self.metalReady) { + PSCComputeDevice *gpu = [[PSCComputeDevice alloc] init]; + gpu.key = @"metal:0"; + gpu.deviceId = 0; + gpu.name = self.client.metalName ?: @"Metal GPU"; + gpu.type = @"metal"; + if (self.client.metalMemory > 0) { + gpu.memory = @(self.client.metalMemory); + } + [devices addObject:gpu]; + } + if (self.cpuSwitch.on) { + PSCComputeDevice *cpu = [[PSCComputeDevice alloc] init]; + cpu.key = @"cpu:0"; + cpu.deviceId = 0; + cpu.name = [NSString stringWithFormat:@"CPU (%lu cores)", (unsigned long)self.client.cpuCores]; + cpu.type = @"cpu"; + cpu.cores = @(self.client.cpuCores); + [devices addObject:cpu]; + } + return devices; +} + +- (void)rebuildDevices { + self.client.cpuWorkers = (NSUInteger)self.workersStepper.value; + [self.client setDevices:[self selectedDevices]]; +} + +- (void)rebuildWorkCards:(NSArray *)workers { + for (UIView *view in self.workStack.arrangedSubviews) { + [self.workStack removeArrangedSubview:view]; + [view removeFromSuperview]; + } + if (workers.count == 0) { + NSArray *selected = [self selectedDevices]; + if (selected.count == 0) { + UILabel *empty = [[UILabel alloc] init]; + empty.text = @"Enable Metal and/or CPU to expose workers."; + empty.textColor = PSCColorMuted(); + empty.font = [UIFont systemFontOfSize:13]; + empty.numberOfLines = 0; + [self.workStack addArrangedSubview:empty]; + return; + } + NSMutableArray *idle = [NSMutableArray array]; + for (PSCComputeDevice *device in selected) { + PSCWorkerSnapshot *row = [[PSCWorkerSnapshot alloc] init]; + row.deviceKey = device.key; + row.deviceName = device.name; + row.deviceType = device.type; + row.current = @"idle"; + [idle addObject:row]; + } + workers = idle; + } + for (PSCWorkerSnapshot *row in workers) { + PSCWorkCard *card = [[PSCWorkCard alloc] init]; + card.deviceKey = row.deviceKey; + UILabel *name = [[UILabel alloc] init]; + name.text = row.deviceName; + name.textColor = PSCColorText(); + name.font = [UIFont systemFontOfSize:14 weight:UIFontWeightSemibold]; + UILabel *meta = [[UILabel alloc] init]; + meta.textColor = PSCColorMuted(); + meta.font = [UIFont monospacedDigitSystemFontOfSize:12 weight:UIFontWeightRegular]; + meta.numberOfLines = 2; + UIProgressView *progress = [[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleDefault]; + progress.progressTintColor = PSCColorTeal(); + progress.trackTintColor = PSCColorBorder(); + card.nameLabel = name; + card.metaLabel = meta; + card.progress = progress; + UIStackView *col = [[UIStackView alloc] initWithArrangedSubviews:@[ name, progress, meta ]]; + col.axis = UILayoutConstraintAxisVertical; + col.spacing = 6; + col.translatesAutoresizingMaskIntoConstraints = NO; + [card addSubview:col]; + [NSLayoutConstraint activateConstraints:@[ + [col.topAnchor constraintEqualToAnchor:card.topAnchor], + [col.leadingAnchor constraintEqualToAnchor:card.leadingAnchor], + [col.trailingAnchor constraintEqualToAnchor:card.trailingAnchor], + [col.bottomAnchor constraintEqualToAnchor:card.bottomAnchor], + ]]; + [self.workStack addArrangedSubview:card]; + [self applySnapshot:row toCard:card]; + } +} + +- (void)applySnapshot:(PSCWorkerSnapshot *)row toCard:(PSCWorkCard *)card { + card.nameLabel.text = row.deviceName; + card.progress.progress = (float)(row.pct / 100.0); + NSString *rate = row.busy ? PSCFormatRate(row.rate) : @"—"; + card.metaLabel.text = [NSString stringWithFormat:@"%@ %@ %ld blocks", row.current, rate, (long)row.completedBlocks]; +} + +- (void)connectTapped { + [self.view endEditing:YES]; + NSString *host = [self.hostField.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + NSInteger port = self.portField.text.integerValue; + if (host.length == 0) { + [self appendLog:@"Enter the master’s IP address."]; + return; + } + if (port <= 0 || port > 65535) { + port = kPSCDefaultPort; + self.portField.text = [NSString stringWithFormat:@"%ld", (long)port]; + } + if ([self selectedDevices].count == 0) { + [self appendLog:@"Enable at least one device before connecting."]; + return; + } + [self saveDefaults]; + [self rebuildDevices]; + [self applyConnectionEnabled:NO]; + [self.client connectToHost:host port:port]; +} + +- (void)disconnectTapped { + [self.client disconnect]; + [self applyConnectionEnabled:YES]; +} + +- (void)devicesChanged { + [self saveDefaults]; + [self rebuildDevices]; +} + +- (void)workersChanged { + self.workersValue.text = [NSString stringWithFormat:@"%.0f", self.workersStepper.value]; + [self saveDefaults]; + [self rebuildDevices]; +} + +- (void)appendLog:(NSString *)line { + NSDateFormatter *fmt = [[NSDateFormatter alloc] init]; + fmt.dateFormat = @"HH:mm:ss"; + NSString *stamp = [fmt stringFromDate:[NSDate date]]; + [self.logText appendFormat:@"[%@] %@\n", stamp, line]; + self.logView.text = self.logText; + if (self.logView.text.length > 0) { + NSRange bottom = NSMakeRange(self.logView.text.length - 1, 1); + [self.logView scrollRangeToVisible:bottom]; + } +} + +- (void)setStatus:(NSString *)status connected:(BOOL)connected { + self.statusPill.text = [NSString stringWithFormat:@" %@ ", status]; + if (connected) { + self.statusPill.textColor = PSCColorBG(); + self.statusPill.backgroundColor = PSCColorTeal(); + self.statusPill.layer.borderColor = PSCColorTeal().CGColor; + } else { + self.statusPill.textColor = PSCColorMuted(); + self.statusPill.backgroundColor = PSCColorField(); + self.statusPill.layer.borderColor = PSCColorBorder().CGColor; + } + if ([status isEqualToString:@"Disconnected"] || [status isEqualToString:@"Offline"]) { + [self applyConnectionEnabled:YES]; + } +} + +- (BOOL)textFieldShouldReturn:(UITextField *)textField { + [textField resignFirstResponder]; + return YES; +} + +#pragma mark - PSCSlaveClientDelegate + +- (void)slaveClient:(PSCSlaveClient *)client didChangeStatus:(NSString *)status connected:(BOOL)connected { + (void)client; + [self setStatus:status connected:connected]; + [UIApplication sharedApplication].idleTimerDisabled = connected; +} + +- (void)slaveClient:(PSCSlaveClient *)client didLog:(NSString *)line { + (void)client; + [self appendLog:line]; +} + +- (void)slaveClient:(PSCSlaveClient *)client didUpdateWorkers:(NSArray *)workers { + (void)client; + if (self.workStack.arrangedSubviews.count != workers.count) { + [self rebuildWorkCards:workers]; + return; + } + for (NSUInteger i = 0; i < workers.count; i++) { + UIView *view = self.workStack.arrangedSubviews[i]; + if (![view isKindOfClass:[PSCWorkCard class]]) { + [self rebuildWorkCards:workers]; + return; + } + PSCWorkCard *card = (PSCWorkCard *)view; + PSCWorkerSnapshot *row = workers[i]; + if (![card.deviceKey isEqualToString:row.deviceKey]) { + [self rebuildWorkCards:workers]; + return; + } + [self applySnapshot:row toCard:card]; + } +} + +- (void)slaveClient:(PSCSlaveClient *)client didHitJob:(NSString *)jobId index:(NSString *)index keyHex:(NSString *)keyHex { + (void)client; + (void)jobId; + [self appendLog:[NSString stringWithFormat:@"HIT index=%@ key=%@", index, keyHex]]; +} + +@end diff --git a/ios/DESCracker/main.m b/ios/DESCracker/main.m new file mode 100644 index 0000000..e4a9eb6 --- /dev/null +++ b/ios/DESCracker/main.m @@ -0,0 +1,8 @@ +#import +#import "AppDelegate.h" + +int main(int argc, char *argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} diff --git a/src/net/slave.ts b/src/net/slave.ts index a1a7eba..ff2e9d4 100644 --- a/src/net/slave.ts +++ b/src/net/slave.ts @@ -4,9 +4,19 @@ import type { ComputeDevice } from "../search/nativeRunner"; import { preferredDevice, runBrute } from "../search/nativeRunner"; import { localWorkerId, type WorkerRow } from "../search/orchestrator"; import { formatNumber, formatRate } from "../search/planner"; -import { decodeMsg, encodeMsg, type AssignBlock, type ServerMessage } from "./protocol"; +import { decodeMsg, encodeMsg, type AssignBlock, type ClientMessage, type ServerMessage } from "./protocol"; import { WebSocket } from "ws"; +const WS_PROGRESS_INTERVAL_MS = 500; + +type ProgressPayload = Extract; + +interface ProgressGate { + lastSent: number; + timer: NodeJS.Timeout | null; + payload: ProgressPayload | null; +} + export interface SlaveEvents { onLog?: (line: string) => void; onStatus?: (text: string) => void; @@ -28,6 +38,7 @@ export class SlaveClient { private ping: NodeJS.Timeout | null = null; private jobs = new Map(); private completedBlocks = new Map(); + private progressGates = new Map(); private rateStats = new Map(); private devices: ComputeDevice[] = []; private cpuWorkers = Math.max(1, os.cpus().length); @@ -157,6 +168,7 @@ export class SlaveClient { for (const [deviceKey, job] of this.jobs) { this.endRate(deviceKey, job.done); job.kill(); + this.clearProgressGate(deviceKey); } this.jobs.clear(); } @@ -174,6 +186,66 @@ export class SlaveClient { } } + private progressGate(deviceKey: string): ProgressGate { + let gate = this.progressGates.get(deviceKey); + if (!gate) { + gate = { lastSent: 0, timer: null, payload: null }; + this.progressGates.set(deviceKey, gate); + } + return gate; + } + + private sendProgress(jobId: string, deviceKey: string, done: bigint, count: bigint, rate: number): void { + const gate = this.progressGate(deviceKey); + gate.payload = { + type: "progress", + jobId, + deviceKey, + done: done.toString(), + count: count.toString(), + rate, + }; + const wait = WS_PROGRESS_INTERVAL_MS - (Date.now() - gate.lastSent); + if (wait <= 0) { + this.flushProgress(deviceKey); + return; + } + if (!gate.timer) { + gate.timer = setTimeout(() => { + gate.timer = null; + this.flushProgress(deviceKey); + }, wait); + } + } + + private flushProgress(deviceKey: string): void { + const gate = this.progressGates.get(deviceKey); + if (!gate?.payload) { + return; + } + const payload = gate.payload; + const live = this.jobs.get(deviceKey); + if (!live || live.jobId !== payload.jobId) { + gate.payload = null; + return; + } + gate.payload = null; + gate.lastSent = Date.now(); + this.send(payload); + } + + private clearProgressGate(deviceKey: string): void { + const gate = this.progressGates.get(deviceKey); + if (!gate) { + return; + } + if (gate.timer) { + clearTimeout(gate.timer); + gate.timer = null; + } + gate.payload = null; + } + private emitWorkers(): void { const host = os.hostname(); const rows: WorkerRow[] = this.devices.map((device) => { @@ -250,6 +322,7 @@ export class SlaveClient { this.events.onLog?.(`No device ${block.deviceKey}; using ${device.name}`); } this.jobs.get(device.key)?.kill(); + this.clearProgressGate(device.key); this.events.onLog?.( `Assigned ${device.name} len=${block.keyLen} pad=0x${block.padByte.toString(16).padStart(2, "0")} ` + `start=${block.start} count=${block.count}`, @@ -276,14 +349,7 @@ export class SlaveClient { live.count = count; live.rate = rate; } - this.send({ - type: "progress", - jobId: block.jobId, - deviceKey: device.key, - done: done.toString(), - count: count.toString(), - rate, - }); + this.sendProgress(block.jobId, device.key, done, count, rate); this.events.onProgress?.(block.jobId, done, count, rate); this.updateStatus(); this.emitWorkers(); @@ -319,6 +385,7 @@ export class SlaveClient { this.jobs.delete(device.key); this.endRate(device.key, BigInt(block.count)); this.completedBlocks.set(device.key, (this.completedBlocks.get(device.key) ?? 0) + 1); + this.clearProgressGate(device.key); this.send({ type: "block_complete", jobId: block.jobId, @@ -335,6 +402,7 @@ export class SlaveClient { const live = this.jobs.get(device.key); this.endRate(device.key, live?.done ?? 0n); this.jobs.delete(device.key); + this.clearProgressGate(device.key); } this.send({ type: "error", jobId: block.jobId, message }); this.events.onLog?.(message); diff --git a/src/search/orchestrator.ts b/src/search/orchestrator.ts index 241c0bb..1037f3c 100644 --- a/src/search/orchestrator.ts +++ b/src/search/orchestrator.ts @@ -72,30 +72,53 @@ export interface WorkerRow { busy: boolean; } +interface KeyRange { + start: bigint; + count: bigint; +} + +interface ReservedBlock extends KeyRange { + workerId: string; +} + class BlockAllocator { private next = 0n; - private inflight = new Map(); - private retries: Array<{ start: bigint; count: bigint }> = []; + private inflight = new Map(); + private retries: KeyRange[] = []; completed = 0n; constructor(readonly space: bigint) {} - take(blockSize: bigint): { jobId: string; start: bigint; count: bigint } | null { + private leftover(): bigint { + return this.next >= this.space ? 0n : this.space - this.next; + } + + /** Next unallocated slice, reserved for `workerId` until complete/fail. */ + take( + workerId: string, + blockSize: bigint, + otherIdle = 0, + ): { jobId: string; start: bigint; count: bigint } | null { const queued = this.retries.shift(); if (queued) { const jobId = randomUUID(); - this.inflight.set(jobId, queued); + this.inflight.set(jobId, { ...queued, workerId }); return { jobId, ...queued }; } - if (this.next >= this.space) { + const leftover = this.leftover(); + if (leftover <= 0n) { return null; } - const size = blockSize < 1n ? 1n : blockSize; + const wanted = blockSize < 1n ? 1n : blockSize; + let count = wanted < leftover ? wanted : leftover; + if (count === leftover && otherIdle > 0 && leftover > 1n) { + const share = leftover / BigInt(otherIdle + 1); + count = share > 0n ? share : 1n; + } const start = this.next; - const count = size < this.space - start ? size : this.space - start; this.next += count; const jobId = randomUUID(); - this.inflight.set(jobId, { start, count }); + this.inflight.set(jobId, { start, count, workerId }); return { jobId, start, count }; } @@ -114,7 +137,7 @@ class BlockAllocator { return; } this.inflight.delete(jobId); - this.retries.push(block); + this.retries.push({ start: block.start, count: block.count }); } get finished(): boolean { @@ -194,6 +217,7 @@ export class MasterSearch { private stageTotal = 1n; private stage: StageJob | null = null; private dispatch: (() => void) | null = null; + private feedWorker: ((worker: TrackedWorker) => boolean) | null = null; private stageAlloc: BlockAllocator | null = null; constructor( @@ -252,6 +276,7 @@ export class MasterSearch { } this.unsub = []; this.dispatch = null; + this.feedWorker = null; } const elapsed = (Date.now() - this.started) / 1000; @@ -350,6 +375,9 @@ export class MasterSearch { } this.stageAlloc?.complete(jobId); this.emitProgress(); + if (tracked) { + this.feedWorker?.(tracked); + } this.dispatch?.(); }; this.server.events.onDropped = (slave, jobId) => { @@ -410,60 +438,80 @@ export class MasterSearch { } }; - const dispatch = () => { - if (this.cancel) { - checkDone(); - return; - } + const idleWorkers = (): TrackedWorker[] => { + const idle: TrackedWorker[] = []; for (const device of this.params.devices) { if (!localIdle.has(device.key)) { continue; } const worker = this.roster.get(localWorkerId(device.key)); - if (!worker) { - continue; + if (worker && !worker.busy) { + idle.push(worker); } - const block = alloc.take(this.sizeFor(worker)); - if (!block) { - break; - } - localIdle.delete(device.key); - this.startLocal(device, job, block, data, firstCt, alloc, localIdle, dispatch, checkDone); } if (this.server) { for (const remote of this.server.idleWorkers()) { const worker = this.roster.get(remoteWorkerId(remote.slaveId, remote.device.key)); - if (!worker) { - continue; - } - const block = alloc.take(this.sizeFor(worker)); - if (!block) { - break; - } - const ok = this.server.assign(remote.slaveId, remote.device.key, { - jobId: block.jobId, - deviceKey: remote.device.key, - start: block.start.toString(), - count: block.count.toString(), - keyLen: job.length, - charset: this.params.charset, - padByte: job.pad, - target: PAD_CT.toString("hex"), - fills: FILL_HEX, - cpuWorkers: remote.device.type === "cpu" ? this.params.cpuWorkersFor(worker.id) : undefined, - }); - if (!ok) { - alloc.fail(block.jobId); - } else { - worker.jobId = block.jobId; - worker.busy = true; - worker.done = 0n; - worker.count = block.count; - worker.rate = 0; - beginWork(worker); + if (worker && !worker.busy) { + idle.push(worker); } } } + return idle; + }; + + const feedWorker = (worker: TrackedWorker): boolean => { + if (this.cancel || worker.busy) { + return false; + } + const others = idleWorkers().filter((w) => w.id !== worker.id).length; + const block = alloc.take(worker.id, this.sizeFor(worker), others); + if (!block) { + return false; + } + if (worker.slaveId && this.server) { + const ok = this.server.assign(worker.slaveId, worker.deviceKey, { + jobId: block.jobId, + deviceKey: worker.deviceKey, + start: block.start.toString(), + count: block.count.toString(), + keyLen: job.length, + charset: this.params.charset, + padByte: job.pad, + target: PAD_CT.toString("hex"), + fills: FILL_HEX, + cpuWorkers: worker.deviceType === "cpu" ? this.params.cpuWorkersFor(worker.id) : undefined, + }); + if (!ok) { + alloc.fail(block.jobId); + return false; + } + worker.jobId = block.jobId; + worker.busy = true; + worker.done = 0n; + worker.count = block.count; + worker.rate = 0; + beginWork(worker); + return true; + } + const device = this.params.devices.find((d) => d.key === worker.deviceKey); + if (!device || !localIdle.has(device.key)) { + alloc.fail(block.jobId); + return false; + } + localIdle.delete(device.key); + this.startLocal(device, job, block, data, firstCt, alloc, localIdle, checkDone); + return true; + }; + + const dispatch = () => { + if (this.cancel) { + checkDone(); + return; + } + for (const worker of idleWorkers()) { + feedWorker(worker); + } this.emitProgress(); const hasRemote = !!this.server && this.server.listWorkers().length > 0; if ( @@ -480,10 +528,12 @@ export class MasterSearch { checkDone(); }; + this.feedWorker = feedWorker; this.dispatch = dispatch; dispatch(); await finished; this.dispatch = null; + this.feedWorker = null; this.stageAlloc = null; this.stageCompleted = job.space; this.emitProgress(); @@ -497,7 +547,6 @@ export class MasterSearch { firstCt: Buffer, alloc: BlockAllocator, localIdle: Set, - dispatch: () => void, checkDone: () => void, ): void { const id = localWorkerId(device.key); @@ -576,7 +625,10 @@ export class MasterSearch { } this.stageCompleted = alloc.completed; this.emitProgress(); - dispatch(); + if (keepDevice && live && !this.cancel) { + this.feedWorker?.(live); + } + this.dispatch?.(); checkDone(); }); }