From 179964406b026db1c754990a2e795c2d1982fcaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20G=C3=B3ral?= <60390247+jan-gogo@users.noreply.github.com> Date: Mon, 31 May 2021 14:04:31 +0200 Subject: [PATCH] docs: Add docs for Corellium modules (#1969) Fixes #1951 Added or updated README.md files: * [:corellium](https://github.com/Flank/flank/tree/1951_Improve_Flank-Corellium_documentation/corellium) * [:corellium:adapter](https://github.com/Flank/flank/tree/1951_Improve_Flank-Corellium_documentation/corellium/adapter) * [:corellium:api](https://github.com/Flank/flank/tree/1951_Improve_Flank-Corellium_documentation/corellium/api) * [:corellium:apk](https://github.com/Flank/flank/tree/1951_Improve_Flank-Corellium_documentation/corellium/apk) * [:corellium:cli](https://github.com/Flank/flank/tree/1951_Improve_Flank-Corellium_documentation/corellium/cli) * [:corellium:client](https://github.com/Flank/flank/tree/1951_Improve_Flank-Corellium_documentation/corellium/client) * [:corellium:domain](https://github.com/Flank/flank/tree/1951_Improve_Flank-Corellium_documentation/corellium/domain) * [:corellium:instrument](https://github.com/Flank/flank/tree/1951_Improve_Flank-Corellium_documentation/corellium/instrument) * [:corellium:instrument:command](https://github.com/Flank/flank/tree/1951_Improve_Flank-Corellium_documentation/corellium/instrument/command) * [:corellium:instrument:log](https://github.com/Flank/flank/tree/1951_Improve_Flank-Corellium_documentation/corellium/instrument/log) * [:corellium:junit](https://github.com/Flank/flank/tree/1951_Improve_Flank-Corellium_documentation/corellium/junit) * [:corellium:sandbox](https://github.com/Flank/flank/tree/1951_Improve_Flank-Corellium_documentation/corellium/sandbox) * [:corellium:shard](https://github.com/Flank/flank/tree/1951_Improve_Flank-Corellium_documentation/corellium/shard) Additionally, this PR is fixing instrument log parsing to match the specification. --- corellium/README.md | 11 + corellium/adapter/README.md | 9 + corellium/api/README.md | 18 ++ corellium/apk/README.md | 15 + .../main/kotlin/flank/apk/internal/Parse.kt | 8 +- corellium/cli/README.md | 9 + corellium/client/README.md | 279 +++--------------- .../flank/corellium/client/agent/Agent.kt | 10 +- .../flank/corellium/client/agent/Connect.kt | 5 +- .../corellium/client/agent/Disconnect.kt | 3 + .../corellium/client/agent/UploadFile.kt | 11 +- .../client/console/{Functions.kt => Api.kt} | 32 +- .../flank/corellium/client/console/Connect.kt | 5 +- .../flank/corellium/client/console/Console.kt | 5 + .../kotlin/flank/corellium/client/core/Api.kt | 98 +++++- .../flank/corellium/client/core/Connect.kt | 8 + .../flank/corellium/client/core/Corellium.kt | 3 + .../ApiKtTest.kt} | 5 +- corellium/domain/README.md | 22 +- .../domain/RunTestCorelliumAndroid.kt | 25 +- corellium/instrument/README.md | 9 + corellium/instrument/command/README.md | 9 + .../command/FormatAmInstrumentCommand.kt | 2 +- corellium/instrument/log/README.md | 107 +++++++ .../kotlin/flank/instrument/log/Internal.kt | 21 +- .../kotlin/flank/instrument/log/Parser.kt | 6 +- .../flank/instrument/log/InternalTest.kt | 13 + corellium/junit/README.md | 10 + .../sandbox/android/AndroidExample.kt | 1 + corellium/shard/README.md | 11 +- corellium/shard/calculate/README.md | 10 +- corellium/shard/dump/README.md | 6 + corellium/shard/obfuscate/README.md | 8 +- docs/corellium/client-api-class.puml | 207 +++++++++++-- docs/corellium/modules.puml | 28 +- docs/corellium/run-test-android.puml | 104 +++++++ docs/corellium/steps-dependencies-class.puml | 26 +- 37 files changed, 815 insertions(+), 344 deletions(-) create mode 100644 corellium/README.md create mode 100644 corellium/adapter/README.md create mode 100644 corellium/api/README.md create mode 100644 corellium/apk/README.md create mode 100644 corellium/cli/README.md rename corellium/client/src/main/kotlin/flank/corellium/client/console/{Functions.kt => Api.kt} (61%) rename corellium/client/src/test/kotlin/flank/corellium/client/{parser/FunctionsKtTest.kt => console/ApiKtTest.kt} (85%) create mode 100644 corellium/instrument/README.md create mode 100644 corellium/instrument/command/README.md create mode 100644 corellium/instrument/log/README.md create mode 100644 corellium/junit/README.md create mode 100644 docs/corellium/run-test-android.puml diff --git a/corellium/README.md b/corellium/README.md new file mode 100644 index 0000000000..54a5259be4 --- /dev/null +++ b/corellium/README.md @@ -0,0 +1,11 @@ +# Flank - Corellium + +The Flank test runner driven on Corellium backend. + +### Modules + +![flank-corellium-modules](http://www.plantuml.com/plantuml/proxy?cache=no&fmt=svg&src=https://raw.githubusercontent.com/Flank/flank/1951_Improve_Flank-Corellium_documentation/docs/corellium/modules.puml) + +### Android test run + +![corellium-run-test-android](http://www.plantuml.com/plantuml/proxy?cache=no&fmt=svg&src=https://raw.githubusercontent.com/Flank/flank/1951_Improve_Flank-Corellium_documentation/docs/corellium/run-test-android.puml) diff --git a/corellium/adapter/README.md b/corellium/adapter/README.md new file mode 100644 index 0000000000..f265ad7195 --- /dev/null +++ b/corellium/adapter/README.md @@ -0,0 +1,9 @@ +# Flank - Corellium - Adapters + +This module is implementing adapters that connect [API](../api) interfaces with Corellium [client](../client). + +### References + +* Module type - [adapter](../../docs/architecture.md#adapter) +* Dependency type - [static](../../docs/architecture.md#static_dependencies) +* Public API - [Api.kt](./src/main/kotlin/flank/corellium/Api.kt) diff --git a/corellium/api/README.md b/corellium/api/README.md new file mode 100644 index 0000000000..85cf1b4d43 --- /dev/null +++ b/corellium/api/README.md @@ -0,0 +1,18 @@ +# Flank - Corellium - API + +This module specifies the API required by the business logic to operate on corellium features. + +The API is designed to exactly meet domain requirements rather than reflect the remote protocol or third-party client. + +### References + +* Module type - [API](../../docs/architecture.md#api) +* Dependency type - [dynamic](../../docs/architecture.md#dynamic_dependencies) +* Public API - [CorelliumApi.kt](./src/main/kotlin/flank/corellium/api/CorelliumApi.kt) + +## Features + +* Authorizing corellium session - [Authorization.kt](./src/main/kotlin/flank/corellium/api/Authorization.kt) +* Invoking android virtual instances - [AndroidInstance.kt](./src/main/kotlin/flank/corellium/api/AndroidInstance.kt) +* Installing android apps and tests - [AndroidApps.kt](./src/main/kotlin/flank/corellium/api/AndroidApps.kt) +* Executing tests & getting logs - [AndroidTestPlan.kt](./src/main/kotlin/flank/corellium/api/AndroidApps.kt) diff --git a/corellium/apk/README.md b/corellium/apk/README.md new file mode 100644 index 0000000000..58f21d9a43 --- /dev/null +++ b/corellium/apk/README.md @@ -0,0 +1,15 @@ +# Android apk parser + +Module responsible for retrieving data like: + +* test cases names +* test runner name +* package name + +from the provided apk files. + +### References + +* Module type - [tool](../../docs/architecture.md#tool) +* Dependency type - [dynamic](../../docs/architecture.md#dynamic_dependencies) +* Public API - [Apk.kt](./src/main/kotlin/flank/apk/Apk.kt) diff --git a/corellium/apk/src/main/kotlin/flank/apk/internal/Parse.kt b/corellium/apk/src/main/kotlin/flank/apk/internal/Parse.kt index eed7188412..1841fca751 100644 --- a/corellium/apk/src/main/kotlin/flank/apk/internal/Parse.kt +++ b/corellium/apk/src/main/kotlin/flank/apk/internal/Parse.kt @@ -9,8 +9,7 @@ import java.io.StringReader import javax.xml.parsers.DocumentBuilderFactory internal val parseApkTestCases = Apk.ParseTestCases { path -> - DexParser.findTestMethods(path) - .map(TestMethod::testName) + DexParser.findTestMethods(path).map(TestMethod::testName) } internal val parseApkPackageName = Apk.ParsePackageName { path -> @@ -18,11 +17,12 @@ internal val parseApkPackageName = Apk.ParsePackageName { path -> } internal val parseApkInfo = Apk.ParseInfo { path -> + val apkFile = ApkFile(path) Apk.Info( - packageName = ApkFile(path).apkMeta.packageName, + packageName = apkFile.apkMeta.packageName, testRunner = DocumentBuilderFactory.newInstance() .newDocumentBuilder() - .parse(InputSource(StringReader(ApkFile(path).manifestXml))) + .parse(InputSource(StringReader(apkFile.manifestXml))) .getElementsByTagName("instrumentation").item(0).attributes .getNamedItem("android:name").nodeValue ) diff --git a/corellium/cli/README.md b/corellium/cli/README.md new file mode 100644 index 0000000000..bc8c6d6dc0 --- /dev/null +++ b/corellium/cli/README.md @@ -0,0 +1,9 @@ +# Flank - Corellium - CLI + +This module is specifying and implementing a command-line user interface for Flank-Corellium features. + +### References + +* Module type - [presentation](../../docs/architecture.md#presentation) +* Dependency type - [static](../../docs/architecture.md#dynamic_dependencies) +* Public API - package [flank.corellium.cli](./src/main/kotlin/flank/corellium/cli) diff --git a/corellium/client/README.md b/corellium/client/README.md index 961630b09a..dc989a8e93 100644 --- a/corellium/client/README.md +++ b/corellium/client/README.md @@ -1,287 +1,84 @@ -## About +## Corellium Client -Only for POC integration purposes. It's based on [corellium-api](https://github.com/corellium/corellium-api). It has -only essential features implemented to be able to run a simple set of IOS tests that coincide -to [example](https://github.com/corellium/corellium-api/blob/master/examples/agent-simple.js) +The kotlin implementation of Corellium API client based on official [JS implementation](https://github.com/corellium/corellium-api). -## **IT IS DEFINITELY NOT PRODUCTION READY!** +Be aware, this library is designed for the Flank-Corellium integration, so the features not required by Flank but available in official client, may not be provided here, also data structures may not contain all properties. -There are lots of features missing (compared to nodeJS corellium [client](https://github.com/corellium/corellium-api)). +### References -## Installing - -JDK 8+ is required (tested on java 8 & 15). [KTOR](https://ktor.io/) is used as HTTP client. - -To build just run (from the root of the project) - -``` -./gradlew build -p corellium/client -``` +* Official Corellium JS client - [corellium-api](https://github.com/corellium/corellium-api) +* Official JS example - [agent-simple.js](https://github.com/corellium/corellium-api/blob/master/examples/agent-simple.js) +* HTTP client - [KTOR](https://ktor.io/) ## API -### class Corellium +Access to the Corellium API is provided through the connection contexts, that are represented by structures. Connection contexts along with API functions are creating hierarchical relations visible on following diagram: -------- +![corellium-client](http://www.plantuml.com/plantuml/proxy?cache=no&fmt=svg&src=https://raw.githubusercontent.com/Flank/flank/1951_Improve_Flank-Corellium_documentation/docs/corellium/client-api-class.puml) -#### new instance +### Corellium -Creates new corellium client instance. Options: +The root connection context that providing core API functions. -* `api` -> your corellium's http adrress, example: `yourcompany.enterprise.corellium.com` (without `https`) -* `username`, `password` -> credentials to log in and acquire token -* `tokenFallback` (optional) -> pass if you don't want to log in -* `logging` (optional) -> logging level for http requests, default: `None`. More - info [LoggingLevel](#sealed-class-logginglevel) +To gain access to the root context, call: ```kotlin -val client = Corellium( - api = String, - username = String, - password = String, - tokenFallback = String, - logging = LoggingLevel +val corellium: Corellium = connectCorellium( + api = "your.company.enterprise.corellium.com", // without protocol (https) + username = "your account user name", + password = "your account password", ) ``` -#### logIn(): String (token) - -Logs in with given credentials. Acquired `token` is persisted inside the client. It is also returned from the function -if you want to reuse it +### Agent -```kotlin -val token: String = client.logIn() -``` - -#### getProjectIdList(): List +The connection context for operations on virtual devices [`instance`](#instance) -Returns a list of project IDs associated with the given credentials +To gain access to the agent context, call: ```kotlin -val ids: List = client.getProjectIdList() +val instanceId = "..." +val instance: Instance = corellium.getInstanceInfo(instanceId) +val agent: Agent = corellium.connectAgent(instance.agent!!.info) ``` -#### getAllProjects(): List - -Returns a list of [Project](#data-class-project) associated with the given account +To obtain `instanceId`, create new instance: ```kotlin -val projects: List = client.getAllProjects() -val testProjectId = projects.first { it.name == "test-project" }.id -``` - -#### getProjectInstancesList(projectId: String): List - -Returns a list of [Instance](#data-class-instance) created for the project with the given id - -```kotlin -val projectId = "1111-2222-3333-aaa-bbbbbb-cccc" -val instances: List = client.getProjectInstancesList(projectId) -``` - -#### createNewInstance(newInstance: Instance): String (instance id) - -Creates a new [Instance](#data-class-instance), returns newly created instance id. - -NOTE: instance created with this method is not ready to use (state == `creating`). -Use [waitUntilInstanceIsReady](#waituntilinstanceisreadyinstanceid-string) to ensure VM has `on` state - -The following options are required (all `Instance` fields are described [here](#data-class-instance)) - -* `project` -> id of the project your instance is going to be created in -* `name` -> instance's name -* `flavor` -> flavor of the instance. Currently supported devices are - listed [here](https://github.com/corellium/corellium-api#async-createinstanceoptions) -* `os` -> software version - -```kotlin -val newInstanceId: String = client.createNewInstance( +val instanceId: String = corellium.createNewInstance( Instance( project = "11111-aaaaa-bbbbb-33333", name = "test-iphone", flavor = "iphone7", - os = "13.0" + os = "13.0", + bootOptions = BootOptions( + screen = screen + ) ) ) ``` -#### waitUntilInstanceIsReady(instanceId: String) - -Waits until instance with `id` == `instanceId` is ready to use (`state` is `on`). It's `suspend` function so the thread -is not blocked. - -```kotlin -val instanceId = "11111-aaaaa-bbbbb-33333" -client.waitUntilInstanceIsReady(instanceId) -``` - -#### getInstanceInfo(instanceId: String): Instance - -Returns [Instance](#data-class-instance) for given `instanceId` - -```kotlin -val instanceId = "11111-aaaaa-bbbbb-33333" -val instance: Instance = client.getInstanceInfo(instanceId) -``` - -#### deleteInstance(instanceId: String) - -Deletes instance by id +Or find the existing one: ```kotlin -val instanceId = "11111-aaaaa-bbbbb-33333" -client.deleteInstance(instanceId) -``` - -#### createAgent(agentInfo: String): Agent - -Creates [Agent](#class-agent) for the given instance - -**NOTE**: Creating an agent can be flaky sometimes and may throw 5xx errors. There is the retry mechanism implemented -but sometimes it's not enough. - -```kotlin -val instanceId = "11111-aaaaa-bbbbb-33333" -val instance = client.getInstanceInfo(instanceId) -val agent: Agent = client.createAgent(instance.agent.info) -``` - -#### getVPNConfig(projectId: String, type: VPN) - -Downloads VPN config file. Currently, filename is hardcoded: - -* `config.ovpn` -> for `VPN.OVPN` -* `tblk.zip` -> for `VPN.TBLK` - -```kotlin -client.getVPNConfig( - projectId = "11111-aaaaa-bbbbb-33333", - type = VPN.TBLK, - id = "asdasd-adaag-324234-dfsdf" // optional, if not passed, UUID.randomUUID() is used to generate -) -``` - -### class Agent - ------ - -This is a wrapper class -for [ClientWebSocketSession](https://api.ktor.io/1.5.1/io.ktor.client.features.websocket/-client-web-socket-session/index.html) -with additional logic. - -#### new instance - -Should be created only with factory method [Corellium#createAgent](#createagentagentinfo-string-agent). - -#### uploadFile(path: String, bytes: ByteArray) - -Uploads file (`ByteArray`) to the given `path`, on the device agent is connected to. - -```kotlin -val path = "/var/usr/temp-file" -val bytes = File("path/to/file").readBytes() - -agent.uploadFile(path, bytes) -``` - -#### close() - -Closes agent's connection. - -```kotlin -agent.close() -``` - -#### waitForAgentReady() - -Suspends coroutine until agent connection is established and ready to use - -```kotlin -agent.waitForAgentReady() -``` - -### data class Project - ----- -**NOTE**: Does not contain all fields returned from corellium API - -```kotlin -data class Project( - val id: String, - val name: String, - val quotas: Quotas, - val quotasUsed: Quotas -) - -data class Quotas( - val cores: Int, - val instances: Int, - val ram: Int, - val cpus: Int, - val gpus: Int? = null, - val instance: Int? = null -) +val projectId = "1111-2222-3333-aaa-bbbbbb-cccc" +val instanceName = "Some instance 1" +val instance: Instance = corellium.getProjectInstancesList(projectId).find { instance -> instance.name == instanceName }!! +val instanceId: String = instance.id ``` -### data class Instance +### Console ----- -**NOTE**: Does not contain all fields returned from corellium API +The context of connection to the shell of specific instance. ```kotlin -data class Instance( - val id: String = "", - val name: String = "", - val key: String = "", - val flavor: String, - val type: String = "", - val project: String, - val state: String = "", - val bootOptions: BootOptions = BootOptions(), - val patches: List = emptyList(), - val os: String = "", - val osbuild: String = "", - val agent: InstanceAgent? = InstanceAgent() -) - -data class BootOptions( - val bootArgs: String = "", - val restoreBootArgs: String = "", - val udid: String = "", - val ecid: String = "" -) - -data class InstanceAgent( - val hash: String = "", - val info: String = "" -) +val console: Console = corellium.connectConsole(instanceId) ``` -### sealed class LoggingLevel - ----- -Subclass names' are corresponding with ktor's -enum [LogLevel](https://api.ktor.io/1.5.1/io.ktor.client.features.logging/-log-level/index.html) values. +### Instance -* `LoggingLevel.All` -> `LogLevel.ALL` -* `LoggingLevel.None` -> `LogLevel.NONE` -* `LoggingLevel.Body` -> `LogLevel.BODY` -* `LoggingLevel.Headers` -> `LogLevel.HEADERS` -* `LoggingLevel.Info` -> `LogLevel.INFO` - -### enum VPN - ----- - -```kotlin -enum class VPN { - OVPN, TBLK -} -``` +The virtual instance of ARM device. ## Additional Info -* There are lots of more features available by API, the current implementation has essential features to run iOS test on - Corellium device, from Flank's perspective -* All `Corellium` client requests have a retry mechanism implemented. Sometimes it's not enough though * Creating new instance usually requires USBFlux restart diff --git a/corellium/client/src/main/kotlin/flank/corellium/client/agent/Agent.kt b/corellium/client/src/main/kotlin/flank/corellium/client/agent/Agent.kt index c43bd01e71..519be56ab2 100644 --- a/corellium/client/src/main/kotlin/flank/corellium/client/agent/Agent.kt +++ b/corellium/client/src/main/kotlin/flank/corellium/client/agent/Agent.kt @@ -6,6 +6,14 @@ import kotlinx.serialization.json.Json import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.atomic.AtomicInteger +/** + * The context of connection with the instance agent. + * + * @property session Web socket session with the agent. + * @property tasks References to the running task result listeners. + * @property counter Task counter. + * @property format [Json] helper for parsing and formatting. + */ class Agent internal constructor( internal val session: ClientWebSocketSession, internal val tasks: TasksMap = TasksMap(), @@ -13,4 +21,4 @@ class Agent internal constructor( internal val format: Json = Json {}, ) -typealias TasksMap = ConcurrentHashMap Unit> +internal typealias TasksMap = ConcurrentHashMap Unit> diff --git a/corellium/client/src/main/kotlin/flank/corellium/client/agent/Connect.kt b/corellium/client/src/main/kotlin/flank/corellium/client/agent/Connect.kt index 6a23d4a0eb..682772632b 100644 --- a/corellium/client/src/main/kotlin/flank/corellium/client/agent/Connect.kt +++ b/corellium/client/src/main/kotlin/flank/corellium/client/agent/Connect.kt @@ -19,7 +19,10 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withTimeout import kotlinx.serialization.decodeFromString -suspend fun connectAgent( +/** + * Internal factory method for creating initialized [Agent] connection context. + */ +internal suspend fun connectAgent( agentUrl: String, token: String, logLevel: LogLevel = LogLevel.NONE diff --git a/corellium/client/src/main/kotlin/flank/corellium/client/agent/Disconnect.kt b/corellium/client/src/main/kotlin/flank/corellium/client/agent/Disconnect.kt index 513958adc1..cd9113cb00 100644 --- a/corellium/client/src/main/kotlin/flank/corellium/client/agent/Disconnect.kt +++ b/corellium/client/src/main/kotlin/flank/corellium/client/agent/Disconnect.kt @@ -2,6 +2,9 @@ package flank.corellium.client.agent import io.ktor.http.cio.websocket.close +/** + * Disconnect agent connection. + */ suspend fun Agent.disconnect() { session.close() } diff --git a/corellium/client/src/main/kotlin/flank/corellium/client/agent/UploadFile.kt b/corellium/client/src/main/kotlin/flank/corellium/client/agent/UploadFile.kt index de75533dcd..10a9ba900b 100644 --- a/corellium/client/src/main/kotlin/flank/corellium/client/agent/UploadFile.kt +++ b/corellium/client/src/main/kotlin/flank/corellium/client/agent/UploadFile.kt @@ -7,7 +7,16 @@ import kotlinx.coroutines.withTimeout import java.nio.ByteBuffer import java.nio.ByteOrder -suspend fun Agent.uploadFile(path: String, bytes: ByteArray) { +/** + * Upload [ByteArray] to a specific virtual instance as a file under a given path. + * + * @param path The path on virtual where the file will be created. + * @param bytes Bytes that will be written under a given path. + */ +suspend fun Agent.uploadFile( + path: String, + bytes: ByteArray +) { val id = counter.getAndIncrement() sendCommand( diff --git a/corellium/client/src/main/kotlin/flank/corellium/client/console/Functions.kt b/corellium/client/src/main/kotlin/flank/corellium/client/console/Api.kt similarity index 61% rename from corellium/client/src/main/kotlin/flank/corellium/client/console/Functions.kt rename to corellium/client/src/main/kotlin/flank/corellium/client/console/Api.kt index 8a18023042..32145ec105 100644 --- a/corellium/client/src/main/kotlin/flank/corellium/client/console/Functions.kt +++ b/corellium/client/src/main/kotlin/flank/corellium/client/console/Api.kt @@ -10,15 +10,24 @@ import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.transform import kotlinx.coroutines.launch -suspend fun Console.sendCommand(command: String) = +/** + * Send a command to serial console. + */ +suspend fun Console.sendCommand(command: String): Unit = session.send(Frame.Binary(true, (command + "\n").encodeToByteArray())) +/** + * Block the execution and wait until the console is not producing output for a specified amount of time. + */ suspend fun Console.waitForIdle(timeToWait: Long) { delay(3_000) while (System.currentTimeMillis() - lastResponseTime < timeToWait) delay(200) } -suspend fun Console.close() = session.close() +/** + * Close the console connection. + */ +suspend fun Console.close(): Unit = session.close() @Deprecated("Use Console.flowLogs()") fun Console.launchOutputPrinter() = session.launch { @@ -31,16 +40,31 @@ fun Console.launchOutputPrinter() = session.launch { } } +/** + * Clear unread log messages buffer. + */ suspend fun Console.clear() { session.incoming.receive() } -fun Console.flowLogs() = session.incoming +/** + * Flow log lines from the serial console output. + * + * Do not collect more than one flow from the same [Console] at the same time to prevent a race condition. + */ +fun Console.flowLogs(): Flow = session.incoming .receiveAsFlow() .onEach { lastResponseTime = System.currentTimeMillis() } - .map { it.data.decodeToString() } + .map { frame: Frame -> frame.data.decodeToString() } .normalizeLines() +/** + * Converts flow of frames into flow of lines. + * + * The incoming console logs are divided into frames for network purposes, but lines are much more convenient to parse. + * + * To check the example, see the unit test of this method. + */ internal fun Flow.normalizeLines(): Flow { var acc = "" return transform { next -> diff --git a/corellium/client/src/main/kotlin/flank/corellium/client/console/Connect.kt b/corellium/client/src/main/kotlin/flank/corellium/client/console/Connect.kt index 3772489844..b285509d82 100644 --- a/corellium/client/src/main/kotlin/flank/corellium/client/console/Connect.kt +++ b/corellium/client/src/main/kotlin/flank/corellium/client/console/Connect.kt @@ -7,7 +7,10 @@ import io.ktor.client.features.websocket.webSocketSession import io.ktor.client.request.header import io.ktor.client.request.url -suspend fun connectConsole( +/** + * Internal factory method for creating initialized [Console] context. + */ +internal suspend fun connectConsole( url: String, token: String ): Console = diff --git a/corellium/client/src/main/kotlin/flank/corellium/client/console/Console.kt b/corellium/client/src/main/kotlin/flank/corellium/client/console/Console.kt index 84ab174415..4d5830ca1e 100644 --- a/corellium/client/src/main/kotlin/flank/corellium/client/console/Console.kt +++ b/corellium/client/src/main/kotlin/flank/corellium/client/console/Console.kt @@ -3,6 +3,11 @@ package flank.corellium.client.console import io.ktor.client.features.websocket.ClientWebSocketSession import kotlinx.coroutines.CoroutineScope +/** + * The context of serial console connection. Allows to write and read the serial console of the virtual device. + * + * @property session Web socket session with bidirectional stream to the serial console. + */ class Console internal constructor( internal val session: ClientWebSocketSession ) : CoroutineScope by session { diff --git a/corellium/client/src/main/kotlin/flank/corellium/client/core/Api.kt b/corellium/client/src/main/kotlin/flank/corellium/client/core/Api.kt index 4e5e0594f2..b01fa5757e 100644 --- a/corellium/client/src/main/kotlin/flank/corellium/client/core/Api.kt +++ b/corellium/client/src/main/kotlin/flank/corellium/client/core/Api.kt @@ -26,6 +26,9 @@ import kotlinx.coroutines.delay import java.io.File import java.util.UUID +/** + * @return A list of [Project] associated with the given account. + */ suspend fun Corellium.getAllProjects(): List = getProjectIdList().map { withRetry { @@ -38,6 +41,9 @@ suspend fun Corellium.getAllProjects(): List = } }.awaitAll() +/** + * @return A list of project IDs associated with the given credentials. + */ suspend fun Corellium.getProjectIdList(): List = withRetry { client.get> { @@ -46,6 +52,9 @@ suspend fun Corellium.getProjectIdList(): List = }.map { it.id } } +/** + * @return A list of [Instance] created for the project with the given id. + */ suspend fun Corellium.getProjectInstancesList( projectId: String ): List = @@ -56,6 +65,22 @@ suspend fun Corellium.getProjectInstancesList( } } +/** + * Creates a new [Instance]. + * + * A newly created instance needs some time to setup (state == `creating`). + * Use [waitUntilInstanceIsReady] to ensure VM has `on` state and is ready to use. + * + * @param newInstance + * The following fields for [Instance] are required: + * * `project` -> id of the project your instance is going to be created in + * * `name` -> instance's name + * * `flavor` -> flavor of the instance. Currently supported devices are +listed here + * * `os` -> software version + * @returns Newly created instance id. + * @see official api options + */ suspend fun Corellium.createNewInstance( newInstance: Instance ): String = @@ -68,6 +93,11 @@ suspend fun Corellium.createNewInstance( }.id } +/** + * Delete virtual device instance by id. + * + * @param instanceId + */ suspend fun Corellium.deleteInstance( instanceId: String ): Unit = @@ -78,6 +108,11 @@ suspend fun Corellium.deleteInstance( } } +/** + * Start virtual device instance by id. + * + * @param instanceId + */ suspend fun Corellium.startInstance( instanceId: String ): Unit = @@ -88,6 +123,11 @@ suspend fun Corellium.startInstance( } } +/** + * Stop virtual device instance by id. + * + * @param instanceId + */ suspend fun Corellium.stopInstance( instanceId: String ): Unit = @@ -98,26 +138,41 @@ suspend fun Corellium.stopInstance( } } +/** + * Pause virtual device instance by id. + * + * @param instanceId + */ suspend fun Corellium.pauseInstance( instanceId: String ): Unit = withRetry { client.post { - url("$urlBase/instances/$instanceId/stop") + url("$urlBase/instances/$instanceId/pause") header("Authorization", token) } } +/** + * Unpause virtual device instance by id. + * + * @param instanceId + */ suspend fun Corellium.unpauseInstance( instanceId: String ): Unit = withRetry { client.post { - url("$urlBase/instances/$instanceId/stop") + url("$urlBase/instances/$instanceId/unpause") header("Authorization", token) } } +/** + * Start virtual device instance by id. + * + * @param instanceId + */ suspend fun Corellium.getInstanceInfo( instanceId: String ): Instance = @@ -128,10 +183,16 @@ suspend fun Corellium.getInstanceInfo( } } +/** + * Wait until the instance will change state into `on`. + * + * @param instanceId + */ suspend fun Corellium.waitUntilInstanceIsReady( instanceId: String ): Unit = withRetry { + // Possible risk of infinity loop while (getInstanceInfo(instanceId).state != Instance.State.ON) { // it could takes long time // if instance was created just moment ago @@ -140,6 +201,12 @@ suspend fun Corellium.waitUntilInstanceIsReady( } } +/** + * Initialize connection with instance agent. + * + * @param agentInfo Can be obtain from [Instance.Agent.info] + * @return [Agent] connection context + */ suspend fun Corellium.connectAgent( agentInfo: String ): Agent = @@ -149,14 +216,28 @@ suspend fun Corellium.connectAgent( logLevel = LogLevel.NONE, ) -suspend fun Corellium.getVPNConfig( +/** + * Download VPN configuration to connect to the project network. + * This is only available for cloud. + * At least one instance must be `on` in the project. + * + * The configuration file will be saved as: + * * `tblk.zip` - [VPN.TBLK] + * * `config.ovpn` - [VPN.OVPN] + * + * @param type Could be either "ovpn" or "tblk" to select between OpenVPN and TunnelBlick configuration formats. + * TunnelBlick files are delivered as a ZIP file and OpenVPN configuration is just a text file. + * @param clientUUID An arbitrary UUID to uniquely associate this VPN configuration with so it can be later identified + * in a list of connected clients. Optional. + */ +suspend fun Corellium.downloadVPNConfig( projectId: String, type: VPN, - id: String = UUID.randomUUID().toString() + clientUUID: String = UUID.randomUUID().toString() ) { val response: HttpResponse = withRetry { client.get { - url("$urlBase/projects/$projectId/vpn-configs/$id.${type.name.lowercase()}") + url("$urlBase/projects/$projectId/vpn-configs/$clientUUID.${type.name.lowercase()}") header("Authorization", token) } } @@ -166,6 +247,13 @@ suspend fun Corellium.getVPNConfig( enum class VPN { OVPN, TBLK } +/** + * Create console context. + * + * @param instanceId Id of instance that serves the console. + * + * @return [Console] connection context. + */ suspend fun Corellium.connectConsole( instanceId: String ): Console = diff --git a/corellium/client/src/main/kotlin/flank/corellium/client/core/Connect.kt b/corellium/client/src/main/kotlin/flank/corellium/client/core/Connect.kt index 0081974a49..44ce100d88 100644 --- a/corellium/client/src/main/kotlin/flank/corellium/client/core/Connect.kt +++ b/corellium/client/src/main/kotlin/flank/corellium/client/core/Connect.kt @@ -15,6 +15,14 @@ import io.ktor.http.ContentType import io.ktor.http.contentType import kotlinx.serialization.json.Json +/** + * Factory method for creating initialized Corellium connection context. + * + * @param api Corellium host address without protocol (https), example: `yourcompany.enterprise.corellium.com`. + * @param username Corellium account user name. + * @param password Corellium account password. + * @return Context of initialized Corellium connection. + */ suspend fun connectCorellium( api: String, username: String, diff --git a/corellium/client/src/main/kotlin/flank/corellium/client/core/Corellium.kt b/corellium/client/src/main/kotlin/flank/corellium/client/core/Corellium.kt index 75e531673f..8d9474ce79 100644 --- a/corellium/client/src/main/kotlin/flank/corellium/client/core/Corellium.kt +++ b/corellium/client/src/main/kotlin/flank/corellium/client/core/Corellium.kt @@ -3,6 +3,9 @@ package flank.corellium.client.core import io.ktor.client.HttpClient import kotlinx.coroutines.CoroutineScope +/** + * The Context of Corellium connection, provides access to the core Corellium features. + */ class Corellium internal constructor( internal val client: HttpClient, internal val urlBase: String, diff --git a/corellium/client/src/test/kotlin/flank/corellium/client/parser/FunctionsKtTest.kt b/corellium/client/src/test/kotlin/flank/corellium/client/console/ApiKtTest.kt similarity index 85% rename from corellium/client/src/test/kotlin/flank/corellium/client/parser/FunctionsKtTest.kt rename to corellium/client/src/test/kotlin/flank/corellium/client/console/ApiKtTest.kt index ec8a3f1a83..f2063e0318 100644 --- a/corellium/client/src/test/kotlin/flank/corellium/client/parser/FunctionsKtTest.kt +++ b/corellium/client/src/test/kotlin/flank/corellium/client/console/ApiKtTest.kt @@ -1,13 +1,12 @@ -package flank.corellium.client.parser +package flank.corellium.client.console -import flank.corellium.client.console.normalizeLines import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.flow.toList import kotlinx.coroutines.runBlocking import org.junit.Assert import org.junit.Test -class FunctionsKtTest { +class ApiKtTest { @Test fun normalizeLinesTest() { diff --git a/corellium/domain/README.md b/corellium/domain/README.md index d9e368cbe1..0fb5bbd49b 100644 --- a/corellium/domain/README.md +++ b/corellium/domain/README.md @@ -1,21 +1,19 @@ -# Flank-Corellium Domain Layer +# Flank - Corellium - Domain -This module is specifying public API and internal implementation of flank-corellium use-cases. +This module is specifying public API and internal implementation of Flank-Corellium use-cases. -## Public API +### References -Functions and structures which are specified directly inside [flank.corellium.domain](./src/main/kotlin/flank/corellium/domain) package. - -## Internal implementation - -Everything inside nested packages of [flank.corellium.domain](./src/main/kotlin/flank/corellium/domain). +* Module type - [domain](../../docs/architecture.md#domain) +* Dependency type - [static](../../docs/architecture.md#static_dependencies) +* Public API - files inside [flank.corellium.domain](./src/main/kotlin/flank/corellium/domain) +* Internal functions - nested packages inside [flank.corellium.domain](./src/main/kotlin/flank/corellium/domain) ## Design ### Stateful execution -Following specification is suitable for complicated long-running use-cases, -when becomes convenient to split execution into smaller atomic chunks of work. +Following specification is suitable for complicated long-running use-cases, when becomes convenient to split execution into smaller atomic chunks of work. #### Definition: @@ -29,6 +27,4 @@ when becomes convenient to split execution into smaller atomic chunks of work. #### Utility: -`Transform.kt` -* [local link](src/main/kotlin/flank/corellium/domain/util/Transform.kt) -* [github link](https://github.com/Flank/flank/blob/master/corellium/domain/src/main/kotlin/flank/corellium/domain/util/Transform.kt) +* [`Transform.kt`](src/main/kotlin/flank/corellium/domain/util/Transform.kt) diff --git a/corellium/domain/src/main/kotlin/flank/corellium/domain/RunTestCorelliumAndroid.kt b/corellium/domain/src/main/kotlin/flank/corellium/domain/RunTestCorelliumAndroid.kt index a793ae5b6f..9d0d03285a 100644 --- a/corellium/domain/src/main/kotlin/flank/corellium/domain/RunTestCorelliumAndroid.kt +++ b/corellium/domain/src/main/kotlin/flank/corellium/domain/RunTestCorelliumAndroid.kt @@ -26,6 +26,9 @@ import kotlinx.coroutines.runBlocking import java.lang.System.currentTimeMillis import java.text.SimpleDateFormat +/** + * Use case for running android tests on corellium virtual devices. + */ object RunTestCorelliumAndroid { /** @@ -39,7 +42,13 @@ object RunTestCorelliumAndroid { } /** - * The user configuration for invocation. + * The user arguments for the test execution. + * + * @param credentials The user credentials for authorizing connection with API. + * @param apks List of app apks with related test apks for testing. + * @param maxShardsCount Maximum amount of shards to create. For each shard Flank is invoking dedicated device instance, so do not use values grater than maximum number available instances in the Corellium account. + * @param obfuscateDumpShards Obfuscate the test names in shards before dumping to file. + * @param outputDir Set output dir. Default value is [DefaultOutputDir.new] */ data class Args( val credentials: Authorization.Credentials, @@ -48,6 +57,11 @@ object RunTestCorelliumAndroid { val obfuscateDumpShards: Boolean = false, val outputDir: String = DefaultOutputDir.new, ) { + /** + * Default output directory scheme. + * + * @property new Directory name in format: `results/corellium/android/yyyy-MM-dd_HH-mm-ss-SSS`. + */ object DefaultOutputDir { private const val PATH = "results/corellium/android/" private val date = SimpleDateFormat("yyyy-MM-dd_HH-mm-ss-SSS") @@ -56,16 +70,25 @@ object RunTestCorelliumAndroid { /** * Abstraction for app and test apk files. + * * @property path Absolut or relative path to apk file. */ sealed class Apk { abstract val path: String + /** + * App apk data + * + * @property tests List of test apks to run on app apk. + */ data class App( override val path: String, val tests: List ) : Apk() + /** + * Test apk data + */ data class Test( override val path: String ) : Apk() diff --git a/corellium/instrument/README.md b/corellium/instrument/README.md new file mode 100644 index 0000000000..a121a04ae1 --- /dev/null +++ b/corellium/instrument/README.md @@ -0,0 +1,9 @@ +# Activity Manager Instrumentation + +Common directory for tools related to `adb shell am instrument` command. + +### References + +* Official android doc - https://developer.android.com/studio/test/command-line#RunTestsDevice +* ADB Commands Manual - http://adbcommand.com/adbshell/am +* Help - https://gist.github.com/tsohr/5711945 diff --git a/corellium/instrument/command/README.md b/corellium/instrument/command/README.md new file mode 100644 index 0000000000..f290ab2a34 --- /dev/null +++ b/corellium/instrument/command/README.md @@ -0,0 +1,9 @@ +# Instrumentation command formatter + +This module is providing function for formatting `am instrument -r -w -e` with additional test cases filtering. + +### References + +* Module type - [tool](../../../docs/architecture.md#tool) +* Dependency type - [static](../../../docs/architecture.md#static_dependencies) +* Public API - [FormatAmInstrumentCommand.kt](./src/main/kotlin/flank/instrument/command/FormatAmInstrumentCommand.kt) diff --git a/corellium/instrument/command/src/main/kotlin/flank/instrument/command/FormatAmInstrumentCommand.kt b/corellium/instrument/command/src/main/kotlin/flank/instrument/command/FormatAmInstrumentCommand.kt index c445f4efe2..6a7121d6cd 100644 --- a/corellium/instrument/command/src/main/kotlin/flank/instrument/command/FormatAmInstrumentCommand.kt +++ b/corellium/instrument/command/src/main/kotlin/flank/instrument/command/FormatAmInstrumentCommand.kt @@ -3,7 +3,7 @@ package flank.instrument.command /** * The helper function for formatting instrumentation test shell command: * ``` - * $ adb am instrument -r -w -e "testCases[0..n]" "$packageName/$testRunner" + * $ adb shell am instrument -r -w -e "testCases[0..n]" "$packageName/$testRunner" * ``` * * Test cases names should match the following format: diff --git a/corellium/instrument/log/README.md b/corellium/instrument/log/README.md new file mode 100644 index 0000000000..1999735246 --- /dev/null +++ b/corellium/instrument/log/README.md @@ -0,0 +1,107 @@ +# Instrumentation log parser + +This module is providing a tool for parsing raw console log from the `adb shell am instrument -r` command into data the structures. + +### References + +* Module type - [tool](../../../docs/architecture.md#tool) +* Dependency type - [static](../../../docs/architecture.md#static_dependencies) +* Public API - [Parser.kt](./src/main/kotlin/flank/instrument/log/Parser.kt) +* Example console log - [0](./src/test/resources/example_android_logs_0.txt) [1](./src/test/resources/example_android_logs_1.txt) [2](./src/test/resources/example_android_logs_2.txt) [3](./src/test/resources/example_android_logs_3.txt) + +### Example input + +The stream of lines: + +``` +>.parseChunks(): Flow = map { group -> internal data class Chunk( val type: String, val code: Int, - val map: Map>, + val map: RawProperties, val timestamp: Long = System.currentTimeMillis(), ) +private typealias RawProperties = Map> + // Stage 3 ============================================ internal fun Flow.parseStatusResult(): Flow { @@ -133,22 +135,25 @@ private fun createStatus(first: Chunk, second: Chunk) = Instrument.Status( code = second.code, startTime = first.timestamp, endTime = second.timestamp, - details = (first.map + second.map).parseValues().createDetails() + details = (first.map to second.map).parseValues().createDetails() ) -private fun Map>.parseValues(): Map = - mapValues { (key, value) -> +private fun Pair.parseValues(): Map = + (first.keys + second.keys).associateWith { key -> + listOfNotNull(first[key], second[key]).flatten() + (first[key] ?: emptyList()) + (second[key] ?: emptyList()) + }.mapValues { (key, value: List) -> when (key) { "id", "test", "class", - -> value.first() + -> value.first() // all values are the same so we need only one "current", - "numTests", - -> value.first().trim().toInt() + "numtests", + -> value.first().trim().toInt() // same here but we want int - else -> value + else -> value // merged lines from stream or stack } } diff --git a/corellium/instrument/log/src/main/kotlin/flank/instrument/log/Parser.kt b/corellium/instrument/log/src/main/kotlin/flank/instrument/log/Parser.kt index f30151215f..d9fa2b73ab 100644 --- a/corellium/instrument/log/src/main/kotlin/flank/instrument/log/Parser.kt +++ b/corellium/instrument/log/src/main/kotlin/flank/instrument/log/Parser.kt @@ -41,14 +41,14 @@ sealed class Instrument { * @property endTime The time of creation the second chunk of status. * @property details The summary details of both chunks. */ - class Status( + data class Status( val code: Int, val startTime: Long, val endTime: Long, val details: Details, ) : Instrument() { - class Details( + data class Details( val raw: Map, val className: String, val testName: String, @@ -74,7 +74,7 @@ sealed class Instrument { * @property time The time of creation the result chunk. * @property details The details recorded for the result (Perhaps only a "stream" value). */ - class Result( + data class Result( val code: Int, val time: Long, val details: Map diff --git a/corellium/instrument/log/src/test/kotlin/flank/instrument/log/InternalTest.kt b/corellium/instrument/log/src/test/kotlin/flank/instrument/log/InternalTest.kt index d8014887f8..9c2fb2433e 100644 --- a/corellium/instrument/log/src/test/kotlin/flank/instrument/log/InternalTest.kt +++ b/corellium/instrument/log/src/test/kotlin/flank/instrument/log/InternalTest.kt @@ -71,4 +71,17 @@ class InternalTest { statusResults.last() is Instrument.Result ) } + + @Test + fun printExample() { + val list = runBlocking { + flowLogs(LOG3) + .groupLines() + .parseChunks() + .parseStatusResult() + .toList() + } + + list.forEach(::println) + } } diff --git a/corellium/junit/README.md b/corellium/junit/README.md new file mode 100644 index 0000000000..7ec65e96fa --- /dev/null +++ b/corellium/junit/README.md @@ -0,0 +1,10 @@ +# JUnit Report + +Module responsible for providing structures and functions required to generate, parse or format JUnit report. + +### References + +* Module type - [tool](../../docs/architecture.md#tool) +* Dependency type - [static](../../docs/architecture.md#static_dependencies) +* Public API - [JUnit.kt](./src/main/kotlin/flank/junit/JUnit.kt) +* JUnit schema - [source](https://raw.githubusercontent.com/windyroad/JUnit-Schema/master/JUnit.xsd) [local](./src/test/resources/JUnit.xsd) diff --git a/corellium/sandbox/src/main/kotlin/flank/corellium/sandbox/android/AndroidExample.kt b/corellium/sandbox/src/main/kotlin/flank/corellium/sandbox/android/AndroidExample.kt index 27df707838..3e93faea1e 100644 --- a/corellium/sandbox/src/main/kotlin/flank/corellium/sandbox/android/AndroidExample.kt +++ b/corellium/sandbox/src/main/kotlin/flank/corellium/sandbox/android/AndroidExample.kt @@ -1,4 +1,5 @@ @file:JvmName("AndroidExample") + package flank.corellium.sandbox.android import flank.corellium.client.core.connectAgent diff --git a/corellium/shard/README.md b/corellium/shard/README.md index 5528a3e2df..1e68ed82cd 100644 --- a/corellium/shard/README.md +++ b/corellium/shard/README.md @@ -1,8 +1,15 @@ # Shards data structures -This module is specifying only a data structures for sharding. +Light-weight module only for sharding data structures - no functions. -## Diagram + +### References + +* Module type - [tool](../../docs/architecture.md#tool) +* Dependency type - [static](../../docs/architecture.md#static_dependencies) +* Public API - [Shard.kt](./src/main/kotlin/flank/corellium/shard/Shard.kt) + +## Structures & relations ![sharding_class_diagram](http://www.plantuml.com/plantuml/proxy?cache=no&fmt=svg&src=https://raw.githubusercontent.com/Flank/flank/master/docs/corellium/shard-structures.puml) diff --git a/corellium/shard/calculate/README.md b/corellium/shard/calculate/README.md index 247e635cbe..0a1af87f40 100644 --- a/corellium/shard/calculate/README.md +++ b/corellium/shard/calculate/README.md @@ -6,6 +6,12 @@ Depending on the provided test cases duration, the sharding algorithm will: * Group the test cases from many test apks to run on a single device. * A mix both of the above options. +### References + +* Module type - [tool](../../../docs/architecture.md#tool) +* Dependency type - [static](../../../docs/architecture.md#static_dependencies) +* Public API - [CalculateShards.kt](./src/main/kotlin/flank/corellium/shard/CalculateShards.kt) + ## Example ### Input @@ -32,7 +38,7 @@ bundle: ### Output -#### Max shards 3 +Max shards = 3 ```yaml shards: @@ -67,7 +73,7 @@ shards: - "class app2.test2.TestClass#test7" // 7s ``` -#### Max shards 2 +Max shards = 2 ```yaml shards: diff --git a/corellium/shard/dump/README.md b/corellium/shard/dump/README.md index bcaab4167f..dc81576eb9 100644 --- a/corellium/shard/dump/README.md +++ b/corellium/shard/dump/README.md @@ -2,6 +2,12 @@ Allows dumping shards as formatted json file. +### References + +* Module type - [tool](../../../docs/architecture.md#tool) +* Dependency type - [static](../../../docs/architecture.md#static_dependencies) +* Public API - [Dump.kt](./src/main/kotlin/flank/corellium/shard/Dump.kt) + ## Example For the example input structure: diff --git a/corellium/shard/obfuscate/README.md b/corellium/shard/obfuscate/README.md index 79af353fcb..b9ad2bc94a 100644 --- a/corellium/shard/obfuscate/README.md +++ b/corellium/shard/obfuscate/README.md @@ -2,7 +2,13 @@ Allows obfuscating test cases names for security reasons. -## Example +### References + +* Module type - [tool](../../../docs/architecture.md#tool) +* Dependency type - [static](../../../docs/architecture.md#static_dependencies) +* Public API - [Parser.kt](./src/main/kotlin/flank/corellium/shard/Obfuscate.kt) + +### Example Obfuscation will change the test cases names as following: diff --git a/docs/corellium/client-api-class.puml b/docs/corellium/client-api-class.puml index 558446139d..ecb180bd3c 100644 --- a/docs/corellium/client-api-class.puml +++ b/docs/corellium/client-api-class.puml @@ -1,50 +1,195 @@ + @startuml left to right direction -object connectCorellium +package flank.corellium.client { +package core { + +object connectCorellium { +api: String +username: String +password: String +} + +interface Corellium + +package core.Api.kt { -class Corellium object getAllProjects object getProjectIdList -object getProjectInstancesList -object createNewInstance -object deleteInstance -object getInstanceInfo -object waitUntilInstanceIsReady -object getVPNConfig -object connectAgent -object connectConsole - -class Agent -object uploadFile - -class Console -object sendCommand -object waitForIdle +object createNewInstance { +newInstance: Instance +} +object getProjectInstancesList { +projectId: String +} +object getInstanceInfo { +instanceId: String +} +object deleteInstance { +instanceId: String +} +object startInstance { +instanceId: String +} +object stopInstance { +instanceId: String +} +object pauseInstance { +instanceId: String +} +object unpauseInstance { +instanceId: String +} +object waitUntilInstanceIsReady { +instanceId: String +} +object connectAgent { +agentInfo: String +} +object connectConsole { +instanceId: String +} +object getVPNConfig { +projectId: String +type: VPN +id: String = UUID.randomUUID().toString() +} +} +} +package agent { +interface Agent +package agent.api { +object uploadFile { +path: String +bytes: ByteArray +} +object disconnect +} +} + +package console { +interface Console +package console.Api.kt { +object sendCommand { +command: String +} +object waitForIdle { +timeToWait: Long +} +object flowLogs +object clear object close +} +} + +package DTO { +class "List" + +class Project { +id: String +name: String +quotas: Quotas +quotasUsed: Quotas +} + +class "List" + +class Id { +String +} + +class ProjectQuotas { +cores: Int +instances: Int +ram: Int +cpus: Int +gpus: Int? +instance: Int? +} +class BootOptions { +bootArgs: String +restoreBootArgs: String +udid: String +ecid: String +screen: String +} + +class "List" + +class Instance { +id: String +name: String +key: String +flavor: String +type: String +project: String +state: String +patches: List +os: String +osbuild: String +serviceIp: String +portAdb: String +bootOptions: BootOptions +agent: InstanceAgent? +} + +class InstanceAgent { +hash: String +info: String +} + +} + +class "Flow" +} connectCorellium ..> Corellium -Corellium -- getAllProjects -Corellium -- getProjectIdList -Corellium -- getProjectInstancesList -Corellium -- createNewInstance -Corellium -- deleteInstance -Corellium -- getInstanceInfo -Corellium -- waitUntilInstanceIsReady -Corellium -- getVPNConfig -Corellium -- connectAgent -Corellium -- connectConsole +Corellium <--- getAllProjects +Corellium <--- getProjectInstancesList +Corellium <--- getInstanceInfo +Corellium <--- deleteInstance +Corellium <--- getProjectIdList +Corellium <--- createNewInstance +Corellium <--- startInstance +Corellium <--- stopInstance +Corellium <--- pauseInstance +Corellium <--- unpauseInstance +Corellium <--- waitUntilInstanceIsReady +Corellium <--- getVPNConfig +Corellium <--- connectAgent +Corellium <--- connectConsole + + +getAllProjects ..> "List" +getProjectIdList ..> "List" +getProjectInstancesList ..> "List" +createNewInstance ..> Id +getInstanceInfo ..> Instance + +"List" o-- Project +"List" o-right- Id +"List" o-right- Instance + connectAgent ..> Agent connectConsole ..> Console -Agent -- uploadFile +Agent <-- uploadFile +Agent <-- disconnect + +Console <-- sendCommand +Console <-- flowLogs +Console <-- waitForIdle +Console <-- clear +Console <-- close -Console -- sendCommand -Console -- waitForIdle -Console -- close +flowLogs ..> "Flow" +Instance *-- BootOptions +Instance o-- InstanceAgent +Project *-- ProjectQuotas @enduml diff --git a/docs/corellium/modules.puml b/docs/corellium/modules.puml index 896045ca5e..aa5ebd425b 100644 --- a/docs/corellium/modules.puml +++ b/docs/corellium/modules.puml @@ -2,13 +2,25 @@ left to right direction -[cli] #snow -[domain] #snow - -[cli] --> [domain] -[domain] --> [api] -[domain] ...> [adapter] -[api] <-- [adapter] -[adapter] --> [client] +[cli] ..> [adapter] +[cli] ..> [apk] +[cli] -right-> [domain] + +[adapter] --* [client] +[adapter] --* [api] + +[domain] --o [api] +[domain] --o [apk] + +[domain] --* [shard:calculate] +[domain] --* [shard:obfuscate] +[domain] --* [shard:dump] +[domain] --* [instrument:command] +[domain] --* [instrument:log] +[domain] --* [junit] + +[shard:dump] --* [shard] +[shard:calculate] --* [shard] +[shard:obfuscate] --* [shard] @enduml diff --git a/docs/corellium/run-test-android.puml b/docs/corellium/run-test-android.puml new file mode 100644 index 0000000000..e63b91d871 --- /dev/null +++ b/docs/corellium/run-test-android.puml @@ -0,0 +1,104 @@ +@startuml + +'----------------- Style + +skinparam componentStyle rectangle +left to right direction + +'----------------- Structures + +package flank.corellium { +package presentation { +[Run command] +} +package domain { +[Run android Tests] +() "Internal steps" +[Authorize] +[Parse test cases] +[Prepare shards] +[Create output dir] +[Dump shards] +[Invoke devices] +[Install Apks] +[Parse apks info] +[Execute Tests] +[Clean up instances] +[Generate Report] +[Finish] +} +package tool { +[Generate JUnit report] +[Parse am instrument log] +[Format am instrument command] +[Parse package name] +[Parse apk info] +[Dump to writer] +[Calculate shards] +[Parse apk test cases] +} +package api { +() "Invoke android devices" +() "Install android apps" +() "Execute android test plan" +() "Request authorization" +} +package adapter { +[Create Corellium API] +[Corellium API Adapter] +} +package client { +[Corellium] +} +} + +'----------------- Relations + +[Run command] -----* [Create Corellium API] +[Run command] -left-* [Run android Tests] + +[Run android Tests] -left-> () "Internal steps" +() "Internal steps" .left- [Authorize] + +[Authorize] .left-> [Parse test cases] +[Parse test cases] .left-> [Prepare shards] +[Prepare shards] .left-> [Create output dir] +[Create output dir] .left-> [Dump shards] +[Dump shards] .left-> [Invoke devices] +[Invoke devices] .left-> [Install Apks] +[Install Apks] .left-> [Parse apks info] +[Parse apks info] .left-> [Execute Tests] +[Execute Tests] .left-> [Clean up instances] +[Clean up instances] .left-> [Generate Report] +[Generate Report] .left-> [Finish] + +[Authorize] --o () "Request authorization" + +[Parse apk test cases] o-down--- [Parse test cases] + +[Calculate shards] *-down- [Prepare shards] + +[Dump to writer] *-down- [Dump shards] + +[Invoke devices] --o () "Invoke android devices" + +[Install Apks] --o () "Install android apps" + +[Parse package name] o-down- [Parse apks info] +[Parse apk info] o-down- [Parse apks info] + +[Format am instrument command] *-down- [Execute Tests] +[Execute Tests] --o [Execute android test plan] +[Parse am instrument log] *-down- [Execute Tests] + +[Generate JUnit report] *-down- [Generate Report] + +[Request authorization] <|--- [Corellium API Adapter] +[Install android apps] <|-- [Corellium API Adapter] +[Invoke android devices] <|-- [Corellium API Adapter] +[Execute android test plan] <|-- [Corellium API Adapter] + +[Create Corellium API] -left-* [Corellium API Adapter] +[Corellium API Adapter] -left-* [Corellium] + +@enduml diff --git a/docs/corellium/steps-dependencies-class.puml b/docs/corellium/steps-dependencies-class.puml index e74847fe69..b006c6c78c 100644 --- a/docs/corellium/steps-dependencies-class.puml +++ b/docs/corellium/steps-dependencies-class.puml @@ -1,7 +1,7 @@ @startuml 'https://plantuml.com/activity-diagram-beta -'left to right direction +skinparam componentStyle rectangle legend left |= Color |= Depends on | @@ -9,18 +9,18 @@ legend left |<#LightBlue>| tool | end legend -object authorize #LightGreen -object parseTestCasesFromApks #LightBlue -object createOutputDir -object prepareShards #LightBlue -object dumpShards #LightBlue -object invokeDevices #LightGreen -object installApks #LightGreen -object parseApksInfo #LightBlue -object executeTests #LightGreen -object cleanUpInstances #LightGreen -object generateReport #LightBlue -object finish +[authorize] #LightGreen +[parseTestCasesFromApks] #LightBlue +[createOutputDir] +[prepareShards] #LightBlue +[dumpShards] #LightBlue +[invokeDevices] #LightGreen +[installApks] #LightGreen +[parseApksInfo] #LightBlue +[executeTests] #LightGreen +[cleanUpInstances] #LightGreen +[generateReport] #LightBlue +[finish] prepareShards --> invokeDevices