Implement initial iOS application structure for DES Cracker
- Added AppDelegate and main entry point for the application. - Created Info.plist for application configuration. - Introduced UI components with PSCSlaveViewController for user interaction. - Implemented networking and computation engines (CPU and Metal) for DES cracking. - Established asset catalog for application resources including icons and colors. - Updated results.json to reflect new elapsed time for brute force operations.
This commit is contained in:
parent
8f2d3a2489
commit
bb690f3a1f
@ -1,5 +1,5 @@
|
||||
{
|
||||
"elapsed_sec": 61.97,
|
||||
"elapsed_sec": 46.262,
|
||||
"pad_matches": [],
|
||||
"confirmed": []
|
||||
}
|
||||
|
||||
431
ios/DESCracker.xcodeproj/project.pbxproj
Normal file
431
ios/DESCracker.xcodeproj/project.pbxproj
Normal file
@ -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 = "<group>"; };
|
||||
A20000000000000000000002 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
|
||||
A20000000000000000000003 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
|
||||
A20000000000000000000004 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
A20000000000000000000005 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
A20000000000000000000006 /* PSCSlaveViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PSCSlaveViewController.h; sourceTree = "<group>"; };
|
||||
A20000000000000000000007 /* PSCSlaveViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PSCSlaveViewController.m; sourceTree = "<group>"; };
|
||||
A20000000000000000000008 /* PSCProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PSCProtocol.h; sourceTree = "<group>"; };
|
||||
A20000000000000000000009 /* PSCProtocol.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PSCProtocol.m; sourceTree = "<group>"; };
|
||||
A20000000000000000000010 /* PSCSlaveClient.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PSCSlaveClient.h; sourceTree = "<group>"; };
|
||||
A20000000000000000000011 /* PSCSlaveClient.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PSCSlaveClient.m; sourceTree = "<group>"; };
|
||||
A20000000000000000000012 /* PSCBruteEngine.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PSCBruteEngine.h; sourceTree = "<group>"; };
|
||||
A20000000000000000000013 /* PSCMetalEngine.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PSCMetalEngine.h; sourceTree = "<group>"; };
|
||||
A20000000000000000000014 /* PSCMetalEngine.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PSCMetalEngine.m; sourceTree = "<group>"; };
|
||||
A20000000000000000000015 /* PSCCpuEngine.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PSCCpuEngine.h; sourceTree = "<group>"; };
|
||||
A20000000000000000000016 /* PSCCpuEngine.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PSCCpuEngine.m; sourceTree = "<group>"; };
|
||||
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 = "<group>";
|
||||
};
|
||||
A30000000000000000000002 /* UI */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A20000000000000000000006 /* PSCSlaveViewController.h */,
|
||||
A20000000000000000000007 /* PSCSlaveViewController.m */,
|
||||
);
|
||||
path = UI;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
A30000000000000000000003 /* Network */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A20000000000000000000008 /* PSCProtocol.h */,
|
||||
A20000000000000000000009 /* PSCProtocol.m */,
|
||||
A20000000000000000000010 /* PSCSlaveClient.h */,
|
||||
A20000000000000000000011 /* PSCSlaveClient.m */,
|
||||
);
|
||||
path = Network;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
A30000000000000000000004 /* Compute */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A20000000000000000000012 /* PSCBruteEngine.h */,
|
||||
A20000000000000000000013 /* PSCMetalEngine.h */,
|
||||
A20000000000000000000014 /* PSCMetalEngine.m */,
|
||||
A20000000000000000000015 /* PSCCpuEngine.h */,
|
||||
A20000000000000000000016 /* PSCCpuEngine.m */,
|
||||
);
|
||||
path = Compute;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
A30000000000000000000005 /* Native */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A20000000000000000000017 /* des.c */,
|
||||
A20000000000000000000018 /* des.h */,
|
||||
A20000000000000000000019 /* des_bruteforce.metal */,
|
||||
);
|
||||
name = Native;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
A30000000000000000000006 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A10000000000000000000003 /* DES Cracker.app */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
A30000000000000000000007 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A20000000000000000000020 /* Metal.framework */,
|
||||
A20000000000000000000021 /* QuartzCore.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
A30000000000000000000000 /* Root */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A30000000000000000000001 /* DESCracker */,
|
||||
A30000000000000000000007 /* Frameworks */,
|
||||
A30000000000000000000006 /* Products */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* 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 */;
|
||||
}
|
||||
@ -0,0 +1,77 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1600"
|
||||
version = "1.7">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "A10000000000000000000002"
|
||||
BuildableName = "DES Cracker.app"
|
||||
BlueprintName = "DESCracker"
|
||||
ReferencedContainer = "container:DESCracker.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
shouldAutocreateTestPlan = "YES">
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "A10000000000000000000002"
|
||||
BuildableName = "DES Cracker.app"
|
||||
BlueprintName = "DESCracker"
|
||||
ReferencedContainer = "container:DESCracker.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "A10000000000000000000002"
|
||||
BuildableName = "DES Cracker.app"
|
||||
BlueprintName = "DESCracker"
|
||||
ReferencedContainer = "container:DESCracker.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
5
ios/DESCracker/AppDelegate.h
Normal file
5
ios/DESCracker/AppDelegate.h
Normal file
@ -0,0 +1,5 @@
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface AppDelegate : UIResponder <UIApplicationDelegate>
|
||||
@property (nonatomic, strong) UIWindow *window;
|
||||
@end
|
||||
14
ios/DESCracker/AppDelegate.m
Normal file
14
ios/DESCracker/AppDelegate.m
Normal file
@ -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
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
BIN
ios/DESCracker/Assets.xcassets/AppIcon.appiconset/AppIcon.png
Normal file
BIN
ios/DESCracker/Assets.xcassets/AppIcon.appiconset/AppIcon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.3 KiB |
@ -0,0 +1,14 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "AppIcon.png",
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
6
ios/DESCracker/Assets.xcassets/Contents.json
Normal file
6
ios/DESCracker/Assets.xcassets/Contents.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
20
ios/DESCracker/Compute/PSCBruteEngine.h
Normal file
20
ios/DESCracker/Compute/PSCBruteEngine.h
Normal file
@ -0,0 +1,20 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@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 <NSObject>
|
||||
- (void)runAssign:(PSCAssignBlock *)block
|
||||
onProgress:(nullable PSCBruteProgressBlock)onProgress
|
||||
onHit:(nullable PSCBruteHitBlock)onHit
|
||||
completion:(nullable PSCBruteFinishBlock)completion;
|
||||
- (void)cancel;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
7
ios/DESCracker/Compute/PSCCpuEngine.h
Normal file
7
ios/DESCracker/Compute/PSCCpuEngine.h
Normal file
@ -0,0 +1,7 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "PSCBruteEngine.h"
|
||||
|
||||
@interface PSCCpuEngine : NSObject <PSCBruteEngine>
|
||||
@property (nonatomic, assign, readonly) NSUInteger coreCount;
|
||||
- (BOOL)prepare:(NSError **)error;
|
||||
@end
|
||||
155
ios/DESCracker/Compute/PSCCpuEngine.m
Normal file
155
ios/DESCracker/Compute/PSCCpuEngine.m
Normal file
@ -0,0 +1,155 @@
|
||||
#import "PSCCpuEngine.h"
|
||||
#import "PSCProtocol.h"
|
||||
#import "des.h"
|
||||
#import <stdatomic.h>
|
||||
#import <string.h>
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
|
||||
@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
|
||||
9
ios/DESCracker/Compute/PSCMetalEngine.h
Normal file
9
ios/DESCracker/Compute/PSCMetalEngine.h
Normal file
@ -0,0 +1,9 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "PSCBruteEngine.h"
|
||||
|
||||
@interface PSCMetalEngine : NSObject <PSCBruteEngine>
|
||||
@property (nonatomic, readonly) BOOL available;
|
||||
@property (nonatomic, copy, readonly) NSString *gpuName;
|
||||
@property (nonatomic, assign, readonly) uint64_t memoryBytes;
|
||||
- (BOOL)prepare:(NSError **)error;
|
||||
@end
|
||||
266
ios/DESCracker/Compute/PSCMetalEngine.m
Normal file
266
ios/DESCracker/Compute/PSCMetalEngine.m
Normal file
@ -0,0 +1,266 @@
|
||||
#import "PSCMetalEngine.h"
|
||||
#import "PSCProtocol.h"
|
||||
#import <Metal/Metal.h>
|
||||
#import <stdatomic.h>
|
||||
#import <string.h>
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
|
||||
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<MTLDevice> device;
|
||||
@property (nonatomic, strong) id<MTLCommandQueue> queue;
|
||||
@property (nonatomic, strong) id<MTLComputePipelineState> 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<MTLLibrary> lib = [self.device newDefaultLibrary];
|
||||
if (!lib) {
|
||||
if (error) {
|
||||
*error = [NSError errorWithDomain:@"DESCracker" code:2 userInfo:@{
|
||||
NSLocalizedDescriptionKey : @"Failed to load Metal library"
|
||||
}];
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
id<MTLFunction> known = [lib newFunctionWithName:@"des_known_answer"];
|
||||
id<MTLFunction> 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<MTLComputePipelineState> 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<MTLBuffer> out = [self.device newBufferWithLength:16 options:MTLResourceStorageModeShared];
|
||||
id<MTLCommandBuffer> cmd = [self.queue commandBuffer];
|
||||
id<MTLComputeCommandEncoder> 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<MTLBuffer> paramsBuf = [self.device newBufferWithLength:kPSCParamsSize options:MTLResourceStorageModeShared];
|
||||
id<MTLBuffer> charsetBuf = [self.device newBufferWithBytes:cs length:MAX(charset_len, 1)
|
||||
options:MTLResourceStorageModeShared];
|
||||
id<MTLBuffer> hitsBuf = [self.device newBufferWithLength:kPSCMaxHits * sizeof(PSCHit)
|
||||
options:MTLResourceStorageModeShared];
|
||||
id<MTLBuffer> 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<MTLCommandBuffer> cmd = [self.queue commandBuffer];
|
||||
id<MTLComputeCommandEncoder> 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
|
||||
57
ios/DESCracker/Info.plist
Normal file
57
ios/DESCracker/Info.plist
Normal file
@ -0,0 +1,57 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>DES Cracker</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>DES Cracker</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
<key>NSAllowsLocalNetworking</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>NSLocalNetworkUsageDescription</key>
|
||||
<string>DES Cracker connects to the desktop master on your local network to receive search work.</string>
|
||||
<key>UILaunchScreen</key>
|
||||
<dict>
|
||||
<key>UIColorName</key>
|
||||
<string>LaunchBackground</string>
|
||||
</dict>
|
||||
<key>UIStatusBarStyle</key>
|
||||
<string>UIStatusBarStyleLightContent</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UIUserInterfaceStyle</key>
|
||||
<string>Dark</string>
|
||||
</dict>
|
||||
</plist>
|
||||
57
ios/DESCracker/Network/PSCProtocol.h
Normal file
57
ios/DESCracker/Network/PSCProtocol.h
Normal file
@ -0,0 +1,57 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#include <stdint.h>
|
||||
|
||||
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<NSNumber *> *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
|
||||
|
||||
146
ios/DESCracker/Network/PSCProtocol.m
Normal file
146
ios/DESCracker/Network/PSCProtocol.m
Normal file
@ -0,0 +1,146 @@
|
||||
#import "PSCProtocol.h"
|
||||
#import <math.h>
|
||||
#import <stdlib.h>
|
||||
|
||||
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<NSNumber *> *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];
|
||||
}
|
||||
25
ios/DESCracker/Network/PSCSlaveClient.h
Normal file
25
ios/DESCracker/Network/PSCSlaveClient.h
Normal file
@ -0,0 +1,25 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "PSCProtocol.h"
|
||||
|
||||
@class PSCSlaveClient;
|
||||
|
||||
@protocol PSCSlaveClientDelegate <NSObject>
|
||||
- (void)slaveClient:(PSCSlaveClient *)client didChangeStatus:(NSString *)status connected:(BOOL)connected;
|
||||
- (void)slaveClient:(PSCSlaveClient *)client didLog:(NSString *)line;
|
||||
- (void)slaveClient:(PSCSlaveClient *)client didUpdateWorkers:(NSArray<PSCWorkerSnapshot *> *)workers;
|
||||
- (void)slaveClient:(PSCSlaveClient *)client didHitJob:(NSString *)jobId index:(NSString *)index keyHex:(NSString *)keyHex;
|
||||
@end
|
||||
|
||||
@interface PSCSlaveClient : NSObject
|
||||
@property (nonatomic, weak) id<PSCSlaveClientDelegate> 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<PSCComputeDevice *> *)devices;
|
||||
- (void)connectToHost:(NSString *)host port:(NSInteger)port;
|
||||
- (void)disconnect;
|
||||
@end
|
||||
604
ios/DESCracker/Network/PSCSlaveClient.m
Normal file
604
ios/DESCracker/Network/PSCSlaveClient.m
Normal file
@ -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 () <NSURLSessionWebSocketDelegate>
|
||||
@property (nonatomic, copy) NSArray<PSCComputeDevice *> *devices;
|
||||
@property (nonatomic, strong) NSURLSession *session;
|
||||
@property (nonatomic, strong) NSURLSessionWebSocketTask *task;
|
||||
@property (nonatomic, strong) NSTimer *pingTimer;
|
||||
@property (nonatomic, strong) NSMutableDictionary<NSString *, PSCDeviceJob *> *jobs;
|
||||
@property (nonatomic, strong) NSMutableDictionary<NSString *, NSNumber *> *completedBlocks;
|
||||
@property (nonatomic, strong) NSMutableDictionary<NSString *, PSCProgressGate *> *progressGates;
|
||||
@property (nonatomic, strong) NSMutableDictionary<NSString *, PSCRateStats *> *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<PSCComputeDevice *> *)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<PSCBruteEngine>)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<PSCBruteEngine> 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<NSString *> *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<PSCWorkerSnapshot *> *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<PSCDeviceJob *> *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
|
||||
5
ios/DESCracker/UI/PSCSlaveViewController.h
Normal file
5
ios/DESCracker/UI/PSCSlaveViewController.h
Normal file
@ -0,0 +1,5 @@
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "PSCSlaveClient.h"
|
||||
|
||||
@interface PSCSlaveViewController : UIViewController
|
||||
@end
|
||||
619
ios/DESCracker/UI/PSCSlaveViewController.m
Normal file
619
ios/DESCracker/UI/PSCSlaveViewController.m
Normal file
@ -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 () <PSCSlaveClientDelegate, UITextFieldDelegate>
|
||||
@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<UIView *> *)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<PSCComputeDevice *> *)selectedDevices {
|
||||
NSMutableArray<PSCComputeDevice *> *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<PSCWorkerSnapshot *> *)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<PSCWorkerSnapshot *> *)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
|
||||
8
ios/DESCracker/main.m
Normal file
8
ios/DESCracker/main.m
Normal file
@ -0,0 +1,8 @@
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "AppDelegate.h"
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
@autoreleasepool {
|
||||
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
|
||||
}
|
||||
}
|
||||
@ -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<ClientMessage, { type: "progress" }>;
|
||||
|
||||
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<string, DeviceJob>();
|
||||
private completedBlocks = new Map<string, number>();
|
||||
private progressGates = new Map<string, ProgressGate>();
|
||||
private rateStats = new Map<string, { keysDone: bigint; activeMs: number; busyStarted: number | null }>();
|
||||
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);
|
||||
|
||||
@ -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<string, { start: bigint; count: bigint }>();
|
||||
private retries: Array<{ start: bigint; count: bigint }> = [];
|
||||
private inflight = new Map<string, ReservedBlock>();
|
||||
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<string>,
|
||||
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();
|
||||
});
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user