@@ -37,6 +37,7 @@ import com.amplifyframework.predictions.aws.models.liveness.ColorDisplayed
3737import com.amplifyframework.predictions.aws.models.liveness.FaceMovementAndLightClientChallenge
3838import com.amplifyframework.predictions.aws.models.liveness.FreshnessColor
3939import com.amplifyframework.predictions.aws.models.liveness.InitialFace
40+ import com.amplifyframework.predictions.aws.models.liveness.InvalidSignatureException
4041import com.amplifyframework.predictions.aws.models.liveness.LivenessResponseStream
4142import com.amplifyframework.predictions.aws.models.liveness.SessionInformation
4243import com.amplifyframework.predictions.aws.models.liveness.TargetFace
@@ -78,23 +79,13 @@ internal class LivenessWebSocket(
7879 private val signer = AWSV4Signer ()
7980 private var credentials: Credentials ? = null
8081
81- internal var offset = 0L
82- internal enum class ReconnectState {
83- INITIAL ,
84- RECONNECTING ,
85- RECONNECTING_AGAIN ;
86-
87- companion object {
88- fun next (state : ReconnectState ): ReconnectState {
89- return when (state) {
90- INITIAL -> RECONNECTING
91- RECONNECTING -> RECONNECTING_AGAIN
92- RECONNECTING_AGAIN -> RECONNECTING_AGAIN
93- }
94- }
95- }
82+ // The reported time difference between the server and client. Only set if diff is higher than 4 minutes
83+ internal var timeDiffOffsetInMillis = 0L
84+ internal enum class ConnectionState {
85+ NORMAL ,
86+ ATTEMPT_RECONNECT ,
9687 }
97- internal var reconnectState = ReconnectState . INITIAL
88+ internal var reconnectState = ConnectionState . NORMAL
9889
9990 @VisibleForTesting
10091 internal var webSocket: WebSocket ? = null
@@ -103,7 +94,7 @@ internal class LivenessWebSocket(
10394 private var faceDetectedStart = 0L
10495 private var videoStartTimestamp = 0L
10596 private var videoEndTimestamp = 0L
106- private var webSocketError: PredictionsException ? = null
97+ @VisibleForTesting internal var webSocketError: PredictionsException ? = null
10798 internal var clientStoppedSession = false
10899 val json = Json { ignoreUnknownKeys = true }
109100
@@ -119,15 +110,15 @@ internal class LivenessWebSocket(
119110 date.time - adjustedDate()
120111 } else 0
121112
122- reconnectState = ReconnectState .next(reconnectState )
123- // if offset is > 5 minutes, server will reject the request
124- if (kotlin.math.abs(tempOffset) < FIVE_MINUTES ) {
125- super .onOpen(webSocket, response)
126- this @LivenessWebSocket.webSocket = webSocket
127- } else {
128- // server will close this websocket, don't report that failure back
129- offset = tempOffset
130- start()
113+ super .onOpen(webSocket, response )
114+
115+ this @LivenessWebSocket.webSocket = webSocket
116+
117+ // If offset is > 4 minutes, server may reject the request
118+ // The real allowed diff from serer is < 5 but we check for 4 to add a buffer
119+ if ( ! isTimeDiffSafe(tempOffset)) {
120+ LOG .info( " Server reported a time difference between client and server of > 4 minutes " )
121+ timeDiffOffsetInMillis = tempOffset
131122 }
132123 }
133124
@@ -169,14 +160,29 @@ internal class LivenessWebSocket(
169160 override fun onClosed (webSocket : WebSocket , code : Int , reason : String ) {
170161 LOG .debug(" WebSocket onClosed" )
171162 super .onClosed(webSocket, code, reason)
172- if (reconnectState == ReconnectState .RECONNECTING ) {
173- // do nothing; we expected the server to close the connection
174- } else if (code != NORMAL_SOCKET_CLOSURE_STATUS_CODE && ! clientStoppedSession) {
175- val faceLivenessException = webSocketError ? : PredictionsException (
176- " An error occurred during the face liveness check." ,
177- reason
178- )
179- onErrorReceived.accept(faceLivenessException)
163+ if (code != NORMAL_SOCKET_CLOSURE_STATUS_CODE && ! clientStoppedSession) {
164+ val recordedError = webSocketError
165+
166+ /*
167+ If the server reports an invalid signature due to a time difference between the local clock and the
168+ server clock, AND we haven't already tried to reconnect, then we should try to reconnect with an offset
169+ */
170+ if (reconnectState == ConnectionState .NORMAL &&
171+ ! isTimeDiffSafe(timeDiffOffsetInMillis) &&
172+ recordedError is PredictionsException &&
173+ recordedError.cause is InvalidSignatureException
174+ ) {
175+ LOG .info(" The server rejected the connection due to a likely time difference. Attempting reconnect" )
176+ reconnectState = ConnectionState .ATTEMPT_RECONNECT
177+ webSocketError = null
178+ start()
179+ } else {
180+ val faceLivenessException = recordedError ? : PredictionsException (
181+ " An error occurred during the face liveness check." ,
182+ reason
183+ )
184+ onErrorReceived.accept(faceLivenessException)
185+ }
180186 } else {
181187 onComplete.call()
182188 }
@@ -197,14 +203,6 @@ internal class LivenessWebSocket(
197203 }
198204
199205 fun start () {
200- if (reconnectState == ReconnectState .RECONNECTING_AGAIN ) {
201- onErrorReceived.accept(
202- PredictionsException (
203- " Invalid device time" ,
204- " Too many attempts were made to correct device time"
205- )
206- )
207- }
208206 val userAgent = getUserAgent()
209207
210208 val okHttpClient = OkHttpClient .Builder ()
@@ -312,6 +310,18 @@ internal class LivenessWebSocket(
312310 AccessDeniedException (
313311 cause = livenessResponse.accessDeniedException
314312 )
313+ } else if (livenessResponse.unrecognizedClientException != null ) {
314+ PredictionsException (
315+ " Unrecognized client" ,
316+ livenessResponse.unrecognizedClientException,
317+ " Please check your credentials"
318+ )
319+ } else if (livenessResponse.invalidSignatureException != null ) {
320+ PredictionsException (
321+ " Invalid signature" ,
322+ livenessResponse.invalidSignatureException,
323+ " Please check your credentials"
324+ )
315325 } else {
316326 PredictionsException (
317327 " An unknown error occurred during the Liveness flow." ,
@@ -477,12 +487,14 @@ internal class LivenessWebSocket(
477487 }
478488
479489 fun adjustedDate (date : Long = Date ().time): Long {
480- return date + offset
490+ return date + timeDiffOffsetInMillis
481491 }
482492
493+ private fun isTimeDiffSafe (diffInMillis : Long ) = kotlin.math.abs(diffInMillis) < FOUR_MINUTES
494+
483495 companion object {
484496 private const val NORMAL_SOCKET_CLOSURE_STATUS_CODE = 1000
485- private val FIVE_MINUTES = 1000 * 60 * 5
497+ private const val FOUR_MINUTES = 1000 * 60 * 4
486498 @VisibleForTesting val datePattern = " EEE, d MMM yyyy HH:mm:ss z"
487499 private val LOG = Amplify .Logging .logger(CategoryType .PREDICTIONS , " amplify:aws-predictions" )
488500 }
0 commit comments