89 lines
4.9 KiB
Swift
89 lines
4.9 KiB
Swift
import Foundation
|
|
import Testing
|
|
@testable import RemoteCore
|
|
|
|
@Test func wireDatesUseUnixMillisecondsForGrantAndCommand() throws {
|
|
let session = UUID(), tv = UUID()
|
|
let issued = Date(timeIntervalSince1970: 1_700_000_000.125)
|
|
let grant = RemoteSessionGrant(sessionID: session, televisionID: tv, issuedAt: issued)
|
|
let data = try RelayJSON.encode(grant)
|
|
let object = try #require(JSONSerialization.jsonObject(with: data) as? [String: Any])
|
|
#expect(object["issuedAt"] as? Double == 1_700_000_000_125)
|
|
#expect(try RelayJSON.decode(RemoteSessionGrant.self, from: data) == grant)
|
|
let command = CommandEnvelope(sessionID: session, televisionID: tv, sequence: 1,
|
|
issuedAt: issued, expiresAt: issued.addingTimeInterval(0.5), command: .right)
|
|
let commandData = try RelayJSON.encode(command)
|
|
let commandObject = try #require(JSONSerialization.jsonObject(with: commandData) as? [String: Any])
|
|
#expect(commandObject["expiresAt"] as? Double == 1_700_000_000_625)
|
|
#expect(try RelayJSON.decode(CommandEnvelope.self, from: commandData) == command)
|
|
}
|
|
|
|
@Test func controlPayloadsRoundTripAndFailureIsAnError() throws {
|
|
let session = UUID()
|
|
let begin = BeginRemoteSessionRequest()
|
|
let end = EndRemoteSessionRequest(sessionID: session)
|
|
let ended = RemoteSessionEnded(sessionID: session)
|
|
let failure = RelayFailure(code: .sessionInvalidated)
|
|
#expect(try RelayJSON.decode(BeginRemoteSessionRequest.self, from: RelayJSON.encode(begin)) == begin)
|
|
#expect(try RelayJSON.decode(EndRemoteSessionRequest.self, from: RelayJSON.encode(end)) == end)
|
|
#expect(try RelayJSON.decode(RemoteSessionEnded.self, from: RelayJSON.encode(ended)) == ended)
|
|
#expect(try RelayJSON.decode(RelayFailure.self, from: RelayJSON.encode(failure)) == failure)
|
|
let result: Result<RemoteSessionGrant, RelayFailure> = .failure(failure)
|
|
#expect(throws: failure) { try result.get() }
|
|
}
|
|
|
|
@Test func malformedOrUnsupportedControlPayloadsFailClosed() throws {
|
|
#expect(throws: RelayContractError.unsupportedSchema(2)) {
|
|
try RelayJSON.decode(BeginRemoteSessionRequest.self, from: Data(#"{"schemaVersion":2}"#.utf8))
|
|
}
|
|
#expect(throws: (any Error).self) {
|
|
try RelayJSON.decode(EndRemoteSessionRequest.self, from: Data(#"{"schemaVersion":1,"sessionID":"bad"}"#.utf8))
|
|
}
|
|
#expect(throws: (any Error).self) {
|
|
try RelayJSON.decode(BeginRemoteSessionRequest.self, from: Data(#"{"schemaVersion":true}"#.utf8))
|
|
}
|
|
#expect(throws: RelayContractError.unsupportedSchema(9)) {
|
|
try RelayJSON.encode(RemoteSessionEnded(sessionID: UUID(), schemaVersion: 9))
|
|
}
|
|
}
|
|
|
|
@Test func invalidGrantLifetimeRejectedOnEncodeAndDecode() throws {
|
|
for lifetime: UInt64 in [0, 1_001, .max] {
|
|
let invalid = RemoteSessionGrant(sessionID: UUID(), televisionID: UUID(),
|
|
issuedAt: Date(timeIntervalSince1970: 1_000), maximumCommandLifetimeMilliseconds: lifetime)
|
|
#expect(throws: RelayContractError.invalidLifetime) { try RelayJSON.encode(invalid) }
|
|
// Bypass the validating encoder to simulate an untrusted peer.
|
|
let encoder = JSONEncoder()
|
|
encoder.dateEncodingStrategy = .millisecondsSince1970
|
|
let raw = try encoder.encode(invalid)
|
|
#expect(throws: RelayContractError.invalidLifetime) {
|
|
try RelayJSON.decode(RemoteSessionGrant.self, from: raw)
|
|
}
|
|
}
|
|
let invalidDate = RemoteSessionGrant(sessionID: UUID(), televisionID: UUID(),
|
|
issuedAt: Date(timeIntervalSince1970: .infinity))
|
|
#expect(throws: RelayContractError.invalidTimestamp) { try RelayJSON.encode(invalidDate) }
|
|
}
|
|
|
|
@Test func byteLimitIsCheckedBeforeDecodingAndAtEncoding() throws {
|
|
// JSON string quotes use 2 bytes. Exactly 4096 bytes is permitted.
|
|
let boundary = String(repeating: "a", count: RelayWire.maximumPayloadBytes - 2)
|
|
let encoded = try RelayJSON.encode(boundary)
|
|
#expect(encoded.count == RelayWire.maximumPayloadBytes)
|
|
#expect(try RelayJSON.decode(String.self, from: encoded) == boundary)
|
|
#expect(throws: RelayContractError.payloadTooLarge) { try RelayJSON.encode(boundary + "a") }
|
|
#expect(throws: RelayContractError.payloadTooLarge) {
|
|
try RelayJSON.decode(BeginRemoteSessionRequest.self, from: Data(repeating: 0, count: 4_097))
|
|
}
|
|
}
|
|
|
|
@Test func commandDecodeDoesNotConsumeOrReplaceAdmission() throws {
|
|
let session = UUID(), tv = UUID(), now = Date(timeIntervalSince1970: 1_000)
|
|
let command = CommandEnvelope(sessionID: session, televisionID: tv, sequence: 1,
|
|
issuedAt: now, expiresAt: now.addingTimeInterval(0.5), command: .left)
|
|
let data = try RelayJSON.encode(command)
|
|
var admission = CommandAdmission(sessionID: session, televisionID: tv)
|
|
#expect(admission.admit(try RelayJSON.decode(CommandEnvelope.self, from: data), now: now) == .accepted)
|
|
#expect(admission.admit(try RelayJSON.decode(CommandEnvelope.self, from: data), now: now) == .rejected(.duplicate))
|
|
}
|