Skip to content

Commit

Permalink
Add initializers for ASN1ObjectIdentifier (#56)
Browse files Browse the repository at this point in the history
* Add initializers for ASN1ObjectIdentifier

* Added comments to public symbols. Updated error name.

* Add new Substring initializer. Refactor UInt Collection initializer.

---------

Co-authored-by: Cory Benfield <[email protected]>
  • Loading branch information
aryan-25 and Lukasa authored Jun 18, 2024
1 parent 77e4ab7 commit e61ef95
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 7 deletions.
62 changes: 55 additions & 7 deletions Sources/SwiftASN1/Basic ASN1 Types/ObjectIdentifier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,9 @@ public struct ASN1ObjectIdentifier: DERImplicitlyTaggable, BERImplicitlyTaggable
}
}

/// An array representing the OID components
@inlinable
var oidComponents: [UInt] {
public var oidComponents: [UInt] {
var content = bytes
// We have to parse the content. From the spec:
//
Expand Down Expand Up @@ -143,25 +144,72 @@ extension ASN1ObjectIdentifier: Hashable {}

extension ASN1ObjectIdentifier: Sendable {}

extension ASN1ObjectIdentifier: ExpressibleByArrayLiteral {
extension ASN1ObjectIdentifier {
/// Initializes ``ASN1ObjectIdentifier`` from its OID components
/// - Parameter elements: The OID components
@inlinable
public init(arrayLiteral elements: UInt...) {
public init(elements: some Collection<UInt>) throws {
var bytes = [UInt8]()
var components = elements[...]
guard let firstComponent = components.popFirst(), let secondComponent = components.popFirst() else {
preconditionFailure("Invalid number of OID components: must be at least two!")
var iterator = elements.makeIterator()

guard let firstComponent = iterator.next(), let secondComponent = iterator.next() else {
throw ASN1Error.tooFewOIDComponents(
reason: "Invalid number of OID components: must be at least two!"
)
}

let serializedFirstComponent = (firstComponent * 40) + secondComponent
ASN1ObjectIdentifier._writeOIDSubidentifier(serializedFirstComponent, into: &bytes)

while let component = components.popFirst() {
while let component = iterator.next() {
ASN1ObjectIdentifier._writeOIDSubidentifier(component, into: &bytes)
}
self.bytes = bytes[...]
}
}

extension ASN1ObjectIdentifier: ExpressibleByStringLiteral {
@inlinable
public init(stringLiteral dotRepresentation: String) {
// To allow for invalid strings to be tested, parsing is performed in a separate initializer that `throws`
// (this initializer conforms to ExpressibleByStringLiteral so cannot throw)
try! self.init(dotRepresentation: dotRepresentation)
}

/// Initializes an instance from a `Substring` containing the dot represented OID
/// - Parameter dotRepresentation: The dot represented OID
@inlinable
public init(dotRepresentation: Substring) throws {
let octetArray = dotRepresentation.utf8.split(
separator: UInt8(ascii: "."),
omittingEmptySubsequences: false
)

try self.init(
elements: octetArray.lazy.map { octet in
guard let uintOctet = UInt(Substring(octet)) else {
throw ASN1Error.invalidStringRepresentation(reason: "Invalid octet in OID")
}
return uintOctet
}
)
}

/// Initializes an instance from a `String` containing the dot represented OID
/// - Parameter dotRepresentation: The dot represented OID
@inlinable
public init(dotRepresentation: String) throws {
try self.init(dotRepresentation: Substring(dotRepresentation))
}
}

extension ASN1ObjectIdentifier: ExpressibleByArrayLiteral {
@inlinable
public init(arrayLiteral elements: UInt...) {
try! self.init(elements: elements)
}
}

extension ASN1ObjectIdentifier: CustomStringConvertible {
@inlinable
public var description: String {
Expand Down
21 changes: 21 additions & 0 deletions Sources/SwiftASN1/Errors.swift
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,23 @@ public struct ASN1Error: Error, Hashable, CustomStringConvertible {
)
)
}

/// Too few OID components were provided. There must be at least two or more.
@inline(never)
public static func tooFewOIDComponents(
reason: String,
file: String = #fileID,
line: UInt = #line
) -> ASN1Error {
return ASN1Error(
backing: .init(
code: .tooFewOIDComponents,
reason: reason,
file: file,
line: line
)
)
}
}

extension ASN1Error {
Expand All @@ -181,6 +198,7 @@ extension ASN1Error {
case unsupportedFieldLength
case invalidPEMDocument
case invalidStringRepresentation
case tooFewOIDComponents
}

fileprivate var backingCode: BackingCode
Expand Down Expand Up @@ -211,6 +229,9 @@ extension ASN1Error {
/// A string was invalid.
public static let invalidStringRepresentation = ErrorCode(.invalidStringRepresentation)

/// Too few OID components were provided. There must be at least two or more.
public static let tooFewOIDComponents = ErrorCode(.tooFewOIDComponents)

public var description: String {
return String(describing: self.backingCode)
}
Expand Down
40 changes: 40 additions & 0 deletions Tests/SwiftASN1Tests/ASN1Tests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1019,6 +1019,46 @@ class ASN1Tests: XCTestCase {
XCTAssertEqual(s, "ASN1Any([5, 0])")
}

func testOIDArrayInitializer() {
let oidArray = try! ASN1ObjectIdentifier(elements: [1, 2, 865, 11241, 3])
XCTAssertEqual(oidArray.oidComponents, [1, 2, 865, 11241, 3])

let anotherOidArray = try! ASN1ObjectIdentifier(elements: [1, 2, 865])
XCTAssertEqual(anotherOidArray.oidComponents, [1, 2, 865])
}

func testOIDArrayInitializerInvalid() {
XCTAssertThrowsError(try ASN1ObjectIdentifier(elements: [1])) { error in
XCTAssertEqual((error as? ASN1Error)?.code, .tooFewOIDComponents)
}

XCTAssertThrowsError(try ASN1ObjectIdentifier(elements: [])) { error in
XCTAssertEqual((error as? ASN1Error)?.code, .tooFewOIDComponents)
}
}

func testOIDStringInitializer() {
let oidFromString: ASN1ObjectIdentifier = "1.2.865.11241.3"
let oidFromArrayLiteral: ASN1ObjectIdentifier = [1, 2, 865, 11241, 3]

XCTAssertEqual(oidFromString, oidFromArrayLiteral)
XCTAssertEqual(oidFromString.oidComponents, [1, 2, 865, 11241, 3])
}

func testOIDStringInitializerInvalid() {
XCTAssertThrowsError(try ASN1ObjectIdentifier(dotRepresentation: "1..2.865.11241.3")) { error in
XCTAssertEqual((error as? ASN1Error)?.code, .invalidStringRepresentation)
}

XCTAssertThrowsError(try ASN1ObjectIdentifier(dotRepresentation: "1.2.<invalid>.11241.3")) { error in
XCTAssertEqual((error as? ASN1Error)?.code, .invalidStringRepresentation)
}

XCTAssertThrowsError(try ASN1ObjectIdentifier(dotRepresentation: "25")) { error in
XCTAssertEqual((error as? ASN1Error)?.code, .tooFewOIDComponents)
}
}

func testSetOfSingleElement() throws {
var serializer = DER.Serializer()
try serializer.serializeSetOf([
Expand Down

0 comments on commit e61ef95

Please sign in to comment.