Compare commits

...

15 Commits

Author SHA1 Message Date
Roberto Viola
6cdd676b5d it works without the encryption!? 2024-02-06 09:26:40 +01:00
Roberto Viola
9d2a68cb28 builds 2024-02-06 09:05:44 +01:00
Roberto Viola
b92bb58651 Update zwiftPlayDevice.swift 2024-02-06 07:44:54 +01:00
Roberto Viola
41970410d1 fixing build error 2024-02-06 07:44:31 +01:00
Roberto Viola
d2ef1607ad playing with cryptoswift 2024-02-05 17:41:39 +01:00
Roberto Viola
b6f76c9a5d adding CryptoSwift 2024-02-05 17:32:45 +01:00
Roberto Viola
73e9e44c86 Update encryptionUtils.swift 2024-02-05 17:31:00 +01:00
Roberto Viola
bc8443e84d Update zapBleUuids.swift 2024-02-05 15:01:08 +01:00
Roberto Viola
425480147e Update zwiftPlayDevice.swift 2024-02-05 15:01:04 +01:00
Roberto Viola
ce60ae7c35 Update localKeyProvider.swift 2024-02-05 14:55:37 +01:00
Roberto Viola
2f157e7c50 Update encryptionUtils.swift 2024-02-05 14:55:33 +01:00
Roberto Viola
c0535c63d4 Update abstractZapDevice.swift 2024-02-05 14:40:40 +01:00
Roberto Viola
9757f20d44 Update encryptionUtils.swift 2024-02-05 14:40:18 +01:00
Roberto Viola
8dd314cb8c Update project.pbxproj 2024-02-05 12:27:39 +01:00
Roberto Viola
2d31add7a5 adding swift file as they are from chatgpt4 2024-02-05 12:25:35 +01:00
13 changed files with 517 additions and 3 deletions

3
.gitmodules vendored
View File

@@ -20,3 +20,6 @@
path = zwiftplay
url = https://github.com/cagnulein/zwiftplay.git
branch = lib
[submodule "src/ios/CryptoSwift"]
path = src/ios/CryptoSwift
url = https://github.com/krzyzanowskim/CryptoSwift.git

View File

@@ -309,6 +309,15 @@
876E50F42B701C050080FAAF /* moc_abstractZapDevice.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 876E50F12B701C040080FAAF /* moc_abstractZapDevice.cpp */; };
876E50F52B701C050080FAAF /* moc_zwiftclickremote.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 876E50F22B701C040080FAAF /* moc_zwiftclickremote.cpp */; };
876E51002B701C1E0080FAAF /* zwiftclickremote.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 876E50FB2B701C1E0080FAAF /* zwiftclickremote.cpp */; };
876E51092B70FDA30080FAAF /* abstractZapDevice.swift in Compile Sources */ = {isa = PBXBuildFile; fileRef = 876E51022B70FDA30080FAAF /* abstractZapDevice.swift */; };
876E510A2B70FDA30080FAAF /* zapCrypto.swift in Compile Sources */ = {isa = PBXBuildFile; fileRef = 876E51032B70FDA30080FAAF /* zapCrypto.swift */; };
876E510B2B70FDA30080FAAF /* encryptionUtils.swift in Compile Sources */ = {isa = PBXBuildFile; fileRef = 876E51042B70FDA30080FAAF /* encryptionUtils.swift */; };
876E510C2B70FDA30080FAAF /* zapConstants.swift in Compile Sources */ = {isa = PBXBuildFile; fileRef = 876E51052B70FDA30080FAAF /* zapConstants.swift */; };
876E510D2B70FDA30080FAAF /* localKeyProvider.swift in Compile Sources */ = {isa = PBXBuildFile; fileRef = 876E51062B70FDA30080FAAF /* localKeyProvider.swift */; };
876E510E2B70FDA30080FAAF /* zwiftPlayDevice.swift in Compile Sources */ = {isa = PBXBuildFile; fileRef = 876E51072B70FDA30080FAAF /* zwiftPlayDevice.swift */; };
876E510F2B70FDA30080FAAF /* zapBleUuids.swift in Compile Sources */ = {isa = PBXBuildFile; fileRef = 876E51082B70FDA30080FAAF /* zapBleUuids.swift */; };
876E51222B7146D90080FAAF /* CryptoSwift.framework in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 876E51192B7146B00080FAAF /* CryptoSwift.framework */; };
876E51232B7146D90080FAAF /* CryptoSwift.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 876E51192B7146B00080FAAF /* CryptoSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
876ED21525C3E8DE0065F3DC /* ftmsbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 876ED21025C3E8DD0065F3DC /* ftmsbike.cpp */; };
876ED21625C3E8DE0065F3DC /* schwinnic4bike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 876ED21425C3E8DE0065F3DC /* schwinnic4bike.cpp */; };
876ED21925C3E9000065F3DC /* moc_ftmsbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 876ED21725C3E9000065F3DC /* moc_ftmsbike.cpp */; };
@@ -596,6 +605,41 @@
remoteGlobalIDString = 876E4E102594747F00BD5714;
remoteInfo = watchkit;
};
876E51182B7146B00080FAAF /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 876E51102B7146B00080FAAF /* CryptoSwift.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 754BE45519693E190098E6F3;
remoteInfo = CryptoSwift;
};
876E511A2B7146B00080FAAF /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 876E51102B7146B00080FAAF /* CryptoSwift.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 75211F92207249D8004E41F8;
remoteInfo = "CryptoSwift-TestHostApp";
};
876E511C2B7146B00080FAAF /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 876E51102B7146B00080FAAF /* CryptoSwift.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 754BE46019693E190098E6F3;
remoteInfo = CryptoSwiftTests;
};
876E511E2B7146B00080FAAF /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 876E51102B7146B00080FAAF /* CryptoSwift.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 7595C14A2072E48C00EA1A5F;
remoteInfo = "TestsPerformance-Mac";
};
876E51202B7146B00080FAAF /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 876E51102B7146B00080FAAF /* CryptoSwift.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 7564F0602072EAEB00CA5A96;
remoteInfo = "TestsPerformance-iOS";
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
@@ -628,6 +672,7 @@
dstSubfolderSpec = 10;
files = (
873D388C29B0D745006A2611 /* ConnectIQ.xcframework in Embed Frameworks */,
876E51232B7146D90080FAAF /* CryptoSwift.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
@@ -1118,6 +1163,14 @@
876E50FD2B701C1E0080FAAF /* abstractZapDevice.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = abstractZapDevice.h; path = ../src/zwift_play/abstractZapDevice.h; sourceTree = "<group>"; };
876E50FE2B701C1E0080FAAF /* controllerNotification.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = controllerNotification.h; path = ../src/zwift_play/controllerNotification.h; sourceTree = "<group>"; };
876E50FF2B701C1E0080FAAF /* zwiftclickremote.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = zwiftclickremote.h; path = ../src/zwift_play/zwiftclickremote.h; sourceTree = "<group>"; };
876E51022B70FDA30080FAAF /* abstractZapDevice.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = abstractZapDevice.swift; path = ../src/ios/zwift_play/abstractZapDevice.swift; sourceTree = "<group>"; };
876E51032B70FDA30080FAAF /* zapCrypto.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = zapCrypto.swift; path = ../src/ios/zwift_play/zapCrypto.swift; sourceTree = "<group>"; };
876E51042B70FDA30080FAAF /* encryptionUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = encryptionUtils.swift; path = ../src/ios/zwift_play/encryptionUtils.swift; sourceTree = "<group>"; };
876E51052B70FDA30080FAAF /* zapConstants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = zapConstants.swift; path = ../src/ios/zwift_play/zapConstants.swift; sourceTree = "<group>"; };
876E51062B70FDA30080FAAF /* localKeyProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = localKeyProvider.swift; path = ../src/ios/zwift_play/localKeyProvider.swift; sourceTree = "<group>"; };
876E51072B70FDA30080FAAF /* zwiftPlayDevice.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = zwiftPlayDevice.swift; path = ../src/ios/zwift_play/zwiftPlayDevice.swift; sourceTree = "<group>"; };
876E51082B70FDA30080FAAF /* zapBleUuids.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = zapBleUuids.swift; path = ../src/ios/zwift_play/zapBleUuids.swift; sourceTree = "<group>"; };
876E51102B7146B00080FAAF /* CryptoSwift.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = CryptoSwift.xcodeproj; path = ../src/ios/CryptoSwift/CryptoSwift.xcodeproj; sourceTree = "<group>"; };
876ED21025C3E8DD0065F3DC /* ftmsbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ftmsbike.cpp; path = ../src/ftmsbike.cpp; sourceTree = "<group>"; };
876ED21125C3E8DD0065F3DC /* ftmsbike.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ftmsbike.h; path = ../src/ftmsbike.h; sourceTree = "<group>"; };
876ED21225C3E8DD0065F3DC /* material.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = material.h; path = ../src/material.h; sourceTree = "<group>"; };
@@ -1667,6 +1720,7 @@
3D010A6A5D13BDBCE8308349 /* qtfreetype in Link Binary With Libraries */,
3B6367B8479BE459243BC9A6 /* Qt5GraphicsSupport in Link Binary With Libraries */,
A48EFCF98552C67DAB8515FD /* Qt5ClipboardSupport in Link Binary With Libraries */,
876E51222B7146D90080FAAF /* CryptoSwift.framework in Link Binary With Libraries */,
4B457EBD757B30AEB87115EA /* qgif in Link Binary With Libraries */,
3CD4CA4EC80AF4F839D1019B /* qicns in Link Binary With Libraries */,
B714316583081A00BDF9C0F2 /* qico in Link Binary With Libraries */,
@@ -1954,6 +2008,7 @@
2EB56BE3C2D93CDAB0C52E67 /* Sources */ = {
isa = PBXGroup;
children = (
876E51012B70FD800080FAAF /* zwift_play */,
876E50FD2B701C1E0080FAAF /* abstractZapDevice.h */,
876E50FE2B701C1E0080FAAF /* controllerNotification.h */,
876E50F62B701C1E0080FAAF /* encryptionUtils.h */,
@@ -2414,6 +2469,32 @@
path = "Preview Content";
sourceTree = "<group>";
};
876E51012B70FD800080FAAF /* zwift_play */ = {
isa = PBXGroup;
children = (
876E51022B70FDA30080FAAF /* abstractZapDevice.swift */,
876E51042B70FDA30080FAAF /* encryptionUtils.swift */,
876E51062B70FDA30080FAAF /* localKeyProvider.swift */,
876E51082B70FDA30080FAAF /* zapBleUuids.swift */,
876E51052B70FDA30080FAAF /* zapConstants.swift */,
876E51032B70FDA30080FAAF /* zapCrypto.swift */,
876E51072B70FDA30080FAAF /* zwiftPlayDevice.swift */,
);
name = zwift_play;
sourceTree = "<group>";
};
876E51112B7146B00080FAAF /* Products */ = {
isa = PBXGroup;
children = (
876E51192B7146B00080FAAF /* CryptoSwift.framework */,
876E511B2B7146B00080FAAF /* CryptoSwift-TestHostApp.app */,
876E511D2B7146B00080FAAF /* CryptoSwiftTests.xctest */,
876E511F2B7146B00080FAAF /* TestsPerformance-Mac.xctest */,
876E51212B7146B00080FAAF /* TestsPerformance-iOS.xctest */,
);
name = Products;
sourceTree = "<group>";
};
87917A6D28E768B000F8D9AC /* AppleWatchToIpad */ = {
isa = PBXGroup;
children = (
@@ -2437,6 +2518,7 @@
AF39DD055C3EF8226FBE929D /* Frameworks */ = {
isa = PBXGroup;
children = (
876E51102B7146B00080FAAF /* CryptoSwift.xcodeproj */,
87FA94682B6B8A5A00B6AB9A /* libSystem.B.tbd */,
87FA94662B6B89FD00B6AB9A /* SwiftUI.framework */,
8745B2742AFCB3B300991A39 /* libadb.a */,
@@ -2959,6 +3041,12 @@
);
productRefGroup = FE0A091FDBFB3E9C31B7A1BD /* Products */;
projectDirPath = "";
projectReferences = (
{
ProductGroup = 876E51112B7146B00080FAAF /* Products */;
ProjectRef = 876E51102B7146B00080FAAF /* CryptoSwift.xcodeproj */;
},
);
projectRoot = "";
targets = (
799833E5566DEFFC37E4BF1E /* qdomyoszwift */,
@@ -2969,6 +3057,44 @@
};
/* End PBXProject section */
/* Begin PBXReferenceProxy section */
876E51192B7146B00080FAAF /* CryptoSwift.framework */ = {
isa = PBXReferenceProxy;
fileType = wrapper.framework;
path = CryptoSwift.framework;
remoteRef = 876E51182B7146B00080FAAF /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
876E511B2B7146B00080FAAF /* CryptoSwift-TestHostApp.app */ = {
isa = PBXReferenceProxy;
fileType = wrapper.application;
path = "CryptoSwift-TestHostApp.app";
remoteRef = 876E511A2B7146B00080FAAF /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
876E511D2B7146B00080FAAF /* CryptoSwiftTests.xctest */ = {
isa = PBXReferenceProxy;
fileType = wrapper.cfbundle;
path = CryptoSwiftTests.xctest;
remoteRef = 876E511C2B7146B00080FAAF /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
876E511F2B7146B00080FAAF /* TestsPerformance-Mac.xctest */ = {
isa = PBXReferenceProxy;
fileType = wrapper.cfbundle;
path = "TestsPerformance-Mac.xctest";
remoteRef = 876E511E2B7146B00080FAAF /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
876E51212B7146B00080FAAF /* TestsPerformance-iOS.xctest */ = {
isa = PBXReferenceProxy;
fileType = wrapper.cfbundle;
path = "TestsPerformance-iOS.xctest";
remoteRef = 876E51202B7146B00080FAAF /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
/* End PBXReferenceProxy section */
/* Begin PBXResourcesBuildPhase section */
30414803F31797EB689AE508 /* Copy Bundle Resources */ = {
isa = PBXResourcesBuildPhase;
@@ -3144,6 +3270,7 @@
873824B927E64707004F1B46 /* moc_provider.cpp in Compile Sources */,
8727A47727849EA600019B5D /* paferstreadmill.cpp in Compile Sources */,
DF1FD9718B75FA591A7E3D80 /* fit_decode.cpp in Compile Sources */,
876E510B2B70FDA30080FAAF /* encryptionUtils.swift in Compile Sources */,
878C9E6B28B77E9800669129 /* moc_nordictrackifitadbbike.cpp in Compile Sources */,
878A331D25AB50C300BD13E1 /* moc_yesoulbike.cpp in Compile Sources */,
952DBD14DF6369E885020EF4 /* fit_developer_field.cpp in Compile Sources */,
@@ -3157,6 +3284,7 @@
87C5F0C326285E5F0067A1B5 /* mimeinlinefile.cpp in Compile Sources */,
87646C2227B5065100F82131 /* moc_bhfitnesselliptical.cpp in Compile Sources */,
876E51002B701C1E0080FAAF /* zwiftclickremote.cpp in Compile Sources */,
876E510C2B70FDA30080FAAF /* zapConstants.swift in Compile Sources */,
873824B127E64706004F1B46 /* moc_browser_p.cpp in Compile Sources */,
873824B627E64707004F1B46 /* moc_hostname_p.cpp in Compile Sources */,
87A0D7542A3A4547005147F2 /* moc_fakerower.cpp in Compile Sources */,
@@ -3181,6 +3309,7 @@
87E34C2D2886F99A00CEDE4B /* moc_octanetreadmill.cpp in Compile Sources */,
87D91F9A2800B9970026D43C /* proformwifibike.cpp in Compile Sources */,
873CD22327EF8E18000131BC /* inappstoreqmltype.cpp in Compile Sources */,
876E510E2B70FDA30080FAAF /* zwiftPlayDevice.swift in Compile Sources */,
87C481FA26DFA7C3006211AD /* eliterizer.cpp in Compile Sources */,
873824EE27E647A9004F1B46 /* service.cpp in Compile Sources */,
8772A0E625E43ADB0080718C /* trxappgateusbbike.cpp in Compile Sources */,
@@ -3233,6 +3362,7 @@
23191C28CB29474279752FD3 /* fit_protocol_validator.cpp in Compile Sources */,
275D55B5D956B2E5F1B7E46E /* fit_unicode.cpp in Compile Sources */,
87F527BE28EEB5AA00A9F8D5 /* qzsettings.cpp in Compile Sources */,
876E510D2B70FDA30080FAAF /* localKeyProvider.swift in Compile Sources */,
8703BAEB273C67A90058E206 /* pafersbike.cpp in Compile Sources */,
87061399286D8D6500D2446E /* moc_wobjectdefs.cpp in Compile Sources */,
873824BA27E64707004F1B46 /* moc_server_p.cpp in Compile Sources */,
@@ -3257,6 +3387,7 @@
87E2F85F291ED315002BDC65 /* moc_lifefitnesstreadmill.cpp in Compile Sources */,
876BFCA127BE35D8001D7645 /* moc_bowflext216treadmill.cpp in Compile Sources */,
25FCD41CCCAF49293B9369E8 /* qfit.cpp in Compile Sources */,
876E510F2B70FDA30080FAAF /* zapBleUuids.swift in Compile Sources */,
87ADD2BD27634C2100B7A0AB /* moc_technogymmyruntreadmill.cpp in Compile Sources */,
8738249227E646E3004F1B46 /* characteristicnotifier2a63.cpp in Compile Sources */,
8738249327E646E3004F1B46 /* characteristicwriteprocessor2ad9.cpp in Compile Sources */,
@@ -3329,6 +3460,7 @@
DF373364C5474D877506CB26 /* FitMesg.mm in Compile Sources */,
872261F0289EA887006A6F75 /* moc_nordictrackelliptical.cpp in Compile Sources */,
873824E327E647A8004F1B46 /* bitmap.cpp in Compile Sources */,
876E510A2B70FDA30080FAAF /* zapCrypto.swift in Compile Sources */,
87FE5BB12692F31E0056EFC8 /* moc_tacxneo2.cpp in Compile Sources */,
873824E827E647A8004F1B46 /* provider.cpp in Compile Sources */,
8791A8AA25C8603F003B50B2 /* moc_inspirebike.cpp in Compile Sources */,
@@ -3373,6 +3505,7 @@
87EFE45927A518F5006EA1C3 /* nautiluselliptical.cpp in Compile Sources */,
E62DA5FF2436135448C94671 /* moc_toorxtreadmill.cpp in Compile Sources */,
87586A4325B8341B00A243C4 /* moc_proformbike.cpp in Compile Sources */,
876E51092B70FDA30080FAAF /* abstractZapDevice.swift in Compile Sources */,
87CC3BA325A0885F001EC5A8 /* domyoselliptical.cpp in Compile Sources */,
87D105542909971100B3935B /* moc_mepanelbike.cpp in Compile Sources */,
87B617F325F260150094A1CB /* moc_snodebike.cpp in Compile Sources */,

1
src/ios/CryptoSwift Submodule

Submodule src/ios/CryptoSwift added at 7892a123f7

View File

@@ -74,6 +74,10 @@ class lockscreen {
int zwift_api_getdistance();
float zwift_api_getlatitude();
float zwift_api_getlongitude();
// Zwift ZAP Device
const char* zapDevice_buildHandshakeStart();
int zapDevice_processCharacteristic(const char* data, int len);
};
#endif // LOCKSCREEN_H

View File

@@ -31,6 +31,8 @@ static ios_eliteariafan* ios_eliteAriaFan = nil;
static zwift_protobuf_layer* zwiftProtobufLayer = nil;
static ZwiftPlayDevice *zapDevice = nil;
void lockscreen::setTimerDisabled() {
[[UIApplication sharedApplication] setIdleTimerDisabled: YES];
}
@@ -47,6 +49,11 @@ void lockscreen::request()
if (@available(iOS 17, *)) {
_adb = [[AdbClient alloc] initWithVerbose:YES];
}
if (@available(iOS 14, *)) {
zapDevice = [[ZwiftPlayDevice alloc] init];
}
}
long lockscreen::heartRate()
@@ -322,4 +329,19 @@ float lockscreen::zwift_api_getlatitude() {
float lockscreen::zwift_api_getlongitude() {
return [zwiftProtobufLayer getLongitude];
}
const char* lockscreen::zapDevice_buildHandshakeStart() {
if(zapDevice) {
return (const char*)[[zapDevice buildHandshakeStart] bytes];
}
return nil;
}
int lockscreen::zapDevice_processCharacteristic(const char* data, int len) {
if(zapDevice) {
NSData *d = [NSData dataWithBytes:data length:len];
return [zapDevice processEncryptedDataWithBytes:d];
}
return 0;
}
#endif

View File

@@ -0,0 +1,63 @@
import Foundation
import os.log
// Assuming Logger and extensions for Data to convert to hex string and to check prefix are defined elsewhere in your Swift project.
extension Data {
func toHexString() -> String {
self.map { String(format: "%02x", $0) }.joined()
}
func starts(with prefix: Data) -> Bool {
self.prefix(prefix.count) == prefix
}
}
@available(iOS 14.0, *)
@objc class AbstractZapDevice: NSObject {
private static let logRaw = true
private var devicePublicKeyBytes: Data?
private let localKeyProvider = LocalKeyProvider()
open var zapEncryption: ZapCrypto
override init() {
self.zapEncryption = ZapCrypto(localKeyProvider: localKeyProvider)
}
@objc func processCharacteristic(characteristicName: String, bytes: Data?) -> Int {
guard let bytes = bytes else { return 0 }
if Self.logRaw {
os_log("%{public}@ %{public}@", log: OSLog.default, type: .debug, characteristicName, bytes.toHexString())
}
if bytes.starts(with: ZapConstants.rideOn + ZapConstants.responseStart) {
processDevicePublicKeyResponse(bytes: bytes)
} else if bytes.count > MemoryLayout<Int>.size + EncryptionUtils.macLength {
return processEncryptedData(bytes: bytes)
} else {
// Logger equivalent in Swift
os_log("Unprocessed - Data Type: %{public}@", log: OSLog.default, type: .error, bytes.toHexString())
}
return 0
}
@objc func buildHandshakeStart() -> Data {
return ZapConstants.rideOn + ZapConstants.requestStart + localKeyProvider.getPublicKeyBytes()
}
private func processDevicePublicKeyResponse(bytes: Data) {
let startIndex = ZapConstants.rideOn.count + ZapConstants.responseStart.count
devicePublicKeyBytes = bytes.subdata(in: startIndex..<bytes.count)
zapEncryption.initialise(devicePublicKeyBytes: devicePublicKeyBytes!)
if Self.logRaw {
// Logger equivalent in Swift
os_log("Device Public Key - %{public}@", log: OSLog.default, type: .debug, devicePublicKeyBytes!.toHexString())
}
}
// Abstract method placeholder - Swift doesn't support abstract classes/methods directly, so this method should be overridden in subclass.
func processEncryptedData(bytes: Data) -> Int {
fatalError("This method should be overridden by subclasses")
}
}

View File

@@ -0,0 +1,42 @@
import Foundation
import CryptoSwift
import CryptoKit
@available(iOS 14.0, *)
struct EncryptionUtils {
static let keyLength = 32
static let hkdfLength = 36
static let macLength = 4
// Converti una chiave pubblica EC in array di byte
static func publicKeyToByteArray(publicKey: P256.KeyAgreement.PublicKey) -> Data {
// Assumendo che `publicKey` sia una rappresentazione simile di CryptoKit's P256,
// la conversione diretta alla rappresentazione di byte è già disponibile
return publicKey.rawRepresentation
}
// Genera una chiave pubblica EC dai byte ricevuti
static func generatePublicKey(publicKeyBytes: Data) throws -> P256.KeyAgreement.PublicKey {
// Assumendo l'uso di CryptoKit per la generazione della chiave pubblica
return try P256.KeyAgreement.PublicKey(x963Representation: publicKeyBytes)
}
// Genera byte di segreto condiviso
static func generateSharedSecretBytes(privateKey: P256.KeyAgreement.PrivateKey, publicKey: P256.KeyAgreement.PublicKey) -> Data {
let sharedSecret = try! privateKey.sharedSecretFromKeyAgreement(with: publicKey)
// Direttamente in formato Data
return sharedSecret.withUnsafeBytes { Data($0) }
}
// Genera byte usando HKDF
static func generateHKDFBytes(secretKey: Data, salt: Data, info: Data = Data(), outputByteCount: Int) -> Data? {
do {
let hkdf = try HKDF(password: secretKey.bytes, salt: salt.bytes, info: info.bytes, keyLength: outputByteCount, variant: .sha256)
let derivedKey = try hkdf.calculate()
return Data(derivedKey)
} catch {
print("Errore durante la derivazione HKDF: \(error)")
return nil
}
}
}

View File

@@ -0,0 +1,36 @@
import Foundation
import CryptoKit
@available(iOS 14.0, *)
class LocalKeyProvider {
private var keyPair: P256.KeyAgreement.PrivateKey?
init() {
generateKeyPair()
}
func getPublicKeyBytes() -> Data {
guard let publicKey = keyPair?.publicKey else {
fatalError("Key pair not initialized correctly.")
}
return publicKey.rawRepresentation
}
func getPublicKey() -> P256.KeyAgreement.PublicKey {
guard let publicKey = keyPair?.publicKey else {
fatalError("Key pair not initialized correctly.")
}
return publicKey
}
func getPrivateKey() -> P256.KeyAgreement.PrivateKey {
guard let keyPair = keyPair else {
fatalError("Key pair not initialized correctly.")
}
return keyPair
}
private func generateKeyPair() {
keyPair = P256.KeyAgreement.PrivateKey()
}
}

View File

@@ -0,0 +1,14 @@
import Foundation
import CoreBluetooth
enum ZapBleUuids {
// ZAP Service - Zwift Accessory Protocol
static let zwiftCustomServiceUUID = CBUUID(string: "00000001-19CA-4651-86E5-FA29DCDD09D1")
static let zwiftAsyncCharacteristicUUID = CBUUID(string: "00000002-19CA-4651-86E5-FA29DCDD09D1")
static let zwiftSyncRxCharacteristicUUID = CBUUID(string: "00000003-19CA-4651-86E5-FA29DCDD09D1")
static let zwiftSyncTxCharacteristicUUID = CBUUID(string: "00000004-19CA-4651-86E5-FA29DCDD09D1")
// This doesn't appear in the real hardware but is found in the companion app code.
// static let zwiftDebugCharacteristicUUID = CBUUID(string: "00000005-19CA-4651-86E5-FA29DCDD09D1")
// I have not seen this characteristic used. Guess it could be for Device Firmware Update (DFU)? it is a chip from Nordic.
static let zwiftUnknown6CharacteristicUUID = CBUUID(string: "00000006-19CA-4651-86E5-FA29DCDD09D1")
}

View File

@@ -0,0 +1,20 @@
import Foundation
enum ZapConstants {
static let zwiftManufacturerId = 2378 // Zwift, Inc
static let rc1LeftSide: UInt8 = 3
static let rc1RightSide: UInt8 = 2
static let zwiftClick: UInt8 = 9
static let rideOn = Data([82, 105, 100, 101, 79, 110])
// these don't actually seem to matter, its just the header has to be 7 bytes RIDEON + 2
static let requestStart = Data([0, 9]) //Data([1, 2])
static let responseStart = Data([1, 3]) // from device
// Message types received from device
static let controllerNotificationMessageType: UInt8 = 7
static let emptyMessageType: UInt8 = 21
static let batteryLevelType: UInt8 = 25
static let clickType: UInt8 = 55
}

View File

@@ -0,0 +1,91 @@
import Foundation
import CryptoKit
@available(iOS 14.0, *)
class ZapCrypto {
private let localKeyProvider: LocalKeyProvider
private var encryptionKeyBytes: Data?
private var ivBytes: Data?
private var counter: Int = 0
init(localKeyProvider: LocalKeyProvider) {
self.localKeyProvider = localKeyProvider
}
func initialise(devicePublicKeyBytes: Data) {
let hkdfBytes: Data = generateHmacKeyDerivationFunctionBytes(devicePublicKeyBytes: devicePublicKeyBytes)
self.encryptionKeyBytes = hkdfBytes.subdata(in: 0..<EncryptionUtils.keyLength)
self.ivBytes = hkdfBytes.subdata(in: 32..<EncryptionUtils.hkdfLength)
}
func encrypt(data: Data) -> Data? {
guard let encryptionKeyBytes = encryptionKeyBytes, let ivBytes = ivBytes else {
assertionFailure("Not initialised")
return nil
}
let counterValue = counter
self.counter += 1
let nonceBytes: Data = createNonce(iv: ivBytes, messageCounter: counterValue)
return encryptDecrypt(encrypt: true, nonceBytes: nonceBytes, data: data)?.prependCounter(counterValue)
}
func decrypt(counterArray: Data, payload: Data) -> Data? {
guard let ivBytes = ivBytes else {
assertionFailure("Not initialised")
return nil
}
let nonceBytes: Data = ivBytes + counterArray
return encryptDecrypt(encrypt: false, nonceBytes: nonceBytes, data: payload)
}
private func encryptDecrypt(encrypt: Bool, nonceBytes: Data, data: Data) -> Data? {
let encryptionKey = SymmetricKey(data: encryptionKeyBytes!)
let sealedBox: AES.GCM.SealedBox
do {
if encrypt {
sealedBox = try AES.GCM.seal(data, using: encryptionKey, nonce: AES.GCM.Nonce(data: nonceBytes))
} else {
sealedBox = try AES.GCM.SealedBox(nonce: AES.GCM.Nonce(data: nonceBytes), ciphertext: data, tag: Data())
return try AES.GCM.open(sealedBox, using: encryptionKey)
}
return sealedBox.ciphertext + sealedBox.tag
} catch {
print(error)
return nil
}
}
private func generateHmacKeyDerivationFunctionBytes(devicePublicKeyBytes: Data) -> Data {
do {
let serverPublicKey = try EncryptionUtils.generatePublicKey(publicKeyBytes: devicePublicKeyBytes)
let sharedSecretBytes = EncryptionUtils.generateSharedSecretBytes(privateKey: localKeyProvider.getPrivateKey(), publicKey: serverPublicKey)
let salt = EncryptionUtils.publicKeyToByteArray(publicKey: serverPublicKey) + localKeyProvider.getPublicKeyBytes()
return EncryptionUtils.generateHKDFBytes(secretKey: sharedSecretBytes, salt: salt, outputByteCount: EncryptionUtils.hkdfLength) ?? Data()
} catch {
print(error)
return Data()
}
}
private func createNonce(iv: Data, messageCounter: Int) -> Data {
return iv + createCounterBytes(messageCounter: messageCounter)
}
private func createCounterBytes(messageCounter: Int) -> Data {
var counterValue = messageCounter.bigEndian
return Data(bytes: &counterValue, count: MemoryLayout.size(ofValue: counterValue))
}
}
extension Data {
func prependCounter(_ counter: Int) -> Data {
var counterValue = counter.bigEndian
var data = Data(bytes: &counterValue, count: MemoryLayout.size(ofValue: counterValue))
data.append(contentsOf: self)
return data
}
}

View File

@@ -0,0 +1,70 @@
import Foundation
import os.log
@available(iOS 14.0, *)
@objc class ZwiftPlayDevice: AbstractZapDevice {
private var batteryLevel = 0
@objc override func processEncryptedData(bytes: Data) -> Int {
do {
os_log("Decrypted: %{public}@", log: OSLog.default, type: .debug, bytes.toHexString())
if bytes.count < 5 {
return 0
}
let counter = bytes.prefix(4)
let payload = bytes.suffix(from: 4)
guard let data = zapEncryption.decrypt(counterArray: counter, payload: payload) else {
os_log("Decrypt failed", log: OSLog.default, type: .error)
return 0
}
let type = data[0]
let message = data.suffix(from: 1)
switch type {
case ZapConstants.controllerNotificationMessageType:
os_log("controllerNotificationMessageType")
//processButtonNotification(notification: ControllerNotification(data: message))
case ZapConstants.clickType:
return processClickButtonNotification(data: data)
case ZapConstants.emptyMessageType:
os_log("emptyMessageType")
case ZapConstants.batteryLevelType:
os_log("batteryLevelType")
default:
os_log("Unprocessed - Type: %{public}@ Data: %{public}@", log: OSLog.default, type: .error, String(describing: type), data.toHexString())
}
} catch {
os_log("Decrypt failed: %{public}@", log: OSLog.default, type: .error, error.localizedDescription)
}
return 0
}
/*
private func processButtonNotification(notification: ControllerNotification) {
if let lastButtonState = lastButtonState {
let diff = notification.diff(lastButtonState: lastButtonState)
if !diff.isEmpty {
os_log("%{public}@", log: OSLog.default, type: .debug, diff)
}
} else {
os_log("%{public}@", log: OSLog.default, type: .debug, notification.description)
}
lastButtonState = notification
}*/
private func processClickButtonNotification(data: Data) -> Int {
if data.count == 5 {
if data[4] == 0 {
os_log("Click '-' Button Press", log: OSLog.default, type: .debug)
return 2
} else if data[2] == 0 {
os_log("Click '+' Button Press", log: OSLog.default, type: .debug)
return 1
}
}
return 0
}
}

View File

@@ -10,6 +10,8 @@
#ifdef Q_OS_ANDROID
#include <QAndroidJniObject>
#include <QAndroidJniEnvironment>
#elif defined Q_OS_IOS
#include "ios/lockscreen.h"
#endif
class AbstractZapDevice: public QObject {
@@ -24,6 +26,12 @@ public:
RIDE_ON = QByteArray::fromRawData("\x52\x69\x64\x65\x4F\x6E", 6); // "RideOn"
REQUEST_START = QByteArray::fromRawData("\x00\x09", 2); // {0, 9}
RESPONSE_START = QByteArray::fromRawData("\x01\x03", 2); // {1, 3}
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
h = new lockscreen();
#endif
#endif
}
int processCharacteristic(const QString& characteristicName, const QByteArray& bytes) {
@@ -31,6 +39,7 @@ public:
qDebug() << characteristicName << bytes.toHex();
int button = 0;
#ifdef Q_OS_ANDROID
QAndroidJniEnvironment env;
jbyteArray d = env->NewByteArray(bytes.length());
@@ -39,16 +48,17 @@ public:
b[i] = bytes[i];
env->SetByteArrayRegion(d, 0, bytes.length(), b);
int button = QAndroidJniObject::callStaticMethod<int>(
button = QAndroidJniObject::callStaticMethod<int>(
"org/cagnulen/qdomyoszwift/ZapClickLayer", "processCharacteristic", "([B)I", d);
env->DeleteLocalRef(d);
#elif defined Q_OS_IOS
button = h->zapDevice_processCharacteristic(bytes, bytes.length());
#endif
if(button == 1)
emit plus();
else if(button == 2)
emit minus();
return button;
#endif
}
QByteArray buildHandshakeStart() {
@@ -71,6 +81,8 @@ public:
// Ora puoi usare byteArray come necessario
return byteArray;
}
#elif defined Q_OS_IOS
return h->zapDevice_buildHandshakeStart();
#endif
//return RIDE_ON + REQUEST_START + localKeyProvider.getPublicKeyBytes();
return QByteArray();
@@ -80,6 +92,9 @@ protected:
virtual void processEncryptedData(const QByteArray& bytes) = 0;
private:
#ifdef Q_OS_IOS
lockscreen *h = 0;
#endif
QByteArray devicePublicKeyBytes;
signals: