Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 0 additions & 49 deletions .github/workflows/publish-github-pages.yml

This file was deleted.

3 changes: 3 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ jobs:

- name: Build package
run: swift build

- name: Build for iOS Simulator
run: xcodebuild build -scheme DesignAlgorithmsKit -destination 'generic/platform=iOS Simulator'

- name: Run tests with code coverage
run: |
Expand Down
35 changes: 35 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Changed (`platform-and-test-hygiene`)
- Building without CryptoKit is a compile-time error. The non-cryptographic placeholder that `SHA256.hash` returned on such platforms, and the `fatalError` paths in `MD5Strategy`/`SHA1Strategy`, are gone. `SHA256.hash(data:)` delegates to `SHA256Strategy`.
- Platform claims match the build: macOS 12 and iOS 15 (both built in CI). WASM, tvOS and watchOS are no longer claimed.
- The DocC catalog lives in the target (`Sources/DesignAlgorithmsKit/DesignAlgorithmsKit.docc`), and its symbol links resolve with no warnings.
- The gzip/gunzip interoperability tests moved to a separate `DesignAlgorithmsKitIntegrationTests` target (macOS, skipped when the tools are missing).
- Corrected doc comments for `FilenameTokenizer`, `ObjectFactory` and `HashAlgorithm`.

### Added (`platform-and-test-hygiene`)
- `HybridEncryption` tests.

### Removed (`platform-and-test-hygiene`)
- The empty `Algorithms/WASMGuard.swift` and its `exclude` entry.
- The GitHub Pages publish workflow and script. They needed `swift-docc-plugin`, which this package does not depend on, so they only ever published a static fallback page.
- The placeholder swift-testing `example()` test.

### Changed (`fix-ulid-spec-compliance`)
- **ULID text format:** `ULID.string`/`description` and `ULID(string:)` now follow the ULID specification (2 padding bits at the front, so the first character is `0`-`7`). `ULID(string:)` rejects strings starting above `7`. The 16 bytes are unchanged; only the text differs.
- **Migration:** strings stored by 1.10.x/1.11.x do not parse the same way. Read them with the new `ULID(legacyString:)` and re-save `string`. Known consumers: RetroboxBBS and RetroServe (TermFoundation).
Expand Down Expand Up @@ -49,6 +64,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Notes
- Releases v1.8.0 (FilenameTokenizer, UUIDv5Generator), v1.9.0 (Visitor, Graph, Gzip) and v1.10.0 were tagged without CHANGELOG entries; backfill is tracked in `platform-and-test-hygiene`

## [1.10.0] - 2026-02-04

### Changed
- `.gitignore` excludes `.build` and other build artifacts.

### Notes
- A MerkleTree consolidation was merged and reverted before the tag; the public API matches 1.9.0.

## [1.9.0] - 2026-01-02

### Added
- `Visitor` pattern and `Graph` data structure (#14).
- `Gzip` in-memory GZIP compression and decompression (#15).

## [1.8.0] - 2025-12-29

### Added
- `UUIDv5Generator` for name-based (RFC 4122 version 5) UUIDs.
- `FilenameTokenizer` for splitting file names into words (#13).

## [1.7.0] - 2025-12-21

### Added
Expand Down
15 changes: 8 additions & 7 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,13 @@ let package = Package(
name: "DesignAlgorithmsKit",
targets: ["DesignAlgorithmsKit"]),
],
dependencies: [
// No dependencies - keep it simple for WASM compatibility
],
// No package dependencies. Apple platforms only (CryptoKit, NSLock).
dependencies: [],
targets: [
.target(
name: "DesignAlgorithmsKit",
dependencies: [],
path: "Sources/DesignAlgorithmsKit",
exclude: [
// Exclude hash/crypto types for WASM builds (they use NSLock)
"Algorithms/WASMGuard.swift"
],
linkerSettings: [
.linkedLibrary("z")
]
Expand All @@ -38,5 +33,11 @@ let package = Package(
.linkedLibrary("z")
]
),
// Tests that run system tools (gzip/gunzip); macOS only, skipped when the tools are missing.
.testTarget(
name: "DesignAlgorithmsKitIntegrationTests",
dependencies: ["DesignAlgorithmsKit"],
path: "Tests/DesignAlgorithmsKitIntegrationTests"
),
]
)
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ One library product and target: **`DesignAlgorithmsKit`** (`Sources/DesignAlgori

## Platforms

`Package.swift` declares macOS 12 and iOS 15, with swift-tools-version 5.10. The library imports CryptoKit and links the system zlib, so in practice it builds only for Apple platforms. The older WASM, tvOS and watchOS claims are not backed by the build.
macOS 12 and iOS 15 (swift-tools-version 5.10); CI builds both. The library uses CryptoKit, `NSLock` and the system zlib, so it builds only for Apple platforms; building without CryptoKit fails with a compile-time error instead of producing placeholder digests. tvOS and watchOS are not declared or built.

## Dependencies

Expand Down Expand Up @@ -93,16 +93,16 @@ swift test
```

- No other packages are needed to build DAK.
- Current baseline: `swift build` succeeds with two warnings, and `swift test` runs 343 XCTest tests and 1 swift-testing placeholder with 0 failures.
- `IntegrationCompressionTests` runs `/usr/bin/gzip` and `/usr/bin/gunzip`, so it needs macOS.
- `swift build` is warning-free. `swift test` runs the unit tests (`DesignAlgorithmsKitTests`, 374 XCTest tests) and the integration tests (`DesignAlgorithmsKitIntegrationTests`), which call `/usr/bin/gzip` and `/usr/bin/gunzip` and are skipped when those tools are missing.
- CI also builds for the iOS Simulator: `xcodebuild build -scheme DesignAlgorithmsKit -destination 'generic/platform=iOS Simulator'`.

## Known issues

- Platform claims are broader than tested, `WASMGuard.swift` is dead code, SHA code is duplicated, and the DocC catalog is not attached to the target.
None known.

## Documentation

- `Documentation.docc/`: DocC articles. They are not yet attached to the target.
- `Sources/DesignAlgorithmsKit/DesignAlgorithmsKit.docc/`: DocC catalog. Build it with Xcode's Product > Build Documentation or `xcodebuild docbuild -scheme DesignAlgorithmsKit -destination 'generic/platform=macOS'`.
- [CHANGELOG.md](CHANGELOG.md), [CONTRIBUTING.md](CONTRIBUTING.md), [SECURITY.md](SECURITY.md), [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md).

## License
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public struct Gzip {
}

/// Decompresses GZIP compressed data.
/// - Parameter data: The compressed data (including GZIP header).
/// - Parameter input: The compressed data (including GZIP header).
/// - Returns: The uncompressed data.
/// - Throws: `Gzip.Error.decompressionFailed`, or `invalidData` for a malformed header or a
/// trailer whose CRC-32 or ISIZE does not match the inflated bytes.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@

import Foundation

/// Hash algorithms supported for file and disk image hashing
/// Hash algorithms for content hashing and checksum validation
public enum HashAlgorithm: String, CaseIterable, Codable, Sendable {
/// SHA-256: Recommended default for new hash generation (cryptographically secure)
case sha256 = "sha256"

/// SHA-1: Legacy support for existing systems (deprecated but collision-resistant)
case sha1 = "sha1"

/// MD5: Read-only legacy support for validation against companion files and existing checksums
/// MD5: Read-only legacy support for validation against existing checksums
/// ⚠️ Do not use for new hash generation - use SHA-256 instead
/// ✅ Supported for: reading companion checksum files (.md5, .md5sum), validating against existing MD5 hashes
case md5 = "md5"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@

import Foundation

#if canImport(CryptoKit)
import CryptoKit
#if !canImport(CryptoKit)
#error("DesignAlgorithmsKit needs CryptoKit for its digests; it builds only for Apple platforms.")
#endif

/// Protocol for hash algorithms
Expand Down Expand Up @@ -43,43 +43,11 @@ public extension HashAlgorithmProtocol {
}
}

/// SHA-256 hash algorithm
/// SHA-256 hash algorithm (CryptoKit, through ``SHA256Strategy``)
public enum SHA256: HashAlgorithmProtocol {
public static let name = "SHA-256"

public static func hash(data: Data) -> Data {
#if canImport(CryptoKit)
let digest = CryptoKit.SHA256.hash(data: data)
return Data(digest)
#else
// Fallback implementation
// In production, use CommonCrypto or another crypto library
return fallbackHash(data: data)
#endif
SHA256Strategy().compute(data: data)
}

#if !canImport(CryptoKit)
/// Fallback hash implementation (simple, not cryptographically secure)
/// For production use, import CryptoKit or CommonCrypto
/// - Note: This path is conditionally compiled and only available when CryptoKit is not available.
/// It cannot be tested in environments where CryptoKit is available (like macOS/iOS test environments).
/// The fallback implementation is intentionally simple and not cryptographically secure.
private static func fallbackHash(data: Data) -> Data {
var hash = Data(count: 32)
data.withUnsafeBytes { dataBytes in
hash.withUnsafeMutableBytes { hashBytes in
// Simple hash (NOT cryptographically secure)
// This is a placeholder - use CryptoKit in production
for i in 0..<32 {
var value: UInt8 = 0
for j in 0..<dataBytes.count {
value ^= dataBytes[j] &+ UInt8(i)
}
hashBytes[i] = value
}
}
}
return hash
}
#endif
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@
// MD5 Hash Strategy

import Foundation
#if canImport(CryptoKit)
import CryptoKit
#endif

/// Strategy for MD5 hashing
/// Note: MD5 is insecure and should only be used for legacy compatibility
Expand All @@ -14,11 +12,7 @@ public struct MD5Strategy: HashStrategy {
public init() {}

public func compute(data: Data) -> Data {
#if canImport(CryptoKit)
let digest = Insecure.MD5.hash(data: data)
return Data(digest)
#else
fatalError("CryptoKit not available for MD5")
#endif
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@
// SHA-1 Hash Strategy

import Foundation
#if canImport(CryptoKit)
import CryptoKit
#endif

/// Strategy for SHA-1 hashing
/// Note: SHA-1 is insecure and should only be used for legacy compatibility
Expand All @@ -14,11 +12,7 @@ public struct SHA1Strategy: HashStrategy {
public init() {}

public func compute(data: Data) -> Data {
#if canImport(CryptoKit)
let digest = Insecure.SHA1.hash(data: data)
return Data(digest)
#else
fatalError("CryptoKit not available for SHA1")
#endif
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@
// SHA-256 Hash Strategy

import Foundation
#if canImport(CryptoKit)
import CryptoKit
#endif

/// Strategy for SHA-256 hashing
public struct SHA256Strategy: HashStrategy {
Expand All @@ -13,13 +11,7 @@ public struct SHA256Strategy: HashStrategy {
public init() {}

public func compute(data: Data) -> Data {
#if canImport(CryptoKit)
let digest = CryptoKit.SHA256.hash(data: data)
return Data(digest)
#else
// Uses the existing SHA256 implementation in DesignAlgorithmsKit (from HashAlgorithmProtocol)
// Since we are INSIDE DAK, we can access SHA256 enum directly.
return SHA256.hash(data: data)
#endif
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ public struct FilenameTokenizer {
///
/// Examples:
/// - "PDFExtractor.swift" -> ["PDF", "Extractor", "swift"]
/// - "disk_ii_controller" -> ["disk", "ii", "controller"]
/// - "iPhone12Pro" -> ["iPhone", "12", "Pro"]
/// - "user_profile_view" -> ["user", "profile", "view"]
/// - "iPhone12Pro" -> ["i", "Phone", "12", "Pro"] (every lowercase-to-uppercase change splits)
public static func tokenize(_ filename: String) -> [String] {
// 1. Replace common separators with spaces
let separators = CharacterSet(charactersIn: "_-.")
Expand Down
6 changes: 0 additions & 6 deletions Sources/DesignAlgorithmsKit/Algorithms/WASMGuard.swift

This file was deleted.

19 changes: 13 additions & 6 deletions Sources/DesignAlgorithmsKit/Creational/Factory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,18 @@ public protocol FactoryProduct {
/// ## Usage
///
/// ```swift
/// // Register a factory method
/// ObjectFactory.register(type: "myType") { config in
/// return MyType(configuration: config)
/// let factory = ObjectFactory.shared
///
/// // Register a factory closure
/// factory.register(type: "myType") { config in
/// return try MyType(configuration: config)
/// }
///
/// // Create object via factory
/// let object = try ObjectFactory.create(type: "myType", configuration: [:])
/// // Or register a FactoryProduct type under its type name
/// factory.register(MyType.self)
///
/// // Create an object
/// let object = try factory.create(type: "myType", configuration: [:])
/// ```
public final class ObjectFactory: @unchecked Sendable {
/// Shared singleton instance (lazy initialization)
Expand Down Expand Up @@ -77,7 +82,9 @@ public final class ObjectFactory: @unchecked Sendable {
}

/// Register a factory method for a FactoryProduct type
/// - Parameter type: The FactoryProduct type
/// - Parameters:
/// - type: The FactoryProduct type
/// - key: Registration key; defaults to the type's name
/// Thread-safe: Can be called concurrently
public func register<T: FactoryProduct>(_ type: T.Type, key: String? = nil) {
#if !os(WASI) && !arch(wasm32)
Expand Down
Loading
Loading