Skip to content

Commit eb99ac2

Browse files
authored
feat(predictions): No-Light/FaceMovementOnly challenge support (#3622)
* feat(predictions): add public definitions for no light challenge support (#3617) * feat(predictions): add public definitions for no light challenge support * Add missing files * fix swiftlint issue * feat(predictions): add implementation for no light support (#3618) * feat(predictions): add implementation for no light support * fix swiftlint issue * address review comments * address review comments * address review comments * chore(predictions): add attempt count changes and unit tests (#3657) * chore(predictions): add attempt count changes and unit tests * remove test url * Add coding keys for Challenge object * chore: fix build after rebase * address review comments
1 parent 04311f3 commit eb99ac2

18 files changed

+584
-59
lines changed

AmplifyPlugins/Predictions/AWSPredictionsPlugin/Liveness/Events/LivenessEvent.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@ public enum LivenessEventKind {
2323
self.rawValue = rawValue
2424
}
2525

26-
public static let challenge = Self(rawValue: "ServerSessionInformationEvent")
26+
public static let sessionInformation = Self(rawValue: "ServerSessionInformationEvent")
2727
public static let disconnect = Self(rawValue: "DisconnectionEvent")
28+
public static let challenge = Self(rawValue: "ChallengeEvent")
2829
}
2930
case server(Server)
3031

@@ -60,6 +61,7 @@ extension LivenessEventKind: CustomDebugStringConvertible {
6061
public var debugDescription: String {
6162
switch self {
6263
case .server(.challenge): return ".server(.challenge)"
64+
case .server(.sessionInformation): return ".server(.sessionInformation)"
6365
case .server(.disconnect): return ".server(.disconnect)"
6466
case .client(.initialFaceDetected): return ".client(.initialFaceDetected)"
6567
case .client(.video): return ".client(.video)"

AmplifyPlugins/Predictions/AWSPredictionsPlugin/Liveness/Events/LivenessFinalCientEvent.swift renamed to AmplifyPlugins/Predictions/AWSPredictionsPlugin/Liveness/Events/LivenessFinalClientEvent.swift

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,13 @@ public struct FinalClientEvent {
2626

2727
extension LivenessEvent where T == FinalClientEvent {
2828
@_spi(PredictionsFaceLiveness)
29-
public static func final(event: FinalClientEvent) throws -> Self {
30-
31-
let clientEvent = ClientSessionInformationEvent(
32-
challenge: .init(
33-
faceMovementAndLightChallenge: .init(
29+
public static func final(event: FinalClientEvent,
30+
challenge: Challenge) throws -> Self {
31+
let clientChallengeType: ClientChallenge.ChallengeType
32+
switch challenge {
33+
case .faceMovementAndLightChallenge:
34+
clientChallengeType = .faceMovementAndLightChallenge(
35+
challenge: .init(
3436
challengeID: event.initialClientEvent.challengeID,
3537
targetFace: .init(
3638
boundingBox: .init(boundingBox: event.targetFace.initialEvent.boundingBox),
@@ -46,7 +48,26 @@ extension LivenessEvent where T == FinalClientEvent {
4648
videoEndTimeStamp: Date().epochMilliseconds
4749
)
4850
)
49-
)
51+
case .faceMovementChallenge:
52+
clientChallengeType = .faceMovementChallenge(
53+
challenge: .init(
54+
challengeID: event.initialClientEvent.challengeID,
55+
targetFace: .init(
56+
boundingBox: .init(boundingBox: event.targetFace.initialEvent.boundingBox),
57+
faceDetectedInTargetPositionStartTimestamp: event.targetFace.initialEvent.startTimestamp,
58+
faceDetectedInTargetPositionEndTimestamp: event.targetFace.endTimestamp
59+
),
60+
initialFace: .init(
61+
boundingBox: .init(boundingBox: event.initialClientEvent.initialFaceLocation.boundingBox),
62+
initialFaceDetectedTimeStamp: event.initialClientEvent.initialFaceLocation.startTimestamp
63+
),
64+
videoStartTimestamp: nil,
65+
videoEndTimeStamp: Date().epochMilliseconds
66+
)
67+
)
68+
}
69+
70+
let clientEvent = ClientSessionInformationEvent(challenge: .init(clientChallengeType: clientChallengeType))
5071
let payload = try JSONEncoder().encode(clientEvent)
5172
return .init(
5273
payload: payload,

AmplifyPlugins/Predictions/AWSPredictionsPlugin/Liveness/Events/LivenessFreshnessEvent.swift

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,20 @@ extension LivenessEvent where T == FreshnessEvent {
2929
public static func freshness(event: FreshnessEvent) throws -> Self {
3030
let clientEvent = ClientSessionInformationEvent(
3131
challenge: .init(
32-
faceMovementAndLightChallenge: .init(
33-
challengeID: event.challengeID,
34-
targetFace: nil,
35-
initialFace: nil,
36-
videoStartTimestamp: nil,
37-
colorDisplayed: .init(
38-
currentColor: .init(rgb: event.color),
39-
sequenceNumber: event.sequenceNumber,
40-
currentColorStartTimeStamp: event.timestamp,
41-
previousColor: .init(rgb: event.previousColor)
42-
),
43-
videoEndTimeStamp: nil
32+
clientChallengeType: .faceMovementAndLightChallenge(
33+
challenge: .init(
34+
challengeID: event.challengeID,
35+
targetFace: nil,
36+
initialFace: nil,
37+
videoStartTimestamp: nil,
38+
colorDisplayed: .init(
39+
currentColor: .init(rgb: event.color),
40+
sequenceNumber: event.sequenceNumber,
41+
currentColorStartTimeStamp: event.timestamp,
42+
previousColor: .init(rgb: event.previousColor)
43+
),
44+
videoEndTimeStamp: nil
45+
)
4446
)
4547
)
4648
)

AmplifyPlugins/Predictions/AWSPredictionsPlugin/Liveness/Events/LivenessInitialClientEvent.swift

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,20 @@ public struct InitialClientEvent {
2626

2727
extension LivenessEvent where T == InitialClientEvent {
2828
@_spi(PredictionsFaceLiveness)
29-
public static func initialFaceDetected(event: InitialClientEvent) throws -> Self {
29+
public static func initialFaceDetected(
30+
event: InitialClientEvent,
31+
challenge: Challenge
32+
) throws -> Self {
3033
let initialFace = InitialFace(
3134
boundingBox: .init(boundingBox: event.initialFaceLocation.boundingBox),
3235
initialFaceDetectedTimeStamp: event.initialFaceLocation.startTimestamp
3336
)
3437

35-
let clientSessionInformationEvent = ClientSessionInformationEvent(
36-
challenge: .init(
37-
faceMovementAndLightChallenge: .init(
38+
let clientChallengeType: ClientChallenge.ChallengeType
39+
switch challenge {
40+
case .faceMovementAndLightChallenge:
41+
clientChallengeType = .faceMovementAndLightChallenge(
42+
challenge: .init(
3843
challengeID: event.challengeID,
3944
targetFace: nil,
4045
initialFace: initialFace,
@@ -43,8 +48,21 @@ extension LivenessEvent where T == InitialClientEvent {
4348
videoEndTimeStamp: nil
4449
)
4550
)
51+
case .faceMovementChallenge:
52+
clientChallengeType = .faceMovementChallenge(
53+
challenge: .init(
54+
challengeID: event.challengeID,
55+
targetFace: nil,
56+
initialFace: initialFace,
57+
videoStartTimestamp: event.videoStartTimestamp,
58+
videoEndTimeStamp: nil
59+
)
60+
)
61+
}
62+
63+
let clientSessionInformationEvent = ClientSessionInformationEvent(
64+
challenge: .init(clientChallengeType: clientChallengeType)
4665
)
47-
4866
let payload = try JSONEncoder().encode(clientSessionInformationEvent)
4967
return .init(
5068
payload: payload,

AmplifyPlugins/Predictions/AWSPredictionsPlugin/Liveness/Model/DTOMapping.swift

Lines changed: 37 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,18 @@
88
import Foundation
99

1010
func ovalChallenge(from event: ServerSessionInformationEvent) -> FaceLivenessSession.OvalMatchChallenge {
11-
let challengeConfig = event.sessionInformation.challenge.faceMovementAndLightChallenge.challengeConfig
12-
let ovalParameters = event.sessionInformation.challenge.faceMovementAndLightChallenge.ovalParameters
11+
let challengeConfig: ChallengeConfig
12+
let ovalParameters: OvalParameters
13+
14+
switch event.sessionInformation.challenge.type {
15+
case .faceMovementAndLightChallenge(let challenge):
16+
challengeConfig = challenge.challengeConfig
17+
ovalParameters = challenge.ovalParameters
18+
case .faceMovementChallenge(let challenge):
19+
challengeConfig = challenge.challengeConfig
20+
ovalParameters = challenge.ovalParameters
21+
}
22+
1323
let ovalBoundingBox = FaceLivenessSession.BoundingBox.init(
1424
x: Double(ovalParameters.centerX - ovalParameters.width / 2),
1525
y: Double(ovalParameters.centerY - ovalParameters.height / 2),
@@ -37,11 +47,10 @@ func ovalChallenge(from event: ServerSessionInformationEvent) -> FaceLivenessSes
3747
)
3848
}
3949

40-
func colorChallenge(from event: ServerSessionInformationEvent) -> FaceLivenessSession.ColorChallenge {
41-
let displayColors = event.sessionInformation.challenge
42-
.faceMovementAndLightChallenge.colorSequences
50+
func colorChallenge(from challenge: FaceMovementAndLightServerChallenge) -> FaceLivenessSession.ColorChallenge {
51+
let displayColors = challenge.colorSequences
4352
.map({ color -> FaceLivenessSession.DisplayColor in
44-
53+
4554
let duration: Double
4655
let shouldScroll: Bool
4756
switch (color.downscrollDuration, color.flatDisplayDuration) {
@@ -52,15 +61,15 @@ func colorChallenge(from event: ServerSessionInformationEvent) -> FaceLivenessSe
5261
duration = Double(color.downscrollDuration)
5362
shouldScroll = true
5463
}
55-
64+
5665
precondition(
5766
color.freshnessColor.rgb.count == 3,
58-
"""
59-
Received invalid freshness colors.
60-
Expected 3 values (r, g, b), received: \(color.freshnessColor.rgb.count)
61-
"""
67+
"""
68+
Received invalid freshness colors.
69+
Expected 3 values (r, g, b), received: \(color.freshnessColor.rgb.count)
70+
"""
6271
)
63-
72+
6473
return .init(
6574
rgb: .init(
6675
red: Double(color.freshnessColor.rgb[0]) / 255,
@@ -72,14 +81,23 @@ func colorChallenge(from event: ServerSessionInformationEvent) -> FaceLivenessSe
7281
shouldScroll: shouldScroll
7382
)
7483
})
75-
return .init(
76-
colors: displayColors
77-
)
84+
return .init(colors: displayColors)
7885
}
7986

8087
func sessionConfiguration(from event: ServerSessionInformationEvent) -> FaceLivenessSession.SessionConfiguration {
81-
.init(
82-
colorChallenge: colorChallenge(from: event),
83-
ovalMatchChallenge: ovalChallenge(from: event)
84-
)
88+
switch event.sessionInformation.challenge.type {
89+
case .faceMovementAndLightChallenge(let challenge):
90+
return .faceMovementAndLight(colorChallenge(from: challenge), ovalChallenge(from: event))
91+
case .faceMovementChallenge:
92+
return .faceMovement(ovalChallenge(from: event))
93+
}
94+
}
95+
96+
func challenge(from event: ChallengeEvent) -> Challenge {
97+
switch event.type {
98+
case .faceMovementAndLightChallenge:
99+
return .faceMovementAndLightChallenge(event.version)
100+
case .faceMovementChallenge:
101+
return .faceMovementChallenge(event.version)
102+
}
85103
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
//
2+
// Copyright Amazon.com Inc. or its affiliates.
3+
// All Rights Reserved.
4+
//
5+
// SPDX-License-Identifier: Apache-2.0
6+
//
7+
8+
import Foundation
9+
10+
extension FaceLivenessSession {
11+
public static let supportedChallenges: [Challenge] = [
12+
.faceMovementAndLightChallenge("2.0.0"),
13+
.faceMovementChallenge("1.0.0")
14+
]
15+
}

AmplifyPlugins/Predictions/AWSPredictionsPlugin/Liveness/Model/FaceLivenessSession+SessionConfiguration.swift

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,8 @@ import Foundation
99

1010
extension FaceLivenessSession {
1111
@_spi(PredictionsFaceLiveness)
12-
public struct SessionConfiguration {
13-
public let colorChallenge: ColorChallenge
14-
public let ovalMatchChallenge: OvalMatchChallenge
15-
16-
public init(colorChallenge: ColorChallenge, ovalMatchChallenge: OvalMatchChallenge) {
17-
self.colorChallenge = colorChallenge
18-
self.ovalMatchChallenge = ovalMatchChallenge
19-
}
12+
public enum SessionConfiguration {
13+
case faceMovement(OvalMatchChallenge)
14+
case faceMovementAndLight(ColorChallenge, OvalMatchChallenge)
2015
}
2116
}

AmplifyPlugins/Predictions/AWSPredictionsPlugin/Liveness/SPI/AWSPredictionsPlugin+Liveness.swift

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ extension AWSPredictionsPlugin {
1515
withID sessionID: String,
1616
credentialsProvider: AWSCredentialsProvider? = nil,
1717
region: String,
18-
options: FaceLivenessSession.Options,
1918
completion: @escaping (Result<Void, FaceLivenessSessionError>) -> Void
2019
) async throws -> FaceLivenessSession {
2120

@@ -48,7 +47,16 @@ extension AWSPredictionsPlugin {
4847
extension FaceLivenessSession {
4948
@_spi(PredictionsFaceLiveness)
5049
public struct Options {
51-
public init() {}
50+
public let attemptCount: Int
51+
public let preCheckViewEnabled: Bool
52+
53+
public init(
54+
attemptCount: Int,
55+
preCheckViewEnabled: Bool
56+
) {
57+
self.attemptCount = attemptCount
58+
self.preCheckViewEnabled = preCheckViewEnabled
59+
}
5260
}
5361
}
5462

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
//
2+
// Copyright Amazon.com Inc. or its affiliates.
3+
// All Rights Reserved.
4+
//
5+
// SPDX-License-Identifier: Apache-2.0
6+
//
7+
8+
import Foundation
9+
public typealias Version = String
10+
11+
@_spi(PredictionsFaceLiveness)
12+
public enum Challenge: Equatable {
13+
case faceMovementChallenge(Version)
14+
case faceMovementAndLightChallenge(Version)
15+
16+
public func queryParameterString() -> String {
17+
switch(self) {
18+
case .faceMovementChallenge(let version):
19+
return "FaceMovementChallenge" + "_" + version
20+
case .faceMovementAndLightChallenge(let version):
21+
return "FaceMovementAndLightChallenge" + "_" + version
22+
}
23+
}
24+
25+
public static func ==(lhs: Challenge, rhs: Challenge) -> Bool {
26+
switch (lhs, rhs) {
27+
case (.faceMovementChallenge(let lhsVersion), .faceMovementChallenge(let rhsVersion)):
28+
return lhsVersion == rhsVersion
29+
case (.faceMovementAndLightChallenge(let lhsVersion), .faceMovementAndLightChallenge(let rhsVersion)):
30+
return lhsVersion == rhsVersion
31+
default:
32+
return false
33+
}
34+
}
35+
}
36+
37+
@_spi(PredictionsFaceLiveness)
38+
public enum ChallengeType: String, Codable {
39+
case faceMovementChallenge = "FaceMovementChallenge"
40+
case faceMovementAndLightChallenge = "FaceMovementAndLightChallenge"
41+
}

0 commit comments

Comments
 (0)