Skip to content

Commit

Permalink
ReceiptParser: Sendable conformance
Browse files Browse the repository at this point in the history
For [CSDK-379].
Extracted #1795 to make it easier to review this fix in isolation.

Also made `ASN1ObjectIdentifierBuilder` an `enum` since we don't need to create instances of it to use it.
  • Loading branch information
NachoSoto committed Aug 13, 2022
1 parent 38f311b commit 72fb74b
Show file tree
Hide file tree
Showing 9 changed files with 36 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ class ASN1ContainerBuilder {
}
}

// @unchecked because:
// - Class is not `final` (it's mocked). This implicitly makes subclasses `Sendable` even if they're not thread-safe.
extension ASN1ContainerBuilder: @unchecked Sendable {}

private extension ASN1ContainerBuilder {

func buildInternalContainers(payload: ArraySlice<UInt8>) throws -> [ASN1Container] {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@

import Foundation

class ASN1ObjectIdentifierBuilder {
enum ASN1ObjectIdentifierBuilder {

// info on the format: https://docs.microsoft.com/en-us/windows/win32/seccertenroll/about-object-identifier
func build(fromPayload payload: ArraySlice<UInt8>) throws -> ASN1ObjectIdentifier? {
static func build(fromPayload payload: ArraySlice<UInt8>) throws -> ASN1ObjectIdentifier? {
guard let firstByte = payload.first else { return nil }

var objectIdentifierNumbers: [UInt] = []
Expand All @@ -37,7 +37,7 @@ class ASN1ObjectIdentifierBuilder {
private extension ASN1ObjectIdentifierBuilder {

// https://en.wikipedia.org/wiki/Variable-length_quantity
func decodeVariableLengthQuantity(payload: ArraySlice<UInt8>) throws -> [UInt] {
static func decodeVariableLengthQuantity(payload: ArraySlice<UInt8>) throws -> [UInt] {
var decodedNumbers = [UInt]()

var currentBuffer: UInt = 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,7 @@ class AppleReceiptBuilder {
}

}

// @unchecked because:
// - Class is not `final` (it's mocked). This implicitly makes subclasses `Sendable` even if they're not thread-safe.
extension AppleReceiptBuilder: @unchecked Sendable {}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import Foundation

class InAppPurchaseBuilder {

private let containerBuilder: ASN1ContainerBuilder
private let typeContainerIndex = 0
private let versionContainerIndex = 1 // unused
Expand Down Expand Up @@ -96,4 +97,9 @@ class InAppPurchaseBuilder {
webOrderLineItemId: webOrderLineItemId,
promotionalOfferIdentifier: promotionalOfferIdentifier)
}

}

// @unchecked because:
// - Class is not `final` (it's mocked). This implicitly makes subclasses `Sendable` even if they're not thread-safe.
extension InAppPurchaseBuilder: @unchecked Sendable {}
13 changes: 8 additions & 5 deletions Sources/LocalReceiptParsing/ReceiptParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,11 @@ import Foundation

class ReceiptParser {

private let objectIdentifierBuilder: ASN1ObjectIdentifierBuilder
private let containerBuilder: ASN1ContainerBuilder
private let receiptBuilder: AppleReceiptBuilder

init(objectIdentifierBuilder: ASN1ObjectIdentifierBuilder = ASN1ObjectIdentifierBuilder(),
containerBuilder: ASN1ContainerBuilder = ASN1ContainerBuilder(),
init(containerBuilder: ASN1ContainerBuilder = ASN1ContainerBuilder(),
receiptBuilder: AppleReceiptBuilder = AppleReceiptBuilder()) {
self.objectIdentifierBuilder = objectIdentifierBuilder
self.containerBuilder = containerBuilder
self.receiptBuilder = receiptBuilder
}
Expand Down Expand Up @@ -53,14 +50,20 @@ class ReceiptParser {
}
}

// @unchecked because:
// - Class is not `final` (it's mocked). This implicitly makes subclasses `Sendable` even if they're not thread-safe.
extension ReceiptParser: @unchecked Sendable {}

// MARK: -

private extension ReceiptParser {

func findASN1Container(withObjectId objectId: ASN1ObjectIdentifier,
inContainer container: ASN1Container) throws -> ASN1Container? {
if container.encodingType == .constructed {
for (index, internalContainer) in container.internalContainers.enumerated() {
if internalContainer.containerIdentifier == .objectIdentifier {
let objectIdentifier = try objectIdentifierBuilder.build(
let objectIdentifier = try ASN1ObjectIdentifierBuilder.build(
fromPayload: internalContainer.internalPayload)
if objectIdentifier == objectId && index < container.internalContainers.count - 1 {
// the container that holds the data comes right after the one with the object identifier
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class LocalReceiptParserStoreKitTests: StoreKitConfigTestCase {
}

func testReceiptParserParsesEmptyReceipt() async throws {
let data = try await XCTAsyncUnwrap(await receiptFetcher.receiptData(refreshPolicy: .always))
let data = try await XCTAsyncUnwrap(await self.receiptFetcher.receiptData(refreshPolicy: .always))

let receipt = try self.parser.parse(from: data)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,42 +8,42 @@ class ASN1ObjectIdentifierBuilderTests: TestCase {
let encoder = ASN1ObjectIdentifierEncoder()
func testBuildFromPayloadBuildsCorrectlyForDataPayload() {
let payload = encoder.objectIdentifierPayload(.data)
expect(try ASN1ObjectIdentifierBuilder().build(fromPayload: payload)) == .data
expect(try ASN1ObjectIdentifierBuilder.build(fromPayload: payload)) == .data
}

func testBuildFromPayloadBuildsCorrectlyForSignedDataPayload() {
let payload = encoder.objectIdentifierPayload(.signedData)
expect(try ASN1ObjectIdentifierBuilder().build(fromPayload: payload)) == .signedData
expect(try ASN1ObjectIdentifierBuilder.build(fromPayload: payload)) == .signedData
}

func testBuildFromPayloadBuildsCorrectlyForEnvelopedDataPayload() {
let payload = encoder.objectIdentifierPayload(.envelopedData)
expect(try ASN1ObjectIdentifierBuilder().build(fromPayload: payload)) == .envelopedData
expect(try ASN1ObjectIdentifierBuilder.build(fromPayload: payload)) == .envelopedData
}

func testBuildFromPayloadBuildsCorrectlyForSignedAndEnvelopedDataPayload() {
let payload = encoder.objectIdentifierPayload(.signedAndEnvelopedData)
expect(try ASN1ObjectIdentifierBuilder().build(fromPayload: payload)) == .signedAndEnvelopedData
expect(try ASN1ObjectIdentifierBuilder.build(fromPayload: payload)) == .signedAndEnvelopedData
}

func testBuildFromPayloadBuildsCorrectlyForDigestedDataPayload() {
let payload = encoder.objectIdentifierPayload(.digestedData)
expect(try ASN1ObjectIdentifierBuilder().build(fromPayload: payload)) == .digestedData
expect(try ASN1ObjectIdentifierBuilder.build(fromPayload: payload)) == .digestedData
}

func testBuildFromPayloadBuildsCorrectlyForEncryptedDataPayload() {
let payload = encoder.objectIdentifierPayload(.encryptedData)
expect(try ASN1ObjectIdentifierBuilder().build(fromPayload: payload)) == .encryptedData
expect(try ASN1ObjectIdentifierBuilder.build(fromPayload: payload)) == .encryptedData
}

func testBuildFromPayloadReturnsNilIfIdentifierNotRecognized() {
let unknownObjectID = [1, 3, 23, 534643, 7454, 1, 7, 2]
let payload = encoder.encodeASN1ObjectIdentifier(numbers: unknownObjectID)
expect(try ASN1ObjectIdentifierBuilder().build(fromPayload: payload)).to(beNil())
expect(try ASN1ObjectIdentifierBuilder.build(fromPayload: payload)).to(beNil())
}

func testBuildFromPayloadReturnsNilIfIdentifierPayloadEmpty() {
let payload: ArraySlice<UInt8> = ArraySlice([])
expect(try ASN1ObjectIdentifierBuilder().build(fromPayload: payload)).to(beNil())
expect(try ASN1ObjectIdentifierBuilder.build(fromPayload: payload)).to(beNil())
}
}
3 changes: 1 addition & 2 deletions Tests/UnitTests/LocalReceiptParsing/ReceiptParserTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ class ReceiptParserTests: TestCase {
super.setUp()
mockAppleReceiptBuilder = MockAppleReceiptBuilder()
mockASN1ContainerBuilder = MockASN1ContainerBuilder()
receiptParser = ReceiptParser(objectIdentifierBuilder: ASN1ObjectIdentifierBuilder(),
containerBuilder: mockASN1ContainerBuilder,
receiptParser = ReceiptParser(containerBuilder: mockASN1ContainerBuilder,
receiptBuilder: mockAppleReceiptBuilder)
}

Expand Down
3 changes: 1 addition & 2 deletions Tests/UnitTests/Mocks/MockReceiptParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ class MockReceiptParser: ReceiptParser {
inAppPurchases: [])

convenience init() {
self.init(objectIdentifierBuilder: ASN1ObjectIdentifierBuilder(),
containerBuilder: MockASN1ContainerBuilder(),
self.init(containerBuilder: MockASN1ContainerBuilder(),
receiptBuilder: MockAppleReceiptBuilder())
}

Expand Down

0 comments on commit 72fb74b

Please sign in to comment.