From 2b5405a87de3ded2a7977d74b5a2d9ccf44e4bda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Stormacq?= Date: Thu, 7 Nov 2024 10:00:05 +0100 Subject: [PATCH 01/10] WIP : init plugin --- .swiftformatignore | 1 + Examples/_MyFirstFunction/create_function.sh | 39 +++++++++ Package.swift | 47 ++++++++++ Plugins/AWSLambdaInitializer/Plugin.swift | 91 ++++++++++++++++++++ Plugins/AWSLambdaInitializer/Template.swift | 35 ++++++++ readme.md | 26 +++++- 6 files changed, 236 insertions(+), 3 deletions(-) create mode 100644 .swiftformatignore create mode 100755 Examples/_MyFirstFunction/create_function.sh create mode 100644 Plugins/AWSLambdaInitializer/Plugin.swift create mode 100644 Plugins/AWSLambdaInitializer/Template.swift diff --git a/.swiftformatignore b/.swiftformatignore new file mode 100644 index 00000000..ea8271df --- /dev/null +++ b/.swiftformatignore @@ -0,0 +1 @@ +Plugins/AWSLambdaInitializer/Template.swift diff --git a/Examples/_MyFirstFunction/create_function.sh b/Examples/_MyFirstFunction/create_function.sh new file mode 100755 index 00000000..71fd4ad7 --- /dev/null +++ b/Examples/_MyFirstFunction/create_function.sh @@ -0,0 +1,39 @@ +#!/bin/sh + +# check if docker is installed +which docker > /dev/null +if [[ $? != 0 ]]; then + echo "Docker is not installed. Please install Docker and try again." + exit 1 +fi + +# check if user has an access key and secret access key +echo "This script creates and deploys a Lambda function on your AWS Account. + +You must have an AWS account and know an AWS access key, secret access key, and an optional session token. These values are read from '~/.aws/credentials' or asked interactively. +" + +read -p "Are you ready to create your first Lambda function in Swift? [y/n] " continue +if [[ continue != ^[Yy]$ ]]; then + echo "OK, try again later when you feel ready" + exit 1 +fi + +echo "⚡️ Create your Swift command line project" +swift package init --type executable --name MyLambda + +echo "📦 Add the AWS Lambda Swift runtime to your project" +swift package add-dependency https://github.com/swift-server/swift-aws-lambda-runtime.git --branch main +swift package add-dependency https://github.com/swift-server/swift-aws-lambda-events.git --branch main +swift package add-target-dependency AWSLambdaRuntime MyLambda --package swift-aws-lambda-runtime +swift package add-target-dependency AWSLambdaEvents MyLambda --package swift-aws-lambda-events + +echo "📝 Write the Swift code" +swift package lambda-init --allow-writing-to-package-directory + +echo "📦 Compile and package the function for deployment" +swift package archive --allow-network-connections docker + +echo "🚀 Deploy to AWS Lambda" + + diff --git a/Package.swift b/Package.swift index 69f5ff36..ff9475ed 100644 --- a/Package.swift +++ b/Package.swift @@ -16,15 +16,21 @@ let package = Package( .library(name: "AWSLambdaRuntime", targets: ["AWSLambdaRuntime"]), // this has all the main functionality for lambda and it does not link Foundation .library(name: "AWSLambdaRuntimeCore", targets: ["AWSLambdaRuntimeCore"]), + // plugin to create a new Lambda function, based on a template + .plugin(name: "AWSLambdaInitializer", targets: ["AWSLambdaInitializer"]), // plugin to package the lambda, creating an archive that can be uploaded to AWS // requires Linux or at least macOS v15 .plugin(name: "AWSLambdaPackager", targets: ["AWSLambdaPackager"]), + // plugin to deploy a Lambda function + .plugin(name: "AWSLambdadeployer", targets: ["AWSLambdaDeployer"]), + .executable(name: "AWSLambdaDeployerHelper", targets: ["AWSLambdaDeployerHelper"]), // for testing only .library(name: "AWSLambdaTesting", targets: ["AWSLambdaTesting"]), ], dependencies: [ .package(url: "https://github.com/apple/swift-nio.git", from: "2.72.0"), .package(url: "https://github.com/apple/swift-log.git", from: "1.5.4"), + .package(url: "https://github.com/apple/swift-crypto.git", from: "3.9.1"), ], targets: [ .target( @@ -45,6 +51,19 @@ let package = Package( ], swiftSettings: [.swiftLanguageMode(.v5)] ), + .plugin( + name: "AWSLambdaInitializer", + capability: .command( + intent: .custom( + verb: "lambda-init", + description: + "Create a new Lambda function in the current project directory." + ), + permissions: [ + .writeToPackageDirectory(reason: "Create a file with an HelloWorld Lambda function.") + ] + ) + ), .plugin( name: "AWSLambdaPackager", capability: .command( @@ -61,6 +80,34 @@ let package = Package( ] ) ), + .plugin( + name: "AWSLambdaDeployer", + capability: .command( + intent: .custom( + verb: "deploy", + description: + "Deploy the Lambda function. You must have an AWS account and know an access key and secret access key." + ), + permissions: [ + .allowNetworkConnections( + scope: .all(ports: [443]), + reason: "This plugin uses the AWS Lambda API to deploy the function." + ) + ] + ), + dependencies: [ + .target(name: "AWSLambdaDeployerHelper") + ] + ), + .executableTarget( + name: "AWSLambdaDeployerHelper", + dependencies: [ + .product(name: "NIOHTTP1", package: "swift-nio"), + .product(name: "NIOCore", package: "swift-nio"), + .product(name: "Crypto", package: "swift-crypto"), + ], + swiftSettings: [.swiftLanguageMode(.v6)] + ), .testTarget( name: "AWSLambdaRuntimeCoreTests", dependencies: [ diff --git a/Plugins/AWSLambdaInitializer/Plugin.swift b/Plugins/AWSLambdaInitializer/Plugin.swift new file mode 100644 index 00000000..21713c0f --- /dev/null +++ b/Plugins/AWSLambdaInitializer/Plugin.swift @@ -0,0 +1,91 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2022 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import Foundation +import PackagePlugin + +@main +@available(macOS 15.0, *) +struct AWSLambdaPackager: CommandPlugin { + + let destFileName = "Sources/main.swift" + + func performCommand(context: PackagePlugin.PluginContext, arguments: [String]) async throws { + let configuration = try Configuration(context: context, arguments: arguments) + + if configuration.help { + self.displayHelpMessage() + return + } + + let destFileURL = context.package.directoryURL.appendingPathComponent(destFileName) + do { + try functionWithUrlTemplate.write(to: destFileURL, atomically: true, encoding: .utf8) + + if configuration.verboseLogging { + Diagnostics.progress("✅ Lambda function written to \(destFileName)") + Diagnostics.progress("📦 You can now package with: 'swift package archive'") + } + + } catch { + Diagnostics.error("🛑Failed to create the Lambda function file: \(error)") + } + } + + private func displayHelpMessage() { + print( + """ + OVERVIEW: A SwiftPM plugin to scaffold a HelloWorld Lambda function. + + USAGE: swift package lambda-init + [--help] [--verbose] + [--allow-writing-to-package-directory] + + OPTIONS: + --allow-writing-to-package-directory Don't ask for permissions to write files. + --verbose Produce verbose output for debugging. + --help Show help information. + """ + ) + } + +} + +private struct Configuration: CustomStringConvertible { + public let help: Bool + public let verboseLogging: Bool + + public init( + context: PluginContext, + arguments: [String] + ) throws { + var argumentExtractor = ArgumentExtractor(arguments) + let verboseArgument = argumentExtractor.extractFlag(named: "verbose") > 0 + let helpArgument = argumentExtractor.extractFlag(named: "help") > 0 + + // help required ? + self.help = helpArgument + + // verbose logging required ? + self.verboseLogging = verboseArgument + } + + var description: String { + """ + { + verboseLogging: \(self.verboseLogging) + } + """ + } +} diff --git a/Plugins/AWSLambdaInitializer/Template.swift b/Plugins/AWSLambdaInitializer/Template.swift new file mode 100644 index 00000000..133e5033 --- /dev/null +++ b/Plugins/AWSLambdaInitializer/Template.swift @@ -0,0 +1,35 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2022 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import Foundation + +let functionWithUrlTemplate = #""" + import AWSLambdaRuntime + import AWSLambdaEvents + + // in this example we receive a FunctionURLRequest and we return a FunctionURLResponse + // https://docs.aws.amazon.com/lambda/latest/dg/urls-invocation.html#urls-payloads + + let runtime = LambdaRuntime { + (event: FunctionURLRequest, context: LambdaContext) -> FunctionURLResponse in + + guard let name = event.queryStringParameters?["name"] else { + return FunctionURLResponse(statusCode: .badRequest) + } + + return FunctionURLResponse(statusCode: .ok, body: #"{ "message" : "Hello \#\#(name)" } "#) + } + + try await runtime.run() + """# diff --git a/readme.md b/readme.md index ca8097e8..181be9ce 100644 --- a/readme.md +++ b/readme.md @@ -18,6 +18,17 @@ ## TL;DR +The `Examples/_MyFirstFunction` contains a script that goes through the steps described in this section. + +If you are really impatient, just type: + +```bash +cd Examples/_MyFirstFunction +./create_and_deploy_function.sh +``` + +Otherwise, continue reading. + 1. Create a new Swift executable project ```bash @@ -64,7 +75,15 @@ swift package init --type executable ) ``` -3. Edit `Sources/main.swift` file and replace the content with this code +3. Scaffold a minimal Lambda function + +The runtime comes with a plugin to generate the code of a simple AWS Lambda function: + +```bash +swift package lambda-init --allow-writing-to-package-directory +``` + +Your `Sources/main.swift` file must look like this. ```swift import AWSLambdaRuntime @@ -81,12 +100,13 @@ try await runtime.run() 4. Build & archive the package +The runtime comes with a plugin to compile on Amazon Linux and create a ZIP archive: + ```bash -swift build swift package archive --allow-network-connections docker ``` -If there is no error, there is a ZIP file ready to deploy. +If there is no error, the ZIP archive is ready to deploy. The ZIP file is located at `.build/plugins/AWSLambdaPackager/outputs/AWSLambdaPackager/MyLambda/MyLambda.zip` 5. Deploy to AWS From cc08a9a5c2019fda0cac7dee9bb9fe79943ebea4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Stormacq?= Date: Thu, 7 Nov 2024 10:00:17 +0100 Subject: [PATCH 02/10] WIP deploy plugin --- Plugins/AWSLambdaDeployer/Plugin.swift | 79 +++++ Plugins/AWSLambdaDeployer/PluginUtils.swift | 156 ++++++++++ .../AWSCredentials.swift | 60 ++++ .../AWSLambdaDeployerHelper/AWSSigner.swift | 269 ++++++++++++++++++ Sources/AWSLambdaDeployerHelper/main.swift | 1 + 5 files changed, 565 insertions(+) create mode 100644 Plugins/AWSLambdaDeployer/Plugin.swift create mode 100644 Plugins/AWSLambdaDeployer/PluginUtils.swift create mode 100644 Sources/AWSLambdaDeployerHelper/AWSCredentials.swift create mode 100644 Sources/AWSLambdaDeployerHelper/AWSSigner.swift create mode 100644 Sources/AWSLambdaDeployerHelper/main.swift diff --git a/Plugins/AWSLambdaDeployer/Plugin.swift b/Plugins/AWSLambdaDeployer/Plugin.swift new file mode 100644 index 00000000..977b1a2d --- /dev/null +++ b/Plugins/AWSLambdaDeployer/Plugin.swift @@ -0,0 +1,79 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2023 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import Foundation +import PackagePlugin + +@main +@available(macOS 15.0, *) +struct AWSLambdaDeployer: CommandPlugin { + + + func performCommand(context: PackagePlugin.PluginContext, arguments: [String]) async throws { + let configuration = try Configuration(context: context, arguments: arguments) + + if configuration.help { + self.displayHelpMessage() + return + } + + let tool = try context.tool(named: "AWSLambdaDeployerHelper") + try Utils.execute(executable: tool.url, arguments: [], logLevel: .debug) + } + + private func displayHelpMessage() { + print( + """ + OVERVIEW: A SwiftPM plugin to deploy a Lambda function. + + USAGE: swift package lambda-deploy + [--with-url] + [--help] [--verbose] + + OPTIONS: + --with-url Add an URL to access the Lambda function + --verbose Produce verbose output for debugging. + --help Show help information. + """ + ) + } +} + +private struct Configuration: CustomStringConvertible { + public let help: Bool + public let verboseLogging: Bool + + public init( + context: PluginContext, + arguments: [String] + ) throws { + var argumentExtractor = ArgumentExtractor(arguments) + let verboseArgument = argumentExtractor.extractFlag(named: "verbose") > 0 + let helpArgument = argumentExtractor.extractFlag(named: "help") > 0 + + // help required ? + self.help = helpArgument + + // verbose logging required ? + self.verboseLogging = verboseArgument + } + + var description: String { + """ + { + verboseLogging: \(self.verboseLogging) + } + """ + } +} diff --git a/Plugins/AWSLambdaDeployer/PluginUtils.swift b/Plugins/AWSLambdaDeployer/PluginUtils.swift new file mode 100644 index 00000000..52d1b2be --- /dev/null +++ b/Plugins/AWSLambdaDeployer/PluginUtils.swift @@ -0,0 +1,156 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2022 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import Dispatch +import Foundation +import PackagePlugin +import Synchronization + +@available(macOS 15.0, *) +struct Utils { + @discardableResult + static func execute( + executable: URL, + arguments: [String], + customWorkingDirectory: URL? = .none, + logLevel: ProcessLogLevel + ) throws -> String { + if logLevel >= .debug { + print("\(executable.path()) \(arguments.joined(separator: " "))") + } + + let fd = dup(1) + let stdout = fdopen(fd, "rw") + defer { if let so = stdout { fclose(so) } } + + // We need to use an unsafe transfer here to get the fd into our Sendable closure. + // This transfer is fine, because we write to the variable from a single SerialDispatchQueue here. + // We wait until the process is run below process.waitUntilExit(). + // This means no further writes to output will happen. + // This makes it save for us to read the output + struct UnsafeTransfer: @unchecked Sendable { + let value: Value + } + + let outputMutex = Mutex("") + let outputSync = DispatchGroup() + let outputQueue = DispatchQueue(label: "AWSLambdaPackager.output") + let unsafeTransfer = UnsafeTransfer(value: stdout) + let outputHandler = { @Sendable (data: Data?) in + dispatchPrecondition(condition: .onQueue(outputQueue)) + + outputSync.enter() + defer { outputSync.leave() } + + guard + let _output = data.flatMap({ + String(data: $0, encoding: .utf8)?.trimmingCharacters(in: CharacterSet(["\n"])) + }), !_output.isEmpty + else { + return + } + + outputMutex.withLock { output in + output += _output + "\n" + } + + switch logLevel { + case .silent: + break + case .debug(let outputIndent), .output(let outputIndent): + print(String(repeating: " ", count: outputIndent), terminator: "") + print(_output) + fflush(unsafeTransfer.value) + } + } + + let pipe = Pipe() + pipe.fileHandleForReading.readabilityHandler = { fileHandle in + outputQueue.async { outputHandler(fileHandle.availableData) } + } + + let process = Process() + process.standardOutput = pipe + process.standardError = pipe + process.executableURL = executable + process.arguments = arguments + if let workingDirectory = customWorkingDirectory { + process.currentDirectoryURL = URL(fileURLWithPath: workingDirectory.path()) + } + process.terminationHandler = { _ in + outputQueue.async { + outputHandler(try? pipe.fileHandleForReading.readToEnd()) + } + } + + try process.run() + process.waitUntilExit() + + // wait for output to be full processed + outputSync.wait() + + let output = outputMutex.withLock { $0 } + + if process.terminationStatus != 0 { + // print output on failure and if not already printed + if logLevel < .output { + print(output) + fflush(stdout) + } + throw ProcessError.processFailed([executable.path()] + arguments, process.terminationStatus) + } + + return output + } + + enum ProcessError: Error, CustomStringConvertible { + case processFailed([String], Int32) + + var description: String { + switch self { + case .processFailed(let arguments, let code): + return "\(arguments.joined(separator: " ")) failed with code \(code)" + } + } + } + + enum ProcessLogLevel: Comparable { + case silent + case output(outputIndent: Int) + case debug(outputIndent: Int) + + var naturalOrder: Int { + switch self { + case .silent: + return 0 + case .output: + return 1 + case .debug: + return 2 + } + } + + static var output: Self { + .output(outputIndent: 2) + } + + static var debug: Self { + .debug(outputIndent: 2) + } + + static func < (lhs: ProcessLogLevel, rhs: ProcessLogLevel) -> Bool { + lhs.naturalOrder < rhs.naturalOrder + } + } +} diff --git a/Sources/AWSLambdaDeployerHelper/AWSCredentials.swift b/Sources/AWSLambdaDeployerHelper/AWSCredentials.swift new file mode 100644 index 00000000..aec469b1 --- /dev/null +++ b/Sources/AWSLambdaDeployerHelper/AWSCredentials.swift @@ -0,0 +1,60 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2023 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +// +// credentials.swift +// aws-sign +// +// Created by Adam Fowler on 29/08/2019. +// +import class Foundation.ProcessInfo + +/// Protocol for providing credential details for accessing AWS services +public protocol Credential { + var accessKeyId: String {get} + var secretAccessKey: String {get} + var sessionToken: String? {get} +} + +/// basic version of Credential where you supply the credentials +public struct StaticCredential: Credential { + public let accessKeyId: String + public let secretAccessKey: String + public let sessionToken: String? + + public init(accessKeyId: String, secretAccessKey: String, sessionToken: String? = nil) { + self.accessKeyId = accessKeyId + self.secretAccessKey = secretAccessKey + self.sessionToken = sessionToken + } +} + +/// environment variable version of credential that uses system environment variables to get credential details +public struct EnvironmentCredential: Credential { + public let accessKeyId: String + public let secretAccessKey: String + public let sessionToken: String? + + public init?() { + guard let accessKeyId = ProcessInfo.processInfo.environment["AWS_ACCESS_KEY_ID"] else { + return nil + } + guard let secretAccessKey = ProcessInfo.processInfo.environment["AWS_SECRET_ACCESS_KEY"] else { + return nil + } + self.accessKeyId = accessKeyId + self.secretAccessKey = secretAccessKey + self.sessionToken = ProcessInfo.processInfo.environment["AWS_SESSION_TOKEN"] + } +} diff --git a/Sources/AWSLambdaDeployerHelper/AWSSigner.swift b/Sources/AWSLambdaDeployerHelper/AWSSigner.swift new file mode 100644 index 00000000..41774b93 --- /dev/null +++ b/Sources/AWSLambdaDeployerHelper/AWSSigner.swift @@ -0,0 +1,269 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2023 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +// +// signer.swift +// AWSSigner +// +// Created by Adam Fowler on 2019/08/29. +// Amazon Web Services V4 Signer +// AWS documentation about signing requests is here https://docs.aws.amazon.com/general/latest/gr/signing_aws_api_requests.html +// + +import struct Foundation.CharacterSet +import struct Foundation.Data +import struct Foundation.Date +import class Foundation.DateFormatter +import struct Foundation.Locale +import struct Foundation.TimeZone +import struct Foundation.URL +import Crypto +import NIO +import NIOHTTP1 + +/// Amazon Web Services V4 Signer +public struct AWSSigner { + /// security credentials for accessing AWS services + public let credentials: Credential + /// service signing name. In general this is the same as the service name + public let name: String + /// AWS region you are working in + public let region: String + + static let hashedEmptyBody = SHA256.hash(data: [UInt8]()).hexDigest() + + static private let timeStampDateFormatter: DateFormatter = createTimeStampDateFormatter() + + /// Initialise the Signer class with AWS credentials + public init(credentials: Credential, name: String, region: String) { + self.credentials = credentials + self.name = name + self.region = region + } + + /// Enum for holding your body data + public enum BodyData { + case string(String) + case data(Data) + case byteBuffer(ByteBuffer) + } + + /// Generate signed headers, for a HTTP request + public func signHeaders(url: URL, method: HTTPMethod = .GET, headers: HTTPHeaders = HTTPHeaders(), body: BodyData? = nil, date: Date = Date()) -> HTTPHeaders { + let bodyHash = AWSSigner.hashedPayload(body) + let dateString = AWSSigner.timestamp(date) + var headers = headers + // add date, host, sha256 and if available security token headers + headers.add(name: "X-Amz-Date", value: dateString) + headers.add(name: "host", value: url.host ?? "") + headers.add(name: "x-amz-content-sha256", value: bodyHash) + if let sessionToken = credentials.sessionToken { + headers.add(name: "x-amz-security-token", value: sessionToken) + } + + // construct signing data. Do this after adding the headers as it uses data from the headers + let signingData = AWSSigner.SigningData(url: url, method: method, headers: headers, body: body, bodyHash: bodyHash, date: dateString, signer: self) + + // construct authorization string + let authorization = "AWS4-HMAC-SHA256 " + + "Credential=\(credentials.accessKeyId)/\(signingData.date)/\(region)/\(name)/aws4_request, " + + "SignedHeaders=\(signingData.signedHeaders), " + + "Signature=\(signature(signingData: signingData))" + + // add Authorization header + headers.add(name: "Authorization", value: authorization) + + return headers + } + + /// Generate a signed URL, for a HTTP request + public func signURL(url: URL, method: HTTPMethod = .GET, body: BodyData? = nil, date: Date = Date(), expires: Int = 86400) -> URL { + let headers = HTTPHeaders([("host", url.host ?? "")]) + // Create signing data + var signingData = AWSSigner.SigningData(url: url, method: method, headers: headers, body: body, date: AWSSigner.timestamp(date), signer: self) + // Construct query string. Start with original query strings and append all the signing info. + var query = url.query ?? "" + if query.count > 0 { + query += "&" + } + query += "X-Amz-Algorithm=AWS4-HMAC-SHA256" + query += "&X-Amz-Credential=\(credentials.accessKeyId)/\(signingData.date)/\(region)/\(name)/aws4_request" + query += "&X-Amz-Date=\(signingData.datetime)" + query += "&X-Amz-Expires=\(expires)" + query += "&X-Amz-SignedHeaders=\(signingData.signedHeaders)" + if let sessionToken = credentials.sessionToken { + query += "&X-Amz-Security-Token=\(sessionToken.uriEncode())" + } + // Split the string and sort to ensure the order of query strings is the same as AWS + query = query.split(separator: "&") + .sorted() + .joined(separator: "&") + .queryEncode() + + // update unsignedURL in the signingData so when the canonical request is constructed it includes all the signing query items + signingData.unsignedURL = URL(string: url.absoluteString.split(separator: "?")[0]+"?"+query)! // NEED TO DEAL WITH SITUATION WHERE THIS FAILS + query += "&X-Amz-Signature=\(signature(signingData: signingData))" + + // Add signature to query items and build a new Request + let signedURL = URL(string: url.absoluteString.split(separator: "?")[0]+"?"+query)! + + return signedURL + } + + /// structure used to store data used throughout the signing process + struct SigningData { + let url : URL + let method : HTTPMethod + let hashedPayload : String + let datetime : String + let headersToSign: [String: String] + let signedHeaders : String + var unsignedURL : URL + + var date : String { return String(datetime.prefix(8))} + + init(url: URL, method: HTTPMethod = .GET, headers: HTTPHeaders = HTTPHeaders(), body: BodyData? = nil, bodyHash: String? = nil, date: String, signer: AWSSigner) { + if url.path == "" { + //URL has to have trailing slash + self.url = url.appendingPathComponent("/") + } else { + self.url = url + } + self.method = method + self.datetime = date + self.unsignedURL = self.url + + if let hash = bodyHash { + self.hashedPayload = hash + } else if signer.name == "s3" { + self.hashedPayload = "UNSIGNED-PAYLOAD" + } else { + self.hashedPayload = AWSSigner.hashedPayload(body) + } + + let headersNotToSign: Set = [ + "Authorization" + ] + var headersToSign: [String: String] = [:] + var signedHeadersArray: [String] = [] + for header in headers { + if headersNotToSign.contains(header.name) { + continue + } + headersToSign[header.name] = header.value + signedHeadersArray.append(header.name.lowercased()) + } + self.headersToSign = headersToSign + self.signedHeaders = signedHeadersArray.sorted().joined(separator: ";") + } + } + + // Stage 3 Calculating signature as in https://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html + func signature(signingData: SigningData) -> String { + let kDate = HMAC.authenticationCode(for: Data(signingData.date.utf8), using: SymmetricKey(data: Array("AWS4\(credentials.secretAccessKey)".utf8))) + let kRegion = HMAC.authenticationCode(for: Data(region.utf8), using: SymmetricKey(data: kDate)) + let kService = HMAC.authenticationCode(for: Data(name.utf8), using: SymmetricKey(data: kRegion)) + let kSigning = HMAC.authenticationCode(for: Data("aws4_request".utf8), using: SymmetricKey(data: kService)) + let kSignature = HMAC.authenticationCode(for: stringToSign(signingData: signingData), using: SymmetricKey(data: kSigning)) + return kSignature.hexDigest() + } + + /// Stage 2 Create the string to sign as in https://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html + func stringToSign(signingData: SigningData) -> Data { + let stringToSign = "AWS4-HMAC-SHA256\n" + + "\(signingData.datetime)\n" + + "\(signingData.date)/\(region)/\(name)/aws4_request\n" + + SHA256.hash(data: canonicalRequest(signingData: signingData)).hexDigest() + return Data(stringToSign.utf8) + } + + /// Stage 1 Create the canonical request as in https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html + func canonicalRequest(signingData: SigningData) -> Data { + let canonicalHeaders = signingData.headersToSign.map { return "\($0.key.lowercased()):\($0.value.trimmingCharacters(in: CharacterSet.whitespaces))" } + .sorted() + .joined(separator: "\n") + let canonicalRequest = "\(signingData.method.rawValue)\n" + + "\(signingData.unsignedURL.path.uriEncodeWithSlash())\n" + + "\(signingData.unsignedURL.query ?? "")\n" + // should really uriEncode all the query string values + "\(canonicalHeaders)\n\n" + + "\(signingData.signedHeaders)\n" + + signingData.hashedPayload + return Data(canonicalRequest.utf8) + } + + /// Create a SHA256 hash of the Requests body + static func hashedPayload(_ payload: BodyData?) -> String { + guard let payload = payload else { return hashedEmptyBody } + let hash : String? + switch payload { + case .string(let string): + hash = SHA256.hash(data: Data(string.utf8)).hexDigest() + case .data(let data): + hash = SHA256.hash(data: data).hexDigest() + case .byteBuffer(let byteBuffer): + let byteBufferView = byteBuffer.readableBytesView + hash = byteBufferView.withContiguousStorageIfAvailable { bytes in + return SHA256.hash(data: bytes).hexDigest() + } + } + if let hash = hash { + return hash + } else { + return hashedEmptyBody + } + } + + /// return a hexEncoded string buffer from an array of bytes + static func hexEncoded(_ buffer: [UInt8]) -> String { + return buffer.map{String(format: "%02x", $0)}.joined(separator: "") + } + /// create timestamp dateformatter + static private func createTimeStampDateFormatter() -> DateFormatter { + let formatter = DateFormatter() + formatter.dateFormat = "yyyyMMdd'T'HHmmss'Z'" + formatter.timeZone = TimeZone(abbreviation: "UTC") + formatter.locale = Locale(identifier: "en_US_POSIX") + return formatter + } + + /// return a timestamp formatted for signing requests + static func timestamp(_ date: Date) -> String { + return timeStampDateFormatter.string(from: date) + } +} + +extension String { + func queryEncode() -> String { + return addingPercentEncoding(withAllowedCharacters: String.queryAllowedCharacters) ?? self + } + + func uriEncode() -> String { + return addingPercentEncoding(withAllowedCharacters: String.uriAllowedCharacters) ?? self + } + + func uriEncodeWithSlash() -> String { + return addingPercentEncoding(withAllowedCharacters: String.uriAllowedWithSlashCharacters) ?? self + } + + static let uriAllowedWithSlashCharacters = CharacterSet(charactersIn:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~/") + static let uriAllowedCharacters = CharacterSet(charactersIn:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~") + static let queryAllowedCharacters = CharacterSet(charactersIn:"/;+").inverted +} + +public extension Sequence where Element == UInt8 { + /// return a hexEncoded string buffer from an array of bytes + func hexDigest() -> String { + return self.map{String(format: "%02x", $0)}.joined(separator: "") + } +} diff --git a/Sources/AWSLambdaDeployerHelper/main.swift b/Sources/AWSLambdaDeployerHelper/main.swift new file mode 100644 index 00000000..c28454c6 --- /dev/null +++ b/Sources/AWSLambdaDeployerHelper/main.swift @@ -0,0 +1 @@ +print("Deployer") \ No newline at end of file From 9f7b0433856ba522109960e541d05ab812e4e54e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Stormacq?= Date: Wed, 13 Nov 2024 15:34:30 +0100 Subject: [PATCH 03/10] change plugin invocation name for consistency --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index ff9475ed..624d37bb 100644 --- a/Package.swift +++ b/Package.swift @@ -68,7 +68,7 @@ let package = Package( name: "AWSLambdaPackager", capability: .command( intent: .custom( - verb: "archive", + verb: "lambda-build", description: "Archive the Lambda binary and prepare it for uploading to AWS. Requires docker on macOS or non Amazonlinux 2 distributions." ), From bd4634555ce11a7d967d4f54290cf951451ff57e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Stormacq?= Date: Sat, 16 Nov 2024 13:40:05 +0100 Subject: [PATCH 04/10] add implementation of AWS v4 Signer from aws-signer-v4 and HMAC+SHA256 from CryptoSwift --- Package.swift | 25 +- .../AWSLambdaPluginHelper/Extensions.swift | 48 +++ .../Vendored/crypto/Array+Extensions.swift | 155 +++++++ .../Vendored/crypto/Authenticator.swift | 20 + .../Vendored/crypto/BatchedCollections.swift | 81 ++++ .../Vendored/crypto/Bit.swift | 26 ++ .../crypto/Collections+Extensions.swift | 75 ++++ .../Vendored/crypto/Digest.swift | 92 +++++ .../Vendored/crypto/DigestType.swift | 32 ++ .../Vendored/crypto/Generics.swift | 57 +++ .../Vendored/crypto/HMAC.swift | 139 +++++++ .../Vendored/crypto/Int+Extension.swift | 47 +++ .../Vendored/crypto/NoPadding.swift | 41 ++ .../Vendored/crypto/Padding.swift | 79 ++++ .../Vendored/crypto/SHA1.swift | 176 ++++++++ .../Vendored/crypto/SHA2.swift | 386 ++++++++++++++++++ .../Vendored/crypto/SHA3.swift | 317 ++++++++++++++ .../Vendored/crypto/UInt16+Extension.swift | 51 +++ .../Vendored/crypto/UInt32+Extension.swift | 65 +++ .../Vendored/crypto/UInt64+Extension.swift | 58 +++ .../Vendored/crypto/UInt8+Extension.swift | 88 ++++ .../Vendored/crypto/Updatable.swift | 121 ++++++ .../Vendored/crypto/Utils.swift | 131 ++++++ .../Vendored/crypto/ZeroPadding.swift | 54 +++ .../Vendored/signer}/AWSCredentials.swift | 2 +- .../Vendored/signer}/AWSSigner.swift | 21 +- .../main.swift | 0 .../AWSSignerTests.swift | 57 +++ .../CryptoTests.swift | 78 ++++ 29 files changed, 2514 insertions(+), 8 deletions(-) create mode 100644 Sources/AWSLambdaPluginHelper/Extensions.swift create mode 100644 Sources/AWSLambdaPluginHelper/Vendored/crypto/Array+Extensions.swift create mode 100644 Sources/AWSLambdaPluginHelper/Vendored/crypto/Authenticator.swift create mode 100644 Sources/AWSLambdaPluginHelper/Vendored/crypto/BatchedCollections.swift create mode 100644 Sources/AWSLambdaPluginHelper/Vendored/crypto/Bit.swift create mode 100644 Sources/AWSLambdaPluginHelper/Vendored/crypto/Collections+Extensions.swift create mode 100644 Sources/AWSLambdaPluginHelper/Vendored/crypto/Digest.swift create mode 100644 Sources/AWSLambdaPluginHelper/Vendored/crypto/DigestType.swift create mode 100644 Sources/AWSLambdaPluginHelper/Vendored/crypto/Generics.swift create mode 100644 Sources/AWSLambdaPluginHelper/Vendored/crypto/HMAC.swift create mode 100644 Sources/AWSLambdaPluginHelper/Vendored/crypto/Int+Extension.swift create mode 100644 Sources/AWSLambdaPluginHelper/Vendored/crypto/NoPadding.swift create mode 100644 Sources/AWSLambdaPluginHelper/Vendored/crypto/Padding.swift create mode 100644 Sources/AWSLambdaPluginHelper/Vendored/crypto/SHA1.swift create mode 100644 Sources/AWSLambdaPluginHelper/Vendored/crypto/SHA2.swift create mode 100644 Sources/AWSLambdaPluginHelper/Vendored/crypto/SHA3.swift create mode 100644 Sources/AWSLambdaPluginHelper/Vendored/crypto/UInt16+Extension.swift create mode 100644 Sources/AWSLambdaPluginHelper/Vendored/crypto/UInt32+Extension.swift create mode 100644 Sources/AWSLambdaPluginHelper/Vendored/crypto/UInt64+Extension.swift create mode 100644 Sources/AWSLambdaPluginHelper/Vendored/crypto/UInt8+Extension.swift create mode 100644 Sources/AWSLambdaPluginHelper/Vendored/crypto/Updatable.swift create mode 100644 Sources/AWSLambdaPluginHelper/Vendored/crypto/Utils.swift create mode 100644 Sources/AWSLambdaPluginHelper/Vendored/crypto/ZeroPadding.swift rename Sources/{AWSLambdaDeployerHelper => AWSLambdaPluginHelper/Vendored/signer}/AWSCredentials.swift (96%) rename Sources/{AWSLambdaDeployerHelper => AWSLambdaPluginHelper/Vendored/signer}/AWSSigner.swift (92%) rename Sources/{AWSLambdaDeployerHelper => AWSLambdaPluginHelper}/main.swift (100%) create mode 100644 Tests/AWSLambdaPluginHelperTests/AWSSignerTests.swift create mode 100644 Tests/AWSLambdaPluginHelperTests/CryptoTests.swift diff --git a/Package.swift b/Package.swift index 5c505623..f34b38eb 100644 --- a/Package.swift +++ b/Package.swift @@ -14,23 +14,30 @@ let package = Package( products: [ // this library exports `AWSLambdaRuntimeCore` and adds Foundation convenience methods .library(name: "AWSLambdaRuntime", targets: ["AWSLambdaRuntime"]), + // this has all the main functionality for lambda and it does not link Foundation .library(name: "AWSLambdaRuntimeCore", targets: ["AWSLambdaRuntimeCore"]), + // plugin to create a new Lambda function, based on a template .plugin(name: "AWSLambdaInitializer", targets: ["AWSLambdaInitializer"]), + // plugin to package the lambda, creating an archive that can be uploaded to AWS // requires Linux or at least macOS v15 .plugin(name: "AWSLambdaPackager", targets: ["AWSLambdaPackager"]), + // plugin to deploy a Lambda function - .plugin(name: "AWSLambdadeployer", targets: ["AWSLambdaDeployer"]), - .executable(name: "AWSLambdaDeployerHelper", targets: ["AWSLambdaDeployerHelper"]), + .plugin(name: "AWSLambdaDeployer", targets: ["AWSLambdaDeployer"]), + + // an executable that implements the business logic for the plugins + .executable(name: "AWSLambdaPluginHelper", targets: ["AWSLambdaPluginHelper"]), + // for testing only .library(name: "AWSLambdaTesting", targets: ["AWSLambdaTesting"]), ], dependencies: [ .package(url: "https://github.com/apple/swift-nio.git", from: "2.76.0"), .package(url: "https://github.com/apple/swift-log.git", from: "1.5.4"), - .package(url: "https://github.com/apple/swift-crypto.git", from: "3.9.1"), + // .package(url: "https://github.com/apple/swift-crypto.git", from: "3.9.1"), ], targets: [ .target( @@ -96,15 +103,14 @@ let package = Package( ] ), dependencies: [ - .target(name: "AWSLambdaDeployerHelper") + .target(name: "AWSLambdaPluginHelper") ] ), .executableTarget( - name: "AWSLambdaDeployerHelper", + name: "AWSLambdaPluginHelper", dependencies: [ .product(name: "NIOHTTP1", package: "swift-nio"), .product(name: "NIOCore", package: "swift-nio"), - .product(name: "Crypto", package: "swift-crypto"), ], swiftSettings: [.swiftLanguageMode(.v6)] ), @@ -148,5 +154,12 @@ let package = Package( ], swiftSettings: [.swiftLanguageMode(.v5)] ), + .testTarget( + name: "AWSLambdaPluginHelperTests", + dependencies: [ + .byName(name: "AWSLambdaPluginHelper") + ] + ), + ] ) diff --git a/Sources/AWSLambdaPluginHelper/Extensions.swift b/Sources/AWSLambdaPluginHelper/Extensions.swift new file mode 100644 index 00000000..671c4cec --- /dev/null +++ b/Sources/AWSLambdaPluginHelper/Extensions.swift @@ -0,0 +1,48 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +#if canImport(FoundationEssentials) +import FoundationEssentials +#else +import Foundation +#endif + +// extension Array where Element == UInt8 { +// public var base64: String { +// Data(self).base64EncodedString() +// } +// } + +extension Data { + var bytes: [UInt8] { + return [UInt8](self) + } +} + +extension String { + public var array: [UInt8] { + Array(self.utf8) + } +} + +extension HMAC { + public static func authenticate(for data: [UInt8], using key: [UInt8], variant: HMAC.Variant = .sha2(.sha256)) throws -> [UInt8] { + let authenticator = HMAC(key: key, variant: variant) + return try authenticator.authenticate(data) + } + public static func authenticate(for data: Data, using key: [UInt8], variant: HMAC.Variant = .sha2(.sha256)) throws -> [UInt8] { + let authenticator = HMAC(key: key, variant: variant) + return try authenticator.authenticate(data.bytes) + } +} \ No newline at end of file diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/Array+Extensions.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Array+Extensions.swift new file mode 100644 index 00000000..fd063167 --- /dev/null +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Array+Extensions.swift @@ -0,0 +1,155 @@ +// +// CryptoSwift +// +// Copyright (C) 2014-2022 Marcin Krzyżanowski +// This software is provided 'as-is', without any express or implied warranty. +// +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: +// +// - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. +// - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +// - This notice may not be removed or altered from any source or binary distribution. +// + +extension Array { + @inlinable + init(reserveCapacity: Int) { + self = Array() + self.reserveCapacity(reserveCapacity) + } + + @inlinable + var slice: ArraySlice { + self[self.startIndex ..< self.endIndex] + } + + @inlinable + subscript (safe index: Index) -> Element? { + return indices.contains(index) ? self[index] : nil + } +} + +extension Array where Element == UInt8 { + public init(hex: String) { + self.init(reserveCapacity: hex.unicodeScalars.lazy.underestimatedCount) + var buffer: UInt8? + var skip = hex.hasPrefix("0x") ? 2 : 0 + for char in hex.unicodeScalars.lazy { + guard skip == 0 else { + skip -= 1 + continue + } + guard char.value >= 48 && char.value <= 102 else { + removeAll() + return + } + let v: UInt8 + let c: UInt8 = UInt8(char.value) + switch c { + case let c where c <= 57: + v = c - 48 + case let c where c >= 65 && c <= 70: + v = c - 55 + case let c where c >= 97: + v = c - 87 + default: + removeAll() + return + } + if let b = buffer { + append(b << 4 | v) + buffer = nil + } else { + buffer = v + } + } + if let b = buffer { + append(b) + } + } + + public func toHexString() -> String { + `lazy`.reduce(into: "") { + var s = String($1, radix: 16) + if s.count == 1 { + s = "0" + s + } + $0 += s + } + } +} + +extension Array where Element == UInt8 { + /// split in chunks with given chunk size + @available(*, deprecated) + public func chunks(size chunksize: Int) -> Array> { + var words = Array>() + words.reserveCapacity(count / chunksize) + for idx in stride(from: chunksize, through: count, by: chunksize) { + words.append(Array(self[idx - chunksize ..< idx])) // slow for large table + } + let remainder = suffix(count % chunksize) + if !remainder.isEmpty { + words.append(Array(remainder)) + } + return words + } + + // public func md5() -> [Element] { + // Digest.md5(self) + // } + + // public func sha1() -> [Element] { + // Digest.sha1(self) + // } + + // public func sha224() -> [Element] { + // Digest.sha224(self) + // } + + public func sha256() -> [Element] { + Digest.sha256(self) + } + + public func sha384() -> [Element] { + Digest.sha384(self) + } + + public func sha512() -> [Element] { + Digest.sha512(self) + } + + public func sha2(_ variant: SHA2.Variant) -> [Element] { + Digest.sha2(self, variant: variant) + } + + public func sha3(_ variant: SHA3.Variant) -> [Element] { + Digest.sha3(self, variant: variant) + } + + // public func crc32(seed: UInt32? = nil, reflect: Bool = true) -> UInt32 { + // Checksum.crc32(self, seed: seed, reflect: reflect) + // } + + // public func crc32c(seed: UInt32? = nil, reflect: Bool = true) -> UInt32 { + // Checksum.crc32c(self, seed: seed, reflect: reflect) + // } + + // public func crc16(seed: UInt16? = nil) -> UInt16 { + // Checksum.crc16(self, seed: seed) + // } + + // public func encrypt(cipher: Cipher) throws -> [Element] { + // try cipher.encrypt(self.slice) + // } + + // public func decrypt(cipher: Cipher) throws -> [Element] { + // try cipher.decrypt(self.slice) + // } + + public func authenticate(with authenticator: A) throws -> [Element] { + try authenticator.authenticate(self) + } +} \ No newline at end of file diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/Authenticator.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Authenticator.swift new file mode 100644 index 00000000..afabf51b --- /dev/null +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Authenticator.swift @@ -0,0 +1,20 @@ +// +// CryptoSwift +// +// Copyright (C) 2014-2022 Marcin Krzyżanowski +// This software is provided 'as-is', without any express or implied warranty. +// +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: +// +// - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. +// - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +// - This notice may not be removed or altered from any source or binary distribution. +// + +/// Message authentication code. +public protocol Authenticator { + /// Calculate Message Authentication Code (MAC) for message. + func authenticate(_ bytes: Array) throws -> Array +} \ No newline at end of file diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/BatchedCollections.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/BatchedCollections.swift new file mode 100644 index 00000000..43efb56a --- /dev/null +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/BatchedCollections.swift @@ -0,0 +1,81 @@ +// +// CryptoSwift +// +// Copyright (C) 2014-2022 Marcin Krzyżanowski +// This software is provided 'as-is', without any express or implied warranty. +// +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: +// +// - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. +// - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +// - This notice may not be removed or altered from any source or binary distribution. +// + +@usableFromInline +struct BatchedCollectionIndex { + let range: Range +} + +extension BatchedCollectionIndex: Comparable { + @usableFromInline + static func == (lhs: BatchedCollectionIndex, rhs: BatchedCollectionIndex) -> Bool { + lhs.range.lowerBound == rhs.range.lowerBound + } + + @usableFromInline + static func < (lhs: BatchedCollectionIndex, rhs: BatchedCollectionIndex) -> Bool { + lhs.range.lowerBound < rhs.range.lowerBound + } +} + +protocol BatchedCollectionType: Collection { + associatedtype Base: Collection +} + +@usableFromInline +struct BatchedCollection: Collection { + let base: Base + let size: Int + + @usableFromInline + init(base: Base, size: Int) { + self.base = base + self.size = size + } + + @usableFromInline + typealias Index = BatchedCollectionIndex + + private func nextBreak(after idx: Base.Index) -> Base.Index { + self.base.index(idx, offsetBy: self.size, limitedBy: self.base.endIndex) ?? self.base.endIndex + } + + @usableFromInline + var startIndex: Index { + Index(range: self.base.startIndex.. Index { + Index(range: idx.range.upperBound.. Base.SubSequence { + self.base[idx.range] + } +} + +extension Collection { + @inlinable + func batched(by size: Int) -> BatchedCollection { + BatchedCollection(base: self, size: size) + } +} \ No newline at end of file diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/Bit.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Bit.swift new file mode 100644 index 00000000..3520b0dd --- /dev/null +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Bit.swift @@ -0,0 +1,26 @@ +// +// CryptoSwift +// +// Copyright (C) 2014-2022 Marcin Krzyżanowski +// This software is provided 'as-is', without any express or implied warranty. +// +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: +// +// - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. +// - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +// - This notice may not be removed or altered from any source or binary distribution. +// + +public enum Bit: Int { + case zero + case one +} + +extension Bit { + @inlinable + func inverted() -> Bit { + self == .zero ? .one : .zero + } +} diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/Collections+Extensions.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Collections+Extensions.swift new file mode 100644 index 00000000..84d68d17 --- /dev/null +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Collections+Extensions.swift @@ -0,0 +1,75 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +// +// CryptoSwift +// +// Copyright (C) 2014-2022 Marcin Krzyżanowski +// This software is provided 'as-is', without any express or implied warranty. +// +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: +// +// - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. +// - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +// - This notice may not be removed or altered from any source or binary distribution. +// +extension Collection where Self.Element == UInt8, Self.Index == Int { + // Big endian order + @inlinable + func toUInt32Array() -> Array { + guard !isEmpty else { + return [] + } + + let c = strideCount(from: startIndex, to: endIndex, by: 4) + return Array(unsafeUninitializedCapacity: c) { buf, count in + var counter = 0 + for idx in stride(from: startIndex, to: endIndex, by: 4) { + let val = UInt32(bytes: self, fromIndex: idx).bigEndian + buf[counter] = val + counter += 1 + } + count = counter + assert(counter == c) + } + } + + // Big endian order + @inlinable + func toUInt64Array() -> Array { + guard !isEmpty else { + return [] + } + + let c = strideCount(from: startIndex, to: endIndex, by: 8) + return Array(unsafeUninitializedCapacity: c) { buf, count in + var counter = 0 + for idx in stride(from: startIndex, to: endIndex, by: 8) { + let val = UInt64(bytes: self, fromIndex: idx).bigEndian + buf[counter] = val + counter += 1 + } + count = counter + assert(counter == c) + } + } +} + +@usableFromInline +func strideCount(from: Int, to: Int, by: Int) -> Int { + let count = to - from + return count / by + (count % by > 0 ? 1 : 0) +} \ No newline at end of file diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/Digest.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Digest.swift new file mode 100644 index 00000000..491f6fdb --- /dev/null +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Digest.swift @@ -0,0 +1,92 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +// +// CryptoSwift +// +// Copyright (C) 2014-2022 Marcin Krzyżanowski +// This software is provided 'as-is', without any express or implied warranty. +// +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: +// +// - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. +// - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +// - This notice may not be removed or altered from any source or binary distribution. +// + +@available(*, renamed: "Digest") +public typealias Hash = Digest + +/// Hash functions to calculate Digest. +public struct Digest { + /// Calculate MD5 Digest + /// - parameter bytes: input message + /// - returns: Digest bytes + // public static func md5(_ bytes: Array) -> Array { + // MD5().calculate(for: bytes) + // } + + /// Calculate SHA1 Digest + /// - parameter bytes: input message + /// - returns: Digest bytes + public static func sha1(_ bytes: Array) -> Array { + SHA1().calculate(for: bytes) + } + + /// Calculate SHA2-224 Digest + /// - parameter bytes: input message + /// - returns: Digest bytes + public static func sha224(_ bytes: Array) -> Array { + self.sha2(bytes, variant: .sha224) + } + + /// Calculate SHA2-256 Digest + /// - parameter bytes: input message + /// - returns: Digest bytes + public static func sha256(_ bytes: Array) -> Array { + self.sha2(bytes, variant: .sha256) + } + + /// Calculate SHA2-384 Digest + /// - parameter bytes: input message + /// - returns: Digest bytes + public static func sha384(_ bytes: Array) -> Array { + self.sha2(bytes, variant: .sha384) + } + + /// Calculate SHA2-512 Digest + /// - parameter bytes: input message + /// - returns: Digest bytes + public static func sha512(_ bytes: Array) -> Array { + self.sha2(bytes, variant: .sha512) + } + + /// Calculate SHA2 Digest + /// - parameter bytes: input message + /// - parameter variant: SHA-2 variant + /// - returns: Digest bytes + public static func sha2(_ bytes: Array, variant: SHA2.Variant) -> Array { + SHA2(variant: variant).calculate(for: bytes) + } + + /// Calculate SHA3 Digest + /// - parameter bytes: input message + /// - parameter variant: SHA-3 variant + /// - returns: Digest bytes + public static func sha3(_ bytes: Array, variant: SHA3.Variant) -> Array { + SHA3(variant: variant).calculate(for: bytes) + } +} \ No newline at end of file diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/DigestType.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/DigestType.swift new file mode 100644 index 00000000..13f27c1f --- /dev/null +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/DigestType.swift @@ -0,0 +1,32 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +// +// CryptoSwift +// +// Copyright (C) 2014-2022 Marcin Krzyżanowski +// This software is provided 'as-is', without any express or implied warranty. +// +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: +// +// - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. +// - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +// - This notice may not be removed or altered from any source or binary distribution. +// + +internal protocol DigestType { + func calculate(for bytes: Array) -> Array +} \ No newline at end of file diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/Generics.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Generics.swift new file mode 100644 index 00000000..2ba535d5 --- /dev/null +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Generics.swift @@ -0,0 +1,57 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +// +// CryptoSwift +// +// Copyright (C) 2014-2022 Marcin Krzyżanowski +// This software is provided 'as-is', without any express or implied warranty. +// +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: +// +// - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. +// - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +// - This notice may not be removed or altered from any source or binary distribution. +// + +/// Array of bytes. Caution: don't use directly because generic is slow. +/// +/// - parameter value: integer value +/// - parameter length: length of output array. By default size of value type +/// +/// - returns: Array of bytes +@_specialize(where T == Int) +@_specialize(where T == UInt) +@_specialize(where T == UInt8) +@_specialize(where T == UInt16) +@_specialize(where T == UInt32) +@_specialize(where T == UInt64) +@inlinable +func arrayOfBytes(value: T, length totalBytes: Int = MemoryLayout.size) -> Array { + let valuePointer = UnsafeMutablePointer.allocate(capacity: 1) + valuePointer.pointee = value + + let bytesPointer = UnsafeMutablePointer(OpaquePointer(valuePointer)) + var bytes = Array(repeating: 0, count: totalBytes) + for j in 0...size, totalBytes) { + bytes[totalBytes - 1 - j] = (bytesPointer + j).pointee + } + + valuePointer.deinitialize(count: 1) + valuePointer.deallocate() + + return bytes +} diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/HMAC.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/HMAC.swift new file mode 100644 index 00000000..b257eea4 --- /dev/null +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/HMAC.swift @@ -0,0 +1,139 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +// +// CryptoSwift +// +// Copyright (C) 2014-2022 Marcin Krzyżanowski +// This software is provided 'as-is', without any express or implied warranty. +// +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: +// +// - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. +// - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +// - This notice may not be removed or altered from any source or binary distribution. +// + +public final class HMAC: Authenticator { + public enum Error: Swift.Error { + case authenticateError + case invalidInput + } + + public enum Variant { + // case md5 + case sha1 + case sha2(SHA2.Variant) + case sha3(SHA3.Variant) + + @available(*, deprecated, message: "Use sha2(variant) instead.") + case sha256, sha384, sha512 + + var digestLength: Int { + switch self { + case .sha1: + return SHA1.digestLength + case .sha256: + return SHA2.Variant.sha256.digestLength + case .sha384: + return SHA2.Variant.sha384.digestLength + case .sha512: + return SHA2.Variant.sha512.digestLength + case .sha2(let variant): + return variant.digestLength + case .sha3(let variant): + return variant.digestLength + // case .md5: + // return MD5.digestLength + } + } + + func calculateHash(_ bytes: Array) -> Array { + switch self { + case .sha1: + return Digest.sha1(bytes) + case .sha256: + return Digest.sha256(bytes) + case .sha384: + return Digest.sha384(bytes) + case .sha512: + return Digest.sha512(bytes) + case .sha2(let variant): + return Digest.sha2(bytes, variant: variant) + case .sha3(let variant): + return Digest.sha3(bytes, variant: variant) + // case .md5: + // return Digest.md5(bytes) + } + } + + func blockSize() -> Int { + switch self { + // case .md5: + // return MD5.blockSize + case .sha1: + return SHA1.blockSize + case .sha256: + return SHA2.Variant.sha256.blockSize + case .sha384: + return SHA2.Variant.sha384.blockSize + case .sha512: + return SHA2.Variant.sha512.blockSize + case .sha2(let variant): + return variant.blockSize + case .sha3(let variant): + return variant.blockSize + } + } + } + + var key: Array + let variant: Variant + + // public init(key: Array, variant: HMAC.Variant = .md5) { + public init(key: Array, variant: HMAC.Variant = .sha2(.sha256)) { + self.variant = variant + self.key = key + + if key.count > variant.blockSize() { + let hash = variant.calculateHash(key) + self.key = hash + } + + if key.count < variant.blockSize() { + self.key = ZeroPadding().add(to: key, blockSize: variant.blockSize()) + } + } + + // MARK: Authenticator + + public func authenticate(_ bytes: Array) throws -> Array { + var opad = Array(repeating: 0x5c, count: variant.blockSize()) + for idx in self.key.indices { + opad[idx] = self.key[idx] ^ opad[idx] + } + var ipad = Array(repeating: 0x36, count: variant.blockSize()) + for idx in self.key.indices { + ipad[idx] = self.key[idx] ^ ipad[idx] + } + + let ipadAndMessageHash = self.variant.calculateHash(ipad + bytes) + let result = self.variant.calculateHash(opad + ipadAndMessageHash) + + // return Array(result[0..<10]) // 80 bits + return result + } +} \ No newline at end of file diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/Int+Extension.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Int+Extension.swift new file mode 100644 index 00000000..b73a19cf --- /dev/null +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Int+Extension.swift @@ -0,0 +1,47 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +// +// CryptoSwift +// +// Created by Marcin Krzyzanowski on 12/08/14. +// Copyright (C) 2014-2022 Marcin Krzyżanowski +// This software is provided 'as-is', without any express or implied warranty. +// +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: +// +// - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. +// - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +// - This notice may not be removed or altered from any source or binary distribution. +// + +#if canImport(Darwin) +import Darwin +#elseif canImport(Glibc) +import Glibc +#elseif canImport(ucrt) +import ucrt +#endif + +extension FixedWidthInteger { + @inlinable + func bytes(totalBytes: Int = MemoryLayout.size) -> Array { + arrayOfBytes(value: self.littleEndian, length: totalBytes) + // TODO: adjust bytes order + // var value = self.littleEndian + // return withUnsafeBytes(of: &value, Array.init).reversed() + } +} diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/NoPadding.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/NoPadding.swift new file mode 100644 index 00000000..fa7f007a --- /dev/null +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/NoPadding.swift @@ -0,0 +1,41 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +// +// CryptoSwift +// +// Copyright (C) 2014-2022 Marcin Krzyżanowski +// This software is provided 'as-is', without any express or implied warranty. +// +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: +// +// - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. +// - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +// - This notice may not be removed or altered from any source or binary distribution. +// + +struct NoPadding: PaddingProtocol { + init() { + } + + func add(to data: Array, blockSize _: Int) -> Array { + data + } + + func remove(from data: Array, blockSize _: Int?) -> Array { + data + } +} diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/Padding.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Padding.swift new file mode 100644 index 00000000..74e70e8d --- /dev/null +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Padding.swift @@ -0,0 +1,79 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +// +// CryptoSwift +// +// Copyright (C) 2014-2022 Marcin Krzyżanowski +// This software is provided 'as-is', without any express or implied warranty. +// +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: +// +// - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. +// - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +// - This notice may not be removed or altered from any source or binary distribution. +// + +public protocol PaddingProtocol { + func add(to: Array, blockSize: Int) -> Array + func remove(from: Array, blockSize: Int?) -> Array +} + +public enum Padding: PaddingProtocol { + case noPadding, zeroPadding //, pkcs7, pkcs5, eme_pkcs1v15, emsa_pkcs1v15, iso78164, iso10126 + + public func add(to: Array, blockSize: Int) -> Array { + switch self { + case .noPadding: + return to // NoPadding().add(to: to, blockSize: blockSize) + case .zeroPadding: + return ZeroPadding().add(to: to, blockSize: blockSize) + // case .pkcs7: + // return PKCS7.Padding().add(to: to, blockSize: blockSize) + // case .pkcs5: + // return PKCS5.Padding().add(to: to, blockSize: blockSize) + // case .eme_pkcs1v15: + // return EMEPKCS1v15Padding().add(to: to, blockSize: blockSize) + // case .emsa_pkcs1v15: + // return EMSAPKCS1v15Padding().add(to: to, blockSize: blockSize) + // case .iso78164: + // return ISO78164Padding().add(to: to, blockSize: blockSize) + // case .iso10126: + // return ISO10126Padding().add(to: to, blockSize: blockSize) + } + } + + public func remove(from: Array, blockSize: Int?) -> Array { + switch self { + case .noPadding: + return from //NoPadding().remove(from: from, blockSize: blockSize) + case .zeroPadding: + return ZeroPadding().remove(from: from, blockSize: blockSize) + // case .pkcs7: + // return PKCS7.Padding().remove(from: from, blockSize: blockSize) + // case .pkcs5: + // return PKCS5.Padding().remove(from: from, blockSize: blockSize) + // case .eme_pkcs1v15: + // return EMEPKCS1v15Padding().remove(from: from, blockSize: blockSize) + // case .emsa_pkcs1v15: + // return EMSAPKCS1v15Padding().remove(from: from, blockSize: blockSize) + // case .iso78164: + // return ISO78164Padding().remove(from: from, blockSize: blockSize) + // case .iso10126: + // return ISO10126Padding().remove(from: from, blockSize: blockSize) + } + } +} diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/SHA1.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/SHA1.swift new file mode 100644 index 00000000..46f57746 --- /dev/null +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/SHA1.swift @@ -0,0 +1,176 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +// +// CryptoSwift +// +// Copyright (C) 2014-2022 Marcin Krzyżanowski +// This software is provided 'as-is', without any express or implied warranty. +// +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: +// +// - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. +// - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +// - This notice may not be removed or altered from any source or binary distribution. +// + +public final class SHA1: DigestType { + + @usableFromInline + static let digestLength: Int = 20 // 160 / 8 + + @usableFromInline + static let blockSize: Int = 64 + + @usableFromInline + static let hashInitialValue: ContiguousArray = [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0] + + @usableFromInline + var accumulated = Array() + + @usableFromInline + var processedBytesTotalCount: Int = 0 + + @usableFromInline + var accumulatedHash: ContiguousArray = SHA1.hashInitialValue + + public init() { + } + + @inlinable + public func calculate(for bytes: Array) -> Array { + do { + return try update(withBytes: bytes.slice, isLast: true) + } catch { + return [] + } + } + + public func callAsFunction(_ bytes: Array) -> Array { + calculate(for: bytes) + } + + @usableFromInline + func process(block chunk: ArraySlice, currentHash hh: inout ContiguousArray) { + // break chunk into sixteen 32-bit words M[j], 0 ≤ j ≤ 15, big-endian + // Extend the sixteen 32-bit words into eighty 32-bit words: + let M = UnsafeMutablePointer.allocate(capacity: 80) + M.initialize(repeating: 0, count: 80) + defer { + M.deinitialize(count: 80) + M.deallocate() + } + + for x in 0..<80 { + switch x { + case 0...15: + let start = chunk.startIndex.advanced(by: x * 4) // * MemoryLayout.size + M[x] = UInt32(bytes: chunk, fromIndex: start) + default: + M[x] = rotateLeft(M[x - 3] ^ M[x - 8] ^ M[x - 14] ^ M[x - 16], by: 1) + } + } + + var A = hh[0] + var B = hh[1] + var C = hh[2] + var D = hh[3] + var E = hh[4] + + // Main loop + for j in 0...79 { + var f: UInt32 = 0 + var k: UInt32 = 0 + + switch j { + case 0...19: + f = (B & C) | ((~B) & D) + k = 0x5a827999 + case 20...39: + f = B ^ C ^ D + k = 0x6ed9eba1 + case 40...59: + f = (B & C) | (B & D) | (C & D) + k = 0x8f1bbcdc + case 60...79: + f = B ^ C ^ D + k = 0xca62c1d6 + default: + break + } + + let temp = rotateLeft(A, by: 5) &+ f &+ E &+ M[j] &+ k + E = D + D = C + C = rotateLeft(B, by: 30) + B = A + A = temp + } + + hh[0] = hh[0] &+ A + hh[1] = hh[1] &+ B + hh[2] = hh[2] &+ C + hh[3] = hh[3] &+ D + hh[4] = hh[4] &+ E + } +} + +extension SHA1: Updatable { + @discardableResult @inlinable + public func update(withBytes bytes: ArraySlice, isLast: Bool = false) throws -> Array { + self.accumulated += bytes + + if isLast { + let lengthInBits = (processedBytesTotalCount + self.accumulated.count) * 8 + let lengthBytes = lengthInBits.bytes(totalBytes: 64 / 8) // A 64-bit representation of b + + // Step 1. Append padding + bitPadding(to: &self.accumulated, blockSize: SHA1.blockSize, allowance: 64 / 8) + + // Step 2. Append Length a 64-bit representation of lengthInBits + self.accumulated += lengthBytes + } + + var processedBytes = 0 + for chunk in self.accumulated.batched(by: SHA1.blockSize) { + if isLast || (self.accumulated.count - processedBytes) >= SHA1.blockSize { + self.process(block: chunk, currentHash: &self.accumulatedHash) + processedBytes += chunk.count + } + } + self.accumulated.removeFirst(processedBytes) + self.processedBytesTotalCount += processedBytes + + // output current hash + var result = Array(repeating: 0, count: SHA1.digestLength) + var pos = 0 + for idx in 0..> 24) & 0xff) + result[pos + 1] = UInt8((h >> 16) & 0xff) + result[pos + 2] = UInt8((h >> 8) & 0xff) + result[pos + 3] = UInt8(h & 0xff) + pos += 4 + } + + // reset hash value for instance + if isLast { + self.accumulatedHash = SHA1.hashInitialValue + } + + return result + } +} \ No newline at end of file diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/SHA2.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/SHA2.swift new file mode 100644 index 00000000..9d4e4aca --- /dev/null +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/SHA2.swift @@ -0,0 +1,386 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +// +// CryptoSwift +// +// Copyright (C) 2014-2022 Marcin Krzyżanowski +// This software is provided 'as-is', without any express or implied warranty. +// +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: +// +// - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. +// - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +// - This notice may not be removed or altered from any source or binary distribution. +// + +// TODO: generic for process32/64 (UInt32/UInt64) +// + +public final class SHA2: DigestType { + @usableFromInline + let variant: Variant + + @usableFromInline + let size: Int + + @usableFromInline + let blockSize: Int + + @usableFromInline + let digestLength: Int + + private let k: Array + + @usableFromInline + var accumulated = Array() + + @usableFromInline + var processedBytesTotalCount: Int = 0 + + @usableFromInline + var accumulatedHash32 = Array() + + @usableFromInline + var accumulatedHash64 = Array() + + @frozen + public enum Variant: RawRepresentable { + case sha224, sha256, sha384, sha512 + + public var digestLength: Int { + self.rawValue / 8 + } + + public var blockSize: Int { + switch self { + case .sha224, .sha256: + return 64 + case .sha384, .sha512: + return 128 + } + } + + public typealias RawValue = Int + public var rawValue: RawValue { + switch self { + case .sha224: + return 224 + case .sha256: + return 256 + case .sha384: + return 384 + case .sha512: + return 512 + } + } + + public init?(rawValue: RawValue) { + switch rawValue { + case 224: + self = .sha224 + case 256: + self = .sha256 + case 384: + self = .sha384 + case 512: + self = .sha512 + default: + return nil + } + } + + @usableFromInline + var h: Array { + switch self { + case .sha224: + return [0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939, 0xffc00b31, 0x68581511, 0x64f98fa7, 0xbefa4fa4] + case .sha256: + return [0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19] + case .sha384: + return [0xcbbb9d5dc1059ed8, 0x629a292a367cd507, 0x9159015a3070dd17, 0x152fecd8f70e5939, 0x67332667ffc00b31, 0x8eb44a8768581511, 0xdb0c2e0d64f98fa7, 0x47b5481dbefa4fa4] + case .sha512: + return [0x6a09e667f3bcc908, 0xbb67ae8584caa73b, 0x3c6ef372fe94f82b, 0xa54ff53a5f1d36f1, 0x510e527fade682d1, 0x9b05688c2b3e6c1f, 0x1f83d9abfb41bd6b, 0x5be0cd19137e2179] + } + } + + @usableFromInline + var finalLength: Int { + switch self { + case .sha224: + return 7 + case .sha384: + return 6 + default: + return Int.max + } + } + } + + public init(variant: SHA2.Variant) { + self.variant = variant + switch self.variant { + case .sha224, .sha256: + self.accumulatedHash32 = variant.h.map { UInt32($0) } // FIXME: UInt64 for process64 + self.blockSize = variant.blockSize + self.size = variant.rawValue + self.digestLength = variant.digestLength + self.k = [ + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 + ] + case .sha384, .sha512: + self.accumulatedHash64 = variant.h + self.blockSize = variant.blockSize + self.size = variant.rawValue + self.digestLength = variant.digestLength + self.k = [ + 0x428a2f98d728ae22, 0x7137449123ef65cd, 0xb5c0fbcfec4d3b2f, 0xe9b5dba58189dbbc, 0x3956c25bf348b538, + 0x59f111f1b605d019, 0x923f82a4af194f9b, 0xab1c5ed5da6d8118, 0xd807aa98a3030242, 0x12835b0145706fbe, + 0x243185be4ee4b28c, 0x550c7dc3d5ffb4e2, 0x72be5d74f27b896f, 0x80deb1fe3b1696b1, 0x9bdc06a725c71235, + 0xc19bf174cf692694, 0xe49b69c19ef14ad2, 0xefbe4786384f25e3, 0x0fc19dc68b8cd5b5, 0x240ca1cc77ac9c65, + 0x2de92c6f592b0275, 0x4a7484aa6ea6e483, 0x5cb0a9dcbd41fbd4, 0x76f988da831153b5, 0x983e5152ee66dfab, + 0xa831c66d2db43210, 0xb00327c898fb213f, 0xbf597fc7beef0ee4, 0xc6e00bf33da88fc2, 0xd5a79147930aa725, + 0x06ca6351e003826f, 0x142929670a0e6e70, 0x27b70a8546d22ffc, 0x2e1b21385c26c926, 0x4d2c6dfc5ac42aed, + 0x53380d139d95b3df, 0x650a73548baf63de, 0x766a0abb3c77b2a8, 0x81c2c92e47edaee6, 0x92722c851482353b, + 0xa2bfe8a14cf10364, 0xa81a664bbc423001, 0xc24b8b70d0f89791, 0xc76c51a30654be30, 0xd192e819d6ef5218, + 0xd69906245565a910, 0xf40e35855771202a, 0x106aa07032bbd1b8, 0x19a4c116b8d2d0c8, 0x1e376c085141ab53, + 0x2748774cdf8eeb99, 0x34b0bcb5e19b48a8, 0x391c0cb3c5c95a63, 0x4ed8aa4ae3418acb, 0x5b9cca4f7763e373, + 0x682e6ff3d6b2b8a3, 0x748f82ee5defb2fc, 0x78a5636f43172f60, 0x84c87814a1f0ab72, 0x8cc702081a6439ec, + 0x90befffa23631e28, 0xa4506cebde82bde9, 0xbef9a3f7b2c67915, 0xc67178f2e372532b, 0xca273eceea26619c, + 0xd186b8c721c0c207, 0xeada7dd6cde0eb1e, 0xf57d4f7fee6ed178, 0x06f067aa72176fba, 0x0a637dc5a2c898a6, + 0x113f9804bef90dae, 0x1b710b35131c471b, 0x28db77f523047d84, 0x32caab7b40c72493, 0x3c9ebe0a15c9bebc, + 0x431d67c49c100d4c, 0x4cc5d4becb3e42b6, 0x597f299cfc657e2a, 0x5fcb6fab3ad6faec, 0x6c44198c4a475817 + ] + } + } + + @inlinable + public func calculate(for bytes: Array) -> Array { + do { + return try update(withBytes: bytes.slice, isLast: true) + } catch { + return [] + } + } + + public func callAsFunction(_ bytes: Array) -> Array { + calculate(for: bytes) + } + + @usableFromInline + func process64(block chunk: ArraySlice, currentHash hh: inout Array) { + // break chunk into sixteen 64-bit words M[j], 0 ≤ j ≤ 15, big-endian + // Extend the sixteen 64-bit words into eighty 64-bit words: + let M = UnsafeMutablePointer.allocate(capacity: self.k.count) + M.initialize(repeating: 0, count: self.k.count) + defer { + M.deinitialize(count: self.k.count) + M.deallocate() + } + for x in 0...size + M[x] = UInt64(bytes: chunk, fromIndex: start) + default: + let s0 = rotateRight(M[x - 15], by: 1) ^ rotateRight(M[x - 15], by: 8) ^ (M[x - 15] >> 7) + let s1 = rotateRight(M[x - 2], by: 19) ^ rotateRight(M[x - 2], by: 61) ^ (M[x - 2] >> 6) + M[x] = M[x - 16] &+ s0 &+ M[x - 7] &+ s1 + } + } + + var A = hh[0] + var B = hh[1] + var C = hh[2] + var D = hh[3] + var E = hh[4] + var F = hh[5] + var G = hh[6] + var H = hh[7] + + // Main loop + for j in 0.., currentHash hh: inout Array) { + // break chunk into sixteen 32-bit words M[j], 0 ≤ j ≤ 15, big-endian + // Extend the sixteen 32-bit words into sixty-four 32-bit words: + let M = UnsafeMutablePointer.allocate(capacity: self.k.count) + M.initialize(repeating: 0, count: self.k.count) + defer { + M.deinitialize(count: self.k.count) + M.deallocate() + } + + for x in 0...size + M[x] = UInt32(bytes: chunk, fromIndex: start) + default: + let s0 = rotateRight(M[x - 15], by: 7) ^ rotateRight(M[x - 15], by: 18) ^ (M[x - 15] >> 3) + let s1 = rotateRight(M[x - 2], by: 17) ^ rotateRight(M[x - 2], by: 19) ^ (M[x - 2] >> 10) + M[x] = M[x - 16] &+ s0 &+ M[x - 7] &+ s1 + } + } + + var A = hh[0] + var B = hh[1] + var C = hh[2] + var D = hh[3] + var E = hh[4] + var F = hh[5] + var G = hh[6] + var H = hh[7] + + // Main loop + for j in 0.., isLast: Bool = false) throws -> Array { + self.accumulated += bytes + + if isLast { + let lengthInBits = (processedBytesTotalCount + self.accumulated.count) * 8 + let lengthBytes = lengthInBits.bytes(totalBytes: self.blockSize / 8) // A 64-bit/128-bit representation of b. blockSize fit by accident. + + // Step 1. Append padding + bitPadding(to: &self.accumulated, blockSize: self.blockSize, allowance: self.blockSize / 8) + + // Step 2. Append Length a 64-bit representation of lengthInBits + self.accumulated += lengthBytes + } + + var processedBytes = 0 + for chunk in self.accumulated.batched(by: self.blockSize) { + if isLast || (self.accumulated.count - processedBytes) >= self.blockSize { + switch self.variant { + case .sha224, .sha256: + self.process32(block: chunk, currentHash: &self.accumulatedHash32) + case .sha384, .sha512: + self.process64(block: chunk, currentHash: &self.accumulatedHash64) + } + processedBytes += chunk.count + } + } + self.accumulated.removeFirst(processedBytes) + self.processedBytesTotalCount += processedBytes + + // output current hash + var result = Array(repeating: 0, count: variant.digestLength) + switch self.variant { + case .sha224, .sha256: + var pos = 0 + for idx in 0..> 24) & 0xff) + result[pos + 1] = UInt8((h >> 16) & 0xff) + result[pos + 2] = UInt8((h >> 8) & 0xff) + result[pos + 3] = UInt8(h & 0xff) + pos += 4 + } + case .sha384, .sha512: + var pos = 0 + for idx in 0..> 56) & 0xff) + result[pos + 1] = UInt8((h >> 48) & 0xff) + result[pos + 2] = UInt8((h >> 40) & 0xff) + result[pos + 3] = UInt8((h >> 32) & 0xff) + result[pos + 4] = UInt8((h >> 24) & 0xff) + result[pos + 5] = UInt8((h >> 16) & 0xff) + result[pos + 6] = UInt8((h >> 8) & 0xff) + result[pos + 7] = UInt8(h & 0xff) + pos += 8 + } + } + + // reset hash value for instance + if isLast { + switch self.variant { + case .sha224, .sha256: + self.accumulatedHash32 = self.variant.h.lazy.map { UInt32($0) } // FIXME: UInt64 for process64 + case .sha384, .sha512: + self.accumulatedHash64 = self.variant.h + } + } + + return result + } +} \ No newline at end of file diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/SHA3.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/SHA3.swift new file mode 100644 index 00000000..24027062 --- /dev/null +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/SHA3.swift @@ -0,0 +1,317 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +// +// CryptoSwift +// +// Copyright (C) 2014-2022 Marcin Krzyżanowski +// This software is provided 'as-is', without any express or implied warranty. +// +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: +// +// - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. +// - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +// - This notice may not be removed or altered from any source or binary distribution. +// + +// http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf +// http://keccak.noekeon.org/specs_summary.html +// + +#if canImport(Darwin) +import Darwin +#elseif canImport(Glibc) +import Glibc +#elseif canImport(ucrt) +import ucrt +#endif + +public final class SHA3: DigestType { + let round_constants: Array = [ + 0x0000000000000001, 0x0000000000008082, 0x800000000000808a, 0x8000000080008000, + 0x000000000000808b, 0x0000000080000001, 0x8000000080008081, 0x8000000000008009, + 0x000000000000008a, 0x0000000000000088, 0x0000000080008009, 0x000000008000000a, + 0x000000008000808b, 0x800000000000008b, 0x8000000000008089, 0x8000000000008003, + 0x8000000000008002, 0x8000000000000080, 0x000000000000800a, 0x800000008000000a, + 0x8000000080008081, 0x8000000000008080, 0x0000000080000001, 0x8000000080008008 + ] + + public let blockSize: Int + public let digestLength: Int + public let markByte: UInt8 + + @usableFromInline + var accumulated = Array() + + + @usableFromInline + var accumulatedHash: Array + + public enum Variant { + case sha224, sha256, sha384, sha512, keccak224, keccak256, keccak384, keccak512 + + var digestLength: Int { + 100 - (self.blockSize / 2) + } + + var blockSize: Int { + (1600 - self.outputLength * 2) / 8 + } + + var markByte: UInt8 { + switch self { + case .sha224, .sha256, .sha384, .sha512: + return 0x06 // 0x1F for SHAKE + case .keccak224, .keccak256, .keccak384, .keccak512: + return 0x01 + } + } + + public var outputLength: Int { + switch self { + case .sha224, .keccak224: + return 224 + case .sha256, .keccak256: + return 256 + case .sha384, .keccak384: + return 384 + case .sha512, .keccak512: + return 512 + } + } + } + + public init(variant: SHA3.Variant) { + self.blockSize = variant.blockSize + self.digestLength = variant.digestLength + self.markByte = variant.markByte + self.accumulatedHash = Array(repeating: 0, count: self.digestLength) + } + + @inlinable + public func calculate(for bytes: Array) -> Array { + do { + return try update(withBytes: bytes.slice, isLast: true) + } catch { + return [] + } + } + + public func callAsFunction(_ bytes: Array) -> Array { + calculate(for: bytes) + } + + /// 1. For all pairs (x,z) such that 0≤x<5 and 0≤z) { + let c = UnsafeMutablePointer.allocate(capacity: 5) + c.initialize(repeating: 0, count: 5) + defer { + c.deinitialize(count: 5) + c.deallocate() + } + let d = UnsafeMutablePointer.allocate(capacity: 5) + d.initialize(repeating: 0, count: 5) + defer { + d.deinitialize(count: 5) + d.deallocate() + } + + for i in 0..<5 { + c[i] = a[i] ^ a[i &+ 5] ^ a[i &+ 10] ^ a[i &+ 15] ^ a[i &+ 20] + } + + d[0] = rotateLeft(c[1], by: 1) ^ c[4] + d[1] = rotateLeft(c[2], by: 1) ^ c[0] + d[2] = rotateLeft(c[3], by: 1) ^ c[1] + d[3] = rotateLeft(c[4], by: 1) ^ c[2] + d[4] = rotateLeft(c[0], by: 1) ^ c[3] + + for i in 0..<5 { + a[i] ^= d[i] + a[i &+ 5] ^= d[i] + a[i &+ 10] ^= d[i] + a[i &+ 15] ^= d[i] + a[i &+ 20] ^= d[i] + } + } + + /// A′[x, y, z]=A[(x &+ 3y) mod 5, x, z] + private func π(_ a: inout Array) { + let a1 = a[1] + a[1] = a[6] + a[6] = a[9] + a[9] = a[22] + a[22] = a[14] + a[14] = a[20] + a[20] = a[2] + a[2] = a[12] + a[12] = a[13] + a[13] = a[19] + a[19] = a[23] + a[23] = a[15] + a[15] = a[4] + a[4] = a[24] + a[24] = a[21] + a[21] = a[8] + a[8] = a[16] + a[16] = a[5] + a[5] = a[3] + a[3] = a[18] + a[18] = a[17] + a[17] = a[11] + a[11] = a[7] + a[7] = a[10] + a[10] = a1 + } + + /// For all triples (x, y, z) such that 0≤x<5, 0≤y<5, and 0≤z) { + for i in stride(from: 0, to: 25, by: 5) { + let a0 = a[0 &+ i] + let a1 = a[1 &+ i] + a[0 &+ i] ^= ~a1 & a[2 &+ i] + a[1 &+ i] ^= ~a[2 &+ i] & a[3 &+ i] + a[2 &+ i] ^= ~a[3 &+ i] & a[4 &+ i] + a[3 &+ i] ^= ~a[4 &+ i] & a0 + a[4 &+ i] ^= ~a0 & a1 + } + } + + private func ι(_ a: inout Array, round: Int) { + a[0] ^= self.round_constants[round] + } + + @usableFromInline + func process(block chunk: ArraySlice, currentHash hh: inout Array) { + // expand + hh[0] ^= chunk[0].littleEndian + hh[1] ^= chunk[1].littleEndian + hh[2] ^= chunk[2].littleEndian + hh[3] ^= chunk[3].littleEndian + hh[4] ^= chunk[4].littleEndian + hh[5] ^= chunk[5].littleEndian + hh[6] ^= chunk[6].littleEndian + hh[7] ^= chunk[7].littleEndian + hh[8] ^= chunk[8].littleEndian + if self.blockSize > 72 { // 72 / 8, sha-512 + hh[9] ^= chunk[9].littleEndian + hh[10] ^= chunk[10].littleEndian + hh[11] ^= chunk[11].littleEndian + hh[12] ^= chunk[12].littleEndian + if self.blockSize > 104 { // 104 / 8, sha-384 + hh[13] ^= chunk[13].littleEndian + hh[14] ^= chunk[14].littleEndian + hh[15] ^= chunk[15].littleEndian + hh[16] ^= chunk[16].littleEndian + if self.blockSize > 136 { // 136 / 8, sha-256 + hh[17] ^= chunk[17].littleEndian + // FULL_SHA3_FAMILY_SUPPORT + if self.blockSize > 144 { // 144 / 8, sha-224 + hh[18] ^= chunk[18].littleEndian + hh[19] ^= chunk[19].littleEndian + hh[20] ^= chunk[20].littleEndian + hh[21] ^= chunk[21].littleEndian + hh[22] ^= chunk[22].littleEndian + hh[23] ^= chunk[23].littleEndian + hh[24] ^= chunk[24].littleEndian + } + } + } + } + + // Keccak-f + for round in 0..<24 { + self.θ(&hh) + + hh[1] = rotateLeft(hh[1], by: 1) + hh[2] = rotateLeft(hh[2], by: 62) + hh[3] = rotateLeft(hh[3], by: 28) + hh[4] = rotateLeft(hh[4], by: 27) + hh[5] = rotateLeft(hh[5], by: 36) + hh[6] = rotateLeft(hh[6], by: 44) + hh[7] = rotateLeft(hh[7], by: 6) + hh[8] = rotateLeft(hh[8], by: 55) + hh[9] = rotateLeft(hh[9], by: 20) + hh[10] = rotateLeft(hh[10], by: 3) + hh[11] = rotateLeft(hh[11], by: 10) + hh[12] = rotateLeft(hh[12], by: 43) + hh[13] = rotateLeft(hh[13], by: 25) + hh[14] = rotateLeft(hh[14], by: 39) + hh[15] = rotateLeft(hh[15], by: 41) + hh[16] = rotateLeft(hh[16], by: 45) + hh[17] = rotateLeft(hh[17], by: 15) + hh[18] = rotateLeft(hh[18], by: 21) + hh[19] = rotateLeft(hh[19], by: 8) + hh[20] = rotateLeft(hh[20], by: 18) + hh[21] = rotateLeft(hh[21], by: 2) + hh[22] = rotateLeft(hh[22], by: 61) + hh[23] = rotateLeft(hh[23], by: 56) + hh[24] = rotateLeft(hh[24], by: 14) + + self.π(&hh) + self.χ(&hh) + self.ι(&hh, round: round) + } + } +} + +extension SHA3: Updatable { + + @inlinable + public func update(withBytes bytes: ArraySlice, isLast: Bool = false) throws -> Array { + self.accumulated += bytes + + if isLast { + // Add padding + let markByteIndex = self.accumulated.count + + // We need to always pad the input. Even if the input is a multiple of blockSize. + let r = self.blockSize * 8 + let q = (r / 8) - (accumulated.count % (r / 8)) + self.accumulated += Array(repeating: 0, count: q) + + self.accumulated[markByteIndex] |= self.markByte + self.accumulated[self.accumulated.count - 1] |= 0x80 + } + + var processedBytes = 0 + for chunk in self.accumulated.batched(by: self.blockSize) { + if isLast || (self.accumulated.count - processedBytes) >= self.blockSize { + self.process(block: chunk.toUInt64Array().slice, currentHash: &self.accumulatedHash) + processedBytes += chunk.count + } + } + self.accumulated.removeFirst(processedBytes) + + // TODO: verify performance, reduce vs for..in + let result = self.accumulatedHash.reduce(into: Array()) { (result, value) in + result += value.bigEndian.bytes() + } + + // reset hash value for instance + if isLast { + self.accumulatedHash = Array(repeating: 0, count: self.digestLength) + } + + return Array(result[0.. +// This software is provided 'as-is', without any express or implied warranty. +// +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: +// +// - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. +// - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +// - This notice may not be removed or altered from any source or binary distribution. +// + +/** array of bytes */ +extension UInt16 { + @_specialize(where T == ArraySlice) + init(bytes: T) where T.Element == UInt8, T.Index == Int { + self = UInt16(bytes: bytes, fromIndex: bytes.startIndex) + } + + @_specialize(where T == ArraySlice) + init(bytes: T, fromIndex index: T.Index) where T.Element == UInt8, T.Index == Int { + if bytes.isEmpty { + self = 0 + return + } + + let count = bytes.count + + let val0 = count > 0 ? UInt16(bytes[index.advanced(by: 0)]) << 8 : 0 + let val1 = count > 1 ? UInt16(bytes[index.advanced(by: 1)]) : 0 + + self = val0 | val1 + } +} diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/UInt32+Extension.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/UInt32+Extension.swift new file mode 100644 index 00000000..18e1599a --- /dev/null +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/UInt32+Extension.swift @@ -0,0 +1,65 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +// +// CryptoSwift +// +// Copyright (C) 2014-2022 Marcin Krzyżanowski +// This software is provided 'as-is', without any express or implied warranty. +// +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: +// +// - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. +// - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +// - This notice may not be removed or altered from any source or binary distribution. +// + +#if canImport(Darwin) +import Darwin +#elseif canImport(Glibc) +import Glibc +#elseif canImport(ucrt) +import ucrt +#endif + +protocol _UInt32Type {} +extension UInt32: _UInt32Type {} + +/** array of bytes */ +extension UInt32 { + @_specialize(where T == ArraySlice) + init(bytes: T) where T.Element == UInt8, T.Index == Int { + self = UInt32(bytes: bytes, fromIndex: bytes.startIndex) + } + + @_specialize(where T == ArraySlice) + @inlinable + init(bytes: T, fromIndex index: T.Index) where T.Element == UInt8, T.Index == Int { + if bytes.isEmpty { + self = 0 + return + } + + let count = bytes.count + + let val0 = count > 0 ? UInt32(bytes[index.advanced(by: 0)]) << 24 : 0 + let val1 = count > 1 ? UInt32(bytes[index.advanced(by: 1)]) << 16 : 0 + let val2 = count > 2 ? UInt32(bytes[index.advanced(by: 2)]) << 8 : 0 + let val3 = count > 3 ? UInt32(bytes[index.advanced(by: 3)]) : 0 + + self = val0 | val1 | val2 | val3 + } +} diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/UInt64+Extension.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/UInt64+Extension.swift new file mode 100644 index 00000000..1bbdc79b --- /dev/null +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/UInt64+Extension.swift @@ -0,0 +1,58 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +// +// CryptoSwift +// +// Copyright (C) 2014-2022 Marcin Krzyżanowski +// This software is provided 'as-is', without any express or implied warranty. +// +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: +// +// - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. +// - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +// - This notice may not be removed or altered from any source or binary distribution. +// + +/** array of bytes */ +extension UInt64 { + @_specialize(where T == ArraySlice) + init(bytes: T) where T.Element == UInt8, T.Index == Int { + self = UInt64(bytes: bytes, fromIndex: bytes.startIndex) + } + + @_specialize(where T == ArraySlice) + @inlinable + init(bytes: T, fromIndex index: T.Index) where T.Element == UInt8, T.Index == Int { + if bytes.isEmpty { + self = 0 + return + } + + let count = bytes.count + + let val0 = count > 0 ? UInt64(bytes[index.advanced(by: 0)]) << 56 : 0 + let val1 = count > 1 ? UInt64(bytes[index.advanced(by: 1)]) << 48 : 0 + let val2 = count > 2 ? UInt64(bytes[index.advanced(by: 2)]) << 40 : 0 + let val3 = count > 3 ? UInt64(bytes[index.advanced(by: 3)]) << 32 : 0 + let val4 = count > 4 ? UInt64(bytes[index.advanced(by: 4)]) << 24 : 0 + let val5 = count > 5 ? UInt64(bytes[index.advanced(by: 5)]) << 16 : 0 + let val6 = count > 6 ? UInt64(bytes[index.advanced(by: 6)]) << 8 : 0 + let val7 = count > 7 ? UInt64(bytes[index.advanced(by: 7)]) : 0 + + self = val0 | val1 | val2 | val3 | val4 | val5 | val6 | val7 + } +} diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/UInt8+Extension.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/UInt8+Extension.swift new file mode 100644 index 00000000..b65ed5f5 --- /dev/null +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/UInt8+Extension.swift @@ -0,0 +1,88 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +// +// CryptoSwift +// +// Copyright (C) 2014-2022 Marcin Krzyżanowski +// This software is provided 'as-is', without any express or implied warranty. +// +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: +// +// - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. +// - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +// - This notice may not be removed or altered from any source or binary distribution. +// + +#if canImport(Darwin) +import Darwin +#elseif canImport(Glibc) +import Glibc +#elseif canImport(ucrt) +import ucrt +#endif + +public protocol _UInt8Type {} +extension UInt8: _UInt8Type {} + +/** casting */ +extension UInt8 { + /** cast because UInt8() because std initializer crash if value is > byte */ + static func with(value: UInt64) -> UInt8 { + let tmp = value & 0xff + return UInt8(tmp) + } + + static func with(value: UInt32) -> UInt8 { + let tmp = value & 0xff + return UInt8(tmp) + } + + static func with(value: UInt16) -> UInt8 { + let tmp = value & 0xff + return UInt8(tmp) + } +} + +/** Bits */ +extension UInt8 { + /** array of bits */ + public func bits() -> [Bit] { + let totalBitsCount = MemoryLayout.size * 8 + + var bitsArray = [Bit](repeating: Bit.zero, count: totalBitsCount) + + for j in 0.. String { + var s = String() + let arr: [Bit] = self.bits() + for idx in arr.indices { + s += (arr[idx] == Bit.one ? "1" : "0") + if idx.advanced(by: 1) % 8 == 0 { s += " " } + } + return s + } +} diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/Updatable.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Updatable.swift new file mode 100644 index 00000000..e54fc60d --- /dev/null +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Updatable.swift @@ -0,0 +1,121 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +// +// CryptoSwift +// +// Copyright (C) 2014-2022 Marcin Krzyżanowski +// This software is provided 'as-is', without any express or implied warranty. +// +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: +// +// - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. +// - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +// - This notice may not be removed or altered from any source or binary distribution. +// + +/// A type that supports incremental updates. For example Digest or Cipher may be updatable +/// and calculate result incerementally. +public protocol Updatable { + /// Update given bytes in chunks. + /// + /// - parameter bytes: Bytes to process. + /// - parameter isLast: Indicate if given chunk is the last one. No more updates after this call. + /// - returns: Processed partial result data or empty array. + mutating func update(withBytes bytes: ArraySlice, isLast: Bool) throws -> Array + + /// Update given bytes in chunks. + /// + /// - Parameters: + /// - bytes: Bytes to process. + /// - isLast: Indicate if given chunk is the last one. No more updates after this call. + /// - output: Resulting bytes callback. + /// - Returns: Processed partial result data or empty array. + mutating func update(withBytes bytes: ArraySlice, isLast: Bool, output: (_ bytes: Array) -> Void) throws +} + +extension Updatable { + @inlinable + public mutating func update(withBytes bytes: ArraySlice, isLast: Bool = false, output: (_ bytes: Array) -> Void) throws { + let processed = try update(withBytes: bytes, isLast: isLast) + if !processed.isEmpty { + output(processed) + } + } + + @inlinable + public mutating func update(withBytes bytes: ArraySlice, isLast: Bool = false) throws -> Array { + try self.update(withBytes: bytes, isLast: isLast) + } + + @inlinable + public mutating func update(withBytes bytes: Array, isLast: Bool = false) throws -> Array { + try self.update(withBytes: bytes.slice, isLast: isLast) + } + + @inlinable + public mutating func update(withBytes bytes: Array, isLast: Bool = false, output: (_ bytes: Array) -> Void) throws { + try self.update(withBytes: bytes.slice, isLast: isLast, output: output) + } + + /// Finish updates. This may apply padding. + /// - parameter bytes: Bytes to process + /// - returns: Processed data. + @inlinable + public mutating func finish(withBytes bytes: ArraySlice) throws -> Array { + try self.update(withBytes: bytes, isLast: true) + } + + @inlinable + public mutating func finish(withBytes bytes: Array) throws -> Array { + try self.finish(withBytes: bytes.slice) + } + + /// Finish updates. May add padding. + /// + /// - Returns: Processed data + /// - Throws: Error + @inlinable + public mutating func finish() throws -> Array { + try self.update(withBytes: [], isLast: true) + } + + /// Finish updates. This may apply padding. + /// - parameter bytes: Bytes to process + /// - parameter output: Resulting data + /// - returns: Processed data. + @inlinable + public mutating func finish(withBytes bytes: ArraySlice, output: (_ bytes: Array) -> Void) throws { + let processed = try update(withBytes: bytes, isLast: true) + if !processed.isEmpty { + output(processed) + } + } + + @inlinable + public mutating func finish(withBytes bytes: Array, output: (_ bytes: Array) -> Void) throws { + try self.finish(withBytes: bytes.slice, output: output) + } + + /// Finish updates. May add padding. + /// + /// - Parameter output: Processed data + /// - Throws: Error + @inlinable + public mutating func finish(output: (Array) -> Void) throws { + try self.finish(withBytes: [], output: output) + } +} \ No newline at end of file diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/Utils.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Utils.swift new file mode 100644 index 00000000..6a5ad9fd --- /dev/null +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Utils.swift @@ -0,0 +1,131 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +// +// CryptoSwift +// +// Copyright (C) 2014-2022 Marcin Krzyżanowski +// This software is provided 'as-is', without any express or implied warranty. +// +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: +// +// - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. +// - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +// - This notice may not be removed or altered from any source or binary distribution. +// + +@inlinable +func rotateLeft(_ value: UInt8, by: UInt8) -> UInt8 { + ((value << by) & 0xff) | (value >> (8 - by)) +} + +@inlinable +func rotateLeft(_ value: UInt16, by: UInt16) -> UInt16 { + ((value << by) & 0xffff) | (value >> (16 - by)) +} + +@inlinable +func rotateLeft(_ value: UInt32, by: UInt32) -> UInt32 { + ((value << by) & 0xffffffff) | (value >> (32 - by)) +} + +@inlinable +func rotateLeft(_ value: UInt64, by: UInt64) -> UInt64 { + (value << by) | (value >> (64 - by)) +} + +@inlinable +func rotateRight(_ value: UInt16, by: UInt16) -> UInt16 { + (value >> by) | (value << (16 - by)) +} + +@inlinable +func rotateRight(_ value: UInt32, by: UInt32) -> UInt32 { + (value >> by) | (value << (32 - by)) +} + +@inlinable +func rotateRight(_ value: UInt64, by: UInt64) -> UInt64 { + ((value >> by) | (value << (64 - by))) +} + +@inlinable +func reversed(_ uint8: UInt8) -> UInt8 { + var v = uint8 + v = (v & 0xf0) >> 4 | (v & 0x0f) << 4 + v = (v & 0xcc) >> 2 | (v & 0x33) << 2 + v = (v & 0xaa) >> 1 | (v & 0x55) << 1 + return v +} + +@inlinable +func reversed(_ uint32: UInt32) -> UInt32 { + var v = uint32 + v = ((v >> 1) & 0x55555555) | ((v & 0x55555555) << 1) + v = ((v >> 2) & 0x33333333) | ((v & 0x33333333) << 2) + v = ((v >> 4) & 0x0f0f0f0f) | ((v & 0x0f0f0f0f) << 4) + v = ((v >> 8) & 0x00ff00ff) | ((v & 0x00ff00ff) << 8) + v = ((v >> 16) & 0xffff) | ((v & 0xffff) << 16) + return v +} + +@inlinable +func xor(_ left: T, _ right: V) -> ArraySlice where T: RandomAccessCollection, V: RandomAccessCollection, T.Element == UInt8, T.Index == Int, V.Element == UInt8, V.Index == Int { + return xor(left, right).slice +} + +@inlinable +func xor(_ left: T, _ right: V) -> Array where T: RandomAccessCollection, V: RandomAccessCollection, T.Element == UInt8, T.Index == Int, V.Element == UInt8, V.Index == Int { + let length = Swift.min(left.count, right.count) + + let buf = UnsafeMutablePointer.allocate(capacity: length) + buf.initialize(repeating: 0, count: length) + defer { + buf.deinitialize(count: length) + buf.deallocate() + } + + // xor + for i in 0.., blockSize: Int, allowance: Int = 0) { + let msgLength = data.count + // Step 1. Append Padding Bits + // append one bit (UInt8 with one bit) to message + data.append(0x80) + + // Step 2. append "0" bit until message length in bits ≡ 448 (mod 512) + let max = blockSize - allowance // 448, 986 + if msgLength % blockSize < max { // 448 + data += Array(repeating: 0, count: max - 1 - (msgLength % blockSize)) + } else { + data += Array(repeating: 0, count: blockSize + max - 1 - (msgLength % blockSize)) + } +} \ No newline at end of file diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/ZeroPadding.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/ZeroPadding.swift new file mode 100644 index 00000000..f6846a45 --- /dev/null +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/ZeroPadding.swift @@ -0,0 +1,54 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +// +// CryptoSwift +// +// Copyright (C) 2014-2022 Marcin Krzyżanowski +// This software is provided 'as-is', without any express or implied warranty. +// +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: +// +// - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. +// - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +// - This notice may not be removed or altered from any source or binary distribution. +// + +/// All the bytes that are required to be padded are padded with zero. +/// Zero padding may not be reversible if the original file ends with one or more zero bytes. +struct ZeroPadding: PaddingProtocol { + init() { + } + + @inlinable + func add(to bytes: Array, blockSize: Int) -> Array { + let paddingCount = blockSize - (bytes.count % blockSize) + if paddingCount > 0 { + return bytes + Array(repeating: 0, count: paddingCount) + } + return bytes + } + + @inlinable + func remove(from bytes: Array, blockSize _: Int?) -> Array { + for (idx, value) in bytes.reversed().enumerated() { + if value != 0 { + return Array(bytes[0.. String { + + var signature = "" + do { + let kDate = try HMAC.authenticate(for: Data(signingData.date.utf8), using: "AWS4\(credentials.secretAccessKey)".array) + let kRegion = try HMAC.authenticate(for: Data(region.utf8), using: kDate) + let kService = try HMAC.authenticate(for: Data(name.utf8), using: kRegion) + let kSigning = try HMAC.authenticate(for: Data("aws4_request".utf8), using: kService) + let kSignature = try HMAC.authenticate(for: stringToSign(signingData: signingData), using: kSigning) + + signature = kSignature.toHexString() + } catch { + fatalError("HMAC computation is not supposed to throw") + } + + return signature + + // original code from Adam Fowler, using swift-crypto package: + /* let kDate = HMAC.authenticationCode(for: Data(signingData.date.utf8), using: SymmetricKey(data: Array("AWS4\(credentials.secretAccessKey)".utf8))) let kRegion = HMAC.authenticationCode(for: Data(region.utf8), using: SymmetricKey(data: kDate)) let kService = HMAC.authenticationCode(for: Data(name.utf8), using: SymmetricKey(data: kRegion)) let kSigning = HMAC.authenticationCode(for: Data("aws4_request".utf8), using: SymmetricKey(data: kService)) let kSignature = HMAC.authenticationCode(for: stringToSign(signingData: signingData), using: SymmetricKey(data: kSigning)) return kSignature.hexDigest() + */ } /// Stage 2 Create the string to sign as in https://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html diff --git a/Sources/AWSLambdaDeployerHelper/main.swift b/Sources/AWSLambdaPluginHelper/main.swift similarity index 100% rename from Sources/AWSLambdaDeployerHelper/main.swift rename to Sources/AWSLambdaPluginHelper/main.swift diff --git a/Tests/AWSLambdaPluginHelperTests/AWSSignerTests.swift b/Tests/AWSLambdaPluginHelperTests/AWSSignerTests.swift new file mode 100644 index 00000000..4220e235 --- /dev/null +++ b/Tests/AWSLambdaPluginHelperTests/AWSSignerTests.swift @@ -0,0 +1,57 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import Logging +import Testing + +@testable import AWSLambdaPluginHelper + +#if canImport(FoundationEssentials) +import FoundationEssentials +#else +import Foundation +#endif + +@Suite +struct SignerTests { + let credentials : Credential = StaticCredential(accessKeyId: "MYACCESSKEY", secretAccessKey: "MYSECRETACCESSKEY") + + @Test + func testSignGetHeaders() { + let signer = AWSSigner(credentials: credentials, name: "glacier", region:"us-east-1") + let headers = signer.signHeaders(url: URL(string:"https://glacier.us-east-1.amazonaws.com/-/vaults")!, method: .GET, headers: ["x-amz-glacier-version":"2012-06-01"], date: Date(timeIntervalSinceReferenceDate: 2000000)) + #expect(headers["Authorization"].first == "AWS4-HMAC-SHA256 Credential=MYACCESSKEY/20010124/us-east-1/glacier/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date;x-amz-glacier-version, Signature=acfa9b03fca6b098d7b88bfd9bbdb4687f5b34e944a9c6ed9f4814c1b0b06d62") + } + + @Test + func testSignPutHeaders() { + let signer = AWSSigner(credentials: credentials, name: "sns", region:"eu-west-1") + let headers = signer.signHeaders(url: URL(string: "https://sns.eu-west-1.amazonaws.com/")!, method: .POST, headers: ["Content-Type": "application/x-www-form-urlencoded; charset=utf-8"], body: .string("Action=ListTopics&Version=2010-03-31"), date: Date(timeIntervalSinceReferenceDate: 200)) + #expect(headers["Authorization"].first == "AWS4-HMAC-SHA256 Credential=MYACCESSKEY/20010101/eu-west-1/sns/aws4_request, SignedHeaders=content-type;host;x-amz-content-sha256;x-amz-date, Signature=1d29943055a8ad094239e8de06082100f2426ebbb2c6a5bbcbb04c63e6a3f274") + } + + @Test + func testSignS3GetURL() { + let signer = AWSSigner(credentials: credentials, name: "s3", region:"us-east-1") + let url = signer.signURL(url: URL(string: "https://s3.us-east-1.amazonaws.com/")!, method: .GET, date:Date(timeIntervalSinceReferenceDate: 100000)) + #expect(url.absoluteString == "https://s3.us-east-1.amazonaws.com/?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=MYACCESSKEY%2F20010102%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20010102T034640Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host&X-Amz-Signature=27957103c8bfdff3560372b1d85976ed29c944f34295eca2d4fdac7fc02c375a") + } + + @Test + func testSignS3PutURL() { + let signer = AWSSigner(credentials: credentials, name: "s3", region:"eu-west-1") + let url = signer.signURL(url: URL(string: "https://test-bucket.s3.amazonaws.com/test-put.txt")!, method: .PUT, body: .string("Testing signed URLs"), date:Date(timeIntervalSinceReferenceDate: 100000)) + #expect(url.absoluteString == "https://test-bucket.s3.amazonaws.com/test-put.txt?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=MYACCESSKEY%2F20010102%2Feu-west-1%2Fs3%2Faws4_request&X-Amz-Date=20010102T034640Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host&X-Amz-Signature=13d665549a6ea5eb6a1615ede83440eaed3e0ee25c964e62d188c896d916d96f") + } +} \ No newline at end of file diff --git a/Tests/AWSLambdaPluginHelperTests/CryptoTests.swift b/Tests/AWSLambdaPluginHelperTests/CryptoTests.swift new file mode 100644 index 00000000..3e88285a --- /dev/null +++ b/Tests/AWSLambdaPluginHelperTests/CryptoTests.swift @@ -0,0 +1,78 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import Logging +import Testing + +@testable import AWSLambdaPluginHelper + +#if canImport(FoundationEssentials) +import FoundationEssentials +#else +import Foundation +#endif + +@Suite +struct CryptoTests { + + @Test + func testSHA256() { + + // given + let input = "hello world" + let expected = "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9" + + // when + let result = Digest.sha256(input.array).toHexString() + + // then + #expect(result == expected) + } + + @Test + func testHMAC() throws { + + // given + let input = "hello world" + let secret = "secretkey" + let expected = "ae6cd2605d622316564d1f76bfc0c04f89d9fafb14f45b3e18c2a3e28bdef29d" + + // when + let authenticator = HMAC(key: secret.array, variant: .sha2(.sha256)) + + #expect(throws: Never.self) { + let result = try authenticator.authenticate(input.array).toHexString() + // then + #expect(result == expected) + } + + } + + @Test + func testHMACExtension() throws { + + // given + let input = "hello world" + let secret = "secretkey" + let expected = "ae6cd2605d622316564d1f76bfc0c04f89d9fafb14f45b3e18c2a3e28bdef29d" + + // when + let result = try HMAC.authenticate(for: input.array, using: secret.array).toHexString() + + // then + #expect(result == expected) + + } + +} \ No newline at end of file From 90517adfe0076818c5d59b9b9b5cc64ed7289888 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Stormacq?= Date: Sat, 16 Nov 2024 14:08:04 +0100 Subject: [PATCH 05/10] swift format --- Package.swift | 10 +- Plugins/AWSLambdaDeployer/Plugin.swift | 3 +- .../AWSLambdaPluginHelper/Extensions.swift | 16 +- .../Vendored/crypto/Array+Extensions.swift | 232 +++---- .../Vendored/crypto/Authenticator.swift | 6 +- .../Vendored/crypto/BatchedCollections.swift | 92 +-- .../Vendored/crypto/Bit.swift | 12 +- .../crypto/Collections+Extensions.swift | 70 +- .../Vendored/crypto/Digest.swift | 102 +-- .../Vendored/crypto/DigestType.swift | 4 +- .../Vendored/crypto/Generics.swift | 22 +- .../Vendored/crypto/HMAC.swift | 188 ++--- .../Vendored/crypto/Int+Extension.swift | 14 +- .../Vendored/crypto/NoPadding.swift | 16 +- .../Vendored/crypto/Padding.swift | 82 +-- .../Vendored/crypto/SHA1.swift | 260 +++---- .../Vendored/crypto/SHA2.swift | 654 +++++++++--------- .../Vendored/crypto/SHA3.swift | 489 +++++++------ .../Vendored/crypto/UInt16+Extension.swift | 32 +- .../Vendored/crypto/UInt32+Extension.swift | 38 +- .../Vendored/crypto/UInt64+Extension.swift | 46 +- .../Vendored/crypto/UInt8+Extension.swift | 70 +- .../Vendored/crypto/Updatable.swift | 156 +++-- .../Vendored/crypto/Utils.swift | 132 ++-- .../Vendored/crypto/ZeroPadding.swift | 34 +- .../Vendored/signer/AWSCredentials.swift | 10 +- .../Vendored/signer/AWSSigner.swift | 183 +++-- Sources/AWSLambdaPluginHelper/main.swift | 2 +- .../AWSSignerTests.swift | 74 +- .../CryptoTests.swift | 100 +-- 30 files changed, 1649 insertions(+), 1500 deletions(-) diff --git a/Package.swift b/Package.swift index f34b38eb..5f6f2d82 100644 --- a/Package.swift +++ b/Package.swift @@ -17,20 +17,20 @@ let package = Package( // this has all the main functionality for lambda and it does not link Foundation .library(name: "AWSLambdaRuntimeCore", targets: ["AWSLambdaRuntimeCore"]), - + // plugin to create a new Lambda function, based on a template .plugin(name: "AWSLambdaInitializer", targets: ["AWSLambdaInitializer"]), - + // plugin to package the lambda, creating an archive that can be uploaded to AWS // requires Linux or at least macOS v15 .plugin(name: "AWSLambdaPackager", targets: ["AWSLambdaPackager"]), - + // plugin to deploy a Lambda function .plugin(name: "AWSLambdaDeployer", targets: ["AWSLambdaDeployer"]), - + // an executable that implements the business logic for the plugins .executable(name: "AWSLambdaPluginHelper", targets: ["AWSLambdaPluginHelper"]), - + // for testing only .library(name: "AWSLambdaTesting", targets: ["AWSLambdaTesting"]), ], diff --git a/Plugins/AWSLambdaDeployer/Plugin.swift b/Plugins/AWSLambdaDeployer/Plugin.swift index 977b1a2d..08cdc269 100644 --- a/Plugins/AWSLambdaDeployer/Plugin.swift +++ b/Plugins/AWSLambdaDeployer/Plugin.swift @@ -19,7 +19,6 @@ import PackagePlugin @available(macOS 15.0, *) struct AWSLambdaDeployer: CommandPlugin { - func performCommand(context: PackagePlugin.PluginContext, arguments: [String]) async throws { let configuration = try Configuration(context: context, arguments: arguments) @@ -27,7 +26,7 @@ struct AWSLambdaDeployer: CommandPlugin { self.displayHelpMessage() return } - + let tool = try context.tool(named: "AWSLambdaDeployerHelper") try Utils.execute(executable: tool.url, arguments: [], logLevel: .debug) } diff --git a/Sources/AWSLambdaPluginHelper/Extensions.swift b/Sources/AWSLambdaPluginHelper/Extensions.swift index 671c4cec..67a8202b 100644 --- a/Sources/AWSLambdaPluginHelper/Extensions.swift +++ b/Sources/AWSLambdaPluginHelper/Extensions.swift @@ -26,7 +26,7 @@ import Foundation extension Data { var bytes: [UInt8] { - return [UInt8](self) + [UInt8](self) } } @@ -37,12 +37,20 @@ extension String { } extension HMAC { - public static func authenticate(for data: [UInt8], using key: [UInt8], variant: HMAC.Variant = .sha2(.sha256)) throws -> [UInt8] { + public static func authenticate( + for data: [UInt8], + using key: [UInt8], + variant: HMAC.Variant = .sha2(.sha256) + ) throws -> [UInt8] { let authenticator = HMAC(key: key, variant: variant) return try authenticator.authenticate(data) } - public static func authenticate(for data: Data, using key: [UInt8], variant: HMAC.Variant = .sha2(.sha256)) throws -> [UInt8] { + public static func authenticate( + for data: Data, + using key: [UInt8], + variant: HMAC.Variant = .sha2(.sha256) + ) throws -> [UInt8] { let authenticator = HMAC(key: key, variant: variant) return try authenticator.authenticate(data.bytes) } -} \ No newline at end of file +} diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/Array+Extensions.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Array+Extensions.swift index fd063167..00010018 100644 --- a/Sources/AWSLambdaPluginHelper/Vendored/crypto/Array+Extensions.swift +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Array+Extensions.swift @@ -14,142 +14,142 @@ // extension Array { - @inlinable - init(reserveCapacity: Int) { - self = Array() - self.reserveCapacity(reserveCapacity) - } - - @inlinable - var slice: ArraySlice { - self[self.startIndex ..< self.endIndex] - } - - @inlinable - subscript (safe index: Index) -> Element? { - return indices.contains(index) ? self[index] : nil - } + @inlinable + init(reserveCapacity: Int) { + self = [Element]() + self.reserveCapacity(reserveCapacity) + } + + @inlinable + var slice: ArraySlice { + self[self.startIndex.. Element? { + indices.contains(index) ? self[index] : nil + } } extension Array where Element == UInt8 { - public init(hex: String) { - self.init(reserveCapacity: hex.unicodeScalars.lazy.underestimatedCount) - var buffer: UInt8? - var skip = hex.hasPrefix("0x") ? 2 : 0 - for char in hex.unicodeScalars.lazy { - guard skip == 0 else { - skip -= 1 - continue - } - guard char.value >= 48 && char.value <= 102 else { - removeAll() - return - } - let v: UInt8 - let c: UInt8 = UInt8(char.value) - switch c { - case let c where c <= 57: - v = c - 48 - case let c where c >= 65 && c <= 70: - v = c - 55 - case let c where c >= 97: - v = c - 87 - default: - removeAll() - return - } - if let b = buffer { - append(b << 4 | v) - buffer = nil - } else { - buffer = v - } - } - if let b = buffer { - append(b) + public init(hex: String) { + self.init(reserveCapacity: hex.unicodeScalars.lazy.underestimatedCount) + var buffer: UInt8? + var skip = hex.hasPrefix("0x") ? 2 : 0 + for char in hex.unicodeScalars.lazy { + guard skip == 0 else { + skip -= 1 + continue + } + guard char.value >= 48 && char.value <= 102 else { + removeAll() + return + } + let v: UInt8 + let c: UInt8 = UInt8(char.value) + switch c { + case let c where c <= 57: + v = c - 48 + case let c where c >= 65 && c <= 70: + v = c - 55 + case let c where c >= 97: + v = c - 87 + default: + removeAll() + return + } + if let b = buffer { + append(b << 4 | v) + buffer = nil + } else { + buffer = v + } + } + if let b = buffer { + append(b) + } } - } - - public func toHexString() -> String { - `lazy`.reduce(into: "") { - var s = String($1, radix: 16) - if s.count == 1 { - s = "0" + s - } - $0 += s + + public func toHexString() -> String { + `lazy`.reduce(into: "") { + var s = String($1, radix: 16) + if s.count == 1 { + s = "0" + s + } + $0 += s + } } - } } extension Array where Element == UInt8 { - /// split in chunks with given chunk size - @available(*, deprecated) - public func chunks(size chunksize: Int) -> Array> { - var words = Array>() - words.reserveCapacity(count / chunksize) - for idx in stride(from: chunksize, through: count, by: chunksize) { - words.append(Array(self[idx - chunksize ..< idx])) // slow for large table - } - let remainder = suffix(count % chunksize) - if !remainder.isEmpty { - words.append(Array(remainder)) + /// split in chunks with given chunk size + @available(*, deprecated) + public func chunks(size chunksize: Int) -> [[Element]] { + var words = [[Element]]() + words.reserveCapacity(count / chunksize) + for idx in stride(from: chunksize, through: count, by: chunksize) { + words.append(Array(self[idx - chunksize.. [Element] { - // Digest.md5(self) - // } + // public func md5() -> [Element] { + // Digest.md5(self) + // } - // public func sha1() -> [Element] { - // Digest.sha1(self) - // } + // public func sha1() -> [Element] { + // Digest.sha1(self) + // } - // public func sha224() -> [Element] { - // Digest.sha224(self) - // } + // public func sha224() -> [Element] { + // Digest.sha224(self) + // } - public func sha256() -> [Element] { - Digest.sha256(self) - } + public func sha256() -> [Element] { + Digest.sha256(self) + } - public func sha384() -> [Element] { - Digest.sha384(self) - } + public func sha384() -> [Element] { + Digest.sha384(self) + } - public func sha512() -> [Element] { - Digest.sha512(self) - } + public func sha512() -> [Element] { + Digest.sha512(self) + } - public func sha2(_ variant: SHA2.Variant) -> [Element] { - Digest.sha2(self, variant: variant) - } + public func sha2(_ variant: SHA2.Variant) -> [Element] { + Digest.sha2(self, variant: variant) + } - public func sha3(_ variant: SHA3.Variant) -> [Element] { - Digest.sha3(self, variant: variant) - } + public func sha3(_ variant: SHA3.Variant) -> [Element] { + Digest.sha3(self, variant: variant) + } - // public func crc32(seed: UInt32? = nil, reflect: Bool = true) -> UInt32 { - // Checksum.crc32(self, seed: seed, reflect: reflect) - // } + // public func crc32(seed: UInt32? = nil, reflect: Bool = true) -> UInt32 { + // Checksum.crc32(self, seed: seed, reflect: reflect) + // } - // public func crc32c(seed: UInt32? = nil, reflect: Bool = true) -> UInt32 { - // Checksum.crc32c(self, seed: seed, reflect: reflect) - // } + // public func crc32c(seed: UInt32? = nil, reflect: Bool = true) -> UInt32 { + // Checksum.crc32c(self, seed: seed, reflect: reflect) + // } - // public func crc16(seed: UInt16? = nil) -> UInt16 { - // Checksum.crc16(self, seed: seed) - // } + // public func crc16(seed: UInt16? = nil) -> UInt16 { + // Checksum.crc16(self, seed: seed) + // } - // public func encrypt(cipher: Cipher) throws -> [Element] { - // try cipher.encrypt(self.slice) - // } + // public func encrypt(cipher: Cipher) throws -> [Element] { + // try cipher.encrypt(self.slice) + // } - // public func decrypt(cipher: Cipher) throws -> [Element] { - // try cipher.decrypt(self.slice) - // } + // public func decrypt(cipher: Cipher) throws -> [Element] { + // try cipher.decrypt(self.slice) + // } - public func authenticate(with authenticator: A) throws -> [Element] { - try authenticator.authenticate(self) - } -} \ No newline at end of file + public func authenticate(with authenticator: A) throws -> [Element] { + try authenticator.authenticate(self) + } +} diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/Authenticator.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Authenticator.swift index afabf51b..d45363e6 100644 --- a/Sources/AWSLambdaPluginHelper/Vendored/crypto/Authenticator.swift +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Authenticator.swift @@ -15,6 +15,6 @@ /// Message authentication code. public protocol Authenticator { - /// Calculate Message Authentication Code (MAC) for message. - func authenticate(_ bytes: Array) throws -> Array -} \ No newline at end of file + /// Calculate Message Authentication Code (MAC) for message. + func authenticate(_ bytes: [UInt8]) throws -> [UInt8] +} diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/BatchedCollections.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/BatchedCollections.swift index 43efb56a..1dd0fd0e 100644 --- a/Sources/AWSLambdaPluginHelper/Vendored/crypto/BatchedCollections.swift +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/BatchedCollections.swift @@ -15,67 +15,73 @@ @usableFromInline struct BatchedCollectionIndex { - let range: Range + let range: Range } extension BatchedCollectionIndex: Comparable { - @usableFromInline - static func == (lhs: BatchedCollectionIndex, rhs: BatchedCollectionIndex) -> Bool { - lhs.range.lowerBound == rhs.range.lowerBound - } + @usableFromInline + static func == ( + lhs: BatchedCollectionIndex, + rhs: BatchedCollectionIndex + ) -> Bool { + lhs.range.lowerBound == rhs.range.lowerBound + } - @usableFromInline - static func < (lhs: BatchedCollectionIndex, rhs: BatchedCollectionIndex) -> Bool { - lhs.range.lowerBound < rhs.range.lowerBound - } + @usableFromInline + static func < ( + lhs: BatchedCollectionIndex, + rhs: BatchedCollectionIndex + ) -> Bool { + lhs.range.lowerBound < rhs.range.lowerBound + } } protocol BatchedCollectionType: Collection { - associatedtype Base: Collection + associatedtype Base: Collection } @usableFromInline struct BatchedCollection: Collection { - let base: Base - let size: Int + let base: Base + let size: Int - @usableFromInline - init(base: Base, size: Int) { - self.base = base - self.size = size - } + @usableFromInline + init(base: Base, size: Int) { + self.base = base + self.size = size + } - @usableFromInline - typealias Index = BatchedCollectionIndex + @usableFromInline + typealias Index = BatchedCollectionIndex - private func nextBreak(after idx: Base.Index) -> Base.Index { - self.base.index(idx, offsetBy: self.size, limitedBy: self.base.endIndex) ?? self.base.endIndex - } + private func nextBreak(after idx: Base.Index) -> Base.Index { + self.base.index(idx, offsetBy: self.size, limitedBy: self.base.endIndex) ?? self.base.endIndex + } - @usableFromInline - var startIndex: Index { - Index(range: self.base.startIndex.. Index { - Index(range: idx.range.upperBound.. Index { + Index(range: idx.range.upperBound.. Base.SubSequence { - self.base[idx.range] - } + @usableFromInline + subscript(idx: Index) -> Base.SubSequence { + self.base[idx.range] + } } extension Collection { - @inlinable - func batched(by size: Int) -> BatchedCollection { - BatchedCollection(base: self, size: size) - } -} \ No newline at end of file + @inlinable + func batched(by size: Int) -> BatchedCollection { + BatchedCollection(base: self, size: size) + } +} diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/Bit.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Bit.swift index 3520b0dd..c2e29de6 100644 --- a/Sources/AWSLambdaPluginHelper/Vendored/crypto/Bit.swift +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Bit.swift @@ -14,13 +14,13 @@ // public enum Bit: Int { - case zero - case one + case zero + case one } extension Bit { - @inlinable - func inverted() -> Bit { - self == .zero ? .one : .zero - } + @inlinable + func inverted() -> Bit { + self == .zero ? .one : .zero + } } diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/Collections+Extensions.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Collections+Extensions.swift index 84d68d17..a205b2df 100644 --- a/Sources/AWSLambdaPluginHelper/Vendored/crypto/Collections+Extensions.swift +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Collections+Extensions.swift @@ -27,49 +27,49 @@ // - This notice may not be removed or altered from any source or binary distribution. // extension Collection where Self.Element == UInt8, Self.Index == Int { - // Big endian order - @inlinable - func toUInt32Array() -> Array { - guard !isEmpty else { - return [] - } + // Big endian order + @inlinable + func toUInt32Array() -> [UInt32] { + guard !isEmpty else { + return [] + } - let c = strideCount(from: startIndex, to: endIndex, by: 4) - return Array(unsafeUninitializedCapacity: c) { buf, count in - var counter = 0 - for idx in stride(from: startIndex, to: endIndex, by: 4) { - let val = UInt32(bytes: self, fromIndex: idx).bigEndian - buf[counter] = val - counter += 1 - } - count = counter - assert(counter == c) + let c = strideCount(from: startIndex, to: endIndex, by: 4) + return [UInt32](unsafeUninitializedCapacity: c) { buf, count in + var counter = 0 + for idx in stride(from: startIndex, to: endIndex, by: 4) { + let val = UInt32(bytes: self, fromIndex: idx).bigEndian + buf[counter] = val + counter += 1 + } + count = counter + assert(counter == c) + } } - } - // Big endian order - @inlinable - func toUInt64Array() -> Array { - guard !isEmpty else { - return [] - } + // Big endian order + @inlinable + func toUInt64Array() -> [UInt64] { + guard !isEmpty else { + return [] + } - let c = strideCount(from: startIndex, to: endIndex, by: 8) - return Array(unsafeUninitializedCapacity: c) { buf, count in - var counter = 0 - for idx in stride(from: startIndex, to: endIndex, by: 8) { - let val = UInt64(bytes: self, fromIndex: idx).bigEndian - buf[counter] = val - counter += 1 - } - count = counter - assert(counter == c) + let c = strideCount(from: startIndex, to: endIndex, by: 8) + return [UInt64](unsafeUninitializedCapacity: c) { buf, count in + var counter = 0 + for idx in stride(from: startIndex, to: endIndex, by: 8) { + let val = UInt64(bytes: self, fromIndex: idx).bigEndian + buf[counter] = val + counter += 1 + } + count = counter + assert(counter == c) + } } - } } @usableFromInline func strideCount(from: Int, to: Int, by: Int) -> Int { let count = to - from return count / by + (count % by > 0 ? 1 : 0) -} \ No newline at end of file +} diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/Digest.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Digest.swift index 491f6fdb..020df591 100644 --- a/Sources/AWSLambdaPluginHelper/Vendored/crypto/Digest.swift +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Digest.swift @@ -32,61 +32,61 @@ public typealias Hash = Digest /// Hash functions to calculate Digest. public struct Digest { - /// Calculate MD5 Digest - /// - parameter bytes: input message - /// - returns: Digest bytes - // public static func md5(_ bytes: Array) -> Array { - // MD5().calculate(for: bytes) - // } + /// Calculate MD5 Digest + /// - parameter bytes: input message + /// - returns: Digest bytes + // public static func md5(_ bytes: Array) -> Array { + // MD5().calculate(for: bytes) + // } - /// Calculate SHA1 Digest - /// - parameter bytes: input message - /// - returns: Digest bytes - public static func sha1(_ bytes: Array) -> Array { - SHA1().calculate(for: bytes) - } + /// Calculate SHA1 Digest + /// - parameter bytes: input message + /// - returns: Digest bytes + public static func sha1(_ bytes: [UInt8]) -> [UInt8] { + SHA1().calculate(for: bytes) + } - /// Calculate SHA2-224 Digest - /// - parameter bytes: input message - /// - returns: Digest bytes - public static func sha224(_ bytes: Array) -> Array { - self.sha2(bytes, variant: .sha224) - } + /// Calculate SHA2-224 Digest + /// - parameter bytes: input message + /// - returns: Digest bytes + public static func sha224(_ bytes: [UInt8]) -> [UInt8] { + self.sha2(bytes, variant: .sha224) + } - /// Calculate SHA2-256 Digest - /// - parameter bytes: input message - /// - returns: Digest bytes - public static func sha256(_ bytes: Array) -> Array { - self.sha2(bytes, variant: .sha256) - } + /// Calculate SHA2-256 Digest + /// - parameter bytes: input message + /// - returns: Digest bytes + public static func sha256(_ bytes: [UInt8]) -> [UInt8] { + self.sha2(bytes, variant: .sha256) + } - /// Calculate SHA2-384 Digest - /// - parameter bytes: input message - /// - returns: Digest bytes - public static func sha384(_ bytes: Array) -> Array { - self.sha2(bytes, variant: .sha384) - } + /// Calculate SHA2-384 Digest + /// - parameter bytes: input message + /// - returns: Digest bytes + public static func sha384(_ bytes: [UInt8]) -> [UInt8] { + self.sha2(bytes, variant: .sha384) + } - /// Calculate SHA2-512 Digest - /// - parameter bytes: input message - /// - returns: Digest bytes - public static func sha512(_ bytes: Array) -> Array { - self.sha2(bytes, variant: .sha512) - } + /// Calculate SHA2-512 Digest + /// - parameter bytes: input message + /// - returns: Digest bytes + public static func sha512(_ bytes: [UInt8]) -> [UInt8] { + self.sha2(bytes, variant: .sha512) + } - /// Calculate SHA2 Digest - /// - parameter bytes: input message - /// - parameter variant: SHA-2 variant - /// - returns: Digest bytes - public static func sha2(_ bytes: Array, variant: SHA2.Variant) -> Array { - SHA2(variant: variant).calculate(for: bytes) - } + /// Calculate SHA2 Digest + /// - parameter bytes: input message + /// - parameter variant: SHA-2 variant + /// - returns: Digest bytes + public static func sha2(_ bytes: [UInt8], variant: SHA2.Variant) -> [UInt8] { + SHA2(variant: variant).calculate(for: bytes) + } - /// Calculate SHA3 Digest - /// - parameter bytes: input message - /// - parameter variant: SHA-3 variant - /// - returns: Digest bytes - public static func sha3(_ bytes: Array, variant: SHA3.Variant) -> Array { - SHA3(variant: variant).calculate(for: bytes) - } -} \ No newline at end of file + /// Calculate SHA3 Digest + /// - parameter bytes: input message + /// - parameter variant: SHA-3 variant + /// - returns: Digest bytes + public static func sha3(_ bytes: [UInt8], variant: SHA3.Variant) -> [UInt8] { + SHA3(variant: variant).calculate(for: bytes) + } +} diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/DigestType.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/DigestType.swift index 13f27c1f..35138786 100644 --- a/Sources/AWSLambdaPluginHelper/Vendored/crypto/DigestType.swift +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/DigestType.swift @@ -28,5 +28,5 @@ // internal protocol DigestType { - func calculate(for bytes: Array) -> Array -} \ No newline at end of file + func calculate(for bytes: [UInt8]) -> [UInt8] +} diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/Generics.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Generics.swift index 2ba535d5..38c6e3b9 100644 --- a/Sources/AWSLambdaPluginHelper/Vendored/crypto/Generics.swift +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Generics.swift @@ -40,18 +40,18 @@ @_specialize(where T == UInt32) @_specialize(where T == UInt64) @inlinable -func arrayOfBytes(value: T, length totalBytes: Int = MemoryLayout.size) -> Array { - let valuePointer = UnsafeMutablePointer.allocate(capacity: 1) - valuePointer.pointee = value +func arrayOfBytes(value: T, length totalBytes: Int = MemoryLayout.size) -> [UInt8] { + let valuePointer = UnsafeMutablePointer.allocate(capacity: 1) + valuePointer.pointee = value - let bytesPointer = UnsafeMutablePointer(OpaquePointer(valuePointer)) - var bytes = Array(repeating: 0, count: totalBytes) - for j in 0...size, totalBytes) { - bytes[totalBytes - 1 - j] = (bytesPointer + j).pointee - } + let bytesPointer = UnsafeMutablePointer(OpaquePointer(valuePointer)) + var bytes = [UInt8](repeating: 0, count: totalBytes) + for j in 0...size, totalBytes) { + bytes[totalBytes - 1 - j] = (bytesPointer + j).pointee + } - valuePointer.deinitialize(count: 1) - valuePointer.deallocate() + valuePointer.deinitialize(count: 1) + valuePointer.deallocate() - return bytes + return bytes } diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/HMAC.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/HMAC.swift index b257eea4..b6fffc88 100644 --- a/Sources/AWSLambdaPluginHelper/Vendored/crypto/HMAC.swift +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/HMAC.swift @@ -28,112 +28,112 @@ // public final class HMAC: Authenticator { - public enum Error: Swift.Error { - case authenticateError - case invalidInput - } + public enum Error: Swift.Error { + case authenticateError + case invalidInput + } - public enum Variant { - // case md5 - case sha1 - case sha2(SHA2.Variant) - case sha3(SHA3.Variant) + public enum Variant { + // case md5 + case sha1 + case sha2(SHA2.Variant) + case sha3(SHA3.Variant) - @available(*, deprecated, message: "Use sha2(variant) instead.") - case sha256, sha384, sha512 + @available(*, deprecated, message: "Use sha2(variant) instead.") + case sha256, sha384, sha512 - var digestLength: Int { - switch self { - case .sha1: - return SHA1.digestLength - case .sha256: - return SHA2.Variant.sha256.digestLength - case .sha384: - return SHA2.Variant.sha384.digestLength - case .sha512: - return SHA2.Variant.sha512.digestLength - case .sha2(let variant): - return variant.digestLength - case .sha3(let variant): - return variant.digestLength - // case .md5: - // return MD5.digestLength - } - } + var digestLength: Int { + switch self { + case .sha1: + return SHA1.digestLength + case .sha256: + return SHA2.Variant.sha256.digestLength + case .sha384: + return SHA2.Variant.sha384.digestLength + case .sha512: + return SHA2.Variant.sha512.digestLength + case .sha2(let variant): + return variant.digestLength + case .sha3(let variant): + return variant.digestLength + // case .md5: + // return MD5.digestLength + } + } - func calculateHash(_ bytes: Array) -> Array { - switch self { - case .sha1: - return Digest.sha1(bytes) - case .sha256: - return Digest.sha256(bytes) - case .sha384: - return Digest.sha384(bytes) - case .sha512: - return Digest.sha512(bytes) - case .sha2(let variant): - return Digest.sha2(bytes, variant: variant) - case .sha3(let variant): - return Digest.sha3(bytes, variant: variant) - // case .md5: - // return Digest.md5(bytes) - } - } + func calculateHash(_ bytes: [UInt8]) -> [UInt8] { + switch self { + case .sha1: + return Digest.sha1(bytes) + case .sha256: + return Digest.sha256(bytes) + case .sha384: + return Digest.sha384(bytes) + case .sha512: + return Digest.sha512(bytes) + case .sha2(let variant): + return Digest.sha2(bytes, variant: variant) + case .sha3(let variant): + return Digest.sha3(bytes, variant: variant) + // case .md5: + // return Digest.md5(bytes) + } + } - func blockSize() -> Int { - switch self { - // case .md5: - // return MD5.blockSize - case .sha1: - return SHA1.blockSize - case .sha256: - return SHA2.Variant.sha256.blockSize - case .sha384: - return SHA2.Variant.sha384.blockSize - case .sha512: - return SHA2.Variant.sha512.blockSize - case .sha2(let variant): - return variant.blockSize - case .sha3(let variant): - return variant.blockSize - } + func blockSize() -> Int { + switch self { + // case .md5: + // return MD5.blockSize + case .sha1: + return SHA1.blockSize + case .sha256: + return SHA2.Variant.sha256.blockSize + case .sha384: + return SHA2.Variant.sha384.blockSize + case .sha512: + return SHA2.Variant.sha512.blockSize + case .sha2(let variant): + return variant.blockSize + case .sha3(let variant): + return variant.blockSize + } + } } - } - var key: Array - let variant: Variant + var key: [UInt8] + let variant: Variant - // public init(key: Array, variant: HMAC.Variant = .md5) { - public init(key: Array, variant: HMAC.Variant = .sha2(.sha256)) { - self.variant = variant - self.key = key + // public init(key: Array, variant: HMAC.Variant = .md5) { + public init(key: [UInt8], variant: HMAC.Variant = .sha2(.sha256)) { + self.variant = variant + self.key = key - if key.count > variant.blockSize() { - let hash = variant.calculateHash(key) - self.key = hash - } + if key.count > variant.blockSize() { + let hash = variant.calculateHash(key) + self.key = hash + } - if key.count < variant.blockSize() { - self.key = ZeroPadding().add(to: key, blockSize: variant.blockSize()) + if key.count < variant.blockSize() { + self.key = ZeroPadding().add(to: key, blockSize: variant.blockSize()) + } } - } - // MARK: Authenticator + // MARK: Authenticator - public func authenticate(_ bytes: Array) throws -> Array { - var opad = Array(repeating: 0x5c, count: variant.blockSize()) - for idx in self.key.indices { - opad[idx] = self.key[idx] ^ opad[idx] - } - var ipad = Array(repeating: 0x36, count: variant.blockSize()) - for idx in self.key.indices { - ipad[idx] = self.key[idx] ^ ipad[idx] - } + public func authenticate(_ bytes: [UInt8]) throws -> [UInt8] { + var opad = [UInt8](repeating: 0x5c, count: variant.blockSize()) + for idx in self.key.indices { + opad[idx] = self.key[idx] ^ opad[idx] + } + var ipad = [UInt8](repeating: 0x36, count: variant.blockSize()) + for idx in self.key.indices { + ipad[idx] = self.key[idx] ^ ipad[idx] + } - let ipadAndMessageHash = self.variant.calculateHash(ipad + bytes) - let result = self.variant.calculateHash(opad + ipadAndMessageHash) + let ipadAndMessageHash = self.variant.calculateHash(ipad + bytes) + let result = self.variant.calculateHash(opad + ipadAndMessageHash) - // return Array(result[0..<10]) // 80 bits - return result - } -} \ No newline at end of file + // return Array(result[0..<10]) // 80 bits + return result + } +} diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/Int+Extension.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Int+Extension.swift index b73a19cf..33dddb51 100644 --- a/Sources/AWSLambdaPluginHelper/Vendored/crypto/Int+Extension.swift +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Int+Extension.swift @@ -37,11 +37,11 @@ import ucrt #endif extension FixedWidthInteger { - @inlinable - func bytes(totalBytes: Int = MemoryLayout.size) -> Array { - arrayOfBytes(value: self.littleEndian, length: totalBytes) - // TODO: adjust bytes order - // var value = self.littleEndian - // return withUnsafeBytes(of: &value, Array.init).reversed() - } + @inlinable + func bytes(totalBytes: Int = MemoryLayout.size) -> [UInt8] { + arrayOfBytes(value: self.littleEndian, length: totalBytes) + // TODO: adjust bytes order + // var value = self.littleEndian + // return withUnsafeBytes(of: &value, Array.init).reversed() + } } diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/NoPadding.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/NoPadding.swift index fa7f007a..a6c51913 100644 --- a/Sources/AWSLambdaPluginHelper/Vendored/crypto/NoPadding.swift +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/NoPadding.swift @@ -28,14 +28,14 @@ // struct NoPadding: PaddingProtocol { - init() { - } + init() { + } - func add(to data: Array, blockSize _: Int) -> Array { - data - } + func add(to data: [UInt8], blockSize _: Int) -> [UInt8] { + data + } - func remove(from data: Array, blockSize _: Int?) -> Array { - data - } + func remove(from data: [UInt8], blockSize _: Int?) -> [UInt8] { + data + } } diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/Padding.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Padding.swift index 74e70e8d..be90ba37 100644 --- a/Sources/AWSLambdaPluginHelper/Vendored/crypto/Padding.swift +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Padding.swift @@ -28,52 +28,52 @@ // public protocol PaddingProtocol { - func add(to: Array, blockSize: Int) -> Array - func remove(from: Array, blockSize: Int?) -> Array + func add(to: [UInt8], blockSize: Int) -> [UInt8] + func remove(from: [UInt8], blockSize: Int?) -> [UInt8] } public enum Padding: PaddingProtocol { - case noPadding, zeroPadding //, pkcs7, pkcs5, eme_pkcs1v15, emsa_pkcs1v15, iso78164, iso10126 + case noPadding, zeroPadding //, pkcs7, pkcs5, eme_pkcs1v15, emsa_pkcs1v15, iso78164, iso10126 - public func add(to: Array, blockSize: Int) -> Array { - switch self { - case .noPadding: - return to // NoPadding().add(to: to, blockSize: blockSize) - case .zeroPadding: - return ZeroPadding().add(to: to, blockSize: blockSize) - // case .pkcs7: - // return PKCS7.Padding().add(to: to, blockSize: blockSize) - // case .pkcs5: - // return PKCS5.Padding().add(to: to, blockSize: blockSize) - // case .eme_pkcs1v15: - // return EMEPKCS1v15Padding().add(to: to, blockSize: blockSize) - // case .emsa_pkcs1v15: - // return EMSAPKCS1v15Padding().add(to: to, blockSize: blockSize) - // case .iso78164: - // return ISO78164Padding().add(to: to, blockSize: blockSize) - // case .iso10126: - // return ISO10126Padding().add(to: to, blockSize: blockSize) + public func add(to: [UInt8], blockSize: Int) -> [UInt8] { + switch self { + case .noPadding: + return to // NoPadding().add(to: to, blockSize: blockSize) + case .zeroPadding: + return ZeroPadding().add(to: to, blockSize: blockSize) + // case .pkcs7: + // return PKCS7.Padding().add(to: to, blockSize: blockSize) + // case .pkcs5: + // return PKCS5.Padding().add(to: to, blockSize: blockSize) + // case .eme_pkcs1v15: + // return EMEPKCS1v15Padding().add(to: to, blockSize: blockSize) + // case .emsa_pkcs1v15: + // return EMSAPKCS1v15Padding().add(to: to, blockSize: blockSize) + // case .iso78164: + // return ISO78164Padding().add(to: to, blockSize: blockSize) + // case .iso10126: + // return ISO10126Padding().add(to: to, blockSize: blockSize) + } } - } - public func remove(from: Array, blockSize: Int?) -> Array { - switch self { - case .noPadding: - return from //NoPadding().remove(from: from, blockSize: blockSize) - case .zeroPadding: - return ZeroPadding().remove(from: from, blockSize: blockSize) - // case .pkcs7: - // return PKCS7.Padding().remove(from: from, blockSize: blockSize) - // case .pkcs5: - // return PKCS5.Padding().remove(from: from, blockSize: blockSize) - // case .eme_pkcs1v15: - // return EMEPKCS1v15Padding().remove(from: from, blockSize: blockSize) - // case .emsa_pkcs1v15: - // return EMSAPKCS1v15Padding().remove(from: from, blockSize: blockSize) - // case .iso78164: - // return ISO78164Padding().remove(from: from, blockSize: blockSize) - // case .iso10126: - // return ISO10126Padding().remove(from: from, blockSize: blockSize) + public func remove(from: [UInt8], blockSize: Int?) -> [UInt8] { + switch self { + case .noPadding: + return from //NoPadding().remove(from: from, blockSize: blockSize) + case .zeroPadding: + return ZeroPadding().remove(from: from, blockSize: blockSize) + // case .pkcs7: + // return PKCS7.Padding().remove(from: from, blockSize: blockSize) + // case .pkcs5: + // return PKCS5.Padding().remove(from: from, blockSize: blockSize) + // case .eme_pkcs1v15: + // return EMEPKCS1v15Padding().remove(from: from, blockSize: blockSize) + // case .emsa_pkcs1v15: + // return EMSAPKCS1v15Padding().remove(from: from, blockSize: blockSize) + // case .iso78164: + // return ISO78164Padding().remove(from: from, blockSize: blockSize) + // case .iso10126: + // return ISO10126Padding().remove(from: from, blockSize: blockSize) + } } - } } diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/SHA1.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/SHA1.swift index 46f57746..b5fcd3fb 100644 --- a/Sources/AWSLambdaPluginHelper/Vendored/crypto/SHA1.swift +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/SHA1.swift @@ -29,148 +29,150 @@ public final class SHA1: DigestType { - @usableFromInline - static let digestLength: Int = 20 // 160 / 8 + @usableFromInline + static let digestLength: Int = 20 // 160 / 8 - @usableFromInline - static let blockSize: Int = 64 + @usableFromInline + static let blockSize: Int = 64 - @usableFromInline - static let hashInitialValue: ContiguousArray = [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0] + @usableFromInline + static let hashInitialValue: ContiguousArray = [ + 0x6745_2301, 0xefcd_ab89, 0x98ba_dcfe, 0x1032_5476, 0xc3d2_e1f0, + ] - @usableFromInline - var accumulated = Array() + @usableFromInline + var accumulated = [UInt8]() - @usableFromInline - var processedBytesTotalCount: Int = 0 + @usableFromInline + var processedBytesTotalCount: Int = 0 - @usableFromInline - var accumulatedHash: ContiguousArray = SHA1.hashInitialValue + @usableFromInline + var accumulatedHash: ContiguousArray = SHA1.hashInitialValue - public init() { - } - - @inlinable - public func calculate(for bytes: Array) -> Array { - do { - return try update(withBytes: bytes.slice, isLast: true) - } catch { - return [] - } - } - - public func callAsFunction(_ bytes: Array) -> Array { - calculate(for: bytes) - } - - @usableFromInline - func process(block chunk: ArraySlice, currentHash hh: inout ContiguousArray) { - // break chunk into sixteen 32-bit words M[j], 0 ≤ j ≤ 15, big-endian - // Extend the sixteen 32-bit words into eighty 32-bit words: - let M = UnsafeMutablePointer.allocate(capacity: 80) - M.initialize(repeating: 0, count: 80) - defer { - M.deinitialize(count: 80) - M.deallocate() + public init() { } - for x in 0..<80 { - switch x { - case 0...15: - let start = chunk.startIndex.advanced(by: x * 4) // * MemoryLayout.size - M[x] = UInt32(bytes: chunk, fromIndex: start) - default: - M[x] = rotateLeft(M[x - 3] ^ M[x - 8] ^ M[x - 14] ^ M[x - 16], by: 1) - } + @inlinable + public func calculate(for bytes: [UInt8]) -> [UInt8] { + do { + return try update(withBytes: bytes.slice, isLast: true) + } catch { + return [] + } } - var A = hh[0] - var B = hh[1] - var C = hh[2] - var D = hh[3] - var E = hh[4] - - // Main loop - for j in 0...79 { - var f: UInt32 = 0 - var k: UInt32 = 0 - - switch j { - case 0...19: - f = (B & C) | ((~B) & D) - k = 0x5a827999 - case 20...39: - f = B ^ C ^ D - k = 0x6ed9eba1 - case 40...59: - f = (B & C) | (B & D) | (C & D) - k = 0x8f1bbcdc - case 60...79: - f = B ^ C ^ D - k = 0xca62c1d6 - default: - break - } - - let temp = rotateLeft(A, by: 5) &+ f &+ E &+ M[j] &+ k - E = D - D = C - C = rotateLeft(B, by: 30) - B = A - A = temp + public func callAsFunction(_ bytes: [UInt8]) -> [UInt8] { + calculate(for: bytes) } - hh[0] = hh[0] &+ A - hh[1] = hh[1] &+ B - hh[2] = hh[2] &+ C - hh[3] = hh[3] &+ D - hh[4] = hh[4] &+ E - } + @usableFromInline + func process(block chunk: ArraySlice, currentHash hh: inout ContiguousArray) { + // break chunk into sixteen 32-bit words M[j], 0 ≤ j ≤ 15, big-endian + // Extend the sixteen 32-bit words into eighty 32-bit words: + let M = UnsafeMutablePointer.allocate(capacity: 80) + M.initialize(repeating: 0, count: 80) + defer { + M.deinitialize(count: 80) + M.deallocate() + } + + for x in 0..<80 { + switch x { + case 0...15: + let start = chunk.startIndex.advanced(by: x * 4) // * MemoryLayout.size + M[x] = UInt32(bytes: chunk, fromIndex: start) + default: + M[x] = rotateLeft(M[x - 3] ^ M[x - 8] ^ M[x - 14] ^ M[x - 16], by: 1) + } + } + + var A = hh[0] + var B = hh[1] + var C = hh[2] + var D = hh[3] + var E = hh[4] + + // Main loop + for j in 0...79 { + var f: UInt32 = 0 + var k: UInt32 = 0 + + switch j { + case 0...19: + f = (B & C) | ((~B) & D) + k = 0x5a82_7999 + case 20...39: + f = B ^ C ^ D + k = 0x6ed9_eba1 + case 40...59: + f = (B & C) | (B & D) | (C & D) + k = 0x8f1b_bcdc + case 60...79: + f = B ^ C ^ D + k = 0xca62_c1d6 + default: + break + } + + let temp = rotateLeft(A, by: 5) &+ f &+ E &+ M[j] &+ k + E = D + D = C + C = rotateLeft(B, by: 30) + B = A + A = temp + } + + hh[0] = hh[0] &+ A + hh[1] = hh[1] &+ B + hh[2] = hh[2] &+ C + hh[3] = hh[3] &+ D + hh[4] = hh[4] &+ E + } } extension SHA1: Updatable { - @discardableResult @inlinable - public func update(withBytes bytes: ArraySlice, isLast: Bool = false) throws -> Array { - self.accumulated += bytes - - if isLast { - let lengthInBits = (processedBytesTotalCount + self.accumulated.count) * 8 - let lengthBytes = lengthInBits.bytes(totalBytes: 64 / 8) // A 64-bit representation of b - - // Step 1. Append padding - bitPadding(to: &self.accumulated, blockSize: SHA1.blockSize, allowance: 64 / 8) - - // Step 2. Append Length a 64-bit representation of lengthInBits - self.accumulated += lengthBytes + @discardableResult @inlinable + public func update(withBytes bytes: ArraySlice, isLast: Bool = false) throws -> [UInt8] { + self.accumulated += bytes + + if isLast { + let lengthInBits = (processedBytesTotalCount + self.accumulated.count) * 8 + let lengthBytes = lengthInBits.bytes(totalBytes: 64 / 8) // A 64-bit representation of b + + // Step 1. Append padding + bitPadding(to: &self.accumulated, blockSize: SHA1.blockSize, allowance: 64 / 8) + + // Step 2. Append Length a 64-bit representation of lengthInBits + self.accumulated += lengthBytes + } + + var processedBytes = 0 + for chunk in self.accumulated.batched(by: SHA1.blockSize) { + if isLast || (self.accumulated.count - processedBytes) >= SHA1.blockSize { + self.process(block: chunk, currentHash: &self.accumulatedHash) + processedBytes += chunk.count + } + } + self.accumulated.removeFirst(processedBytes) + self.processedBytesTotalCount += processedBytes + + // output current hash + var result = [UInt8](repeating: 0, count: SHA1.digestLength) + var pos = 0 + for idx in 0..> 24) & 0xff) + result[pos + 1] = UInt8((h >> 16) & 0xff) + result[pos + 2] = UInt8((h >> 8) & 0xff) + result[pos + 3] = UInt8(h & 0xff) + pos += 4 + } + + // reset hash value for instance + if isLast { + self.accumulatedHash = SHA1.hashInitialValue + } + + return result } - - var processedBytes = 0 - for chunk in self.accumulated.batched(by: SHA1.blockSize) { - if isLast || (self.accumulated.count - processedBytes) >= SHA1.blockSize { - self.process(block: chunk, currentHash: &self.accumulatedHash) - processedBytes += chunk.count - } - } - self.accumulated.removeFirst(processedBytes) - self.processedBytesTotalCount += processedBytes - - // output current hash - var result = Array(repeating: 0, count: SHA1.digestLength) - var pos = 0 - for idx in 0..> 24) & 0xff) - result[pos + 1] = UInt8((h >> 16) & 0xff) - result[pos + 2] = UInt8((h >> 8) & 0xff) - result[pos + 3] = UInt8(h & 0xff) - pos += 4 - } - - // reset hash value for instance - if isLast { - self.accumulatedHash = SHA1.hashInitialValue - } - - return result - } -} \ No newline at end of file +} diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/SHA2.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/SHA2.swift index 9d4e4aca..4f54e39e 100644 --- a/Sources/AWSLambdaPluginHelper/Vendored/crypto/SHA2.swift +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/SHA2.swift @@ -31,356 +31,386 @@ // public final class SHA2: DigestType { - @usableFromInline - let variant: Variant + @usableFromInline + let variant: Variant + + @usableFromInline + let size: Int + + @usableFromInline + let blockSize: Int + + @usableFromInline + let digestLength: Int - @usableFromInline - let size: Int + private let k: [UInt64] - @usableFromInline - let blockSize: Int + @usableFromInline + var accumulated = [UInt8]() - @usableFromInline - let digestLength: Int + @usableFromInline + var processedBytesTotalCount: Int = 0 - private let k: Array + @usableFromInline + var accumulatedHash32 = [UInt32]() - @usableFromInline - var accumulated = Array() + @usableFromInline + var accumulatedHash64 = [UInt64]() - @usableFromInline - var processedBytesTotalCount: Int = 0 + @frozen + public enum Variant: RawRepresentable { + case sha224, sha256, sha384, sha512 - @usableFromInline - var accumulatedHash32 = Array() + public var digestLength: Int { + self.rawValue / 8 + } - @usableFromInline - var accumulatedHash64 = Array() + public var blockSize: Int { + switch self { + case .sha224, .sha256: + return 64 + case .sha384, .sha512: + return 128 + } + } - @frozen - public enum Variant: RawRepresentable { - case sha224, sha256, sha384, sha512 + public typealias RawValue = Int + public var rawValue: RawValue { + switch self { + case .sha224: + return 224 + case .sha256: + return 256 + case .sha384: + return 384 + case .sha512: + return 512 + } + } - public var digestLength: Int { - self.rawValue / 8 + public init?(rawValue: RawValue) { + switch rawValue { + case 224: + self = .sha224 + case 256: + self = .sha256 + case 384: + self = .sha384 + case 512: + self = .sha512 + default: + return nil + } + } + + @usableFromInline + var h: [UInt64] { + switch self { + case .sha224: + return [ + 0xc105_9ed8, 0x367c_d507, 0x3070_dd17, 0xf70e_5939, 0xffc0_0b31, 0x6858_1511, 0x64f9_8fa7, + 0xbefa_4fa4, + ] + case .sha256: + return [ + 0x6a09_e667, 0xbb67_ae85, 0x3c6e_f372, 0xa54f_f53a, 0x510e_527f, 0x9b05_688c, 0x1f83_d9ab, + 0x5be0_cd19, + ] + case .sha384: + return [ + 0xcbbb_9d5d_c105_9ed8, 0x629a_292a_367c_d507, 0x9159_015a_3070_dd17, 0x152f_ecd8_f70e_5939, + 0x6733_2667_ffc0_0b31, 0x8eb4_4a87_6858_1511, 0xdb0c_2e0d_64f9_8fa7, 0x47b5_481d_befa_4fa4, + ] + case .sha512: + return [ + 0x6a09_e667_f3bc_c908, 0xbb67_ae85_84ca_a73b, 0x3c6e_f372_fe94_f82b, 0xa54f_f53a_5f1d_36f1, + 0x510e_527f_ade6_82d1, 0x9b05_688c_2b3e_6c1f, 0x1f83_d9ab_fb41_bd6b, 0x5be0_cd19_137e_2179, + ] + } + } + + @usableFromInline + var finalLength: Int { + switch self { + case .sha224: + return 7 + case .sha384: + return 6 + default: + return Int.max + } + } } - public var blockSize: Int { - switch self { + public init(variant: SHA2.Variant) { + self.variant = variant + switch self.variant { case .sha224, .sha256: - return 64 + self.accumulatedHash32 = variant.h.map { UInt32($0) } // FIXME: UInt64 for process64 + self.blockSize = variant.blockSize + self.size = variant.rawValue + self.digestLength = variant.digestLength + self.k = [ + 0x428a_2f98, 0x7137_4491, 0xb5c0_fbcf, 0xe9b5_dba5, 0x3956_c25b, 0x59f1_11f1, 0x923f_82a4, 0xab1c_5ed5, + 0xd807_aa98, 0x1283_5b01, 0x2431_85be, 0x550c_7dc3, 0x72be_5d74, 0x80de_b1fe, 0x9bdc_06a7, 0xc19b_f174, + 0xe49b_69c1, 0xefbe_4786, 0x0fc1_9dc6, 0x240c_a1cc, 0x2de9_2c6f, 0x4a74_84aa, 0x5cb0_a9dc, 0x76f9_88da, + 0x983e_5152, 0xa831_c66d, 0xb003_27c8, 0xbf59_7fc7, 0xc6e0_0bf3, 0xd5a7_9147, 0x06ca_6351, 0x1429_2967, + 0x27b7_0a85, 0x2e1b_2138, 0x4d2c_6dfc, 0x5338_0d13, 0x650a_7354, 0x766a_0abb, 0x81c2_c92e, 0x9272_2c85, + 0xa2bf_e8a1, 0xa81a_664b, 0xc24b_8b70, 0xc76c_51a3, 0xd192_e819, 0xd699_0624, 0xf40e_3585, 0x106a_a070, + 0x19a4_c116, 0x1e37_6c08, 0x2748_774c, 0x34b0_bcb5, 0x391c_0cb3, 0x4ed8_aa4a, 0x5b9c_ca4f, 0x682e_6ff3, + 0x748f_82ee, 0x78a5_636f, 0x84c8_7814, 0x8cc7_0208, 0x90be_fffa, 0xa450_6ceb, 0xbef9_a3f7, 0xc671_78f2, + ] case .sha384, .sha512: - return 128 - } + self.accumulatedHash64 = variant.h + self.blockSize = variant.blockSize + self.size = variant.rawValue + self.digestLength = variant.digestLength + self.k = [ + 0x428a_2f98_d728_ae22, 0x7137_4491_23ef_65cd, 0xb5c0_fbcf_ec4d_3b2f, 0xe9b5_dba5_8189_dbbc, + 0x3956_c25b_f348_b538, + 0x59f1_11f1_b605_d019, 0x923f_82a4_af19_4f9b, 0xab1c_5ed5_da6d_8118, 0xd807_aa98_a303_0242, + 0x1283_5b01_4570_6fbe, + 0x2431_85be_4ee4_b28c, 0x550c_7dc3_d5ff_b4e2, 0x72be_5d74_f27b_896f, 0x80de_b1fe_3b16_96b1, + 0x9bdc_06a7_25c7_1235, + 0xc19b_f174_cf69_2694, 0xe49b_69c1_9ef1_4ad2, 0xefbe_4786_384f_25e3, 0x0fc1_9dc6_8b8c_d5b5, + 0x240c_a1cc_77ac_9c65, + 0x2de9_2c6f_592b_0275, 0x4a74_84aa_6ea6_e483, 0x5cb0_a9dc_bd41_fbd4, 0x76f9_88da_8311_53b5, + 0x983e_5152_ee66_dfab, + 0xa831_c66d_2db4_3210, 0xb003_27c8_98fb_213f, 0xbf59_7fc7_beef_0ee4, 0xc6e0_0bf3_3da8_8fc2, + 0xd5a7_9147_930a_a725, + 0x06ca_6351_e003_826f, 0x1429_2967_0a0e_6e70, 0x27b7_0a85_46d2_2ffc, 0x2e1b_2138_5c26_c926, + 0x4d2c_6dfc_5ac4_2aed, + 0x5338_0d13_9d95_b3df, 0x650a_7354_8baf_63de, 0x766a_0abb_3c77_b2a8, 0x81c2_c92e_47ed_aee6, + 0x9272_2c85_1482_353b, + 0xa2bf_e8a1_4cf1_0364, 0xa81a_664b_bc42_3001, 0xc24b_8b70_d0f8_9791, 0xc76c_51a3_0654_be30, + 0xd192_e819_d6ef_5218, + 0xd699_0624_5565_a910, 0xf40e_3585_5771_202a, 0x106a_a070_32bb_d1b8, 0x19a4_c116_b8d2_d0c8, + 0x1e37_6c08_5141_ab53, + 0x2748_774c_df8e_eb99, 0x34b0_bcb5_e19b_48a8, 0x391c_0cb3_c5c9_5a63, 0x4ed8_aa4a_e341_8acb, + 0x5b9c_ca4f_7763_e373, + 0x682e_6ff3_d6b2_b8a3, 0x748f_82ee_5def_b2fc, 0x78a5_636f_4317_2f60, 0x84c8_7814_a1f0_ab72, + 0x8cc7_0208_1a64_39ec, + 0x90be_fffa_2363_1e28, 0xa450_6ceb_de82_bde9, 0xbef9_a3f7_b2c6_7915, 0xc671_78f2_e372_532b, + 0xca27_3ece_ea26_619c, + 0xd186_b8c7_21c0_c207, 0xeada_7dd6_cde0_eb1e, 0xf57d_4f7f_ee6e_d178, 0x06f0_67aa_7217_6fba, + 0x0a63_7dc5_a2c8_98a6, + 0x113f_9804_bef9_0dae, 0x1b71_0b35_131c_471b, 0x28db_77f5_2304_7d84, 0x32ca_ab7b_40c7_2493, + 0x3c9e_be0a_15c9_bebc, + 0x431d_67c4_9c10_0d4c, 0x4cc5_d4be_cb3e_42b6, 0x597f_299c_fc65_7e2a, 0x5fcb_6fab_3ad6_faec, + 0x6c44_198c_4a47_5817, + ] + } } - public typealias RawValue = Int - public var rawValue: RawValue { - switch self { - case .sha224: - return 224 - case .sha256: - return 256 - case .sha384: - return 384 - case .sha512: - return 512 - } + @inlinable + public func calculate(for bytes: [UInt8]) -> [UInt8] { + do { + return try update(withBytes: bytes.slice, isLast: true) + } catch { + return [] + } } - public init?(rawValue: RawValue) { - switch rawValue { - case 224: - self = .sha224 - case 256: - self = .sha256 - case 384: - self = .sha384 - case 512: - self = .sha512 - default: - return nil - } + public func callAsFunction(_ bytes: [UInt8]) -> [UInt8] { + calculate(for: bytes) } @usableFromInline - var h: Array { - switch self { - case .sha224: - return [0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939, 0xffc00b31, 0x68581511, 0x64f98fa7, 0xbefa4fa4] - case .sha256: - return [0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19] - case .sha384: - return [0xcbbb9d5dc1059ed8, 0x629a292a367cd507, 0x9159015a3070dd17, 0x152fecd8f70e5939, 0x67332667ffc00b31, 0x8eb44a8768581511, 0xdb0c2e0d64f98fa7, 0x47b5481dbefa4fa4] - case .sha512: - return [0x6a09e667f3bcc908, 0xbb67ae8584caa73b, 0x3c6ef372fe94f82b, 0xa54ff53a5f1d36f1, 0x510e527fade682d1, 0x9b05688c2b3e6c1f, 0x1f83d9abfb41bd6b, 0x5be0cd19137e2179] - } - } + func process64(block chunk: ArraySlice, currentHash hh: inout [UInt64]) { + // break chunk into sixteen 64-bit words M[j], 0 ≤ j ≤ 15, big-endian + // Extend the sixteen 64-bit words into eighty 64-bit words: + let M = UnsafeMutablePointer.allocate(capacity: self.k.count) + M.initialize(repeating: 0, count: self.k.count) + defer { + M.deinitialize(count: self.k.count) + M.deallocate() + } + for x in 0...size + M[x] = UInt64(bytes: chunk, fromIndex: start) + default: + let s0 = rotateRight(M[x - 15], by: 1) ^ rotateRight(M[x - 15], by: 8) ^ (M[x - 15] >> 7) + let s1 = rotateRight(M[x - 2], by: 19) ^ rotateRight(M[x - 2], by: 61) ^ (M[x - 2] >> 6) + M[x] = M[x - 16] &+ s0 &+ M[x - 7] &+ s1 + } + } - @usableFromInline - var finalLength: Int { - switch self { - case .sha224: - return 7 - case .sha384: - return 6 - default: - return Int.max - } - } - } - - public init(variant: SHA2.Variant) { - self.variant = variant - switch self.variant { - case .sha224, .sha256: - self.accumulatedHash32 = variant.h.map { UInt32($0) } // FIXME: UInt64 for process64 - self.blockSize = variant.blockSize - self.size = variant.rawValue - self.digestLength = variant.digestLength - self.k = [ - 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, - 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, - 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, - 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, - 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, - 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, - 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, - 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 - ] - case .sha384, .sha512: - self.accumulatedHash64 = variant.h - self.blockSize = variant.blockSize - self.size = variant.rawValue - self.digestLength = variant.digestLength - self.k = [ - 0x428a2f98d728ae22, 0x7137449123ef65cd, 0xb5c0fbcfec4d3b2f, 0xe9b5dba58189dbbc, 0x3956c25bf348b538, - 0x59f111f1b605d019, 0x923f82a4af194f9b, 0xab1c5ed5da6d8118, 0xd807aa98a3030242, 0x12835b0145706fbe, - 0x243185be4ee4b28c, 0x550c7dc3d5ffb4e2, 0x72be5d74f27b896f, 0x80deb1fe3b1696b1, 0x9bdc06a725c71235, - 0xc19bf174cf692694, 0xe49b69c19ef14ad2, 0xefbe4786384f25e3, 0x0fc19dc68b8cd5b5, 0x240ca1cc77ac9c65, - 0x2de92c6f592b0275, 0x4a7484aa6ea6e483, 0x5cb0a9dcbd41fbd4, 0x76f988da831153b5, 0x983e5152ee66dfab, - 0xa831c66d2db43210, 0xb00327c898fb213f, 0xbf597fc7beef0ee4, 0xc6e00bf33da88fc2, 0xd5a79147930aa725, - 0x06ca6351e003826f, 0x142929670a0e6e70, 0x27b70a8546d22ffc, 0x2e1b21385c26c926, 0x4d2c6dfc5ac42aed, - 0x53380d139d95b3df, 0x650a73548baf63de, 0x766a0abb3c77b2a8, 0x81c2c92e47edaee6, 0x92722c851482353b, - 0xa2bfe8a14cf10364, 0xa81a664bbc423001, 0xc24b8b70d0f89791, 0xc76c51a30654be30, 0xd192e819d6ef5218, - 0xd69906245565a910, 0xf40e35855771202a, 0x106aa07032bbd1b8, 0x19a4c116b8d2d0c8, 0x1e376c085141ab53, - 0x2748774cdf8eeb99, 0x34b0bcb5e19b48a8, 0x391c0cb3c5c95a63, 0x4ed8aa4ae3418acb, 0x5b9cca4f7763e373, - 0x682e6ff3d6b2b8a3, 0x748f82ee5defb2fc, 0x78a5636f43172f60, 0x84c87814a1f0ab72, 0x8cc702081a6439ec, - 0x90befffa23631e28, 0xa4506cebde82bde9, 0xbef9a3f7b2c67915, 0xc67178f2e372532b, 0xca273eceea26619c, - 0xd186b8c721c0c207, 0xeada7dd6cde0eb1e, 0xf57d4f7fee6ed178, 0x06f067aa72176fba, 0x0a637dc5a2c898a6, - 0x113f9804bef90dae, 0x1b710b35131c471b, 0x28db77f523047d84, 0x32caab7b40c72493, 0x3c9ebe0a15c9bebc, - 0x431d67c49c100d4c, 0x4cc5d4becb3e42b6, 0x597f299cfc657e2a, 0x5fcb6fab3ad6faec, 0x6c44198c4a475817 - ] - } - } - - @inlinable - public func calculate(for bytes: Array) -> Array { - do { - return try update(withBytes: bytes.slice, isLast: true) - } catch { - return [] - } - } - - public func callAsFunction(_ bytes: Array) -> Array { - calculate(for: bytes) - } - - @usableFromInline - func process64(block chunk: ArraySlice, currentHash hh: inout Array) { - // break chunk into sixteen 64-bit words M[j], 0 ≤ j ≤ 15, big-endian - // Extend the sixteen 64-bit words into eighty 64-bit words: - let M = UnsafeMutablePointer.allocate(capacity: self.k.count) - M.initialize(repeating: 0, count: self.k.count) - defer { - M.deinitialize(count: self.k.count) - M.deallocate() - } - for x in 0...size - M[x] = UInt64(bytes: chunk, fromIndex: start) - default: - let s0 = rotateRight(M[x - 15], by: 1) ^ rotateRight(M[x - 15], by: 8) ^ (M[x - 15] >> 7) - let s1 = rotateRight(M[x - 2], by: 19) ^ rotateRight(M[x - 2], by: 61) ^ (M[x - 2] >> 6) - M[x] = M[x - 16] &+ s0 &+ M[x - 7] &+ s1 - } - } + var A = hh[0] + var B = hh[1] + var C = hh[2] + var D = hh[3] + var E = hh[4] + var F = hh[5] + var G = hh[6] + var H = hh[7] + + // Main loop + for j in 0.., currentHash hh: inout Array) { - // break chunk into sixteen 32-bit words M[j], 0 ≤ j ≤ 15, big-endian - // Extend the sixteen 32-bit words into sixty-four 32-bit words: - let M = UnsafeMutablePointer.allocate(capacity: self.k.count) - M.initialize(repeating: 0, count: self.k.count) - defer { - M.deinitialize(count: self.k.count) - M.deallocate() - } + // mutating currentHash in place is way faster than returning new result + @usableFromInline + func process32(block chunk: ArraySlice, currentHash hh: inout [UInt32]) { + // break chunk into sixteen 32-bit words M[j], 0 ≤ j ≤ 15, big-endian + // Extend the sixteen 32-bit words into sixty-four 32-bit words: + let M = UnsafeMutablePointer.allocate(capacity: self.k.count) + M.initialize(repeating: 0, count: self.k.count) + defer { + M.deinitialize(count: self.k.count) + M.deallocate() + } - for x in 0...size - M[x] = UInt32(bytes: chunk, fromIndex: start) - default: - let s0 = rotateRight(M[x - 15], by: 7) ^ rotateRight(M[x - 15], by: 18) ^ (M[x - 15] >> 3) - let s1 = rotateRight(M[x - 2], by: 17) ^ rotateRight(M[x - 2], by: 19) ^ (M[x - 2] >> 10) - M[x] = M[x - 16] &+ s0 &+ M[x - 7] &+ s1 - } - } + for x in 0...size + M[x] = UInt32(bytes: chunk, fromIndex: start) + default: + let s0 = rotateRight(M[x - 15], by: 7) ^ rotateRight(M[x - 15], by: 18) ^ (M[x - 15] >> 3) + let s1 = rotateRight(M[x - 2], by: 17) ^ rotateRight(M[x - 2], by: 19) ^ (M[x - 2] >> 10) + M[x] = M[x - 16] &+ s0 &+ M[x - 7] &+ s1 + } + } - var A = hh[0] - var B = hh[1] - var C = hh[2] - var D = hh[3] - var E = hh[4] - var F = hh[5] - var G = hh[6] - var H = hh[7] - - // Main loop - for j in 0.., isLast: Bool = false) throws -> Array { - self.accumulated += bytes + @inlinable + public func update(withBytes bytes: ArraySlice, isLast: Bool = false) throws -> [UInt8] { + self.accumulated += bytes - if isLast { - let lengthInBits = (processedBytesTotalCount + self.accumulated.count) * 8 - let lengthBytes = lengthInBits.bytes(totalBytes: self.blockSize / 8) // A 64-bit/128-bit representation of b. blockSize fit by accident. + if isLast { + let lengthInBits = (processedBytesTotalCount + self.accumulated.count) * 8 + let lengthBytes = lengthInBits.bytes(totalBytes: self.blockSize / 8) + // A 64-bit/128-bit representation of b. blockSize fit by accident. - // Step 1. Append padding - bitPadding(to: &self.accumulated, blockSize: self.blockSize, allowance: self.blockSize / 8) + // Step 1. Append padding + bitPadding(to: &self.accumulated, blockSize: self.blockSize, allowance: self.blockSize / 8) - // Step 2. Append Length a 64-bit representation of lengthInBits - self.accumulated += lengthBytes - } - - var processedBytes = 0 - for chunk in self.accumulated.batched(by: self.blockSize) { - if isLast || (self.accumulated.count - processedBytes) >= self.blockSize { - switch self.variant { - case .sha224, .sha256: - self.process32(block: chunk, currentHash: &self.accumulatedHash32) - case .sha384, .sha512: - self.process64(block: chunk, currentHash: &self.accumulatedHash64) - } - processedBytes += chunk.count - } - } - self.accumulated.removeFirst(processedBytes) - self.processedBytesTotalCount += processedBytes - - // output current hash - var result = Array(repeating: 0, count: variant.digestLength) - switch self.variant { - case .sha224, .sha256: - var pos = 0 - for idx in 0..> 24) & 0xff) - result[pos + 1] = UInt8((h >> 16) & 0xff) - result[pos + 2] = UInt8((h >> 8) & 0xff) - result[pos + 3] = UInt8(h & 0xff) - pos += 4 + // Step 2. Append Length a 64-bit representation of lengthInBits + self.accumulated += lengthBytes } - case .sha384, .sha512: - var pos = 0 - for idx in 0..> 56) & 0xff) - result[pos + 1] = UInt8((h >> 48) & 0xff) - result[pos + 2] = UInt8((h >> 40) & 0xff) - result[pos + 3] = UInt8((h >> 32) & 0xff) - result[pos + 4] = UInt8((h >> 24) & 0xff) - result[pos + 5] = UInt8((h >> 16) & 0xff) - result[pos + 6] = UInt8((h >> 8) & 0xff) - result[pos + 7] = UInt8(h & 0xff) - pos += 8 + + var processedBytes = 0 + for chunk in self.accumulated.batched(by: self.blockSize) { + if isLast || (self.accumulated.count - processedBytes) >= self.blockSize { + switch self.variant { + case .sha224, .sha256: + self.process32(block: chunk, currentHash: &self.accumulatedHash32) + case .sha384, .sha512: + self.process64(block: chunk, currentHash: &self.accumulatedHash64) + } + processedBytes += chunk.count + } } - } + self.accumulated.removeFirst(processedBytes) + self.processedBytesTotalCount += processedBytes - // reset hash value for instance - if isLast { - switch self.variant { + // output current hash + var result = [UInt8](repeating: 0, count: variant.digestLength) + switch self.variant { case .sha224, .sha256: - self.accumulatedHash32 = self.variant.h.lazy.map { UInt32($0) } // FIXME: UInt64 for process64 + var pos = 0 + for idx in 0..> 24) & 0xff) + result[pos + 1] = UInt8((h >> 16) & 0xff) + result[pos + 2] = UInt8((h >> 8) & 0xff) + result[pos + 3] = UInt8(h & 0xff) + pos += 4 + } case .sha384, .sha512: - self.accumulatedHash64 = self.variant.h - } - } + var pos = 0 + for idx in 0..> 56) & 0xff) + result[pos + 1] = UInt8((h >> 48) & 0xff) + result[pos + 2] = UInt8((h >> 40) & 0xff) + result[pos + 3] = UInt8((h >> 32) & 0xff) + result[pos + 4] = UInt8((h >> 24) & 0xff) + result[pos + 5] = UInt8((h >> 16) & 0xff) + result[pos + 6] = UInt8((h >> 8) & 0xff) + result[pos + 7] = UInt8(h & 0xff) + pos += 8 + } + } + + // reset hash value for instance + if isLast { + switch self.variant { + case .sha224, .sha256: + // FIXME: UInt64 for process64 + self.accumulatedHash32 = self.variant.h.lazy.map { UInt32($0) } + case .sha384, .sha512: + self.accumulatedHash64 = self.variant.h + } + } - return result - } -} \ No newline at end of file + return result + } +} diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/SHA3.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/SHA3.swift index 24027062..6fceac6d 100644 --- a/Sources/AWSLambdaPluginHelper/Vendored/crypto/SHA3.swift +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/SHA3.swift @@ -40,278 +40,277 @@ import ucrt #endif public final class SHA3: DigestType { - let round_constants: Array = [ - 0x0000000000000001, 0x0000000000008082, 0x800000000000808a, 0x8000000080008000, - 0x000000000000808b, 0x0000000080000001, 0x8000000080008081, 0x8000000000008009, - 0x000000000000008a, 0x0000000000000088, 0x0000000080008009, 0x000000008000000a, - 0x000000008000808b, 0x800000000000008b, 0x8000000000008089, 0x8000000000008003, - 0x8000000000008002, 0x8000000000000080, 0x000000000000800a, 0x800000008000000a, - 0x8000000080008081, 0x8000000000008080, 0x0000000080000001, 0x8000000080008008 - ] - - public let blockSize: Int - public let digestLength: Int - public let markByte: UInt8 - - @usableFromInline - var accumulated = Array() - + let round_constants: [UInt64] = [ + 0x0000_0000_0000_0001, 0x0000_0000_0000_8082, 0x8000_0000_0000_808a, 0x8000_0000_8000_8000, + 0x0000_0000_0000_808b, 0x0000_0000_8000_0001, 0x8000_0000_8000_8081, 0x8000_0000_0000_8009, + 0x0000_0000_0000_008a, 0x0000_0000_0000_0088, 0x0000_0000_8000_8009, 0x0000_0000_8000_000a, + 0x0000_0000_8000_808b, 0x8000_0000_0000_008b, 0x8000_0000_0000_8089, 0x8000_0000_0000_8003, + 0x8000_0000_0000_8002, 0x8000_0000_0000_0080, 0x0000_0000_0000_800a, 0x8000_0000_8000_000a, + 0x8000_0000_8000_8081, 0x8000_0000_0000_8080, 0x0000_0000_8000_0001, 0x8000_0000_8000_8008, + ] + + public let blockSize: Int + public let digestLength: Int + public let markByte: UInt8 + + @usableFromInline + var accumulated = [UInt8]() + + @usableFromInline + var accumulatedHash: [UInt64] + + public enum Variant { + case sha224, sha256, sha384, sha512, keccak224, keccak256, keccak384, keccak512 + + var digestLength: Int { + 100 - (self.blockSize / 2) + } - @usableFromInline - var accumulatedHash: Array + var blockSize: Int { + (1600 - self.outputLength * 2) / 8 + } - public enum Variant { - case sha224, sha256, sha384, sha512, keccak224, keccak256, keccak384, keccak512 + var markByte: UInt8 { + switch self { + case .sha224, .sha256, .sha384, .sha512: + return 0x06 // 0x1F for SHAKE + case .keccak224, .keccak256, .keccak384, .keccak512: + return 0x01 + } + } - var digestLength: Int { - 100 - (self.blockSize / 2) + public var outputLength: Int { + switch self { + case .sha224, .keccak224: + return 224 + case .sha256, .keccak256: + return 256 + case .sha384, .keccak384: + return 384 + case .sha512, .keccak512: + return 512 + } + } } - var blockSize: Int { - (1600 - self.outputLength * 2) / 8 + public init(variant: SHA3.Variant) { + self.blockSize = variant.blockSize + self.digestLength = variant.digestLength + self.markByte = variant.markByte + self.accumulatedHash = [UInt64](repeating: 0, count: self.digestLength) } - var markByte: UInt8 { - switch self { - case .sha224, .sha256, .sha384, .sha512: - return 0x06 // 0x1F for SHAKE - case .keccak224, .keccak256, .keccak384, .keccak512: - return 0x01 - } + @inlinable + public func calculate(for bytes: [UInt8]) -> [UInt8] { + do { + return try update(withBytes: bytes.slice, isLast: true) + } catch { + return [] + } } - public var outputLength: Int { - switch self { - case .sha224, .keccak224: - return 224 - case .sha256, .keccak256: - return 256 - case .sha384, .keccak384: - return 384 - case .sha512, .keccak512: - return 512 - } + public func callAsFunction(_ bytes: [UInt8]) -> [UInt8] { + calculate(for: bytes) } - } - - public init(variant: SHA3.Variant) { - self.blockSize = variant.blockSize - self.digestLength = variant.digestLength - self.markByte = variant.markByte - self.accumulatedHash = Array(repeating: 0, count: self.digestLength) - } - - @inlinable - public func calculate(for bytes: Array) -> Array { - do { - return try update(withBytes: bytes.slice, isLast: true) - } catch { - return [] - } - } - - public func callAsFunction(_ bytes: Array) -> Array { - calculate(for: bytes) - } - - /// 1. For all pairs (x,z) such that 0≤x<5 and 0≤z) { - let c = UnsafeMutablePointer.allocate(capacity: 5) - c.initialize(repeating: 0, count: 5) - defer { - c.deinitialize(count: 5) - c.deallocate() - } - let d = UnsafeMutablePointer.allocate(capacity: 5) - d.initialize(repeating: 0, count: 5) - defer { - d.deinitialize(count: 5) - d.deallocate() + + /// 1. For all pairs (x,z) such that 0≤x<5 and 0≤z.allocate(capacity: 5) + c.initialize(repeating: 0, count: 5) + defer { + c.deinitialize(count: 5) + c.deallocate() + } + let d = UnsafeMutablePointer.allocate(capacity: 5) + d.initialize(repeating: 0, count: 5) + defer { + d.deinitialize(count: 5) + d.deallocate() + } + + for i in 0..<5 { + c[i] = a[i] ^ a[i &+ 5] ^ a[i &+ 10] ^ a[i &+ 15] ^ a[i &+ 20] + } + + d[0] = rotateLeft(c[1], by: 1) ^ c[4] + d[1] = rotateLeft(c[2], by: 1) ^ c[0] + d[2] = rotateLeft(c[3], by: 1) ^ c[1] + d[3] = rotateLeft(c[4], by: 1) ^ c[2] + d[4] = rotateLeft(c[0], by: 1) ^ c[3] + + for i in 0..<5 { + a[i] ^= d[i] + a[i &+ 5] ^= d[i] + a[i &+ 10] ^= d[i] + a[i &+ 15] ^= d[i] + a[i &+ 20] ^= d[i] + } } - for i in 0..<5 { - c[i] = a[i] ^ a[i &+ 5] ^ a[i &+ 10] ^ a[i &+ 15] ^ a[i &+ 20] + /// A′[x, y, z]=A[(x &+ 3y) mod 5, x, z] + private func π(_ a: inout [UInt64]) { + let a1 = a[1] + a[1] = a[6] + a[6] = a[9] + a[9] = a[22] + a[22] = a[14] + a[14] = a[20] + a[20] = a[2] + a[2] = a[12] + a[12] = a[13] + a[13] = a[19] + a[19] = a[23] + a[23] = a[15] + a[15] = a[4] + a[4] = a[24] + a[24] = a[21] + a[21] = a[8] + a[8] = a[16] + a[16] = a[5] + a[5] = a[3] + a[3] = a[18] + a[18] = a[17] + a[17] = a[11] + a[11] = a[7] + a[7] = a[10] + a[10] = a1 } - d[0] = rotateLeft(c[1], by: 1) ^ c[4] - d[1] = rotateLeft(c[2], by: 1) ^ c[0] - d[2] = rotateLeft(c[3], by: 1) ^ c[1] - d[3] = rotateLeft(c[4], by: 1) ^ c[2] - d[4] = rotateLeft(c[0], by: 1) ^ c[3] - - for i in 0..<5 { - a[i] ^= d[i] - a[i &+ 5] ^= d[i] - a[i &+ 10] ^= d[i] - a[i &+ 15] ^= d[i] - a[i &+ 20] ^= d[i] + /// For all triples (x, y, z) such that 0≤x<5, 0≤y<5, and 0≤z) { - let a1 = a[1] - a[1] = a[6] - a[6] = a[9] - a[9] = a[22] - a[22] = a[14] - a[14] = a[20] - a[20] = a[2] - a[2] = a[12] - a[12] = a[13] - a[13] = a[19] - a[19] = a[23] - a[23] = a[15] - a[15] = a[4] - a[4] = a[24] - a[24] = a[21] - a[21] = a[8] - a[8] = a[16] - a[16] = a[5] - a[5] = a[3] - a[3] = a[18] - a[18] = a[17] - a[17] = a[11] - a[11] = a[7] - a[7] = a[10] - a[10] = a1 - } - - /// For all triples (x, y, z) such that 0≤x<5, 0≤y<5, and 0≤z) { - for i in stride(from: 0, to: 25, by: 5) { - let a0 = a[0 &+ i] - let a1 = a[1 &+ i] - a[0 &+ i] ^= ~a1 & a[2 &+ i] - a[1 &+ i] ^= ~a[2 &+ i] & a[3 &+ i] - a[2 &+ i] ^= ~a[3 &+ i] & a[4 &+ i] - a[3 &+ i] ^= ~a[4 &+ i] & a0 - a[4 &+ i] ^= ~a0 & a1 + + private func ι(_ a: inout [UInt64], round: Int) { + a[0] ^= self.round_constants[round] } - } - - private func ι(_ a: inout Array, round: Int) { - a[0] ^= self.round_constants[round] - } - - @usableFromInline - func process(block chunk: ArraySlice, currentHash hh: inout Array) { - // expand - hh[0] ^= chunk[0].littleEndian - hh[1] ^= chunk[1].littleEndian - hh[2] ^= chunk[2].littleEndian - hh[3] ^= chunk[3].littleEndian - hh[4] ^= chunk[4].littleEndian - hh[5] ^= chunk[5].littleEndian - hh[6] ^= chunk[6].littleEndian - hh[7] ^= chunk[7].littleEndian - hh[8] ^= chunk[8].littleEndian - if self.blockSize > 72 { // 72 / 8, sha-512 - hh[9] ^= chunk[9].littleEndian - hh[10] ^= chunk[10].littleEndian - hh[11] ^= chunk[11].littleEndian - hh[12] ^= chunk[12].littleEndian - if self.blockSize > 104 { // 104 / 8, sha-384 - hh[13] ^= chunk[13].littleEndian - hh[14] ^= chunk[14].littleEndian - hh[15] ^= chunk[15].littleEndian - hh[16] ^= chunk[16].littleEndian - if self.blockSize > 136 { // 136 / 8, sha-256 - hh[17] ^= chunk[17].littleEndian - // FULL_SHA3_FAMILY_SUPPORT - if self.blockSize > 144 { // 144 / 8, sha-224 - hh[18] ^= chunk[18].littleEndian - hh[19] ^= chunk[19].littleEndian - hh[20] ^= chunk[20].littleEndian - hh[21] ^= chunk[21].littleEndian - hh[22] ^= chunk[22].littleEndian - hh[23] ^= chunk[23].littleEndian - hh[24] ^= chunk[24].littleEndian - } + + @usableFromInline + func process(block chunk: ArraySlice, currentHash hh: inout [UInt64]) { + // expand + hh[0] ^= chunk[0].littleEndian + hh[1] ^= chunk[1].littleEndian + hh[2] ^= chunk[2].littleEndian + hh[3] ^= chunk[3].littleEndian + hh[4] ^= chunk[4].littleEndian + hh[5] ^= chunk[5].littleEndian + hh[6] ^= chunk[6].littleEndian + hh[7] ^= chunk[7].littleEndian + hh[8] ^= chunk[8].littleEndian + if self.blockSize > 72 { // 72 / 8, sha-512 + hh[9] ^= chunk[9].littleEndian + hh[10] ^= chunk[10].littleEndian + hh[11] ^= chunk[11].littleEndian + hh[12] ^= chunk[12].littleEndian + if self.blockSize > 104 { // 104 / 8, sha-384 + hh[13] ^= chunk[13].littleEndian + hh[14] ^= chunk[14].littleEndian + hh[15] ^= chunk[15].littleEndian + hh[16] ^= chunk[16].littleEndian + if self.blockSize > 136 { // 136 / 8, sha-256 + hh[17] ^= chunk[17].littleEndian + // FULL_SHA3_FAMILY_SUPPORT + if self.blockSize > 144 { // 144 / 8, sha-224 + hh[18] ^= chunk[18].littleEndian + hh[19] ^= chunk[19].littleEndian + hh[20] ^= chunk[20].littleEndian + hh[21] ^= chunk[21].littleEndian + hh[22] ^= chunk[22].littleEndian + hh[23] ^= chunk[23].littleEndian + hh[24] ^= chunk[24].littleEndian + } + } + } } - } - } - // Keccak-f - for round in 0..<24 { - self.θ(&hh) - - hh[1] = rotateLeft(hh[1], by: 1) - hh[2] = rotateLeft(hh[2], by: 62) - hh[3] = rotateLeft(hh[3], by: 28) - hh[4] = rotateLeft(hh[4], by: 27) - hh[5] = rotateLeft(hh[5], by: 36) - hh[6] = rotateLeft(hh[6], by: 44) - hh[7] = rotateLeft(hh[7], by: 6) - hh[8] = rotateLeft(hh[8], by: 55) - hh[9] = rotateLeft(hh[9], by: 20) - hh[10] = rotateLeft(hh[10], by: 3) - hh[11] = rotateLeft(hh[11], by: 10) - hh[12] = rotateLeft(hh[12], by: 43) - hh[13] = rotateLeft(hh[13], by: 25) - hh[14] = rotateLeft(hh[14], by: 39) - hh[15] = rotateLeft(hh[15], by: 41) - hh[16] = rotateLeft(hh[16], by: 45) - hh[17] = rotateLeft(hh[17], by: 15) - hh[18] = rotateLeft(hh[18], by: 21) - hh[19] = rotateLeft(hh[19], by: 8) - hh[20] = rotateLeft(hh[20], by: 18) - hh[21] = rotateLeft(hh[21], by: 2) - hh[22] = rotateLeft(hh[22], by: 61) - hh[23] = rotateLeft(hh[23], by: 56) - hh[24] = rotateLeft(hh[24], by: 14) - - self.π(&hh) - self.χ(&hh) - self.ι(&hh, round: round) + // Keccak-f + for round in 0..<24 { + self.θ(&hh) + + hh[1] = rotateLeft(hh[1], by: 1) + hh[2] = rotateLeft(hh[2], by: 62) + hh[3] = rotateLeft(hh[3], by: 28) + hh[4] = rotateLeft(hh[4], by: 27) + hh[5] = rotateLeft(hh[5], by: 36) + hh[6] = rotateLeft(hh[6], by: 44) + hh[7] = rotateLeft(hh[7], by: 6) + hh[8] = rotateLeft(hh[8], by: 55) + hh[9] = rotateLeft(hh[9], by: 20) + hh[10] = rotateLeft(hh[10], by: 3) + hh[11] = rotateLeft(hh[11], by: 10) + hh[12] = rotateLeft(hh[12], by: 43) + hh[13] = rotateLeft(hh[13], by: 25) + hh[14] = rotateLeft(hh[14], by: 39) + hh[15] = rotateLeft(hh[15], by: 41) + hh[16] = rotateLeft(hh[16], by: 45) + hh[17] = rotateLeft(hh[17], by: 15) + hh[18] = rotateLeft(hh[18], by: 21) + hh[19] = rotateLeft(hh[19], by: 8) + hh[20] = rotateLeft(hh[20], by: 18) + hh[21] = rotateLeft(hh[21], by: 2) + hh[22] = rotateLeft(hh[22], by: 61) + hh[23] = rotateLeft(hh[23], by: 56) + hh[24] = rotateLeft(hh[24], by: 14) + + self.π(&hh) + self.χ(&hh) + self.ι(&hh, round: round) + } } - } } extension SHA3: Updatable { - @inlinable - public func update(withBytes bytes: ArraySlice, isLast: Bool = false) throws -> Array { - self.accumulated += bytes + @inlinable + public func update(withBytes bytes: ArraySlice, isLast: Bool = false) throws -> [UInt8] { + self.accumulated += bytes - if isLast { - // Add padding - let markByteIndex = self.accumulated.count + if isLast { + // Add padding + let markByteIndex = self.accumulated.count - // We need to always pad the input. Even if the input is a multiple of blockSize. - let r = self.blockSize * 8 - let q = (r / 8) - (accumulated.count % (r / 8)) - self.accumulated += Array(repeating: 0, count: q) + // We need to always pad the input. Even if the input is a multiple of blockSize. + let r = self.blockSize * 8 + let q = (r / 8) - (accumulated.count % (r / 8)) + self.accumulated += [UInt8](repeating: 0, count: q) - self.accumulated[markByteIndex] |= self.markByte - self.accumulated[self.accumulated.count - 1] |= 0x80 - } + self.accumulated[markByteIndex] |= self.markByte + self.accumulated[self.accumulated.count - 1] |= 0x80 + } - var processedBytes = 0 - for chunk in self.accumulated.batched(by: self.blockSize) { - if isLast || (self.accumulated.count - processedBytes) >= self.blockSize { - self.process(block: chunk.toUInt64Array().slice, currentHash: &self.accumulatedHash) - processedBytes += chunk.count - } - } - self.accumulated.removeFirst(processedBytes) + var processedBytes = 0 + for chunk in self.accumulated.batched(by: self.blockSize) { + if isLast || (self.accumulated.count - processedBytes) >= self.blockSize { + self.process(block: chunk.toUInt64Array().slice, currentHash: &self.accumulatedHash) + processedBytes += chunk.count + } + } + self.accumulated.removeFirst(processedBytes) - // TODO: verify performance, reduce vs for..in - let result = self.accumulatedHash.reduce(into: Array()) { (result, value) in - result += value.bigEndian.bytes() - } + // TODO: verify performance, reduce vs for..in + let result = self.accumulatedHash.reduce(into: [UInt8]()) { (result, value) in + result += value.bigEndian.bytes() + } - // reset hash value for instance - if isLast { - self.accumulatedHash = Array(repeating: 0, count: self.digestLength) - } + // reset hash value for instance + if isLast { + self.accumulatedHash = [UInt64](repeating: 0, count: self.digestLength) + } - return Array(result[0..) - init(bytes: T) where T.Element == UInt8, T.Index == Int { - self = UInt16(bytes: bytes, fromIndex: bytes.startIndex) - } - - @_specialize(where T == ArraySlice) - init(bytes: T, fromIndex index: T.Index) where T.Element == UInt8, T.Index == Int { - if bytes.isEmpty { - self = 0 - return + @_specialize(where T == ArraySlice) + init(bytes: T) where T.Element == UInt8, T.Index == Int { + self = UInt16(bytes: bytes, fromIndex: bytes.startIndex) } - let count = bytes.count + @_specialize(where T == ArraySlice) + init(bytes: T, fromIndex index: T.Index) where T.Element == UInt8, T.Index == Int { + if bytes.isEmpty { + self = 0 + return + } + + let count = bytes.count - let val0 = count > 0 ? UInt16(bytes[index.advanced(by: 0)]) << 8 : 0 - let val1 = count > 1 ? UInt16(bytes[index.advanced(by: 1)]) : 0 + let val0 = count > 0 ? UInt16(bytes[index.advanced(by: 0)]) << 8 : 0 + let val1 = count > 1 ? UInt16(bytes[index.advanced(by: 1)]) : 0 - self = val0 | val1 - } + self = val0 | val1 + } } diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/UInt32+Extension.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/UInt32+Extension.swift index 18e1599a..e0e6434f 100644 --- a/Sources/AWSLambdaPluginHelper/Vendored/crypto/UInt32+Extension.swift +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/UInt32+Extension.swift @@ -38,28 +38,28 @@ import ucrt protocol _UInt32Type {} extension UInt32: _UInt32Type {} -/** array of bytes */ +/// array of bytes extension UInt32 { - @_specialize(where T == ArraySlice) - init(bytes: T) where T.Element == UInt8, T.Index == Int { - self = UInt32(bytes: bytes, fromIndex: bytes.startIndex) - } - - @_specialize(where T == ArraySlice) - @inlinable - init(bytes: T, fromIndex index: T.Index) where T.Element == UInt8, T.Index == Int { - if bytes.isEmpty { - self = 0 - return + @_specialize(where T == ArraySlice) + init(bytes: T) where T.Element == UInt8, T.Index == Int { + self = UInt32(bytes: bytes, fromIndex: bytes.startIndex) } - let count = bytes.count + @_specialize(where T == ArraySlice) + @inlinable + init(bytes: T, fromIndex index: T.Index) where T.Element == UInt8, T.Index == Int { + if bytes.isEmpty { + self = 0 + return + } + + let count = bytes.count - let val0 = count > 0 ? UInt32(bytes[index.advanced(by: 0)]) << 24 : 0 - let val1 = count > 1 ? UInt32(bytes[index.advanced(by: 1)]) << 16 : 0 - let val2 = count > 2 ? UInt32(bytes[index.advanced(by: 2)]) << 8 : 0 - let val3 = count > 3 ? UInt32(bytes[index.advanced(by: 3)]) : 0 + let val0 = count > 0 ? UInt32(bytes[index.advanced(by: 0)]) << 24 : 0 + let val1 = count > 1 ? UInt32(bytes[index.advanced(by: 1)]) << 16 : 0 + let val2 = count > 2 ? UInt32(bytes[index.advanced(by: 2)]) << 8 : 0 + let val3 = count > 3 ? UInt32(bytes[index.advanced(by: 3)]) : 0 - self = val0 | val1 | val2 | val3 - } + self = val0 | val1 | val2 | val3 + } } diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/UInt64+Extension.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/UInt64+Extension.swift index 1bbdc79b..afcd7e47 100644 --- a/Sources/AWSLambdaPluginHelper/Vendored/crypto/UInt64+Extension.swift +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/UInt64+Extension.swift @@ -27,32 +27,32 @@ // - This notice may not be removed or altered from any source or binary distribution. // -/** array of bytes */ +/// array of bytes extension UInt64 { - @_specialize(where T == ArraySlice) - init(bytes: T) where T.Element == UInt8, T.Index == Int { - self = UInt64(bytes: bytes, fromIndex: bytes.startIndex) - } - - @_specialize(where T == ArraySlice) - @inlinable - init(bytes: T, fromIndex index: T.Index) where T.Element == UInt8, T.Index == Int { - if bytes.isEmpty { - self = 0 - return + @_specialize(where T == ArraySlice) + init(bytes: T) where T.Element == UInt8, T.Index == Int { + self = UInt64(bytes: bytes, fromIndex: bytes.startIndex) } - let count = bytes.count + @_specialize(where T == ArraySlice) + @inlinable + init(bytes: T, fromIndex index: T.Index) where T.Element == UInt8, T.Index == Int { + if bytes.isEmpty { + self = 0 + return + } + + let count = bytes.count - let val0 = count > 0 ? UInt64(bytes[index.advanced(by: 0)]) << 56 : 0 - let val1 = count > 1 ? UInt64(bytes[index.advanced(by: 1)]) << 48 : 0 - let val2 = count > 2 ? UInt64(bytes[index.advanced(by: 2)]) << 40 : 0 - let val3 = count > 3 ? UInt64(bytes[index.advanced(by: 3)]) << 32 : 0 - let val4 = count > 4 ? UInt64(bytes[index.advanced(by: 4)]) << 24 : 0 - let val5 = count > 5 ? UInt64(bytes[index.advanced(by: 5)]) << 16 : 0 - let val6 = count > 6 ? UInt64(bytes[index.advanced(by: 6)]) << 8 : 0 - let val7 = count > 7 ? UInt64(bytes[index.advanced(by: 7)]) : 0 + let val0 = count > 0 ? UInt64(bytes[index.advanced(by: 0)]) << 56 : 0 + let val1 = count > 1 ? UInt64(bytes[index.advanced(by: 1)]) << 48 : 0 + let val2 = count > 2 ? UInt64(bytes[index.advanced(by: 2)]) << 40 : 0 + let val3 = count > 3 ? UInt64(bytes[index.advanced(by: 3)]) << 32 : 0 + let val4 = count > 4 ? UInt64(bytes[index.advanced(by: 4)]) << 24 : 0 + let val5 = count > 5 ? UInt64(bytes[index.advanced(by: 5)]) << 16 : 0 + let val6 = count > 6 ? UInt64(bytes[index.advanced(by: 6)]) << 8 : 0 + let val7 = count > 7 ? UInt64(bytes[index.advanced(by: 7)]) : 0 - self = val0 | val1 | val2 | val3 | val4 | val5 | val6 | val7 - } + self = val0 | val1 | val2 | val3 | val4 | val5 | val6 | val7 + } } diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/UInt8+Extension.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/UInt8+Extension.swift index b65ed5f5..f6651013 100644 --- a/Sources/AWSLambdaPluginHelper/Vendored/crypto/UInt8+Extension.swift +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/UInt8+Extension.swift @@ -38,51 +38,51 @@ import ucrt public protocol _UInt8Type {} extension UInt8: _UInt8Type {} -/** casting */ +/// casting extension UInt8 { - /** cast because UInt8() because std initializer crash if value is > byte */ - static func with(value: UInt64) -> UInt8 { - let tmp = value & 0xff - return UInt8(tmp) - } + // cast because UInt8() because std initializer crash if value is > byte + static func with(value: UInt64) -> UInt8 { + let tmp = value & 0xff + return UInt8(tmp) + } - static func with(value: UInt32) -> UInt8 { - let tmp = value & 0xff - return UInt8(tmp) - } + static func with(value: UInt32) -> UInt8 { + let tmp = value & 0xff + return UInt8(tmp) + } - static func with(value: UInt16) -> UInt8 { - let tmp = value & 0xff - return UInt8(tmp) - } + static func with(value: UInt16) -> UInt8 { + let tmp = value & 0xff + return UInt8(tmp) + } } -/** Bits */ +/// Bits extension UInt8 { - /** array of bits */ - public func bits() -> [Bit] { - let totalBitsCount = MemoryLayout.size * 8 + // array of bits + public func bits() -> [Bit] { + let totalBitsCount = MemoryLayout.size * 8 - var bitsArray = [Bit](repeating: Bit.zero, count: totalBitsCount) + var bitsArray = [Bit](repeating: Bit.zero, count: totalBitsCount) - for j in 0.. String { - var s = String() - let arr: [Bit] = self.bits() - for idx in arr.indices { - s += (arr[idx] == Bit.one ? "1" : "0") - if idx.advanced(by: 1) % 8 == 0 { s += " " } + public func bits() -> String { + var s = String() + let arr: [Bit] = self.bits() + for idx in arr.indices { + s += (arr[idx] == Bit.one ? "1" : "0") + if idx.advanced(by: 1) % 8 == 0 { s += " " } + } + return s } - return s - } } diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/Updatable.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Updatable.swift index e54fc60d..94d0efaa 100644 --- a/Sources/AWSLambdaPluginHelper/Vendored/crypto/Updatable.swift +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Updatable.swift @@ -30,92 +30,100 @@ /// A type that supports incremental updates. For example Digest or Cipher may be updatable /// and calculate result incerementally. public protocol Updatable { - /// Update given bytes in chunks. - /// - /// - parameter bytes: Bytes to process. - /// - parameter isLast: Indicate if given chunk is the last one. No more updates after this call. - /// - returns: Processed partial result data or empty array. - mutating func update(withBytes bytes: ArraySlice, isLast: Bool) throws -> Array + /// Update given bytes in chunks. + /// + /// - parameter bytes: Bytes to process. + /// - parameter isLast: Indicate if given chunk is the last one. No more updates after this call. + /// - returns: Processed partial result data or empty array. + mutating func update(withBytes bytes: ArraySlice, isLast: Bool) throws -> [UInt8] - /// Update given bytes in chunks. - /// - /// - Parameters: - /// - bytes: Bytes to process. - /// - isLast: Indicate if given chunk is the last one. No more updates after this call. - /// - output: Resulting bytes callback. - /// - Returns: Processed partial result data or empty array. - mutating func update(withBytes bytes: ArraySlice, isLast: Bool, output: (_ bytes: Array) -> Void) throws + /// Update given bytes in chunks. + /// + /// - Parameters: + /// - bytes: Bytes to process. + /// - isLast: Indicate if given chunk is the last one. No more updates after this call. + /// - output: Resulting bytes callback. + /// - Returns: Processed partial result data or empty array. + mutating func update(withBytes bytes: ArraySlice, isLast: Bool, output: (_ bytes: [UInt8]) -> Void) throws } extension Updatable { - @inlinable - public mutating func update(withBytes bytes: ArraySlice, isLast: Bool = false, output: (_ bytes: Array) -> Void) throws { - let processed = try update(withBytes: bytes, isLast: isLast) - if !processed.isEmpty { - output(processed) + @inlinable + public mutating func update( + withBytes bytes: ArraySlice, + isLast: Bool = false, + output: (_ bytes: [UInt8]) -> Void + ) throws { + let processed = try update(withBytes: bytes, isLast: isLast) + if !processed.isEmpty { + output(processed) + } } - } - @inlinable - public mutating func update(withBytes bytes: ArraySlice, isLast: Bool = false) throws -> Array { - try self.update(withBytes: bytes, isLast: isLast) - } + @inlinable + public mutating func update(withBytes bytes: ArraySlice, isLast: Bool = false) throws -> [UInt8] { + try self.update(withBytes: bytes, isLast: isLast) + } - @inlinable - public mutating func update(withBytes bytes: Array, isLast: Bool = false) throws -> Array { - try self.update(withBytes: bytes.slice, isLast: isLast) - } + @inlinable + public mutating func update(withBytes bytes: [UInt8], isLast: Bool = false) throws -> [UInt8] { + try self.update(withBytes: bytes.slice, isLast: isLast) + } - @inlinable - public mutating func update(withBytes bytes: Array, isLast: Bool = false, output: (_ bytes: Array) -> Void) throws { - try self.update(withBytes: bytes.slice, isLast: isLast, output: output) - } + @inlinable + public mutating func update( + withBytes bytes: [UInt8], + isLast: Bool = false, + output: (_ bytes: [UInt8]) -> Void + ) throws { + try self.update(withBytes: bytes.slice, isLast: isLast, output: output) + } - /// Finish updates. This may apply padding. - /// - parameter bytes: Bytes to process - /// - returns: Processed data. - @inlinable - public mutating func finish(withBytes bytes: ArraySlice) throws -> Array { - try self.update(withBytes: bytes, isLast: true) - } + /// Finish updates. This may apply padding. + /// - parameter bytes: Bytes to process + /// - returns: Processed data. + @inlinable + public mutating func finish(withBytes bytes: ArraySlice) throws -> [UInt8] { + try self.update(withBytes: bytes, isLast: true) + } - @inlinable - public mutating func finish(withBytes bytes: Array) throws -> Array { - try self.finish(withBytes: bytes.slice) - } + @inlinable + public mutating func finish(withBytes bytes: [UInt8]) throws -> [UInt8] { + try self.finish(withBytes: bytes.slice) + } - /// Finish updates. May add padding. - /// - /// - Returns: Processed data - /// - Throws: Error - @inlinable - public mutating func finish() throws -> Array { - try self.update(withBytes: [], isLast: true) - } + /// Finish updates. May add padding. + /// + /// - Returns: Processed data + /// - Throws: Error + @inlinable + public mutating func finish() throws -> [UInt8] { + try self.update(withBytes: [], isLast: true) + } - /// Finish updates. This may apply padding. - /// - parameter bytes: Bytes to process - /// - parameter output: Resulting data - /// - returns: Processed data. - @inlinable - public mutating func finish(withBytes bytes: ArraySlice, output: (_ bytes: Array) -> Void) throws { - let processed = try update(withBytes: bytes, isLast: true) - if !processed.isEmpty { - output(processed) + /// Finish updates. This may apply padding. + /// - parameter bytes: Bytes to process + /// - parameter output: Resulting data + /// - returns: Processed data. + @inlinable + public mutating func finish(withBytes bytes: ArraySlice, output: (_ bytes: [UInt8]) -> Void) throws { + let processed = try update(withBytes: bytes, isLast: true) + if !processed.isEmpty { + output(processed) + } } - } - @inlinable - public mutating func finish(withBytes bytes: Array, output: (_ bytes: Array) -> Void) throws { - try self.finish(withBytes: bytes.slice, output: output) - } + @inlinable + public mutating func finish(withBytes bytes: [UInt8], output: (_ bytes: [UInt8]) -> Void) throws { + try self.finish(withBytes: bytes.slice, output: output) + } - /// Finish updates. May add padding. - /// - /// - Parameter output: Processed data - /// - Throws: Error - @inlinable - public mutating func finish(output: (Array) -> Void) throws { - try self.finish(withBytes: [], output: output) - } -} \ No newline at end of file + /// Finish updates. May add padding. + /// + /// - Parameter output: Processed data + /// - Throws: Error + @inlinable + public mutating func finish(output: ([UInt8]) -> Void) throws { + try self.finish(withBytes: [], output: output) + } +} diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/Utils.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Utils.swift index 6a5ad9fd..8a4ea776 100644 --- a/Sources/AWSLambdaPluginHelper/Vendored/crypto/Utils.swift +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Utils.swift @@ -29,103 +29,117 @@ @inlinable func rotateLeft(_ value: UInt8, by: UInt8) -> UInt8 { - ((value << by) & 0xff) | (value >> (8 - by)) + ((value << by) & 0xff) | (value >> (8 - by)) } @inlinable func rotateLeft(_ value: UInt16, by: UInt16) -> UInt16 { - ((value << by) & 0xffff) | (value >> (16 - by)) + ((value << by) & 0xffff) | (value >> (16 - by)) } @inlinable func rotateLeft(_ value: UInt32, by: UInt32) -> UInt32 { - ((value << by) & 0xffffffff) | (value >> (32 - by)) + ((value << by) & 0xffff_ffff) | (value >> (32 - by)) } @inlinable func rotateLeft(_ value: UInt64, by: UInt64) -> UInt64 { - (value << by) | (value >> (64 - by)) + (value << by) | (value >> (64 - by)) } @inlinable func rotateRight(_ value: UInt16, by: UInt16) -> UInt16 { - (value >> by) | (value << (16 - by)) + (value >> by) | (value << (16 - by)) } @inlinable func rotateRight(_ value: UInt32, by: UInt32) -> UInt32 { - (value >> by) | (value << (32 - by)) + (value >> by) | (value << (32 - by)) } @inlinable func rotateRight(_ value: UInt64, by: UInt64) -> UInt64 { - ((value >> by) | (value << (64 - by))) + ((value >> by) | (value << (64 - by))) } @inlinable func reversed(_ uint8: UInt8) -> UInt8 { - var v = uint8 - v = (v & 0xf0) >> 4 | (v & 0x0f) << 4 - v = (v & 0xcc) >> 2 | (v & 0x33) << 2 - v = (v & 0xaa) >> 1 | (v & 0x55) << 1 - return v + var v = uint8 + v = (v & 0xf0) >> 4 | (v & 0x0f) << 4 + v = (v & 0xcc) >> 2 | (v & 0x33) << 2 + v = (v & 0xaa) >> 1 | (v & 0x55) << 1 + return v } @inlinable func reversed(_ uint32: UInt32) -> UInt32 { - var v = uint32 - v = ((v >> 1) & 0x55555555) | ((v & 0x55555555) << 1) - v = ((v >> 2) & 0x33333333) | ((v & 0x33333333) << 2) - v = ((v >> 4) & 0x0f0f0f0f) | ((v & 0x0f0f0f0f) << 4) - v = ((v >> 8) & 0x00ff00ff) | ((v & 0x00ff00ff) << 8) - v = ((v >> 16) & 0xffff) | ((v & 0xffff) << 16) - return v + var v = uint32 + v = ((v >> 1) & 0x5555_5555) | ((v & 0x5555_5555) << 1) + v = ((v >> 2) & 0x3333_3333) | ((v & 0x3333_3333) << 2) + v = ((v >> 4) & 0x0f0f_0f0f) | ((v & 0x0f0f_0f0f) << 4) + v = ((v >> 8) & 0x00ff_00ff) | ((v & 0x00ff_00ff) << 8) + v = ((v >> 16) & 0xffff) | ((v & 0xffff) << 16) + return v } @inlinable -func xor(_ left: T, _ right: V) -> ArraySlice where T: RandomAccessCollection, V: RandomAccessCollection, T.Element == UInt8, T.Index == Int, V.Element == UInt8, V.Index == Int { - return xor(left, right).slice +func xor(_ left: T, _ right: V) -> ArraySlice +where + T: RandomAccessCollection, + V: RandomAccessCollection, + T.Element == UInt8, + T.Index == Int, + V.Element == UInt8, + V.Index == Int +{ + xor(left, right).slice } @inlinable -func xor(_ left: T, _ right: V) -> Array where T: RandomAccessCollection, V: RandomAccessCollection, T.Element == UInt8, T.Index == Int, V.Element == UInt8, V.Index == Int { - let length = Swift.min(left.count, right.count) - - let buf = UnsafeMutablePointer.allocate(capacity: length) - buf.initialize(repeating: 0, count: length) - defer { - buf.deinitialize(count: length) - buf.deallocate() - } - - // xor - for i in 0..(_ left: T, _ right: V) -> [UInt8] +where + T: RandomAccessCollection, + V: RandomAccessCollection, + T.Element == UInt8, + T.Index == Int, + V.Element == UInt8, + V.Index == Int +{ + let length = Swift.min(left.count, right.count) + + let buf = UnsafeMutablePointer.allocate(capacity: length) + buf.initialize(repeating: 0, count: length) + defer { + buf.deinitialize(count: length) + buf.deallocate() + } + + // xor + for i in 0.., blockSize: Int, allowance: Int = 0) { - let msgLength = data.count - // Step 1. Append Padding Bits - // append one bit (UInt8 with one bit) to message - data.append(0x80) - - // Step 2. append "0" bit until message length in bits ≡ 448 (mod 512) - let max = blockSize - allowance // 448, 986 - if msgLength % blockSize < max { // 448 - data += Array(repeating: 0, count: max - 1 - (msgLength % blockSize)) - } else { - data += Array(repeating: 0, count: blockSize + max - 1 - (msgLength % blockSize)) - } -} \ No newline at end of file +func bitPadding(to data: inout [UInt8], blockSize: Int, allowance: Int = 0) { + let msgLength = data.count + // Step 1. Append Padding Bits + // append one bit (UInt8 with one bit) to message + data.append(0x80) + + // Step 2. append "0" bit until message length in bits ≡ 448 (mod 512) + let max = blockSize - allowance // 448, 986 + if msgLength % blockSize < max { // 448 + data += [UInt8](repeating: 0, count: max - 1 - (msgLength % blockSize)) + } else { + data += [UInt8](repeating: 0, count: blockSize + max - 1 - (msgLength % blockSize)) + } +} diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/ZeroPadding.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/ZeroPadding.swift index f6846a45..34607598 100644 --- a/Sources/AWSLambdaPluginHelper/Vendored/crypto/ZeroPadding.swift +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/ZeroPadding.swift @@ -30,25 +30,25 @@ /// All the bytes that are required to be padded are padded with zero. /// Zero padding may not be reversible if the original file ends with one or more zero bytes. struct ZeroPadding: PaddingProtocol { - init() { - } + init() { + } - @inlinable - func add(to bytes: Array, blockSize: Int) -> Array { - let paddingCount = blockSize - (bytes.count % blockSize) - if paddingCount > 0 { - return bytes + Array(repeating: 0, count: paddingCount) + @inlinable + func add(to bytes: [UInt8], blockSize: Int) -> [UInt8] { + let paddingCount = blockSize - (bytes.count % blockSize) + if paddingCount > 0 { + return bytes + [UInt8](repeating: 0, count: paddingCount) + } + return bytes } - return bytes - } - @inlinable - func remove(from bytes: Array, blockSize _: Int?) -> Array { - for (idx, value) in bytes.reversed().enumerated() { - if value != 0 { - return Array(bytes[0.. [UInt8] { + for (idx, value) in bytes.reversed().enumerated() { + if value != 0 { + return Array(bytes[0.. HTTPHeaders { + public func signHeaders( + url: URL, + method: HTTPMethod = .GET, + headers: HTTPHeaders = HTTPHeaders(), + body: BodyData? = nil, + date: Date = Date() + ) -> HTTPHeaders { let bodyHash = AWSSigner.hashedPayload(body) let dateString = AWSSigner.timestamp(date) var headers = headers @@ -72,14 +80,23 @@ public struct AWSSigner { headers.add(name: "x-amz-security-token", value: sessionToken) } - // construct signing data. Do this after adding the headers as it uses data from the headers - let signingData = AWSSigner.SigningData(url: url, method: method, headers: headers, body: body, bodyHash: bodyHash, date: dateString, signer: self) + // construct signing data. + // Do this after adding the headers as it uses data from the headers + let signingData = AWSSigner.SigningData( + url: url, + method: method, + headers: headers, + body: body, + bodyHash: bodyHash, + date: dateString, + signer: self + ) // construct authorization string - let authorization = "AWS4-HMAC-SHA256 " + - "Credential=\(credentials.accessKeyId)/\(signingData.date)/\(region)/\(name)/aws4_request, " + - "SignedHeaders=\(signingData.signedHeaders), " + - "Signature=\(signature(signingData: signingData))" + let authorization = + "AWS4-HMAC-SHA256 " + + "Credential=\(credentials.accessKeyId)/\(signingData.date)/\(region)/\(name)/aws4_request, " + + "SignedHeaders=\(signingData.signedHeaders), " + "Signature=\(signature(signingData: signingData))" // add Authorization header headers.add(name: "Authorization", value: authorization) @@ -88,11 +105,25 @@ public struct AWSSigner { } /// Generate a signed URL, for a HTTP request - public func signURL(url: URL, method: HTTPMethod = .GET, body: BodyData? = nil, date: Date = Date(), expires: Int = 86400) -> URL { + public func signURL( + url: URL, + method: HTTPMethod = .GET, + body: BodyData? = nil, + date: Date = Date(), + expires: Int = 86400 + ) -> URL { let headers = HTTPHeaders([("host", url.host ?? "")]) // Create signing data - var signingData = AWSSigner.SigningData(url: url, method: method, headers: headers, body: body, date: AWSSigner.timestamp(date), signer: self) - // Construct query string. Start with original query strings and append all the signing info. + var signingData = AWSSigner.SigningData( + url: url, + method: method, + headers: headers, + body: body, + date: AWSSigner.timestamp(date), + signer: self + ) + // Construct query string. + // Start with original query strings and append all the signing info. var query = url.query ?? "" if query.count > 0 { query += "&" @@ -111,29 +142,39 @@ public struct AWSSigner { .joined(separator: "&") .queryEncode() - // update unsignedURL in the signingData so when the canonical request is constructed it includes all the signing query items - signingData.unsignedURL = URL(string: url.absoluteString.split(separator: "?")[0]+"?"+query)! // NEED TO DEAL WITH SITUATION WHERE THIS FAILS + // update unsignedURL in the signingData + // so when the canonical request is constructed it includes all the signing query items + signingData.unsignedURL = URL(string: url.absoluteString.split(separator: "?")[0] + "?" + query)! + // FIXME: NEED TO DEAL WITH SITUATION WHERE THIS FAILS query += "&X-Amz-Signature=\(signature(signingData: signingData))" // Add signature to query items and build a new Request - let signedURL = URL(string: url.absoluteString.split(separator: "?")[0]+"?"+query)! + let signedURL = URL(string: url.absoluteString.split(separator: "?")[0] + "?" + query)! return signedURL } /// structure used to store data used throughout the signing process struct SigningData { - let url : URL - let method : HTTPMethod - let hashedPayload : String - let datetime : String + let url: URL + let method: HTTPMethod + let hashedPayload: String + let datetime: String let headersToSign: [String: String] - let signedHeaders : String - var unsignedURL : URL - - var date : String { return String(datetime.prefix(8))} - - init(url: URL, method: HTTPMethod = .GET, headers: HTTPHeaders = HTTPHeaders(), body: BodyData? = nil, bodyHash: String? = nil, date: String, signer: AWSSigner) { + let signedHeaders: String + var unsignedURL: URL + + var date: String { String(datetime.prefix(8)) } + + init( + url: URL, + method: HTTPMethod = .GET, + headers: HTTPHeaders = HTTPHeaders(), + body: BodyData? = nil, + bodyHash: String? = nil, + date: String, + signer: AWSSigner + ) { if url.path == "" { //URL has to have trailing slash self.url = url.appendingPathComponent("/") @@ -169,16 +210,20 @@ public struct AWSSigner { } } - // Stage 3 Calculating signature as in https://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html + // Stage 3 Calculating signature as in + // https://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html func signature(signingData: SigningData) -> String { var signature = "" do { - let kDate = try HMAC.authenticate(for: Data(signingData.date.utf8), using: "AWS4\(credentials.secretAccessKey)".array) + let kDate = try HMAC.authenticate( + for: Data(signingData.date.utf8), + using: "AWS4\(credentials.secretAccessKey)".array + ) let kRegion = try HMAC.authenticate(for: Data(region.utf8), using: kDate) let kService = try HMAC.authenticate(for: Data(name.utf8), using: kRegion) let kSigning = try HMAC.authenticate(for: Data("aws4_request".utf8), using: kService) - let kSignature = try HMAC.authenticate(for: stringToSign(signingData: signingData), using: kSigning) + let kSignature = try HMAC.authenticate(for: stringToSign(signingData: signingData), using: kSigning) signature = kSignature.toHexString() } catch { @@ -188,43 +233,45 @@ public struct AWSSigner { return signature // original code from Adam Fowler, using swift-crypto package: - /* - let kDate = HMAC.authenticationCode(for: Data(signingData.date.utf8), using: SymmetricKey(data: Array("AWS4\(credentials.secretAccessKey)".utf8))) - let kRegion = HMAC.authenticationCode(for: Data(region.utf8), using: SymmetricKey(data: kDate)) - let kService = HMAC.authenticationCode(for: Data(name.utf8), using: SymmetricKey(data: kRegion)) - let kSigning = HMAC.authenticationCode(for: Data("aws4_request".utf8), using: SymmetricKey(data: kService)) - let kSignature = HMAC.authenticationCode(for: stringToSign(signingData: signingData), using: SymmetricKey(data: kSigning)) - return kSignature.hexDigest() - */ + + // let kDate = HMAC.authenticationCode(for: Data(signingData.date.utf8), using: SymmetricKey(data: Array("AWS4\(credentials.secretAccessKey)".utf8))) + // let kRegion = HMAC.authenticationCode(for: Data(region.utf8), using: SymmetricKey(data: kDate)) + // let kService = HMAC.authenticationCode(for: Data(name.utf8), using: SymmetricKey(data: kRegion)) + // let kSigning = HMAC.authenticationCode(for: Data("aws4_request".utf8), using: SymmetricKey(data: kService)) + // let kSignature = HMAC.authenticationCode(for: stringToSign(signingData: signingData), using: SymmetricKey(data: kSigning)) + // return kSignature.hexDigest() + } - /// Stage 2 Create the string to sign as in https://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html + /// Stage 2 Create the string to sign as in + // https://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html func stringToSign(signingData: SigningData) -> Data { - let stringToSign = "AWS4-HMAC-SHA256\n" + - "\(signingData.datetime)\n" + - "\(signingData.date)/\(region)/\(name)/aws4_request\n" + - SHA256.hash(data: canonicalRequest(signingData: signingData)).hexDigest() + let stringToSign = + "AWS4-HMAC-SHA256\n" + "\(signingData.datetime)\n" + "\(signingData.date)/\(region)/\(name)/aws4_request\n" + + SHA256.hash(data: canonicalRequest(signingData: signingData)).hexDigest() return Data(stringToSign.utf8) } - /// Stage 1 Create the canonical request as in https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html + /// Stage 1 Create the canonical request as in + // https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html func canonicalRequest(signingData: SigningData) -> Data { - let canonicalHeaders = signingData.headersToSign.map { return "\($0.key.lowercased()):\($0.value.trimmingCharacters(in: CharacterSet.whitespaces))" } - .sorted() - .joined(separator: "\n") - let canonicalRequest = "\(signingData.method.rawValue)\n" + - "\(signingData.unsignedURL.path.uriEncodeWithSlash())\n" + - "\(signingData.unsignedURL.query ?? "")\n" + // should really uriEncode all the query string values - "\(canonicalHeaders)\n\n" + - "\(signingData.signedHeaders)\n" + - signingData.hashedPayload + let canonicalHeaders = signingData.headersToSign.map { + "\($0.key.lowercased()):\($0.value.trimmingCharacters(in: CharacterSet.whitespaces))" + } + .sorted() + .joined(separator: "\n") + let canonicalRequest = + "\(signingData.method.rawValue)\n" + "\(signingData.unsignedURL.path.uriEncodeWithSlash())\n" + // should really uriEncode all the query string values + + "\(signingData.unsignedURL.query ?? "")\n" + + "\(canonicalHeaders)\n\n" + "\(signingData.signedHeaders)\n" + signingData.hashedPayload return Data(canonicalRequest.utf8) } /// Create a SHA256 hash of the Requests body static func hashedPayload(_ payload: BodyData?) -> String { guard let payload = payload else { return hashedEmptyBody } - let hash : String? + let hash: String? switch payload { case .string(let string): hash = SHA256.hash(data: Data(string.utf8)).hexDigest() @@ -233,7 +280,7 @@ public struct AWSSigner { case .byteBuffer(let byteBuffer): let byteBufferView = byteBuffer.readableBytesView hash = byteBufferView.withContiguousStorageIfAvailable { bytes in - return SHA256.hash(data: bytes).hexDigest() + SHA256.hash(data: bytes).hexDigest() } } if let hash = hash { @@ -245,7 +292,7 @@ public struct AWSSigner { /// return a hexEncoded string buffer from an array of bytes static func hexEncoded(_ buffer: [UInt8]) -> String { - return buffer.map{String(format: "%02x", $0)}.joined(separator: "") + buffer.map { String(format: "%02x", $0) }.joined(separator: "") } /// create timestamp dateformatter static private func createTimeStampDateFormatter() -> DateFormatter { @@ -258,31 +305,35 @@ public struct AWSSigner { /// return a timestamp formatted for signing requests static func timestamp(_ date: Date) -> String { - return timeStampDateFormatter.string(from: date) + timeStampDateFormatter.string(from: date) } } extension String { func queryEncode() -> String { - return addingPercentEncoding(withAllowedCharacters: String.queryAllowedCharacters) ?? self + addingPercentEncoding(withAllowedCharacters: String.queryAllowedCharacters) ?? self } func uriEncode() -> String { - return addingPercentEncoding(withAllowedCharacters: String.uriAllowedCharacters) ?? self + addingPercentEncoding(withAllowedCharacters: String.uriAllowedCharacters) ?? self } func uriEncodeWithSlash() -> String { - return addingPercentEncoding(withAllowedCharacters: String.uriAllowedWithSlashCharacters) ?? self + addingPercentEncoding(withAllowedCharacters: String.uriAllowedWithSlashCharacters) ?? self } - static let uriAllowedWithSlashCharacters = CharacterSet(charactersIn:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~/") - static let uriAllowedCharacters = CharacterSet(charactersIn:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~") - static let queryAllowedCharacters = CharacterSet(charactersIn:"/;+").inverted + static let uriAllowedWithSlashCharacters = CharacterSet( + charactersIn: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~/" + ) + static let uriAllowedCharacters = CharacterSet( + charactersIn: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~" + ) + static let queryAllowedCharacters = CharacterSet(charactersIn: "/;+").inverted } -public extension Sequence where Element == UInt8 { +extension Sequence where Element == UInt8 { /// return a hexEncoded string buffer from an array of bytes - func hexDigest() -> String { - return self.map{String(format: "%02x", $0)}.joined(separator: "") + public func hexDigest() -> String { + self.map { String(format: "%02x", $0) }.joined(separator: "") } } diff --git a/Sources/AWSLambdaPluginHelper/main.swift b/Sources/AWSLambdaPluginHelper/main.swift index c28454c6..3ed1e5b5 100644 --- a/Sources/AWSLambdaPluginHelper/main.swift +++ b/Sources/AWSLambdaPluginHelper/main.swift @@ -1 +1 @@ -print("Deployer") \ No newline at end of file +print("Deployer") diff --git a/Tests/AWSLambdaPluginHelperTests/AWSSignerTests.swift b/Tests/AWSLambdaPluginHelperTests/AWSSignerTests.swift index 4220e235..4cf5727d 100644 --- a/Tests/AWSLambdaPluginHelperTests/AWSSignerTests.swift +++ b/Tests/AWSLambdaPluginHelperTests/AWSSignerTests.swift @@ -25,33 +25,65 @@ import Foundation @Suite struct SignerTests { - let credentials : Credential = StaticCredential(accessKeyId: "MYACCESSKEY", secretAccessKey: "MYSECRETACCESSKEY") + let credentials: Credential = StaticCredential(accessKeyId: "MYACCESSKEY", secretAccessKey: "MYSECRETACCESSKEY") - @Test + @Test func testSignGetHeaders() { - let signer = AWSSigner(credentials: credentials, name: "glacier", region:"us-east-1") - let headers = signer.signHeaders(url: URL(string:"https://glacier.us-east-1.amazonaws.com/-/vaults")!, method: .GET, headers: ["x-amz-glacier-version":"2012-06-01"], date: Date(timeIntervalSinceReferenceDate: 2000000)) - #expect(headers["Authorization"].first == "AWS4-HMAC-SHA256 Credential=MYACCESSKEY/20010124/us-east-1/glacier/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date;x-amz-glacier-version, Signature=acfa9b03fca6b098d7b88bfd9bbdb4687f5b34e944a9c6ed9f4814c1b0b06d62") + let signer = AWSSigner(credentials: credentials, name: "glacier", region: "us-east-1") + let headers = signer.signHeaders( + url: URL(string: "https://glacier.us-east-1.amazonaws.com/-/vaults")!, + method: .GET, + headers: ["x-amz-glacier-version": "2012-06-01"], + date: Date(timeIntervalSinceReferenceDate: 2_000_000) + ) + #expect( + headers["Authorization"].first + == "AWS4-HMAC-SHA256 Credential=MYACCESSKEY/20010124/us-east-1/glacier/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date;x-amz-glacier-version, Signature=acfa9b03fca6b098d7b88bfd9bbdb4687f5b34e944a9c6ed9f4814c1b0b06d62" + ) } - - @Test + + @Test func testSignPutHeaders() { - let signer = AWSSigner(credentials: credentials, name: "sns", region:"eu-west-1") - let headers = signer.signHeaders(url: URL(string: "https://sns.eu-west-1.amazonaws.com/")!, method: .POST, headers: ["Content-Type": "application/x-www-form-urlencoded; charset=utf-8"], body: .string("Action=ListTopics&Version=2010-03-31"), date: Date(timeIntervalSinceReferenceDate: 200)) - #expect(headers["Authorization"].first == "AWS4-HMAC-SHA256 Credential=MYACCESSKEY/20010101/eu-west-1/sns/aws4_request, SignedHeaders=content-type;host;x-amz-content-sha256;x-amz-date, Signature=1d29943055a8ad094239e8de06082100f2426ebbb2c6a5bbcbb04c63e6a3f274") + let signer = AWSSigner(credentials: credentials, name: "sns", region: "eu-west-1") + let headers = signer.signHeaders( + url: URL(string: "https://sns.eu-west-1.amazonaws.com/")!, + method: .POST, + headers: ["Content-Type": "application/x-www-form-urlencoded; charset=utf-8"], + body: .string("Action=ListTopics&Version=2010-03-31"), + date: Date(timeIntervalSinceReferenceDate: 200) + ) + #expect( + headers["Authorization"].first + == "AWS4-HMAC-SHA256 Credential=MYACCESSKEY/20010101/eu-west-1/sns/aws4_request, SignedHeaders=content-type;host;x-amz-content-sha256;x-amz-date, Signature=1d29943055a8ad094239e8de06082100f2426ebbb2c6a5bbcbb04c63e6a3f274" + ) } - - @Test + + @Test func testSignS3GetURL() { - let signer = AWSSigner(credentials: credentials, name: "s3", region:"us-east-1") - let url = signer.signURL(url: URL(string: "https://s3.us-east-1.amazonaws.com/")!, method: .GET, date:Date(timeIntervalSinceReferenceDate: 100000)) - #expect(url.absoluteString == "https://s3.us-east-1.amazonaws.com/?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=MYACCESSKEY%2F20010102%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20010102T034640Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host&X-Amz-Signature=27957103c8bfdff3560372b1d85976ed29c944f34295eca2d4fdac7fc02c375a") + let signer = AWSSigner(credentials: credentials, name: "s3", region: "us-east-1") + let url = signer.signURL( + url: URL(string: "https://s3.us-east-1.amazonaws.com/")!, + method: .GET, + date: Date(timeIntervalSinceReferenceDate: 100000) + ) + #expect( + url.absoluteString + == "https://s3.us-east-1.amazonaws.com/?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=MYACCESSKEY%2F20010102%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20010102T034640Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host&X-Amz-Signature=27957103c8bfdff3560372b1d85976ed29c944f34295eca2d4fdac7fc02c375a" + ) } - - @Test + + @Test func testSignS3PutURL() { - let signer = AWSSigner(credentials: credentials, name: "s3", region:"eu-west-1") - let url = signer.signURL(url: URL(string: "https://test-bucket.s3.amazonaws.com/test-put.txt")!, method: .PUT, body: .string("Testing signed URLs"), date:Date(timeIntervalSinceReferenceDate: 100000)) - #expect(url.absoluteString == "https://test-bucket.s3.amazonaws.com/test-put.txt?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=MYACCESSKEY%2F20010102%2Feu-west-1%2Fs3%2Faws4_request&X-Amz-Date=20010102T034640Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host&X-Amz-Signature=13d665549a6ea5eb6a1615ede83440eaed3e0ee25c964e62d188c896d916d96f") + let signer = AWSSigner(credentials: credentials, name: "s3", region: "eu-west-1") + let url = signer.signURL( + url: URL(string: "https://test-bucket.s3.amazonaws.com/test-put.txt")!, + method: .PUT, + body: .string("Testing signed URLs"), + date: Date(timeIntervalSinceReferenceDate: 100000) + ) + #expect( + url.absoluteString + == "https://test-bucket.s3.amazonaws.com/test-put.txt?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=MYACCESSKEY%2F20010102%2Feu-west-1%2Fs3%2Faws4_request&X-Amz-Date=20010102T034640Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host&X-Amz-Signature=13d665549a6ea5eb6a1615ede83440eaed3e0ee25c964e62d188c896d916d96f" + ) } -} \ No newline at end of file +} diff --git a/Tests/AWSLambdaPluginHelperTests/CryptoTests.swift b/Tests/AWSLambdaPluginHelperTests/CryptoTests.swift index 3e88285a..7a65e304 100644 --- a/Tests/AWSLambdaPluginHelperTests/CryptoTests.swift +++ b/Tests/AWSLambdaPluginHelperTests/CryptoTests.swift @@ -26,53 +26,53 @@ import Foundation @Suite struct CryptoTests { - @Test - func testSHA256() { - - // given - let input = "hello world" - let expected = "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9" - - // when - let result = Digest.sha256(input.array).toHexString() - - // then - #expect(result == expected) - } - - @Test - func testHMAC() throws { - - // given - let input = "hello world" - let secret = "secretkey" - let expected = "ae6cd2605d622316564d1f76bfc0c04f89d9fafb14f45b3e18c2a3e28bdef29d" - - // when - let authenticator = HMAC(key: secret.array, variant: .sha2(.sha256)) - - #expect(throws: Never.self) { - let result = try authenticator.authenticate(input.array).toHexString() - // then - #expect(result == expected) - } - - } - - @Test - func testHMACExtension() throws { - - // given - let input = "hello world" - let secret = "secretkey" - let expected = "ae6cd2605d622316564d1f76bfc0c04f89d9fafb14f45b3e18c2a3e28bdef29d" - - // when - let result = try HMAC.authenticate(for: input.array, using: secret.array).toHexString() - - // then - #expect(result == expected) - - } - -} \ No newline at end of file + @Test + func testSHA256() { + + // given + let input = "hello world" + let expected = "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9" + + // when + let result = Digest.sha256(input.array).toHexString() + + // then + #expect(result == expected) + } + + @Test + func testHMAC() throws { + + // given + let input = "hello world" + let secret = "secretkey" + let expected = "ae6cd2605d622316564d1f76bfc0c04f89d9fafb14f45b3e18c2a3e28bdef29d" + + // when + let authenticator = HMAC(key: secret.array, variant: .sha2(.sha256)) + + #expect(throws: Never.self) { + let result = try authenticator.authenticate(input.array).toHexString() + // then + #expect(result == expected) + } + + } + + @Test + func testHMACExtension() throws { + + // given + let input = "hello world" + let secret = "secretkey" + let expected = "ae6cd2605d622316564d1f76bfc0c04f89d9fafb14f45b3e18c2a3e28bdef29d" + + // when + let result = try HMAC.authenticate(for: input.array, using: secret.array).toHexString() + + // then + #expect(result == expected) + + } + +} From 2bdd645a4b542eb0dbf00ce1c85ab67cda354a22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Stormacq?= Date: Sun, 17 Nov 2024 22:06:26 +0100 Subject: [PATCH 06/10] WIP : migrate archive plugin to new lambda-build --- Package.swift | 57 +- Plugins/AWSLambdaBuilder/Plugin.swift | 179 ++++++ Plugins/AWSLambdaDeployer/Plugin.swift | 68 +-- Plugins/AWSLambdaDeployer/PluginUtils.swift | 156 ------ Plugins/AWSLambdaInitializer/Plugin.swift | 86 +-- Plugins/AWSLambdaPackager/Plugin.swift | 513 ------------------ .../AWSLambdaPluginHelper.swift | 57 ++ .../AWSLambdaPluginHelper/Process.swift | 1 - .../Vendored/crypto/Padding.swift | 27 +- .../Vendored/signer/AWSSigner.swift | 24 +- .../Vendored/spm/ArgumentExtractor.swift | 92 ++++ .../lambda-build/Builder.swift | 423 +++++++++++++++ .../lambda-deploy/Deployer.swift | 76 +++ .../lambda-init/Initializer.swift | 97 ++++ .../lambda-init}/Template.swift | 2 - Sources/AWSLambdaPluginHelper/main.swift | 1 - 16 files changed, 1031 insertions(+), 828 deletions(-) create mode 100644 Plugins/AWSLambdaBuilder/Plugin.swift delete mode 100644 Plugins/AWSLambdaDeployer/PluginUtils.swift delete mode 100644 Plugins/AWSLambdaPackager/Plugin.swift create mode 100644 Sources/AWSLambdaPluginHelper/AWSLambdaPluginHelper.swift rename Plugins/AWSLambdaPackager/PluginUtils.swift => Sources/AWSLambdaPluginHelper/Process.swift (99%) create mode 100644 Sources/AWSLambdaPluginHelper/Vendored/spm/ArgumentExtractor.swift create mode 100644 Sources/AWSLambdaPluginHelper/lambda-build/Builder.swift create mode 100644 Sources/AWSLambdaPluginHelper/lambda-deploy/Deployer.swift create mode 100644 Sources/AWSLambdaPluginHelper/lambda-init/Initializer.swift rename {Plugins/AWSLambdaInitializer => Sources/AWSLambdaPluginHelper/lambda-init}/Template.swift (98%) delete mode 100644 Sources/AWSLambdaPluginHelper/main.swift diff --git a/Package.swift b/Package.swift index 5f6f2d82..280977d4 100644 --- a/Package.swift +++ b/Package.swift @@ -12,32 +12,44 @@ let package = Package( name: "swift-aws-lambda-runtime", platforms: platforms, products: [ + + /* + The runtime library targets + */ + // this library exports `AWSLambdaRuntimeCore` and adds Foundation convenience methods .library(name: "AWSLambdaRuntime", targets: ["AWSLambdaRuntime"]), // this has all the main functionality for lambda and it does not link Foundation .library(name: "AWSLambdaRuntimeCore", targets: ["AWSLambdaRuntimeCore"]), + /* + The plugins + 'lambda-init' creates a new Lambda function + 'lambda-build' packages the Lambda function + 'lambda-deploy' deploys the Lambda function + + Plugins requires Linux or at least macOS v15 + + */ // plugin to create a new Lambda function, based on a template .plugin(name: "AWSLambdaInitializer", targets: ["AWSLambdaInitializer"]), // plugin to package the lambda, creating an archive that can be uploaded to AWS - // requires Linux or at least macOS v15 - .plugin(name: "AWSLambdaPackager", targets: ["AWSLambdaPackager"]), + .plugin(name: "AWSLambdaBuilder", targets: ["AWSLambdaBuilder"]), // plugin to deploy a Lambda function .plugin(name: "AWSLambdaDeployer", targets: ["AWSLambdaDeployer"]), - // an executable that implements the business logic for the plugins - .executable(name: "AWSLambdaPluginHelper", targets: ["AWSLambdaPluginHelper"]), - + /* + Testing targets + */ // for testing only .library(name: "AWSLambdaTesting", targets: ["AWSLambdaTesting"]), ], dependencies: [ .package(url: "https://github.com/apple/swift-nio.git", from: "2.76.0"), .package(url: "https://github.com/apple/swift-log.git", from: "1.5.4"), - // .package(url: "https://github.com/apple/swift-crypto.git", from: "3.9.1"), ], targets: [ .target( @@ -69,10 +81,32 @@ let package = Package( permissions: [ .writeToPackageDirectory(reason: "Create a file with an HelloWorld Lambda function.") ] - ) + ), + dependencies: [ + .target(name: "AWSLambdaPluginHelper") + ] ), + // keep this one (with "archive") to not break workflows + // This will be deprecated at some point in the future +// .plugin( +// name: "AWSLambdaPackager", +// capability: .command( +// intent: .custom( +// verb: "archive", +// description: +// "Archive the Lambda binary and prepare it for uploading to AWS. Requires docker on macOS or non Amazonlinux 2 distributions." +// ), +// permissions: [ +// .allowNetworkConnections( +// scope: .docker, +// reason: "This plugin uses Docker to create the AWS Lambda ZIP package." +// ) +// ] +// ), +// path: "Plugins/AWSLambdaBuilder" // same sources as the new "lambda-build" plugin +// ), .plugin( - name: "AWSLambdaPackager", + name: "AWSLambdaBuilder", capability: .command( intent: .custom( verb: "lambda-build", @@ -85,13 +119,16 @@ let package = Package( reason: "This plugin uses Docker to create the AWS Lambda ZIP package." ) ] - ) + ), + dependencies: [ + .target(name: "AWSLambdaPluginHelper") + ] ), .plugin( name: "AWSLambdaDeployer", capability: .command( intent: .custom( - verb: "deploy", + verb: "lambda-deploy", description: "Deploy the Lambda function. You must have an AWS account and know an access key and secret access key." ), diff --git a/Plugins/AWSLambdaBuilder/Plugin.swift b/Plugins/AWSLambdaBuilder/Plugin.swift new file mode 100644 index 00000000..8dd8a81c --- /dev/null +++ b/Plugins/AWSLambdaBuilder/Plugin.swift @@ -0,0 +1,179 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2022 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import PackagePlugin + +#if canImport(FoundationEssentials) +import FoundationEssentials +#else +import Foundation +#endif + +@main +struct AWSLambdaPackager: CommandPlugin { + + func performCommand(context: PackagePlugin.PluginContext, arguments: [String]) async throws { + + // values to pass to the AWSLambdaPluginHelper + let outputDirectory: URL + let products: [Product] + let buildConfiguration: PackageManager.BuildConfiguration + let packageID: String = context.package.id + let packageDisplayName = context.package.displayName + let packageDirectory = context.package.directoryURL + let dockerToolPath = try context.tool(named: "docker").url + let zipToolPath = try context.tool(named: "zip").url + + // extract arguments that require PluginContext to fully resolve + // resolve them here and pass them to the AWSLambdaPluginHelper as arguments + var argumentExtractor = ArgumentExtractor(arguments) + + let outputPathArgument = argumentExtractor.extractOption(named: "output-path") + let productsArgument = argumentExtractor.extractOption(named: "products") + let configurationArgument = argumentExtractor.extractOption(named: "configuration") + + if let outputPath = outputPathArgument.first { + #if os(Linux) + var isDirectory: Bool = false + #else + var isDirectory: ObjCBool = false + #endif + guard FileManager.default.fileExists(atPath: outputPath, isDirectory: &isDirectory) + else { + throw BuilderErrors.invalidArgument("invalid output directory '\(outputPath)'") + } + outputDirectory = URL(string: outputPath)! + } else { + outputDirectory = context.pluginWorkDirectoryURL.appending(path: "\(AWSLambdaPackager.self)") + } + + let explicitProducts = !productsArgument.isEmpty + if explicitProducts { + let _products = try context.package.products(named: productsArgument) + for product in _products { + guard product is ExecutableProduct else { + throw BuilderErrors.invalidArgument("product named '\(product.name)' is not an executable product") + } + } + products = _products + + } else { + products = context.package.products.filter { $0 is ExecutableProduct } + } + + if let _buildConfigurationName = configurationArgument.first { + guard let _buildConfiguration = PackageManager.BuildConfiguration(rawValue: _buildConfigurationName) else { + throw BuilderErrors.invalidArgument("invalid build configuration named '\(_buildConfigurationName)'") + } + buildConfiguration = _buildConfiguration + } else { + buildConfiguration = .release + } + + // TODO: When running on Amazon Linux 2, we have to build directly from the plugin + // launch the build, then call the helper just for the ZIP part + + let tool = try context.tool(named: "AWSLambdaPluginHelper") + let args = [ + "build", + "--output-path", outputDirectory.path(), + "--products", products.map { $0.name }.joined(separator: ","), + "--configuration", buildConfiguration.rawValue, + "--package-id", packageID, + "--package-display-name", packageDisplayName, + "--package-directory", packageDirectory.path(), + "--docker-tool-path", dockerToolPath.path, + "--zip-tool-path", zipToolPath.path + ] + arguments + + // Invoke the plugin helper on the target directory, passing a configuration + // file from the package directory. + let process = try Process.run(tool.url, arguments: args) + process.waitUntilExit() + + // Check whether the subprocess invocation was successful. + if !(process.terminationReason == .exit && process.terminationStatus == 0) { + let problem = "\(process.terminationReason):\(process.terminationStatus)" + Diagnostics.error("AWSLambdaPluginHelper invocation failed: \(problem)") + } + } + + // TODO: When running on Amazon Linux 2, we have to build directly from the plugin +// private func build( +// packageIdentity: Package.ID, +// products: [Product], +// buildConfiguration: PackageManager.BuildConfiguration, +// verboseLogging: Bool +// ) throws -> [LambdaProduct: URL] { +// print("-------------------------------------------------------------------------") +// print("building \"\(packageIdentity)\"") +// print("-------------------------------------------------------------------------") +// +// var results = [LambdaProduct: URL]() +// for product in products { +// print("building \"\(product.name)\"") +// var parameters = PackageManager.BuildParameters() +// parameters.configuration = buildConfiguration +// parameters.otherSwiftcFlags = ["-static-stdlib"] +// parameters.logging = verboseLogging ? .verbose : .concise +// +// let result = try packageManager.build( +// .product(product.name), +// parameters: parameters +// ) +// guard let artifact = result.executableArtifact(for: product) else { +// throw Errors.productExecutableNotFound(product.name) +// } +// results[.init(product)] = artifact.url +// } +// return results +// } + +// private func isAmazonLinux2() -> Bool { +// if let data = FileManager.default.contents(atPath: "/etc/system-release"), +// let release = String(data: data, encoding: .utf8) +// { +// return release.hasPrefix("Amazon Linux release 2") +// } else { +// return false +// } +// } +} + +private enum BuilderErrors: Error, CustomStringConvertible { + case invalidArgument(String) + + var description: String { + switch self { + case .invalidArgument(let description): + return description + } + } +} + +extension PackageManager.BuildResult { + // find the executable produced by the build + func executableArtifact(for product: Product) -> PackageManager.BuildResult.BuiltArtifact? { + let executables = self.builtArtifacts.filter { + $0.kind == .executable && $0.url.lastPathComponent == product.name + } + guard !executables.isEmpty else { + return nil + } + guard executables.count == 1, let executable = executables.first else { + return nil + } + return executable + } +} diff --git a/Plugins/AWSLambdaDeployer/Plugin.swift b/Plugins/AWSLambdaDeployer/Plugin.swift index 08cdc269..9ae59b0b 100644 --- a/Plugins/AWSLambdaDeployer/Plugin.swift +++ b/Plugins/AWSLambdaDeployer/Plugin.swift @@ -12,67 +12,33 @@ // //===----------------------------------------------------------------------===// -import Foundation import PackagePlugin +#if canImport(FoundationEssentials) +import FoundationEssentials +#else +import Foundation +#endif + @main -@available(macOS 15.0, *) struct AWSLambdaDeployer: CommandPlugin { func performCommand(context: PackagePlugin.PluginContext, arguments: [String]) async throws { - let configuration = try Configuration(context: context, arguments: arguments) - - if configuration.help { - self.displayHelpMessage() - return - } - let tool = try context.tool(named: "AWSLambdaDeployerHelper") - try Utils.execute(executable: tool.url, arguments: [], logLevel: .debug) - } + let tool = try context.tool(named: "AWSLambdaPluginHelper") - private func displayHelpMessage() { - print( - """ - OVERVIEW: A SwiftPM plugin to deploy a Lambda function. + let args = ["deploy"] + arguments - USAGE: swift package lambda-deploy - [--with-url] - [--help] [--verbose] + // Invoke the plugin helper on the target directory, passing a configuration + // file from the package directory. + let process = try Process.run(tool.url, arguments: args) + process.waitUntilExit() - OPTIONS: - --with-url Add an URL to access the Lambda function - --verbose Produce verbose output for debugging. - --help Show help information. - """ - ) - } -} - -private struct Configuration: CustomStringConvertible { - public let help: Bool - public let verboseLogging: Bool - - public init( - context: PluginContext, - arguments: [String] - ) throws { - var argumentExtractor = ArgumentExtractor(arguments) - let verboseArgument = argumentExtractor.extractFlag(named: "verbose") > 0 - let helpArgument = argumentExtractor.extractFlag(named: "help") > 0 - - // help required ? - self.help = helpArgument - - // verbose logging required ? - self.verboseLogging = verboseArgument - } - - var description: String { - """ - { - verboseLogging: \(self.verboseLogging) + // Check whether the subprocess invocation was successful. + if !(process.terminationReason == .exit && process.terminationStatus == 0) { + let problem = "\(process.terminationReason):\(process.terminationStatus)" + Diagnostics.error("AWSLambdaPluginHelper invocation failed: \(problem)") } - """ } + } diff --git a/Plugins/AWSLambdaDeployer/PluginUtils.swift b/Plugins/AWSLambdaDeployer/PluginUtils.swift deleted file mode 100644 index 52d1b2be..00000000 --- a/Plugins/AWSLambdaDeployer/PluginUtils.swift +++ /dev/null @@ -1,156 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the SwiftAWSLambdaRuntime open source project -// -// Copyright (c) 2022 Apple Inc. and the SwiftAWSLambdaRuntime project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -import Dispatch -import Foundation -import PackagePlugin -import Synchronization - -@available(macOS 15.0, *) -struct Utils { - @discardableResult - static func execute( - executable: URL, - arguments: [String], - customWorkingDirectory: URL? = .none, - logLevel: ProcessLogLevel - ) throws -> String { - if logLevel >= .debug { - print("\(executable.path()) \(arguments.joined(separator: " "))") - } - - let fd = dup(1) - let stdout = fdopen(fd, "rw") - defer { if let so = stdout { fclose(so) } } - - // We need to use an unsafe transfer here to get the fd into our Sendable closure. - // This transfer is fine, because we write to the variable from a single SerialDispatchQueue here. - // We wait until the process is run below process.waitUntilExit(). - // This means no further writes to output will happen. - // This makes it save for us to read the output - struct UnsafeTransfer: @unchecked Sendable { - let value: Value - } - - let outputMutex = Mutex("") - let outputSync = DispatchGroup() - let outputQueue = DispatchQueue(label: "AWSLambdaPackager.output") - let unsafeTransfer = UnsafeTransfer(value: stdout) - let outputHandler = { @Sendable (data: Data?) in - dispatchPrecondition(condition: .onQueue(outputQueue)) - - outputSync.enter() - defer { outputSync.leave() } - - guard - let _output = data.flatMap({ - String(data: $0, encoding: .utf8)?.trimmingCharacters(in: CharacterSet(["\n"])) - }), !_output.isEmpty - else { - return - } - - outputMutex.withLock { output in - output += _output + "\n" - } - - switch logLevel { - case .silent: - break - case .debug(let outputIndent), .output(let outputIndent): - print(String(repeating: " ", count: outputIndent), terminator: "") - print(_output) - fflush(unsafeTransfer.value) - } - } - - let pipe = Pipe() - pipe.fileHandleForReading.readabilityHandler = { fileHandle in - outputQueue.async { outputHandler(fileHandle.availableData) } - } - - let process = Process() - process.standardOutput = pipe - process.standardError = pipe - process.executableURL = executable - process.arguments = arguments - if let workingDirectory = customWorkingDirectory { - process.currentDirectoryURL = URL(fileURLWithPath: workingDirectory.path()) - } - process.terminationHandler = { _ in - outputQueue.async { - outputHandler(try? pipe.fileHandleForReading.readToEnd()) - } - } - - try process.run() - process.waitUntilExit() - - // wait for output to be full processed - outputSync.wait() - - let output = outputMutex.withLock { $0 } - - if process.terminationStatus != 0 { - // print output on failure and if not already printed - if logLevel < .output { - print(output) - fflush(stdout) - } - throw ProcessError.processFailed([executable.path()] + arguments, process.terminationStatus) - } - - return output - } - - enum ProcessError: Error, CustomStringConvertible { - case processFailed([String], Int32) - - var description: String { - switch self { - case .processFailed(let arguments, let code): - return "\(arguments.joined(separator: " ")) failed with code \(code)" - } - } - } - - enum ProcessLogLevel: Comparable { - case silent - case output(outputIndent: Int) - case debug(outputIndent: Int) - - var naturalOrder: Int { - switch self { - case .silent: - return 0 - case .output: - return 1 - case .debug: - return 2 - } - } - - static var output: Self { - .output(outputIndent: 2) - } - - static var debug: Self { - .debug(outputIndent: 2) - } - - static func < (lhs: ProcessLogLevel, rhs: ProcessLogLevel) -> Bool { - lhs.naturalOrder < rhs.naturalOrder - } - } -} diff --git a/Plugins/AWSLambdaInitializer/Plugin.swift b/Plugins/AWSLambdaInitializer/Plugin.swift index 21713c0f..bbfa90e2 100644 --- a/Plugins/AWSLambdaInitializer/Plugin.swift +++ b/Plugins/AWSLambdaInitializer/Plugin.swift @@ -12,80 +12,32 @@ // //===----------------------------------------------------------------------===// -import Foundation import PackagePlugin +#if canImport(FoundationEssentials) +import FoundationEssentials +#else +import Foundation +#endif + @main -@available(macOS 15.0, *) struct AWSLambdaPackager: CommandPlugin { - let destFileName = "Sources/main.swift" - func performCommand(context: PackagePlugin.PluginContext, arguments: [String]) async throws { - let configuration = try Configuration(context: context, arguments: arguments) - - if configuration.help { - self.displayHelpMessage() - return + let tool = try context.tool(named: "AWSLambdaPluginHelper") + + let args = ["init", "--dest-dir", context.package.directoryURL.path()] + arguments + + // Invoke the plugin helper on the target directory, passing a configuration + // file from the package directory. + let process = try Process.run(tool.url, arguments: args) + process.waitUntilExit() + + // Check whether the subprocess invocation was successful. + if !(process.terminationReason == .exit && process.terminationStatus == 0) { + let problem = "\(process.terminationReason):\(process.terminationStatus)" + Diagnostics.error("AWSLambdaPluginHelper invocation failed: \(problem)") } - - let destFileURL = context.package.directoryURL.appendingPathComponent(destFileName) - do { - try functionWithUrlTemplate.write(to: destFileURL, atomically: true, encoding: .utf8) - - if configuration.verboseLogging { - Diagnostics.progress("✅ Lambda function written to \(destFileName)") - Diagnostics.progress("📦 You can now package with: 'swift package archive'") - } - - } catch { - Diagnostics.error("🛑Failed to create the Lambda function file: \(error)") - } - } - - private func displayHelpMessage() { - print( - """ - OVERVIEW: A SwiftPM plugin to scaffold a HelloWorld Lambda function. - - USAGE: swift package lambda-init - [--help] [--verbose] - [--allow-writing-to-package-directory] - - OPTIONS: - --allow-writing-to-package-directory Don't ask for permissions to write files. - --verbose Produce verbose output for debugging. - --help Show help information. - """ - ) } - } -private struct Configuration: CustomStringConvertible { - public let help: Bool - public let verboseLogging: Bool - - public init( - context: PluginContext, - arguments: [String] - ) throws { - var argumentExtractor = ArgumentExtractor(arguments) - let verboseArgument = argumentExtractor.extractFlag(named: "verbose") > 0 - let helpArgument = argumentExtractor.extractFlag(named: "help") > 0 - - // help required ? - self.help = helpArgument - - // verbose logging required ? - self.verboseLogging = verboseArgument - } - - var description: String { - """ - { - verboseLogging: \(self.verboseLogging) - } - """ - } -} diff --git a/Plugins/AWSLambdaPackager/Plugin.swift b/Plugins/AWSLambdaPackager/Plugin.swift deleted file mode 100644 index a8693945..00000000 --- a/Plugins/AWSLambdaPackager/Plugin.swift +++ /dev/null @@ -1,513 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the SwiftAWSLambdaRuntime open source project -// -// Copyright (c) 2022 Apple Inc. and the SwiftAWSLambdaRuntime project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -import Foundation -import PackagePlugin - -@main -@available(macOS 15.0, *) -struct AWSLambdaPackager: CommandPlugin { - func performCommand(context: PackagePlugin.PluginContext, arguments: [String]) async throws { - let configuration = try Configuration(context: context, arguments: arguments) - - if configuration.help { - self.displayHelpMessage() - return - } - - guard !configuration.products.isEmpty else { - throw Errors.unknownProduct("no appropriate products found to package") - } - - if configuration.products.count > 1 && !configuration.explicitProducts { - let productNames = configuration.products.map(\.name) - print( - "No explicit products named, building all executable products: '\(productNames.joined(separator: "', '"))'" - ) - } - - let builtProducts: [LambdaProduct: URL] - if self.isAmazonLinux2() { - // build directly on the machine - builtProducts = try self.build( - packageIdentity: context.package.id, - products: configuration.products, - buildConfiguration: configuration.buildConfiguration, - verboseLogging: configuration.verboseLogging - ) - } else { - // build with docker - builtProducts = try self.buildInDocker( - packageIdentity: context.package.id, - packageDirectory: context.package.directoryURL, - products: configuration.products, - toolsProvider: { name in try context.tool(named: name).url }, - outputDirectory: configuration.outputDirectory, - baseImage: configuration.baseDockerImage, - disableDockerImageUpdate: configuration.disableDockerImageUpdate, - buildConfiguration: configuration.buildConfiguration, - verboseLogging: configuration.verboseLogging - ) - } - - // create the archive - let archives = try self.package( - packageName: context.package.displayName, - products: builtProducts, - toolsProvider: { name in try context.tool(named: name).url }, - outputDirectory: configuration.outputDirectory, - verboseLogging: configuration.verboseLogging - ) - - print( - "\(archives.count > 0 ? archives.count.description : "no") archive\(archives.count != 1 ? "s" : "") created" - ) - for (product, archivePath) in archives { - print(" * \(product.name) at \(archivePath.path())") - } - } - - private func buildInDocker( - packageIdentity: Package.ID, - packageDirectory: URL, - products: [Product], - toolsProvider: (String) throws -> URL, - outputDirectory: URL, - baseImage: String, - disableDockerImageUpdate: Bool, - buildConfiguration: PackageManager.BuildConfiguration, - verboseLogging: Bool - ) throws -> [LambdaProduct: URL] { - let dockerToolPath = try toolsProvider("docker") - - print("-------------------------------------------------------------------------") - print("building \"\(packageIdentity)\" in docker") - print("-------------------------------------------------------------------------") - - if !disableDockerImageUpdate { - // update the underlying docker image, if necessary - print("updating \"\(baseImage)\" docker image") - try Utils.execute( - executable: dockerToolPath, - arguments: ["pull", baseImage], - logLevel: verboseLogging ? .debug : .output - ) - } - - // get the build output path - let buildOutputPathCommand = "swift build -c \(buildConfiguration.rawValue) --show-bin-path" - let dockerBuildOutputPath = try Utils.execute( - executable: dockerToolPath, - arguments: [ - "run", "--rm", "-v", "\(packageDirectory.path()):/workspace", "-w", "/workspace", baseImage, "bash", - "-cl", buildOutputPathCommand, - ], - logLevel: verboseLogging ? .debug : .silent - ) - guard let buildPathOutput = dockerBuildOutputPath.split(separator: "\n").last else { - throw Errors.failedParsingDockerOutput(dockerBuildOutputPath) - } - let buildOutputPath = URL( - string: buildPathOutput.replacingOccurrences(of: "/workspace/", with: packageDirectory.description) - )! - - // build the products - var builtProducts = [LambdaProduct: URL]() - for product in products { - print("building \"\(product.name)\"") - let buildCommand = - "swift build -c \(buildConfiguration.rawValue) --product \(product.name) --static-swift-stdlib" - if let localPath = ProcessInfo.processInfo.environment["LAMBDA_USE_LOCAL_DEPS"] { - // when developing locally, we must have the full swift-aws-lambda-runtime project in the container - // because Examples' Package.swift have a dependency on ../.. - // just like Package.swift's examples assume ../.., we assume we are two levels below the root project - let slice = packageDirectory.pathComponents.suffix(2) - try Utils.execute( - executable: dockerToolPath, - arguments: [ - "run", "--rm", "--env", "LAMBDA_USE_LOCAL_DEPS=\(localPath)", "-v", - "\(packageDirectory.path())../..:/workspace", "-w", - "/workspace/\(slice.joined(separator: "/"))", baseImage, "bash", "-cl", buildCommand, - ], - logLevel: verboseLogging ? .debug : .output - ) - } else { - try Utils.execute( - executable: dockerToolPath, - arguments: [ - "run", "--rm", "-v", "\(packageDirectory.path()):/workspace", "-w", "/workspace", baseImage, - "bash", "-cl", buildCommand, - ], - logLevel: verboseLogging ? .debug : .output - ) - } - let productPath = buildOutputPath.appending(path: product.name) - - guard FileManager.default.fileExists(atPath: productPath.path()) else { - Diagnostics.error("expected '\(product.name)' binary at \"\(productPath.path())\"") - throw Errors.productExecutableNotFound(product.name) - } - builtProducts[.init(product)] = productPath - } - return builtProducts - } - - private func build( - packageIdentity: Package.ID, - products: [Product], - buildConfiguration: PackageManager.BuildConfiguration, - verboseLogging: Bool - ) throws -> [LambdaProduct: URL] { - print("-------------------------------------------------------------------------") - print("building \"\(packageIdentity)\"") - print("-------------------------------------------------------------------------") - - var results = [LambdaProduct: URL]() - for product in products { - print("building \"\(product.name)\"") - var parameters = PackageManager.BuildParameters() - parameters.configuration = buildConfiguration - parameters.otherSwiftcFlags = ["-static-stdlib"] - parameters.logging = verboseLogging ? .verbose : .concise - - let result = try packageManager.build( - .product(product.name), - parameters: parameters - ) - guard let artifact = result.executableArtifact(for: product) else { - throw Errors.productExecutableNotFound(product.name) - } - results[.init(product)] = artifact.url - } - return results - } - - // TODO: explore using ziplib or similar instead of shelling out - private func package( - packageName: String, - products: [LambdaProduct: URL], - toolsProvider: (String) throws -> URL, - outputDirectory: URL, - verboseLogging: Bool - ) throws -> [LambdaProduct: URL] { - let zipToolPath = try toolsProvider("zip") - - var archives = [LambdaProduct: URL]() - for (product, artifactPath) in products { - print("-------------------------------------------------------------------------") - print("archiving \"\(product.name)\"") - print("-------------------------------------------------------------------------") - - // prep zipfile location - let workingDirectory = outputDirectory.appending(path: product.name) - let zipfilePath = workingDirectory.appending(path: "\(product.name).zip") - if FileManager.default.fileExists(atPath: workingDirectory.path()) { - try FileManager.default.removeItem(atPath: workingDirectory.path()) - } - try FileManager.default.createDirectory(atPath: workingDirectory.path(), withIntermediateDirectories: true) - - // rename artifact to "bootstrap" - let relocatedArtifactPath = workingDirectory.appending(path: "bootstrap") - try FileManager.default.copyItem(atPath: artifactPath.path(), toPath: relocatedArtifactPath.path()) - - var arguments: [String] = [] - #if os(macOS) || os(Linux) - arguments = [ - "--recurse-paths", - "--symlinks", - zipfilePath.lastPathComponent, - relocatedArtifactPath.lastPathComponent, - ] - #else - throw Errors.unsupportedPlatform("can't or don't know how to create a zip file on this platform") - #endif - - // add resources - var artifactPathComponents = artifactPath.pathComponents - _ = artifactPathComponents.removeFirst() // Get rid of beginning "/" - _ = artifactPathComponents.removeLast() // Get rid of the name of the package - let artifactDirectory = "/\(artifactPathComponents.joined(separator: "/"))" - for fileInArtifactDirectory in try FileManager.default.contentsOfDirectory(atPath: artifactDirectory) { - guard let artifactURL = URL(string: "\(artifactDirectory)/\(fileInArtifactDirectory)") else { - continue - } - - guard artifactURL.pathExtension == "resources" else { - continue // Not resources, so don't copy - } - let resourcesDirectoryName = artifactURL.lastPathComponent - let relocatedResourcesDirectory = workingDirectory.appending(path: resourcesDirectoryName) - if FileManager.default.fileExists(atPath: artifactURL.path()) { - try FileManager.default.copyItem( - atPath: artifactURL.path(), - toPath: relocatedResourcesDirectory.path() - ) - arguments.append(resourcesDirectoryName) - } - } - - // run the zip tool - try Utils.execute( - executable: zipToolPath, - arguments: arguments, - customWorkingDirectory: workingDirectory, - logLevel: verboseLogging ? .debug : .silent - ) - - archives[product] = zipfilePath - } - return archives - } - - private func isAmazonLinux2() -> Bool { - if let data = FileManager.default.contents(atPath: "/etc/system-release"), - let release = String(data: data, encoding: .utf8) - { - return release.hasPrefix("Amazon Linux release 2") - } else { - return false - } - } - - private func displayHelpMessage() { - print( - """ - OVERVIEW: A SwiftPM plugin to build and package your lambda function. - - REQUIREMENTS: To use this plugin, you must have docker installed and started. - - USAGE: swift package --allow-network-connections docker archive - [--help] [--verbose] - [--output-directory ] - [--products ] - [--configuration debug | release] - [--swift-version ] - [--base-docker-image ] - [--disable-docker-image-update] - - - OPTIONS: - --verbose Produce verbose output for debugging. - --output-directory The path of the binary package. - (default is `.build/plugins/AWSLambdaPackager/outputs/...`) - --products The list of executable targets to build. - (default is taken from Package.swift) - --configuration The build configuration (debug or release) - (default is release) - --swift-version The swift version to use for building. - (default is latest) - This parameter cannot be used when --base-docker-image is specified. - --base-docker-image The name of the base docker image to use for the build. - (default : swift-:amazonlinux2) - This parameter cannot be used when --swift-version is specified. - --disable-docker-image-update Do not attempt to update the docker image - --help Show help information. - """ - ) - } -} - -@available(macOS 15.0, *) -private struct Configuration: CustomStringConvertible { - public let help: Bool - public let outputDirectory: URL - public let products: [Product] - public let explicitProducts: Bool - public let buildConfiguration: PackageManager.BuildConfiguration - public let verboseLogging: Bool - public let baseDockerImage: String - public let disableDockerImageUpdate: Bool - - public init( - context: PluginContext, - arguments: [String] - ) throws { - var argumentExtractor = ArgumentExtractor(arguments) - let verboseArgument = argumentExtractor.extractFlag(named: "verbose") > 0 - let outputPathArgument = argumentExtractor.extractOption(named: "output-path") - let productsArgument = argumentExtractor.extractOption(named: "products") - let configurationArgument = argumentExtractor.extractOption(named: "configuration") - let swiftVersionArgument = argumentExtractor.extractOption(named: "swift-version") - let baseDockerImageArgument = argumentExtractor.extractOption(named: "base-docker-image") - let disableDockerImageUpdateArgument = argumentExtractor.extractFlag(named: "disable-docker-image-update") > 0 - let helpArgument = argumentExtractor.extractFlag(named: "help") > 0 - - // help required ? - self.help = helpArgument - - // verbose logging required ? - self.verboseLogging = verboseArgument - - if let outputPath = outputPathArgument.first { - #if os(Linux) - var isDirectory: Bool = false - #else - var isDirectory: ObjCBool = false - #endif - guard FileManager.default.fileExists(atPath: outputPath, isDirectory: &isDirectory) - else { - throw Errors.invalidArgument("invalid output directory '\(outputPath)'") - } - self.outputDirectory = URL(string: outputPath)! - } else { - self.outputDirectory = context.pluginWorkDirectoryURL.appending(path: "\(AWSLambdaPackager.self)") - } - - self.explicitProducts = !productsArgument.isEmpty - if self.explicitProducts { - let products = try context.package.products(named: productsArgument) - for product in products { - guard product is ExecutableProduct else { - throw Errors.invalidArgument("product named '\(product.name)' is not an executable product") - } - } - self.products = products - - } else { - self.products = context.package.products.filter { $0 is ExecutableProduct } - } - - if let buildConfigurationName = configurationArgument.first { - guard let buildConfiguration = PackageManager.BuildConfiguration(rawValue: buildConfigurationName) else { - throw Errors.invalidArgument("invalid build configuration named '\(buildConfigurationName)'") - } - self.buildConfiguration = buildConfiguration - } else { - self.buildConfiguration = .release - } - - guard !(!swiftVersionArgument.isEmpty && !baseDockerImageArgument.isEmpty) else { - throw Errors.invalidArgument("--swift-version and --base-docker-image are mutually exclusive") - } - - let swiftVersion = swiftVersionArgument.first ?? .none // undefined version will yield the latest docker image - self.baseDockerImage = - baseDockerImageArgument.first ?? "swift:\(swiftVersion.map { $0 + "-" } ?? "")amazonlinux2" - - self.disableDockerImageUpdate = disableDockerImageUpdateArgument - - if self.verboseLogging { - print("-------------------------------------------------------------------------") - print("configuration") - print("-------------------------------------------------------------------------") - print(self) - } - } - - var description: String { - """ - { - outputDirectory: \(self.outputDirectory) - products: \(self.products.map(\.name)) - buildConfiguration: \(self.buildConfiguration) - baseDockerImage: \(self.baseDockerImage) - disableDockerImageUpdate: \(self.disableDockerImageUpdate) - } - """ - } -} - -private enum ProcessLogLevel: Comparable { - case silent - case output(outputIndent: Int) - case debug(outputIndent: Int) - - var naturalOrder: Int { - switch self { - case .silent: - return 0 - case .output: - return 1 - case .debug: - return 2 - } - } - - static var output: Self { - .output(outputIndent: 2) - } - - static var debug: Self { - .debug(outputIndent: 2) - } - - static func < (lhs: ProcessLogLevel, rhs: ProcessLogLevel) -> Bool { - lhs.naturalOrder < rhs.naturalOrder - } -} - -private enum Errors: Error, CustomStringConvertible { - case invalidArgument(String) - case unsupportedPlatform(String) - case unknownProduct(String) - case productExecutableNotFound(String) - case failedWritingDockerfile - case failedParsingDockerOutput(String) - case processFailed([String], Int32) - - var description: String { - switch self { - case .invalidArgument(let description): - return description - case .unsupportedPlatform(let description): - return description - case .unknownProduct(let description): - return description - case .productExecutableNotFound(let product): - return "product executable not found '\(product)'" - case .failedWritingDockerfile: - return "failed writing dockerfile" - case .failedParsingDockerOutput(let output): - return "failed parsing docker output: '\(output)'" - case .processFailed(let arguments, let code): - return "\(arguments.joined(separator: " ")) failed with code \(code)" - } - } -} - -private struct LambdaProduct: Hashable { - let underlying: Product - - init(_ underlying: Product) { - self.underlying = underlying - } - - var name: String { - self.underlying.name - } - - func hash(into hasher: inout Hasher) { - self.underlying.id.hash(into: &hasher) - } - - static func == (lhs: Self, rhs: Self) -> Bool { - lhs.underlying.id == rhs.underlying.id - } -} - -extension PackageManager.BuildResult { - // find the executable produced by the build - func executableArtifact(for product: Product) -> PackageManager.BuildResult.BuiltArtifact? { - let executables = self.builtArtifacts.filter { - $0.kind == .executable && $0.url.lastPathComponent == product.name - } - guard !executables.isEmpty else { - return nil - } - guard executables.count == 1, let executable = executables.first else { - return nil - } - return executable - } -} diff --git a/Sources/AWSLambdaPluginHelper/AWSLambdaPluginHelper.swift b/Sources/AWSLambdaPluginHelper/AWSLambdaPluginHelper.swift new file mode 100644 index 00000000..71b5af7e --- /dev/null +++ b/Sources/AWSLambdaPluginHelper/AWSLambdaPluginHelper.swift @@ -0,0 +1,57 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +@main +struct AWSLambdaPluginHelper { + + private enum Command: String { + case `init` + case build + case deploy + } + + public static func main() async throws { + let args = CommandLine.arguments + let helper = AWSLambdaPluginHelper() + let command = try helper.command(from: args) + switch command { + case .`init`: + try await Initializer().initialize(arguments: args) + case .build: + try await Builder().build(arguments: args) + case .deploy: + try await Deployer().deploy(arguments: args) + } + } + + private func command(from arguments: [String]) throws -> Command { + let args = CommandLine.arguments + + guard args.count > 2 else { + throw AWSLambdaPluginHelperError.noCommand + } + let commandName = args[1] + guard let command = Command(rawValue: commandName) else { + throw AWSLambdaPluginHelperError.invalidCommand(commandName) + } + + return command + } +} + +private enum AWSLambdaPluginHelperError: Error { + case noCommand + case invalidCommand(String) +} + diff --git a/Plugins/AWSLambdaPackager/PluginUtils.swift b/Sources/AWSLambdaPluginHelper/Process.swift similarity index 99% rename from Plugins/AWSLambdaPackager/PluginUtils.swift rename to Sources/AWSLambdaPluginHelper/Process.swift index f4e8cb02..235ae056 100644 --- a/Plugins/AWSLambdaPackager/PluginUtils.swift +++ b/Sources/AWSLambdaPluginHelper/Process.swift @@ -14,7 +14,6 @@ import Dispatch import Foundation -import PackagePlugin import Synchronization @available(macOS 15.0, *) diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/Padding.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Padding.swift index be90ba37..679e161f 100644 --- a/Sources/AWSLambdaPluginHelper/Vendored/crypto/Padding.swift +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Padding.swift @@ -33,26 +33,13 @@ public protocol PaddingProtocol { } public enum Padding: PaddingProtocol { - case noPadding, zeroPadding //, pkcs7, pkcs5, eme_pkcs1v15, emsa_pkcs1v15, iso78164, iso10126 - + case noPadding, zeroPadding public func add(to: [UInt8], blockSize: Int) -> [UInt8] { switch self { case .noPadding: return to // NoPadding().add(to: to, blockSize: blockSize) case .zeroPadding: return ZeroPadding().add(to: to, blockSize: blockSize) - // case .pkcs7: - // return PKCS7.Padding().add(to: to, blockSize: blockSize) - // case .pkcs5: - // return PKCS5.Padding().add(to: to, blockSize: blockSize) - // case .eme_pkcs1v15: - // return EMEPKCS1v15Padding().add(to: to, blockSize: blockSize) - // case .emsa_pkcs1v15: - // return EMSAPKCS1v15Padding().add(to: to, blockSize: blockSize) - // case .iso78164: - // return ISO78164Padding().add(to: to, blockSize: blockSize) - // case .iso10126: - // return ISO10126Padding().add(to: to, blockSize: blockSize) } } @@ -62,18 +49,6 @@ public enum Padding: PaddingProtocol { return from //NoPadding().remove(from: from, blockSize: blockSize) case .zeroPadding: return ZeroPadding().remove(from: from, blockSize: blockSize) - // case .pkcs7: - // return PKCS7.Padding().remove(from: from, blockSize: blockSize) - // case .pkcs5: - // return PKCS5.Padding().remove(from: from, blockSize: blockSize) - // case .eme_pkcs1v15: - // return EMEPKCS1v15Padding().remove(from: from, blockSize: blockSize) - // case .emsa_pkcs1v15: - // return EMSAPKCS1v15Padding().remove(from: from, blockSize: blockSize) - // case .iso78164: - // return ISO78164Padding().remove(from: from, blockSize: blockSize) - // case .iso10126: - // return ISO10126Padding().remove(from: from, blockSize: blockSize) } } } diff --git a/Sources/AWSLambdaPluginHelper/Vendored/signer/AWSSigner.swift b/Sources/AWSLambdaPluginHelper/Vendored/signer/AWSSigner.swift index e06d5b30..7196982e 100644 --- a/Sources/AWSLambdaPluginHelper/Vendored/signer/AWSSigner.swift +++ b/Sources/AWSLambdaPluginHelper/Vendored/signer/AWSSigner.swift @@ -22,7 +22,6 @@ // https://docs.aws.amazon.com/general/latest/gr/signing_aws_api_requests.html // -import Crypto import NIO import NIOHTTP1 @@ -34,6 +33,29 @@ import struct Foundation.Locale import struct Foundation.TimeZone import struct Foundation.URL +// adapter for the vendored SHA256 hashing function +private struct SHA256 { + static func hash(data: [UInt8]) -> [UInt8] { + Digest.sha256(data) + } + + static func hash(data: Data) -> [UInt8] { + SHA256.hash(data: data.bytes) + } + + static func hash(data: UnsafeBufferPointer) -> [UInt8] { + SHA256.hash(data: [UInt8](data)) + } +} + +// adapter for the vendored hexDigest function +extension Array where Element == UInt8 { + fileprivate func hexDigest() -> String { + // call the hexEncoded function from the Array+Extensions.swift file + self.toHexString() + } +} + /// Amazon Web Services V4 Signer public struct AWSSigner { /// security credentials for accessing AWS services diff --git a/Sources/AWSLambdaPluginHelper/Vendored/spm/ArgumentExtractor.swift b/Sources/AWSLambdaPluginHelper/Vendored/spm/ArgumentExtractor.swift new file mode 100644 index 00000000..8406692c --- /dev/null +++ b/Sources/AWSLambdaPluginHelper/Vendored/spm/ArgumentExtractor.swift @@ -0,0 +1,92 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2022 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +/// A rudimentary helper for extracting options and flags from a string list representing command line arguments. The idea is to extract all known options and flags, leaving just the positional arguments. This does not handle well the case in which positional arguments (or option argument values) happen to have the same name as an option or a flag. It only handles the long `--` form of options, but it does respect `--` as an indication that all remaining arguments are positional. +public struct ArgumentExtractor { + private var args: [String] + private let literals: [String] + + /// Initializes a ArgumentExtractor with a list of strings from which to extract flags and options. If the list contains `--`, any arguments that follow it are considered to be literals. + public init(_ arguments: [String]) { + // Split the array on the first `--`, if there is one. Everything after that is a literal. + let parts = arguments.split(separator: "--", maxSplits: 1, omittingEmptySubsequences: false) + self.args = Array(parts[0]) + self.literals = Array(parts.count == 2 ? parts[1] : []) + } + + /// Extracts options of the form `-- ` or `--=` from the remaining arguments, and returns the extracted values. + public mutating func extractOption(named name: String) -> [String] { + var values: [String] = [] + var idx = 0 + while idx < args.count { + var arg = args[idx] + if arg == "--\(name)" { + args.remove(at: idx) + if idx < args.count { + let val = args[idx] + values.append(val) + args.remove(at: idx) + } + } + else if arg.starts(with: "--\(name)=") { + arg.removeFirst(2 + name.count + 1) + values.append(arg) + args.remove(at: idx) + } + else { + idx += 1 + } + } + return values + } + + /// Extracts flags of the form `--` from the remaining arguments, and returns the count. + public mutating func extractFlag(named name: String) -> Int { + var count = 0 + var idx = 0 + while idx < args.count { + let arg = args[idx] + if arg == "--\(name)" { + args.remove(at: idx) + count += 1 + } + else { + idx += 1 + } + } + return count + } + + /// Returns any unextracted flags or options (based strictly on whether remaining arguments have a "--" prefix). + public var unextractedOptionsOrFlags: [String] { + return args.filter{ $0.hasPrefix("--") } + } + + /// Returns all remaining arguments, including any literals after the first `--` if there is one. + public var remainingArguments: [String] { + return args + literals + } +} \ No newline at end of file diff --git a/Sources/AWSLambdaPluginHelper/lambda-build/Builder.swift b/Sources/AWSLambdaPluginHelper/lambda-build/Builder.swift new file mode 100644 index 00000000..fb8ff73a --- /dev/null +++ b/Sources/AWSLambdaPluginHelper/lambda-build/Builder.swift @@ -0,0 +1,423 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +#if canImport(FoundationEssentials) +import FoundationEssentials +#else +import Foundation +#endif + +@available(macOS 15.0, *) +struct Builder { + func build(arguments: [String]) async throws { + let configuration = try BuilderConfiguration(arguments: arguments) + + if configuration.help { + self.displayHelpMessage() + return + } + + let builtProducts: [String: URL] + + // build with docker + // TODO: check if dockerToolPath is provided + // When not provided, it means we're building on Amazon Linux 2 + builtProducts = try self.buildInDocker( + packageIdentity: configuration.packageID, + packageDirectory: configuration.packageDirectory, + products: configuration.products, + dockerToolPath: configuration.dockerToolPath, + outputDirectory: configuration.outputDirectory, + baseImage: configuration.baseDockerImage, + disableDockerImageUpdate: configuration.disableDockerImageUpdate, + buildConfiguration: configuration.buildConfiguration, + verboseLogging: configuration.verboseLogging + ) + + // create the archive + let archives = try self.package( + packageName: configuration.packageDisplayName, + products: builtProducts, + zipToolPath: configuration.zipToolPath, + outputDirectory: configuration.outputDirectory, + verboseLogging: configuration.verboseLogging + ) + + print( + "\(archives.count > 0 ? archives.count.description : "no") archive\(archives.count != 1 ? "s" : "") created" + ) + for (product, archivePath) in archives { + print(" * \(product) at \(archivePath.path())") + } + } + + private func buildInDocker( + packageIdentity: String, + packageDirectory: URL, + products: [String], + dockerToolPath: URL, + outputDirectory: URL, + baseImage: String, + disableDockerImageUpdate: Bool, + buildConfiguration: BuildConfiguration, + verboseLogging: Bool + ) throws -> [String: URL] { + + print("-------------------------------------------------------------------------") + print("building \"\(packageIdentity)\" in docker") + print("-------------------------------------------------------------------------") + + if !disableDockerImageUpdate { + // update the underlying docker image, if necessary + print("updating \"\(baseImage)\" docker image") + try Utils.execute( + executable: dockerToolPath, + arguments: ["pull", baseImage], + logLevel: verboseLogging ? .debug : .silent + ) + } + + // get the build output path + let buildOutputPathCommand = "swift build -c \(buildConfiguration.rawValue) --show-bin-path" + let dockerBuildOutputPath = try Utils.execute( + executable: dockerToolPath, + arguments: [ + "run", "--rm", "-v", "\(packageDirectory.path()):/workspace", "-w", "/workspace", baseImage, "bash", + "-cl", buildOutputPathCommand, + ], + logLevel: verboseLogging ? .debug : .silent + ) + guard let buildPathOutput = dockerBuildOutputPath.split(separator: "\n").last else { + throw BuilderErrors.failedParsingDockerOutput(dockerBuildOutputPath) + } + let buildOutputPath = URL( + string: buildPathOutput.replacingOccurrences(of: "/workspace/", with: packageDirectory.description) + )! + + // build the products + var builtProducts = [String: URL]() + for product in products { + print("building \"\(product)\"") + let buildCommand = + "swift build -c \(buildConfiguration.rawValue) --product \(product) --static-swift-stdlib" + if let localPath = ProcessInfo.processInfo.environment["LAMBDA_USE_LOCAL_DEPS"] { + // when developing locally, we must have the full swift-aws-lambda-runtime project in the container + // because Examples' Package.swift have a dependency on ../.. + // just like Package.swift's examples assume ../.., we assume we are two levels below the root project + let slice = packageDirectory.pathComponents.suffix(2) + try Utils.execute( + executable: dockerToolPath, + arguments: [ + "run", "--rm", "--env", "LAMBDA_USE_LOCAL_DEPS=\(localPath)", "-v", + "\(packageDirectory.path())../..:/workspace", "-w", + "/workspace/\(slice.joined(separator: "/"))", baseImage, "bash", "-cl", buildCommand, + ], + logLevel: verboseLogging ? .debug : .output + ) + } else { + try Utils.execute( + executable: dockerToolPath, + arguments: [ + "run", "--rm", "-v", "\(packageDirectory.path()):/workspace", "-w", "/workspace", baseImage, + "bash", "-cl", buildCommand, + ], + logLevel: verboseLogging ? .debug : .output + ) + } + let productPath = buildOutputPath.appending(path: product) + + guard FileManager.default.fileExists(atPath: productPath.path()) else { + print("expected '\(product)' binary at \"\(productPath.path())\"") + throw BuilderErrors.productExecutableNotFound(product) + } + builtProducts[.init(product)] = productPath + } + return builtProducts + } + + // TODO: explore using ziplib or similar instead of shelling out + private func package( + packageName: String, + products: [String: URL], + zipToolPath: URL, + outputDirectory: URL, + verboseLogging: Bool + ) throws -> [String: URL] { + + var archives = [String: URL]() + for (product, artifactPath) in products { + print("-------------------------------------------------------------------------") + print("archiving \"\(product)\"") + print("-------------------------------------------------------------------------") + + // prep zipfile location + let workingDirectory = outputDirectory.appending(path: product) + let zipfilePath = workingDirectory.appending(path: "\(product).zip") + if FileManager.default.fileExists(atPath: workingDirectory.path()) { + try FileManager.default.removeItem(atPath: workingDirectory.path()) + } + try FileManager.default.createDirectory(atPath: workingDirectory.path(), withIntermediateDirectories: true) + + // rename artifact to "bootstrap" + let relocatedArtifactPath = workingDirectory.appending(path: "bootstrap") + try FileManager.default.copyItem(atPath: artifactPath.path(), toPath: relocatedArtifactPath.path()) + + var arguments: [String] = [] +#if os(macOS) || os(Linux) + arguments = [ + "--recurse-paths", + "--symlinks", + zipfilePath.lastPathComponent, + relocatedArtifactPath.lastPathComponent, + ] +#else + throw Errors.unsupportedPlatform("can't or don't know how to create a zip file on this platform") +#endif + + // add resources + var artifactPathComponents = artifactPath.pathComponents + _ = artifactPathComponents.removeFirst() // Get rid of beginning "/" + _ = artifactPathComponents.removeLast() // Get rid of the name of the package + let artifactDirectory = "/\(artifactPathComponents.joined(separator: "/"))" + for fileInArtifactDirectory in try FileManager.default.contentsOfDirectory(atPath: artifactDirectory) { + guard let artifactURL = URL(string: "\(artifactDirectory)/\(fileInArtifactDirectory)") else { + continue + } + + guard artifactURL.pathExtension == "resources" else { + continue // Not resources, so don't copy + } + let resourcesDirectoryName = artifactURL.lastPathComponent + let relocatedResourcesDirectory = workingDirectory.appending(path: resourcesDirectoryName) + if FileManager.default.fileExists(atPath: artifactURL.path()) { + try FileManager.default.copyItem( + atPath: artifactURL.path(), + toPath: relocatedResourcesDirectory.path() + ) + arguments.append(resourcesDirectoryName) + } + } + + // run the zip tool + try Utils.execute( + executable: zipToolPath, + arguments: arguments, + customWorkingDirectory: workingDirectory, + logLevel: verboseLogging ? .debug : .silent + ) + + archives[product] = zipfilePath + } + return archives + } + + private func displayHelpMessage() { + print( + """ + OVERVIEW: A SwiftPM plugin to build and package your lambda function. + + REQUIREMENTS: To use this plugin, you must have docker installed and started. + + USAGE: swift package --allow-network-connections docker archive + [--help] [--verbose] + [--output-directory ] + [--products ] + [--configuration debug | release] + [--swift-version ] + [--base-docker-image ] + [--disable-docker-image-update] + + + OPTIONS: + --verbose Produce verbose output for debugging. + --output-directory The path of the binary package. + (default is `.build/plugins/AWSLambdaPackager/outputs/...`) + --products The list of executable targets to build. + (default is taken from Package.swift) + --configuration The build configuration (debug or release) + (default is release) + --swift-version The swift version to use for building. + (default is latest) + This parameter cannot be used when --base-docker-image is specified. + --base-docker-image The name of the base docker image to use for the build. + (default : swift-:amazonlinux2) + This parameter cannot be used when --swift-version is specified. + --disable-docker-image-update Do not attempt to update the docker image + --help Show help information. + """ + ) + } +} + +private struct BuilderConfiguration: CustomStringConvertible { + + // passed by the user + public let help: Bool + public let outputDirectory: URL + public let products: [String] + public let buildConfiguration: BuildConfiguration + public let verboseLogging: Bool + public let baseDockerImage: String + public let disableDockerImageUpdate: Bool + + // passed by the plugin + public let packageID: String + public let packageDisplayName: String + public let packageDirectory: URL + public let dockerToolPath: URL + public let zipToolPath: URL + + public init(arguments: [String]) throws { + var argumentExtractor = ArgumentExtractor(arguments) + + let verboseArgument = argumentExtractor.extractFlag(named: "verbose") > 0 + let outputPathArgument = argumentExtractor.extractOption(named: "output-path") + let packageIDArgument = argumentExtractor.extractOption(named: "package-id") + let packageDisplayNameArgument = argumentExtractor.extractOption(named: "package-display-name") + let packageDirectoryArgument = argumentExtractor.extractOption(named: "package-directory") + let dockerToolPathArgument = argumentExtractor.extractOption(named: "docker-tool-path") + let zipToolPathArgument = argumentExtractor.extractOption(named: "zip-tool-path") + let productsArgument = argumentExtractor.extractOption(named: "products") + let configurationArgument = argumentExtractor.extractOption(named: "configuration") + let swiftVersionArgument = argumentExtractor.extractOption(named: "swift-version") + let baseDockerImageArgument = argumentExtractor.extractOption(named: "base-docker-image") + let disableDockerImageUpdateArgument = argumentExtractor.extractFlag(named: "disable-docker-image-update") > 0 + let helpArgument = argumentExtractor.extractFlag(named: "help") > 0 + + // help required ? + self.help = helpArgument + + // verbose logging required ? + self.verboseLogging = verboseArgument + + // package id + guard !packageIDArgument.isEmpty else { + throw BuilderErrors.invalidArgument("--package-id argument is required") + } + self.packageID = packageIDArgument.first! + + // package display name + guard !packageDisplayNameArgument.isEmpty else { + throw BuilderErrors.invalidArgument("--package-display-name argument is required") + } + self.packageDisplayName = packageDisplayNameArgument.first! + + // package directory + guard !packageDirectoryArgument.isEmpty else { + throw BuilderErrors.invalidArgument("--package-directory argument is required") + } + self.packageDirectory = URL(fileURLWithPath: packageDirectoryArgument.first!) + + // docker tool path + guard !dockerToolPathArgument.isEmpty else { + throw BuilderErrors.invalidArgument("--docker-tool-path argument is required") + } + self.dockerToolPath = URL(fileURLWithPath: dockerToolPathArgument.first!) + + // zip tool path + guard !zipToolPathArgument.isEmpty else { + throw BuilderErrors.invalidArgument("--zip-tool-path argument is required") + } + self.zipToolPath = URL(fileURLWithPath: zipToolPathArgument.first!) + + // output directory + guard !outputPathArgument.isEmpty else { + throw BuilderErrors.invalidArgument("--output-path is required") + } + self.outputDirectory = URL(fileURLWithPath: outputPathArgument.first!) + + // products + guard !productsArgument.isEmpty else { + throw BuilderErrors.invalidArgument("--products argument is required") + } + self.products = productsArgument.flatMap { $0.split(separator: ",").map(String.init) } + + // build configuration + guard let buildConfigurationName = configurationArgument.first else { + throw BuilderErrors.invalidArgument("--configuration argument is equired") + } + guard let _buildConfiguration = BuildConfiguration(rawValue: buildConfigurationName) else { + throw BuilderErrors.invalidArgument("invalid build configuration named '\(buildConfigurationName)'") + } + self.buildConfiguration = _buildConfiguration + + guard !(!swiftVersionArgument.isEmpty && !baseDockerImageArgument.isEmpty) else { + throw BuilderErrors.invalidArgument("--swift-version and --base-docker-image are mutually exclusive") + } + + let swiftVersion = swiftVersionArgument.first ?? .none // undefined version will yield the latest docker image + self.baseDockerImage = + baseDockerImageArgument.first ?? "swift:\(swiftVersion.map { $0 + "-" } ?? "")amazonlinux2" + + self.disableDockerImageUpdate = disableDockerImageUpdateArgument + + if self.verboseLogging { + print("-------------------------------------------------------------------------") + print("configuration") + print("-------------------------------------------------------------------------") + print(self) + } + } + + var description: String { + """ + { + outputDirectory: \(self.outputDirectory) + products: \(self.products) + buildConfiguration: \(self.buildConfiguration) + dockerToolPath: \(self.dockerToolPath) + baseDockerImage: \(self.baseDockerImage) + disableDockerImageUpdate: \(self.disableDockerImageUpdate) + zipToolPath: \(self.zipToolPath) + packageID: \(self.packageID) + packageDisplayName: \(self.packageDisplayName) + packageDirectory: \(self.packageDirectory) + } + """ + } +} + +private enum BuilderErrors: Error, CustomStringConvertible { + case invalidArgument(String) + case unsupportedPlatform(String) + case unknownProduct(String) + case productExecutableNotFound(String) + case failedWritingDockerfile + case failedParsingDockerOutput(String) + case processFailed([String], Int32) + + var description: String { + switch self { + case .invalidArgument(let description): + return description + case .unsupportedPlatform(let description): + return description + case .unknownProduct(let description): + return description + case .productExecutableNotFound(let product): + return "product executable not found '\(product)'" + case .failedWritingDockerfile: + return "failed writing dockerfile" + case .failedParsingDockerOutput(let output): + return "failed parsing docker output: '\(output)'" + case .processFailed(let arguments, let code): + return "\(arguments.joined(separator: " ")) failed with code \(code)" + } + } +} + +private enum BuildConfiguration: String { + case debug + case release +} diff --git a/Sources/AWSLambdaPluginHelper/lambda-deploy/Deployer.swift b/Sources/AWSLambdaPluginHelper/lambda-deploy/Deployer.swift new file mode 100644 index 00000000..a9e2462f --- /dev/null +++ b/Sources/AWSLambdaPluginHelper/lambda-deploy/Deployer.swift @@ -0,0 +1,76 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +#if canImport(FoundationEssentials) +import FoundationEssentials +#else +import Foundation +#endif + +struct Deployer { + + func deploy(arguments: [String]) async throws { + let configuration = try DeployerConfiguration(arguments: arguments) + + if configuration.help { + self.displayHelpMessage() + return + } + + //FIXME: use Logger + print("TODO: deploy") + } + + private func displayHelpMessage() { + print( + """ + OVERVIEW: A SwiftPM plugin to deploy a Lambda function. + + USAGE: swift package lambda-deploy + [--with-url] + [--help] [--verbose] + + OPTIONS: + --with-url Add an URL to access the Lambda function + --verbose Produce verbose output for debugging. + --help Show help information. + """ + ) + } +} + +private struct DeployerConfiguration: CustomStringConvertible { + public let help: Bool + public let verboseLogging: Bool + + public init(arguments: [String]) throws { + var argumentExtractor = ArgumentExtractor(arguments) + let verboseArgument = argumentExtractor.extractFlag(named: "verbose") > 0 + let helpArgument = argumentExtractor.extractFlag(named: "help") > 0 + + // help required ? + self.help = helpArgument + + // verbose logging required ? + self.verboseLogging = verboseArgument + } + + var description: String { + """ + { + verboseLogging: \(self.verboseLogging) + } + """ + } +} diff --git a/Sources/AWSLambdaPluginHelper/lambda-init/Initializer.swift b/Sources/AWSLambdaPluginHelper/lambda-init/Initializer.swift new file mode 100644 index 00000000..8515d586 --- /dev/null +++ b/Sources/AWSLambdaPluginHelper/lambda-init/Initializer.swift @@ -0,0 +1,97 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +#if canImport(FoundationEssentials) +import FoundationEssentials +#else +import Foundation +#endif + +struct Initializer { + + private let destFileName = "Sources/main.swift" + + func initialize(arguments: [String]) async throws { + + let configuration = try InitializerConfiguration(arguments: arguments) + + if configuration.help { + self.displayHelpMessage() + return + } + + let destFileURL = configuration.destinationDir.appendingPathComponent(destFileName) + do { + try functionWithUrlTemplate.write(to: destFileURL, atomically: true, encoding: .utf8) + + if configuration.verboseLogging { + print("File created at: \(destFileURL)") + } + + print("✅ Lambda function written to \(destFileName)") + print("📦 You can now package with: 'swift package archive'") + } catch { + print("🛑Failed to create the Lambda function file: \(error)") + } + } + + + private func displayHelpMessage() { + print( + """ + OVERVIEW: A SwiftPM plugin to scaffold a HelloWorld Lambda function. + + USAGE: swift package lambda-init + [--help] [--verbose] + [--allow-writing-to-package-directory] + + OPTIONS: + --allow-writing-to-package-directory Don't ask for permissions to write files. + --verbose Produce verbose output for debugging. + --help Show help information. + """ + ) + } +} + +private struct InitializerConfiguration: CustomStringConvertible { + public let help: Bool + public let verboseLogging: Bool + public let destinationDir: URL + + public init(arguments: [String]) throws { + var argumentExtractor = ArgumentExtractor(arguments) + let verboseArgument = argumentExtractor.extractFlag(named: "verbose") > 0 + let helpArgument = argumentExtractor.extractFlag(named: "help") > 0 + let destDirArgument = argumentExtractor.extractOption(named: "dest-dir") + + // help required ? + self.help = helpArgument + + // verbose logging required ? + self.verboseLogging = verboseArgument + + // dest dir + self.destinationDir = URL(fileURLWithPath: destDirArgument[0]) + } + + var description: String { + """ + { + verboseLogging: \(self.verboseLogging) + destinationDir: \(self.destinationDir) + } + """ + } +} diff --git a/Plugins/AWSLambdaInitializer/Template.swift b/Sources/AWSLambdaPluginHelper/lambda-init/Template.swift similarity index 98% rename from Plugins/AWSLambdaInitializer/Template.swift rename to Sources/AWSLambdaPluginHelper/lambda-init/Template.swift index 133e5033..0ca37ace 100644 --- a/Plugins/AWSLambdaInitializer/Template.swift +++ b/Sources/AWSLambdaPluginHelper/lambda-init/Template.swift @@ -12,8 +12,6 @@ // //===----------------------------------------------------------------------===// -import Foundation - let functionWithUrlTemplate = #""" import AWSLambdaRuntime import AWSLambdaEvents diff --git a/Sources/AWSLambdaPluginHelper/main.swift b/Sources/AWSLambdaPluginHelper/main.swift deleted file mode 100644 index 3ed1e5b5..00000000 --- a/Sources/AWSLambdaPluginHelper/main.swift +++ /dev/null @@ -1 +0,0 @@ -print("Deployer") From 47dd2453f4dfd98b3dfe10666794d5a34a7931cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Stormacq?= Date: Sun, 17 Nov 2024 22:14:28 +0100 Subject: [PATCH 07/10] use Foundation to access Process on Linux --- Plugins/AWSLambdaBuilder/Plugin.swift | 7 +------ Plugins/AWSLambdaDeployer/Plugin.swift | 7 +------ Plugins/AWSLambdaInitializer/Plugin.swift | 7 +------ 3 files changed, 3 insertions(+), 18 deletions(-) diff --git a/Plugins/AWSLambdaBuilder/Plugin.swift b/Plugins/AWSLambdaBuilder/Plugin.swift index 8dd8a81c..e6158b8b 100644 --- a/Plugins/AWSLambdaBuilder/Plugin.swift +++ b/Plugins/AWSLambdaBuilder/Plugin.swift @@ -12,13 +12,8 @@ // //===----------------------------------------------------------------------===// -import PackagePlugin - -#if canImport(FoundationEssentials) -import FoundationEssentials -#else import Foundation -#endif +import PackagePlugin @main struct AWSLambdaPackager: CommandPlugin { diff --git a/Plugins/AWSLambdaDeployer/Plugin.swift b/Plugins/AWSLambdaDeployer/Plugin.swift index 9ae59b0b..b8a0915e 100644 --- a/Plugins/AWSLambdaDeployer/Plugin.swift +++ b/Plugins/AWSLambdaDeployer/Plugin.swift @@ -12,13 +12,8 @@ // //===----------------------------------------------------------------------===// -import PackagePlugin - -#if canImport(FoundationEssentials) -import FoundationEssentials -#else import Foundation -#endif +import PackagePlugin @main struct AWSLambdaDeployer: CommandPlugin { diff --git a/Plugins/AWSLambdaInitializer/Plugin.swift b/Plugins/AWSLambdaInitializer/Plugin.swift index bbfa90e2..026e70cd 100644 --- a/Plugins/AWSLambdaInitializer/Plugin.swift +++ b/Plugins/AWSLambdaInitializer/Plugin.swift @@ -12,13 +12,8 @@ // //===----------------------------------------------------------------------===// -import PackagePlugin - -#if canImport(FoundationEssentials) -import FoundationEssentials -#else import Foundation -#endif +import PackagePlugin @main struct AWSLambdaPackager: CommandPlugin { From 06654302d57c075247e4b68290ef8cb138d4c953 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Stormacq?= Date: Sun, 24 Nov 2024 20:40:18 +0100 Subject: [PATCH 08/10] add --with-url option --- .../lambda-init/Initializer.swift | 54 +++++++++++++------ .../lambda-init/Template.swift | 29 ++++++++++ 2 files changed, 68 insertions(+), 15 deletions(-) diff --git a/Sources/AWSLambdaPluginHelper/lambda-init/Initializer.swift b/Sources/AWSLambdaPluginHelper/lambda-init/Initializer.swift index 8515d586..02954317 100644 --- a/Sources/AWSLambdaPluginHelper/lambda-init/Initializer.swift +++ b/Sources/AWSLambdaPluginHelper/lambda-init/Initializer.swift @@ -21,42 +21,48 @@ import Foundation struct Initializer { private let destFileName = "Sources/main.swift" - + func initialize(arguments: [String]) async throws { let configuration = try InitializerConfiguration(arguments: arguments) - + if configuration.help { self.displayHelpMessage() return } - + let destFileURL = configuration.destinationDir.appendingPathComponent(destFileName) do { - try functionWithUrlTemplate.write(to: destFileURL, atomically: true, encoding: .utf8) - + + let template = TemplateType.template(for: configuration.templateType) + try template.write(to: destFileURL, atomically: true, encoding: .utf8) + if configuration.verboseLogging { print("File created at: \(destFileURL)") } - + print("✅ Lambda function written to \(destFileName)") - print("📦 You can now package with: 'swift package archive'") + print("📦 You can now package with: 'swift package lambda-build'") } catch { print("🛑Failed to create the Lambda function file: \(error)") } } - - + + private func displayHelpMessage() { print( """ OVERVIEW: A SwiftPM plugin to scaffold a HelloWorld Lambda function. - + By default, it creates a Lambda function that receives a JSON + document and responds with another JSON document. + USAGE: swift package lambda-init [--help] [--verbose] + [--with-url] [--allow-writing-to-package-directory] - + OPTIONS: + --with-url Create a Lambda function exposed with an URL --allow-writing-to-package-directory Don't ask for permissions to write files. --verbose Produce verbose output for debugging. --help Show help information. @@ -65,32 +71,50 @@ struct Initializer { } } +private enum TemplateType { + case `default` + case url + + static func template(for type: TemplateType) -> String { + switch type { + case .default: return functionWithJSONTemplate + case .url: return functionWithUrlTemplate + } + } +} + private struct InitializerConfiguration: CustomStringConvertible { public let help: Bool public let verboseLogging: Bool public let destinationDir: URL - + public let templateType: TemplateType + public init(arguments: [String]) throws { var argumentExtractor = ArgumentExtractor(arguments) let verboseArgument = argumentExtractor.extractFlag(named: "verbose") > 0 let helpArgument = argumentExtractor.extractFlag(named: "help") > 0 let destDirArgument = argumentExtractor.extractOption(named: "dest-dir") - + let templateURLArgument = argumentExtractor.extractFlag(named: "with-url") > 0 + // help required ? self.help = helpArgument - + // verbose logging required ? self.verboseLogging = verboseArgument // dest dir self.destinationDir = URL(fileURLWithPath: destDirArgument[0]) + + // template type. Default is the JSON one + self.templateType = templateURLArgument ? .url : .default } - + var description: String { """ { verboseLogging: \(self.verboseLogging) destinationDir: \(self.destinationDir) + templateType: \(self.templateType) } """ } diff --git a/Sources/AWSLambdaPluginHelper/lambda-init/Template.swift b/Sources/AWSLambdaPluginHelper/lambda-init/Template.swift index 0ca37ace..865a122a 100644 --- a/Sources/AWSLambdaPluginHelper/lambda-init/Template.swift +++ b/Sources/AWSLambdaPluginHelper/lambda-init/Template.swift @@ -31,3 +31,32 @@ let functionWithUrlTemplate = #""" try await runtime.run() """# + +let functionWithJSONTemplate = #""" + import AWSLambdaRuntime + + // the data structure to represent the input parameter + struct HelloRequest: Decodable { + let name: String + let age: Int + } + + // the data structure to represent the output response + struct HelloResponse: Encodable { + let greetings: String + } + + // in this example we receive a HelloRequest JSON and we return a HelloResponse JSON + + // the Lambda runtime + let runtime = LambdaRuntime { + (event: HelloRequest, context: LambdaContext) in + + HelloResponse( + greetings: "Hello \(event.name). You look \(event.age > 30 ? "younger" : "older") than your age." + ) + } + + // start the loop + try await runtime.run() + """# From 0bdc8135277cdba860db106bc633384c6477a924 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Stormacq?= Date: Mon, 25 Nov 2024 15:27:06 +0100 Subject: [PATCH 09/10] swift format --- Package.swift | 67 +++---- Plugins/AWSLambdaBuilder/Plugin.swift | 125 ++++++------ Plugins/AWSLambdaInitializer/Plugin.swift | 3 +- .../AWSLambdaPluginHelper.swift | 11 +- .../Vendored/crypto/Padding.swift | 2 +- .../Vendored/spm/ArgumentExtractor.swift | 15 +- .../lambda-build/Builder.swift | 180 +++++++++--------- .../lambda-deploy/Deployer.swift | 2 +- .../lambda-init/Initializer.swift | 37 ++-- .../lambda-init/Template.swift | 2 +- 10 files changed, 220 insertions(+), 224 deletions(-) diff --git a/Package.swift b/Package.swift index 280977d4..5548b313 100644 --- a/Package.swift +++ b/Package.swift @@ -12,26 +12,26 @@ let package = Package( name: "swift-aws-lambda-runtime", platforms: platforms, products: [ - - /* - The runtime library targets - */ - + + // + // The runtime library targets + // + // this library exports `AWSLambdaRuntimeCore` and adds Foundation convenience methods .library(name: "AWSLambdaRuntime", targets: ["AWSLambdaRuntime"]), // this has all the main functionality for lambda and it does not link Foundation .library(name: "AWSLambdaRuntimeCore", targets: ["AWSLambdaRuntimeCore"]), - /* - The plugins - 'lambda-init' creates a new Lambda function - 'lambda-build' packages the Lambda function - 'lambda-deploy' deploys the Lambda function - - Plugins requires Linux or at least macOS v15 + // + // The plugins + // 'lambda-init' creates a new Lambda function + // 'lambda-build' packages the Lambda function + // 'lambda-deploy' deploys the Lambda function + // + // Plugins requires Linux or at least macOS v15 + // - */ // plugin to create a new Lambda function, based on a template .plugin(name: "AWSLambdaInitializer", targets: ["AWSLambdaInitializer"]), @@ -41,9 +41,10 @@ let package = Package( // plugin to deploy a Lambda function .plugin(name: "AWSLambdaDeployer", targets: ["AWSLambdaDeployer"]), - /* - Testing targets - */ + // + // Testing targets + // + // for testing only .library(name: "AWSLambdaTesting", targets: ["AWSLambdaTesting"]), ], @@ -88,23 +89,23 @@ let package = Package( ), // keep this one (with "archive") to not break workflows // This will be deprecated at some point in the future -// .plugin( -// name: "AWSLambdaPackager", -// capability: .command( -// intent: .custom( -// verb: "archive", -// description: -// "Archive the Lambda binary and prepare it for uploading to AWS. Requires docker on macOS or non Amazonlinux 2 distributions." -// ), -// permissions: [ -// .allowNetworkConnections( -// scope: .docker, -// reason: "This plugin uses Docker to create the AWS Lambda ZIP package." -// ) -// ] -// ), -// path: "Plugins/AWSLambdaBuilder" // same sources as the new "lambda-build" plugin -// ), + // .plugin( + // name: "AWSLambdaPackager", + // capability: .command( + // intent: .custom( + // verb: "archive", + // description: + // "Archive the Lambda binary and prepare it for uploading to AWS. Requires docker on macOS or non Amazonlinux 2 distributions." + // ), + // permissions: [ + // .allowNetworkConnections( + // scope: .docker, + // reason: "This plugin uses Docker to create the AWS Lambda ZIP package." + // ) + // ] + // ), + // path: "Plugins/AWSLambdaBuilder" // same sources as the new "lambda-build" plugin + // ), .plugin( name: "AWSLambdaBuilder", capability: .command( diff --git a/Plugins/AWSLambdaBuilder/Plugin.swift b/Plugins/AWSLambdaBuilder/Plugin.swift index e6158b8b..fdb97efb 100644 --- a/Plugins/AWSLambdaBuilder/Plugin.swift +++ b/Plugins/AWSLambdaBuilder/Plugin.swift @@ -17,9 +17,9 @@ import PackagePlugin @main struct AWSLambdaPackager: CommandPlugin { - + func performCommand(context: PackagePlugin.PluginContext, arguments: [String]) async throws { - + // values to pass to the AWSLambdaPluginHelper let outputDirectory: URL let products: [Product] @@ -29,15 +29,15 @@ struct AWSLambdaPackager: CommandPlugin { let packageDirectory = context.package.directoryURL let dockerToolPath = try context.tool(named: "docker").url let zipToolPath = try context.tool(named: "zip").url - + // extract arguments that require PluginContext to fully resolve // resolve them here and pass them to the AWSLambdaPluginHelper as arguments var argumentExtractor = ArgumentExtractor(arguments) - + let outputPathArgument = argumentExtractor.extractOption(named: "output-path") let productsArgument = argumentExtractor.extractOption(named: "products") let configurationArgument = argumentExtractor.extractOption(named: "configuration") - + if let outputPath = outputPathArgument.first { #if os(Linux) var isDirectory: Bool = false @@ -52,7 +52,7 @@ struct AWSLambdaPackager: CommandPlugin { } else { outputDirectory = context.pluginWorkDirectoryURL.appending(path: "\(AWSLambdaPackager.self)") } - + let explicitProducts = !productsArgument.isEmpty if explicitProducts { let _products = try context.package.products(named: productsArgument) @@ -62,11 +62,11 @@ struct AWSLambdaPackager: CommandPlugin { } } products = _products - + } else { products = context.package.products.filter { $0 is ExecutableProduct } } - + if let _buildConfigurationName = configurationArgument.first { guard let _buildConfiguration = PackageManager.BuildConfiguration(rawValue: _buildConfigurationName) else { throw BuilderErrors.invalidArgument("invalid build configuration named '\(_buildConfigurationName)'") @@ -75,22 +75,23 @@ struct AWSLambdaPackager: CommandPlugin { } else { buildConfiguration = .release } - + // TODO: When running on Amazon Linux 2, we have to build directly from the plugin // launch the build, then call the helper just for the ZIP part - + let tool = try context.tool(named: "AWSLambdaPluginHelper") - let args = [ - "build", - "--output-path", outputDirectory.path(), - "--products", products.map { $0.name }.joined(separator: ","), - "--configuration", buildConfiguration.rawValue, - "--package-id", packageID, - "--package-display-name", packageDisplayName, - "--package-directory", packageDirectory.path(), - "--docker-tool-path", dockerToolPath.path, - "--zip-tool-path", zipToolPath.path - ] + arguments + let args = + [ + "build", + "--output-path", outputDirectory.path(), + "--products", products.map { $0.name }.joined(separator: ","), + "--configuration", buildConfiguration.rawValue, + "--package-id", packageID, + "--package-display-name", packageDisplayName, + "--package-directory", packageDirectory.path(), + "--docker-tool-path", dockerToolPath.path, + "--zip-tool-path", zipToolPath.path, + ] + arguments // Invoke the plugin helper on the target directory, passing a configuration // file from the package directory. @@ -103,52 +104,52 @@ struct AWSLambdaPackager: CommandPlugin { Diagnostics.error("AWSLambdaPluginHelper invocation failed: \(problem)") } } - + // TODO: When running on Amazon Linux 2, we have to build directly from the plugin -// private func build( -// packageIdentity: Package.ID, -// products: [Product], -// buildConfiguration: PackageManager.BuildConfiguration, -// verboseLogging: Bool -// ) throws -> [LambdaProduct: URL] { -// print("-------------------------------------------------------------------------") -// print("building \"\(packageIdentity)\"") -// print("-------------------------------------------------------------------------") -// -// var results = [LambdaProduct: URL]() -// for product in products { -// print("building \"\(product.name)\"") -// var parameters = PackageManager.BuildParameters() -// parameters.configuration = buildConfiguration -// parameters.otherSwiftcFlags = ["-static-stdlib"] -// parameters.logging = verboseLogging ? .verbose : .concise -// -// let result = try packageManager.build( -// .product(product.name), -// parameters: parameters -// ) -// guard let artifact = result.executableArtifact(for: product) else { -// throw Errors.productExecutableNotFound(product.name) -// } -// results[.init(product)] = artifact.url -// } -// return results -// } - -// private func isAmazonLinux2() -> Bool { -// if let data = FileManager.default.contents(atPath: "/etc/system-release"), -// let release = String(data: data, encoding: .utf8) -// { -// return release.hasPrefix("Amazon Linux release 2") -// } else { -// return false -// } -// } + // private func build( + // packageIdentity: Package.ID, + // products: [Product], + // buildConfiguration: PackageManager.BuildConfiguration, + // verboseLogging: Bool + // ) throws -> [LambdaProduct: URL] { + // print("-------------------------------------------------------------------------") + // print("building \"\(packageIdentity)\"") + // print("-------------------------------------------------------------------------") + // + // var results = [LambdaProduct: URL]() + // for product in products { + // print("building \"\(product.name)\"") + // var parameters = PackageManager.BuildParameters() + // parameters.configuration = buildConfiguration + // parameters.otherSwiftcFlags = ["-static-stdlib"] + // parameters.logging = verboseLogging ? .verbose : .concise + // + // let result = try packageManager.build( + // .product(product.name), + // parameters: parameters + // ) + // guard let artifact = result.executableArtifact(for: product) else { + // throw Errors.productExecutableNotFound(product.name) + // } + // results[.init(product)] = artifact.url + // } + // return results + // } + + // private func isAmazonLinux2() -> Bool { + // if let data = FileManager.default.contents(atPath: "/etc/system-release"), + // let release = String(data: data, encoding: .utf8) + // { + // return release.hasPrefix("Amazon Linux release 2") + // } else { + // return false + // } + // } } private enum BuilderErrors: Error, CustomStringConvertible { case invalidArgument(String) - + var description: String { switch self { case .invalidArgument(let description): diff --git a/Plugins/AWSLambdaInitializer/Plugin.swift b/Plugins/AWSLambdaInitializer/Plugin.swift index 026e70cd..5b508ff3 100644 --- a/Plugins/AWSLambdaInitializer/Plugin.swift +++ b/Plugins/AWSLambdaInitializer/Plugin.swift @@ -20,7 +20,7 @@ struct AWSLambdaPackager: CommandPlugin { func performCommand(context: PackagePlugin.PluginContext, arguments: [String]) async throws { let tool = try context.tool(named: "AWSLambdaPluginHelper") - + let args = ["init", "--dest-dir", context.package.directoryURL.path()] + arguments // Invoke the plugin helper on the target directory, passing a configuration @@ -35,4 +35,3 @@ struct AWSLambdaPackager: CommandPlugin { } } } - diff --git a/Sources/AWSLambdaPluginHelper/AWSLambdaPluginHelper.swift b/Sources/AWSLambdaPluginHelper/AWSLambdaPluginHelper.swift index 71b5af7e..2c124b5f 100644 --- a/Sources/AWSLambdaPluginHelper/AWSLambdaPluginHelper.swift +++ b/Sources/AWSLambdaPluginHelper/AWSLambdaPluginHelper.swift @@ -14,13 +14,13 @@ @main struct AWSLambdaPluginHelper { - + private enum Command: String { case `init` case build case deploy } - + public static func main() async throws { let args = CommandLine.arguments let helper = AWSLambdaPluginHelper() @@ -34,10 +34,10 @@ struct AWSLambdaPluginHelper { try await Deployer().deploy(arguments: args) } } - + private func command(from arguments: [String]) throws -> Command { let args = CommandLine.arguments - + guard args.count > 2 else { throw AWSLambdaPluginHelperError.noCommand } @@ -45,7 +45,7 @@ struct AWSLambdaPluginHelper { guard let command = Command(rawValue: commandName) else { throw AWSLambdaPluginHelperError.invalidCommand(commandName) } - + return command } } @@ -54,4 +54,3 @@ private enum AWSLambdaPluginHelperError: Error { case noCommand case invalidCommand(String) } - diff --git a/Sources/AWSLambdaPluginHelper/Vendored/crypto/Padding.swift b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Padding.swift index 679e161f..fb714a4f 100644 --- a/Sources/AWSLambdaPluginHelper/Vendored/crypto/Padding.swift +++ b/Sources/AWSLambdaPluginHelper/Vendored/crypto/Padding.swift @@ -33,7 +33,7 @@ public protocol PaddingProtocol { } public enum Padding: PaddingProtocol { - case noPadding, zeroPadding + case noPadding, zeroPadding public func add(to: [UInt8], blockSize: Int) -> [UInt8] { switch self { case .noPadding: diff --git a/Sources/AWSLambdaPluginHelper/Vendored/spm/ArgumentExtractor.swift b/Sources/AWSLambdaPluginHelper/Vendored/spm/ArgumentExtractor.swift index 8406692c..cee229a0 100644 --- a/Sources/AWSLambdaPluginHelper/Vendored/spm/ArgumentExtractor.swift +++ b/Sources/AWSLambdaPluginHelper/Vendored/spm/ArgumentExtractor.swift @@ -50,13 +50,11 @@ public struct ArgumentExtractor { values.append(val) args.remove(at: idx) } - } - else if arg.starts(with: "--\(name)=") { + } else if arg.starts(with: "--\(name)=") { arg.removeFirst(2 + name.count + 1) values.append(arg) args.remove(at: idx) - } - else { + } else { idx += 1 } } @@ -72,8 +70,7 @@ public struct ArgumentExtractor { if arg == "--\(name)" { args.remove(at: idx) count += 1 - } - else { + } else { idx += 1 } } @@ -82,11 +79,11 @@ public struct ArgumentExtractor { /// Returns any unextracted flags or options (based strictly on whether remaining arguments have a "--" prefix). public var unextractedOptionsOrFlags: [String] { - return args.filter{ $0.hasPrefix("--") } + args.filter { $0.hasPrefix("--") } } /// Returns all remaining arguments, including any literals after the first `--` if there is one. public var remainingArguments: [String] { - return args + literals + args + literals } -} \ No newline at end of file +} diff --git a/Sources/AWSLambdaPluginHelper/lambda-build/Builder.swift b/Sources/AWSLambdaPluginHelper/lambda-build/Builder.swift index fb8ff73a..acd01c28 100644 --- a/Sources/AWSLambdaPluginHelper/lambda-build/Builder.swift +++ b/Sources/AWSLambdaPluginHelper/lambda-build/Builder.swift @@ -22,14 +22,14 @@ import Foundation struct Builder { func build(arguments: [String]) async throws { let configuration = try BuilderConfiguration(arguments: arguments) - + if configuration.help { self.displayHelpMessage() return } - + let builtProducts: [String: URL] - + // build with docker // TODO: check if dockerToolPath is provided // When not provided, it means we're building on Amazon Linux 2 @@ -44,7 +44,7 @@ struct Builder { buildConfiguration: configuration.buildConfiguration, verboseLogging: configuration.verboseLogging ) - + // create the archive let archives = try self.package( packageName: configuration.packageDisplayName, @@ -53,7 +53,7 @@ struct Builder { outputDirectory: configuration.outputDirectory, verboseLogging: configuration.verboseLogging ) - + print( "\(archives.count > 0 ? archives.count.description : "no") archive\(archives.count != 1 ? "s" : "") created" ) @@ -61,7 +61,7 @@ struct Builder { print(" * \(product) at \(archivePath.path())") } } - + private func buildInDocker( packageIdentity: String, packageDirectory: URL, @@ -73,11 +73,11 @@ struct Builder { buildConfiguration: BuildConfiguration, verboseLogging: Bool ) throws -> [String: URL] { - + print("-------------------------------------------------------------------------") print("building \"\(packageIdentity)\" in docker") print("-------------------------------------------------------------------------") - + if !disableDockerImageUpdate { // update the underlying docker image, if necessary print("updating \"\(baseImage)\" docker image") @@ -87,7 +87,7 @@ struct Builder { logLevel: verboseLogging ? .debug : .silent ) } - + // get the build output path let buildOutputPathCommand = "swift build -c \(buildConfiguration.rawValue) --show-bin-path" let dockerBuildOutputPath = try Utils.execute( @@ -104,13 +104,13 @@ struct Builder { let buildOutputPath = URL( string: buildPathOutput.replacingOccurrences(of: "/workspace/", with: packageDirectory.description) )! - + // build the products var builtProducts = [String: URL]() for product in products { print("building \"\(product)\"") let buildCommand = - "swift build -c \(buildConfiguration.rawValue) --product \(product) --static-swift-stdlib" + "swift build -c \(buildConfiguration.rawValue) --product \(product) --static-swift-stdlib" if let localPath = ProcessInfo.processInfo.environment["LAMBDA_USE_LOCAL_DEPS"] { // when developing locally, we must have the full swift-aws-lambda-runtime project in the container // because Examples' Package.swift have a dependency on ../.. @@ -136,7 +136,7 @@ struct Builder { ) } let productPath = buildOutputPath.appending(path: product) - + guard FileManager.default.fileExists(atPath: productPath.path()) else { print("expected '\(product)' binary at \"\(productPath.path())\"") throw BuilderErrors.productExecutableNotFound(product) @@ -145,7 +145,7 @@ struct Builder { } return builtProducts } - + // TODO: explore using ziplib or similar instead of shelling out private func package( packageName: String, @@ -154,13 +154,13 @@ struct Builder { outputDirectory: URL, verboseLogging: Bool ) throws -> [String: URL] { - + var archives = [String: URL]() for (product, artifactPath) in products { print("-------------------------------------------------------------------------") print("archiving \"\(product)\"") print("-------------------------------------------------------------------------") - + // prep zipfile location let workingDirectory = outputDirectory.appending(path: product) let zipfilePath = workingDirectory.appending(path: "\(product).zip") @@ -168,23 +168,23 @@ struct Builder { try FileManager.default.removeItem(atPath: workingDirectory.path()) } try FileManager.default.createDirectory(atPath: workingDirectory.path(), withIntermediateDirectories: true) - + // rename artifact to "bootstrap" let relocatedArtifactPath = workingDirectory.appending(path: "bootstrap") try FileManager.default.copyItem(atPath: artifactPath.path(), toPath: relocatedArtifactPath.path()) - + var arguments: [String] = [] -#if os(macOS) || os(Linux) + #if os(macOS) || os(Linux) arguments = [ "--recurse-paths", "--symlinks", zipfilePath.lastPathComponent, relocatedArtifactPath.lastPathComponent, ] -#else + #else throw Errors.unsupportedPlatform("can't or don't know how to create a zip file on this platform") -#endif - + #endif + // add resources var artifactPathComponents = artifactPath.pathComponents _ = artifactPathComponents.removeFirst() // Get rid of beginning "/" @@ -194,7 +194,7 @@ struct Builder { guard let artifactURL = URL(string: "\(artifactDirectory)/\(fileInArtifactDirectory)") else { continue } - + guard artifactURL.pathExtension == "resources" else { continue // Not resources, so don't copy } @@ -208,7 +208,7 @@ struct Builder { arguments.append(resourcesDirectoryName) } } - + // run the zip tool try Utils.execute( executable: zipToolPath, @@ -216,52 +216,52 @@ struct Builder { customWorkingDirectory: workingDirectory, logLevel: verboseLogging ? .debug : .silent ) - + archives[product] = zipfilePath } return archives } - + private func displayHelpMessage() { print( - """ - OVERVIEW: A SwiftPM plugin to build and package your lambda function. - - REQUIREMENTS: To use this plugin, you must have docker installed and started. - - USAGE: swift package --allow-network-connections docker archive - [--help] [--verbose] - [--output-directory ] - [--products ] - [--configuration debug | release] - [--swift-version ] - [--base-docker-image ] - [--disable-docker-image-update] - - - OPTIONS: - --verbose Produce verbose output for debugging. - --output-directory The path of the binary package. - (default is `.build/plugins/AWSLambdaPackager/outputs/...`) - --products The list of executable targets to build. - (default is taken from Package.swift) - --configuration The build configuration (debug or release) - (default is release) - --swift-version The swift version to use for building. - (default is latest) - This parameter cannot be used when --base-docker-image is specified. - --base-docker-image The name of the base docker image to use for the build. - (default : swift-:amazonlinux2) - This parameter cannot be used when --swift-version is specified. - --disable-docker-image-update Do not attempt to update the docker image - --help Show help information. - """ + """ + OVERVIEW: A SwiftPM plugin to build and package your lambda function. + + REQUIREMENTS: To use this plugin, you must have docker installed and started. + + USAGE: swift package --allow-network-connections docker archive + [--help] [--verbose] + [--output-directory ] + [--products ] + [--configuration debug | release] + [--swift-version ] + [--base-docker-image ] + [--disable-docker-image-update] + + + OPTIONS: + --verbose Produce verbose output for debugging. + --output-directory The path of the binary package. + (default is `.build/plugins/AWSLambdaPackager/outputs/...`) + --products The list of executable targets to build. + (default is taken from Package.swift) + --configuration The build configuration (debug or release) + (default is release) + --swift-version The swift version to use for building. + (default is latest) + This parameter cannot be used when --base-docker-image is specified. + --base-docker-image The name of the base docker image to use for the build. + (default : swift-:amazonlinux2) + This parameter cannot be used when --swift-version is specified. + --disable-docker-image-update Do not attempt to update the docker image + --help Show help information. + """ ) } } private struct BuilderConfiguration: CustomStringConvertible { - + // passed by the user public let help: Bool public let outputDirectory: URL @@ -280,7 +280,7 @@ private struct BuilderConfiguration: CustomStringConvertible { public init(arguments: [String]) throws { var argumentExtractor = ArgumentExtractor(arguments) - + let verboseArgument = argumentExtractor.extractFlag(named: "verbose") > 0 let outputPathArgument = argumentExtractor.extractOption(named: "output-path") let packageIDArgument = argumentExtractor.extractOption(named: "package-id") @@ -294,55 +294,55 @@ private struct BuilderConfiguration: CustomStringConvertible { let baseDockerImageArgument = argumentExtractor.extractOption(named: "base-docker-image") let disableDockerImageUpdateArgument = argumentExtractor.extractFlag(named: "disable-docker-image-update") > 0 let helpArgument = argumentExtractor.extractFlag(named: "help") > 0 - + // help required ? self.help = helpArgument - + // verbose logging required ? self.verboseLogging = verboseArgument - + // package id guard !packageIDArgument.isEmpty else { throw BuilderErrors.invalidArgument("--package-id argument is required") } self.packageID = packageIDArgument.first! - + // package display name guard !packageDisplayNameArgument.isEmpty else { throw BuilderErrors.invalidArgument("--package-display-name argument is required") } self.packageDisplayName = packageDisplayNameArgument.first! - + // package directory guard !packageDirectoryArgument.isEmpty else { throw BuilderErrors.invalidArgument("--package-directory argument is required") } self.packageDirectory = URL(fileURLWithPath: packageDirectoryArgument.first!) - + // docker tool path guard !dockerToolPathArgument.isEmpty else { throw BuilderErrors.invalidArgument("--docker-tool-path argument is required") } self.dockerToolPath = URL(fileURLWithPath: dockerToolPathArgument.first!) - + // zip tool path guard !zipToolPathArgument.isEmpty else { throw BuilderErrors.invalidArgument("--zip-tool-path argument is required") } self.zipToolPath = URL(fileURLWithPath: zipToolPathArgument.first!) - + // output directory guard !outputPathArgument.isEmpty else { throw BuilderErrors.invalidArgument("--output-path is required") } self.outputDirectory = URL(fileURLWithPath: outputPathArgument.first!) - + // products guard !productsArgument.isEmpty else { throw BuilderErrors.invalidArgument("--products argument is required") } self.products = productsArgument.flatMap { $0.split(separator: ",").map(String.init) } - + // build configuration guard let buildConfigurationName = configurationArgument.first else { throw BuilderErrors.invalidArgument("--configuration argument is equired") @@ -351,17 +351,17 @@ private struct BuilderConfiguration: CustomStringConvertible { throw BuilderErrors.invalidArgument("invalid build configuration named '\(buildConfigurationName)'") } self.buildConfiguration = _buildConfiguration - + guard !(!swiftVersionArgument.isEmpty && !baseDockerImageArgument.isEmpty) else { throw BuilderErrors.invalidArgument("--swift-version and --base-docker-image are mutually exclusive") } - + let swiftVersion = swiftVersionArgument.first ?? .none // undefined version will yield the latest docker image self.baseDockerImage = - baseDockerImageArgument.first ?? "swift:\(swiftVersion.map { $0 + "-" } ?? "")amazonlinux2" - + baseDockerImageArgument.first ?? "swift:\(swiftVersion.map { $0 + "-" } ?? "")amazonlinux2" + self.disableDockerImageUpdate = disableDockerImageUpdateArgument - + if self.verboseLogging { print("-------------------------------------------------------------------------") print("configuration") @@ -369,22 +369,22 @@ private struct BuilderConfiguration: CustomStringConvertible { print(self) } } - + var description: String { - """ - { - outputDirectory: \(self.outputDirectory) - products: \(self.products) - buildConfiguration: \(self.buildConfiguration) - dockerToolPath: \(self.dockerToolPath) - baseDockerImage: \(self.baseDockerImage) - disableDockerImageUpdate: \(self.disableDockerImageUpdate) - zipToolPath: \(self.zipToolPath) - packageID: \(self.packageID) - packageDisplayName: \(self.packageDisplayName) - packageDirectory: \(self.packageDirectory) - } - """ + """ + { + outputDirectory: \(self.outputDirectory) + products: \(self.products) + buildConfiguration: \(self.buildConfiguration) + dockerToolPath: \(self.dockerToolPath) + baseDockerImage: \(self.baseDockerImage) + disableDockerImageUpdate: \(self.disableDockerImageUpdate) + zipToolPath: \(self.zipToolPath) + packageID: \(self.packageID) + packageDisplayName: \(self.packageDisplayName) + packageDirectory: \(self.packageDirectory) + } + """ } } @@ -396,7 +396,7 @@ private enum BuilderErrors: Error, CustomStringConvertible { case failedWritingDockerfile case failedParsingDockerOutput(String) case processFailed([String], Int32) - + var description: String { switch self { case .invalidArgument(let description): diff --git a/Sources/AWSLambdaPluginHelper/lambda-deploy/Deployer.swift b/Sources/AWSLambdaPluginHelper/lambda-deploy/Deployer.swift index a9e2462f..480ff2d4 100644 --- a/Sources/AWSLambdaPluginHelper/lambda-deploy/Deployer.swift +++ b/Sources/AWSLambdaPluginHelper/lambda-deploy/Deployer.swift @@ -47,7 +47,7 @@ struct Deployer { --help Show help information. """ ) - } + } } private struct DeployerConfiguration: CustomStringConvertible { diff --git a/Sources/AWSLambdaPluginHelper/lambda-init/Initializer.swift b/Sources/AWSLambdaPluginHelper/lambda-init/Initializer.swift index 02954317..8c3d0f28 100644 --- a/Sources/AWSLambdaPluginHelper/lambda-init/Initializer.swift +++ b/Sources/AWSLambdaPluginHelper/lambda-init/Initializer.swift @@ -19,48 +19,47 @@ import Foundation #endif struct Initializer { - + private let destFileName = "Sources/main.swift" - + func initialize(arguments: [String]) async throws { - + let configuration = try InitializerConfiguration(arguments: arguments) - + if configuration.help { self.displayHelpMessage() return } - + let destFileURL = configuration.destinationDir.appendingPathComponent(destFileName) do { - + let template = TemplateType.template(for: configuration.templateType) try template.write(to: destFileURL, atomically: true, encoding: .utf8) - + if configuration.verboseLogging { print("File created at: \(destFileURL)") } - + print("✅ Lambda function written to \(destFileName)") print("📦 You can now package with: 'swift package lambda-build'") } catch { print("🛑Failed to create the Lambda function file: \(error)") } } - - + private func displayHelpMessage() { print( """ OVERVIEW: A SwiftPM plugin to scaffold a HelloWorld Lambda function. By default, it creates a Lambda function that receives a JSON document and responds with another JSON document. - + USAGE: swift package lambda-init [--help] [--verbose] [--with-url] [--allow-writing-to-package-directory] - + OPTIONS: --with-url Create a Lambda function exposed with an URL --allow-writing-to-package-directory Don't ask for permissions to write files. @@ -74,7 +73,7 @@ struct Initializer { private enum TemplateType { case `default` case url - + static func template(for type: TemplateType) -> String { switch type { case .default: return functionWithJSONTemplate @@ -88,27 +87,27 @@ private struct InitializerConfiguration: CustomStringConvertible { public let verboseLogging: Bool public let destinationDir: URL public let templateType: TemplateType - + public init(arguments: [String]) throws { var argumentExtractor = ArgumentExtractor(arguments) let verboseArgument = argumentExtractor.extractFlag(named: "verbose") > 0 let helpArgument = argumentExtractor.extractFlag(named: "help") > 0 let destDirArgument = argumentExtractor.extractOption(named: "dest-dir") let templateURLArgument = argumentExtractor.extractFlag(named: "with-url") > 0 - + // help required ? self.help = helpArgument - + // verbose logging required ? self.verboseLogging = verboseArgument - + // dest dir self.destinationDir = URL(fileURLWithPath: destDirArgument[0]) - + // template type. Default is the JSON one self.templateType = templateURLArgument ? .url : .default } - + var description: String { """ { diff --git a/Sources/AWSLambdaPluginHelper/lambda-init/Template.swift b/Sources/AWSLambdaPluginHelper/lambda-init/Template.swift index 865a122a..404d743b 100644 --- a/Sources/AWSLambdaPluginHelper/lambda-init/Template.swift +++ b/Sources/AWSLambdaPluginHelper/lambda-init/Template.swift @@ -45,7 +45,7 @@ let functionWithJSONTemplate = #""" struct HelloResponse: Encodable { let greetings: String } - + // in this example we receive a HelloRequest JSON and we return a HelloResponse JSON // the Lambda runtime From e3a579c6c0054c3c2cf5f32a1aa0e22e549ffea8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Stormacq?= Date: Wed, 30 Jul 2025 20:51:35 +0400 Subject: [PATCH 10/10] minor wording changes --- Package.swift | 9 ++++----- Package@swift-6.0.swift | 9 ++++----- Sources/AWSLambdaPluginHelper/lambda-build/Builder.swift | 2 +- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/Package.swift b/Package.swift index b83b04c8..55508532 100644 --- a/Package.swift +++ b/Package.swift @@ -101,12 +101,12 @@ let package = Package( intent: .custom( verb: "lambda-build", description: - "Archive the Lambda binary and prepare it for uploading to AWS. Requires docker on macOS or non Amazonlinux 2 distributions." + "Compile and archive (zip) the Lambda binary and prepare it for uploading to AWS. Requires docker on macOS or non Amazonlinux 2 distributions." ), permissions: [ .allowNetworkConnections( scope: .docker, - reason: "This plugin uses Docker to create the AWS Lambda ZIP package." + reason: "This plugin uses Docker to compile code for Amazon Linux." ) ] ), @@ -120,7 +120,7 @@ let package = Package( intent: .custom( verb: "lambda-deploy", description: - "Deploy the Lambda function. You must have an AWS account and know an access key and secret access key." + "Deploy the Lambda function. You must have an AWS account and an access key and secret access key." ), permissions: [ .allowNetworkConnections( @@ -138,8 +138,7 @@ let package = Package( dependencies: [ .product(name: "NIOHTTP1", package: "swift-nio"), .product(name: "NIOCore", package: "swift-nio"), - ], - swiftSettings: [.swiftLanguageMode(.v6)] + ] ), .testTarget( name: "AWSLambdaRuntimeTests", diff --git a/Package@swift-6.0.swift b/Package@swift-6.0.swift index 726af108..52aea491 100644 --- a/Package@swift-6.0.swift +++ b/Package@swift-6.0.swift @@ -90,12 +90,12 @@ let package = Package( intent: .custom( verb: "lambda-build", description: - "Archive the Lambda binary and prepare it for uploading to AWS. Requires docker on macOS or non Amazonlinux 2 distributions." + "Compile and archive (zip) the Lambda binary and prepare it for uploading to AWS. Requires docker on macOS or non Amazonlinux 2 distributions." ), permissions: [ .allowNetworkConnections( scope: .docker, - reason: "This plugin uses Docker to create the AWS Lambda ZIP package." + reason: "This plugin uses Docker to compile code for Amazon Linux." ) ] ), @@ -109,7 +109,7 @@ let package = Package( intent: .custom( verb: "lambda-deploy", description: - "Deploy the Lambda function. You must have an AWS account and know an access key and secret access key." + "Deploy the Lambda function. You must have an AWS account and an access key and secret access key." ), permissions: [ .allowNetworkConnections( @@ -127,8 +127,7 @@ let package = Package( dependencies: [ .product(name: "NIOHTTP1", package: "swift-nio"), .product(name: "NIOCore", package: "swift-nio"), - ], - swiftSettings: [.swiftLanguageMode(.v6)] + ] ), .testTarget( name: "AWSLambdaRuntimeTests", diff --git a/Sources/AWSLambdaPluginHelper/lambda-build/Builder.swift b/Sources/AWSLambdaPluginHelper/lambda-build/Builder.swift index 9a3c33fe..c1f8b45a 100644 --- a/Sources/AWSLambdaPluginHelper/lambda-build/Builder.swift +++ b/Sources/AWSLambdaPluginHelper/lambda-build/Builder.swift @@ -247,7 +247,7 @@ struct Builder { REQUIREMENTS: To use this plugin, you must have docker installed and started. - USAGE: swift package --allow-network-connections docker archive + USAGE: swift package --allow-network-connections docker lambda-build [--help] [--verbose] [--output-path ] [--products ]