# [Legato] Legato 框架基本介紹
- Legato Application Framework 是由 Sierra Wireless Inc. 公司所開發的框架,主要是用來開發 Internet of Everything。
- Qualcomm 的 TelAF 是基於 Legato 所開發的,所以在使用 TelAF 前要先稍微瞭解 Legato 才能比較快上手
## Legato 的 System、App、Executable、以及 Component 架構

(圖片擷取自 Legato 官網:https://docs.legato.io/latest/conceptsEnvironment.html)
Legato 框架有幾個重要的概念,分別是 Component、Executable、App、以及 System。

從上圖可以看到,使用 Legato Application Framework 的話,我們的系統裡面會有一個一個的 App,而每個 App 裡面可以由一個或多個 Executable 組成,而每個 Executable 可以由一個或多個 Component 組成。
### Component
- 一個 Component 是一個 Legato App 最小的組成單位,其 entry point 是 `COMPONENT_INIT`。
- 寫在 `COMPONENT_INIT` 內的東西,在程式剛執行時的 start-up 階段就會馬上被該程式的 main thread 執行,主要是用來初始化該 Component 用的,所以一個 Component 只會有一個 `COMPONENT_INIT`。
此外,`COMPONENT_INIT` 不可 return 東西。
- 一個 Component 會以一個資料夾表示,該資料夾的名稱就是該 Component 的名稱,該資料夾內的檔案都是屬於該 Component 的,這些檔案可以是 source code、pre-compiled binary files、resource files(如音檔、圖片檔等等)、或任何該 Component 所需的檔案。
- 每個 Component 都一定要有一個 `Component.cdef` 檔案(`cdef`:**C**omponent **DEF**inition),用來告訴 build tools 該怎麼 build 這個 component。
- `Component.cdef` 檔案也可用來描述說這個 Component 提供了怎樣的 API 或者是需要什麼 API。
- 更多關於 Component 細節的說明可參考官網的說明:
- https://docs.legato.io/latest/conceptsComponents.html
### Executable
- 一個 Executable 由一個或多個 Component 組成。
- 一個 Executable 會由一個 process 去執行。
- 所以一個 Executable 可以由多個 Component 組成,而這些 Component 都執行在同一個 process 內,所以這些 Component 可互相存取彼此的資源。
- Executable 是由 `.adef` 檔案做設定(後面會介紹)。
- Executable 若要去存取另一個 Executable(不論是否在同一個 App 內)的東西,僅能透過 IPC 機制(後面會再說明)。
### App
Legato Application Framework 的一個 App 跟平常說的一個應用程式(application)是不太一樣的。
- 一個 App 由一個或多個 Executable 組成,在同一個 App 內的 Executable 都是在相同的執行環境內執行,也就是執行在同一個 sandbox,他們的環境變數和資源訪問權限都是一樣的。
- App 之間被隔離,無法直接存取其他 App 的資源。若需存取,僅可透過IPC 機制(後面會再說明)。這樣的設計可以增加系統的安全性。
- 每個 App 都會有一個 `.adef` 檔案(**A**pplication **DEF**inition),定義這個 App 該怎麼執行。
- `.adef` 的名稱就會是這個 App 的名稱,例如 `helloPrintApp.adef`,則使用 `app status` 所看到的該 App 名稱就會是 `helloPrintApp`
:::info
- Legato 使用 sandbox 為執行在同一個系統的 App 提供安全防護,sandbox 會將 App 與系統的其他部分隔離,使得 original equipment manufacturer(OEM)和 independent software vendor(ISV)的 Apps 可以安全地共存在同一個裝置,而不用擔心互相干擾。
- 每個 App 都有自己獨特的 user ID、group ID、以及 root directory。
- Legato sandbox 使用 `chroot` 來限制一個 App 只能看到其 directory 下的檔案和目錄。檔案使用 bind mounts 機制來跟 `chroot` 產生的隔離環境做連結。Processes 只能存取其 root directory 下的檔案和目錄。
- 只有開啟特定功能的 processes 可以看到其 root directory 以外的東西;若是 sandboxed Apps 則無這種能力。
- 我們可以在 `.adef` 檔案的 `sandboxed` section 設定該 App 是否要執行在 sandbox。
- unsandboxed app 可以看到 target device 的 root file system;sandboxed app 則無法看到 target device 的 root file system,因為它會有一個隔離的 root file system。每個 app 都有自己獨特的 user 和 primary groups IDs。App 的 user name 和 primary group name 都是 `appxxxx`,其中的 `xxxx` 是 App 的名稱。
- 更多 sandbox 機制的細節可參考官網說明:
- https://docs.legato.io/latest/conceptsSecurity.html
- https://docs.legato.io/latest/defFilesAdef.html#defFilesAdef_sandboxed
:::
### System
由於目前我在使用上都沒用到 System 層級的東西,所以對他較不熟,有需要可以參考官網的說明(後續摸熟的話會再補充 >_\<):
- https://docs.legato.io/latest/conceptsEnvironment.html#conceptsEnvironment__systems
- https://docs.legato.io/latest/defFiles.html#defFilesOverview_sdef
- https://docs.legato.io/latest/defFilesSdef.html
:::info
前面簡單的介紹完了 Component、Executable、以及 App,來看一下在 target machine 上的 Legato App 會怎麼呈現~
我們可以在前面安裝的 TelAF simulation docker container 內執行 `app status`,就可以看到目前系統內有安裝的 App 有哪些,以及他們的狀態,如下圖中一項一項的都是一個 App:

其中 `taf` 開頭的 App 都是 TelAF 提供的 service,例如 `tafDataCallSvc` 就會提供跟 Data call 有關的 service,若我們的 App 想要使用 Data call 的 API,就必須要去存取這個 App,並且確保它是 `running` 的狀態。
:::
## Basic Legato App Structure
接下來,來看一個簡單的 Legato App 的架構會是長怎樣~
假設目前有一個 Legato App 的資料夾,叫做 `helloServer`,其架構如下圖:

首先,一個 Legato App 一定會有:
- 一個 `.adef` 檔案:其檔名就會是該 App 的名稱,也就是執行 `app status` 所看到的名稱,所以這個 App 的名稱是 `helloServerApp`
- 一個或多個 Component 資料夾:
- Component 資料夾的名稱就會是該 Component 的名稱,如此例,該 Component 名稱即為 `ServerComp`。
- 每個 Component 的資料夾內一定會有一個 `Component.cdef` 檔案
- `Component.cdef` 的名稱是固定的,不可擅自修改。
- 與該 Component 相關的檔案都會放在此資料夾內,像是 `Component.cdef` 和 source code file(如 `server.c`)等等。
此外,一個 Legato App 若有提供 service 給其他人使用,那就還會有 `.api` 檔案:
- `.api` 檔案使用 Legato 的 interface definition language(IDL)撰寫 API 介面。
- 我們在 `.api` 檔案內定義每個 API 的名稱、參數、return type、以及其他資訊等等。
- 此例子是因為 `serverComp` 有提供 API 給其他 App 使用,所以才會有 `.api` 檔案。
上圖右側則是一個簡單的示意圖:
- 這個 App 的名稱是 `helloServerApp`,其內含有一個 Executable,名稱是 `serverExec`(Executable 的名稱由 `helloServerApp.adef` 內定義)
- 此 Executable 內有一個 Component,其名稱是 `serverComp`。
- `serverComp` 這個 Component 有提供一個 API 介面,它會被 expose 出來讓其他 App 可以使用(這部分是由 `helloServerApp.adef` 內的 `extern` section 所定義),該 API interface 名稱是 `server_interface`,想使用這個 API 的 App 只要連接到 `helloServerApp` 的 `server_interface` 即可(怎麼連接到別人的 API,後面會再介紹~)。
### Application Definition Files & Component Definition Files
接下來,從前面這個 `helloServerApp` 例子來簡單介紹一下 `.adef` 和 `Component.cdef` 裡面會長怎樣~
(這裡只是做基本的簡單介紹,讓第一次接觸的人有些概念,詳細內容還是要參閱官網喔~)

Application definition files 和 component definition files 都是由一個一個的 section 所組成,上圖僅列出部分 section,各 section 詳細的說明可參閱官網說明。
(各 section 的擺放順序,我目前測試起來好像沒有一定要特定的順序,官網也沒特別提到)
#### Application Definition File ( `.adef` )
- Application definition file 由許多 section 構成,一個最簡單的 App 至少一定要有 `executables` 和 `processes` 這兩個 sections。
- `executables` section:
- 列出要建立哪些 Executable,以及這些 Executable 由哪些 Component 構成。這些 Component 的 `COMPONENT_INIT` 會在 process 的 start-up 階段被執行。
- 建立好的 Executable 會被移到 App 的 `bin` directory 內。
- `processes` section:
- 當 App 啟動時,有哪些 processes 要被執行。
- 一個 Executable 由一個 process 執行。
- 我們可以為同一個 `processes` section 內的 process 設定環境變數(藉由 `envVar` 這個 subsection),但若要給不同 process 設定不同的環境變數,則要寫在不同的 `processes` section。
- `extern` section:
- 此例子由於此 App 有提供 API 給其他 App 使用,所以會用到 `extern` section 將其 API interface 露出來給其他 App 使用。
其他 App 若要使用此 App 的 API,則必須在其 `.adef` 的 `bindings` section 做設定(後面會再提到)。
- 其他重要的 section 還有 `bindings`、`bundles` 等等,更多 Application Definition 的細節請參閱 Legato 官網的說明:
- https://docs.legato.io/latest/defFiles.html#defFilesOverview_adef
- https://docs.legato.io/latest/defFilesAdef.html
:::danger
在 `processes` section 底下有 [`faultAction`](https://docs.legato.io/latest/defFilesAdef.html#defFilesAdef_processFaultAction) 這個 subsection,千萬不要在 TelAF simulation 內測試:
```adef
faultAction: reboot
```
也就是如果有錯誤發生,process crash 了,那要採取什麼行動,`reboot` 則是重新開機。
我這樣測試後,我的 TelAF simulation 就壞掉惹QQ,刪掉重新安裝 TelAF simulation 也沒用 😭 最後只能靠著之前 VirtualBox 的 snapshot 才救回來 @_@
:::
#### Component Definition File ( `.cdef` )
- Component definition file 也是由許多 section 構成,一個最簡單的 Component 一定要有 `sources` section 來指定這個 Component 的 source files 有誰。
- 更多 Component Definition 的細節請參閱 Legato 官網的說明:
- https://docs.legato.io/latest/defFiles.html#defFilesOverview_cdef
- https://docs.legato.io/latest/defFilesCdef.html
- https://docs.legato.io/latest/conceptsComponents.html
### API Files & Source Code Files
接下來來看 `helloServerApp` 的 `server_api.api` 以及 `server.c`。
PS. `.api` 檔的命名並沒有特別規範,這裡只是為了幫助識別,才命名成 `server_api`。

這個 `server_api.api` 檔案有定義兩個 API,第一個是 `server_func1`,第二個是 `server_func2`:
- `server_func1`
- 沒有寫出 return type,所以是 return `void`。
- return type 會寫在 `FUNCTION` 跟 API name 之間,也就是 `FUNCTION` 跟 `server_func1` 之間。
- 此 API 有一個參數,是 `string` 型態的參數,且是 `IN` 參數。
- 在 `server.c` 裡面,API 的 function name 的組成規則是 `<interface_name>_<API_name>`
- `interface_name` 是在 `Component.cdef` 的 `provides` section 的 `server_interface = server_api.api` 所定義的 interface name。
這樣宣告的話 `interface_name` 就會是 `server_interface`。
- 所以在 `server.c` 裡面,此 API 的 function name 就是 `server_interface_server_func1`,該 function 的參數是 `const char *message`
- `server_func2`
- 此 API 有 return 東西,是 return `int32`
- 且此 API 有三個參數,分別是 `n1`、`n2`、和 `res`,其中 `n1` 和 `n2` 是 `IN` 參數,而 `res` 是 `OUT` 參數
:::info
若要確認 API 在 C 語言確切的 function prototype,可以用以下方式確認:
假如在 `~/test/` 內有 `<yourApp>.adef`、`.api` 檔案以及 Component 的資料夾。
接下來在 `~/test/` 內執行:`mkapp -t simulation <yourApp>.adef`。
則會在執行 `mkapp` 的資料夾內產生 `_build_<yourApp>` 資料夾,查看 `_build_<yourApp>/simulation/api/<一串數字>/server/<interface_name>_server.h` 這份檔案的內容即可確認在 C source code 裡面該使用怎樣的 function prototype。
我們的 source file 要依據這份檔案內的 function prototype 去實作 API 的內容。

:::
Legato 官網有 API 檔更多的說明:
- https://docs.legato.io/latest/apiFiles.html
- https://docs.legato.io/latest/apiFilesSyntax.html
## Communication Mechanisms in Legato

Legato 的 communication 的機制可以探討以下三種情況:
1. 同一個 Executable 內不同 Components 之間的溝通
2. 同一個 App 內的 Executables 之間的溝通
3. 不同 Apps 之間的溝通
### 1. 同一個 Executable 內不同 Components 之間的溝通
由於是位在同一個 Executable 內的 Components,也就是都在同一個 process 內,所以可以存取彼此的東西。
如下例,有兩個 Component,`serverComp` 和 `clientComp`,server 提供 `server_showMsg` 這個 function,並將 function prototype 放在 `server.h`。
Client 若要使用的話,就要在 source code 內 include 該 header file,並且在 client 的 `Component.cdef` 的 `cflags` section 內將 `serverComp` 的路徑加入 search path,讓 build tool 能找到 `server.h` 即可。

#### `helloIPC1App.adef`
```adef
executables:
{
helloExec = ( clientComp serverComp )
}
processes:
{
run:
{
helloProc = ( helloExec )
}
}
```
#### `server.h`
Server 端把欲提供出去的東西寫在一個 header file,`server.h`。以此例而言,server 分享了一個 `int` 型態的變數 `num`,以及一個 function `server_ShorMsg`。
欲分享出去的東西前面都要加上 `LE_SHARED` 這個 macro,此 macro 可將 symbol 分享出去
- `LE_SHARED`:https://docs.legato.io/latest/le__basics_8h.html#a1661bfbb29c2e0372ca9f0c556a8fc24
```cpp
#ifndef SERVER_H_INC
#define SERVER_H_INC
LE_SHARED int num;
LE_SHARED void server_showMsg(const char *message);
#endif
```
#### `server.c`
```cpp
#include "legato.h"
#include "interfaces.h"
#include "server.h"
int num = 100;
void server_showMsg(const char *msg)
{
LE_INFO("Client says: '%s', and the value of num is %d", msg, num);
}
COMPONENT_INIT
{
}
```
#### `serverComp/Component.cdef`
```cpp
sources:
{
server.c
}
```
#### `client.c`
```cpp
#include "legato.h"
#include "interfaces.h"
#include "server.h" // 記得要 include server.h
COMPONENT_INIT
{
LE_INFO("Asking server to print 'Hello, world!' and print the value of num");
server_showMsg("Hello, world!");
LE_INFO("client printing the value of num: %d", num);
LE_INFO("client changes the value of num to 777");
num = 777;
server_showMsg("Changed num to 777");
}
```
#### `clientComp/Component.cdef`
```cdef
sources:
{
client.c
}
cflags:
{
// 將以下這個新的 directory 加入到 search path,這樣 build tool 就會知道去哪找 server.h
-I $CURDIR/../serverComp
}
```
#### Result

### 2. 同一個 App 內不同 Executables 之間的溝通
此情況需要透過 IPC 的機制來溝通,且需要定義 API。
如下例,有兩個 Component,`clientComp` 和 `serverComp`。在 `helloIPC2App.adef` 內,將這兩個 Component 各自放到一個 Executable 內執行,由於這樣就是執行在不同的 process 內,無法直接存取彼此的東西,因此需要定義 API 檔案,並透過 IPC 的機制來溝通。

#### `helloIPC2App.adef`
```cpp
executables:
{
clientExec = ( clientComp )
serverExec = ( serverComp )
}
processes:
{
run:
{
serverProc = ( serverExec )
clientProc = ( clientExec )
}
}
bindings:
// bindings section 讓 client 端的 IPC API interface 能連結到 server 端的 interface
// client 端的 interface 就是寫在 client Component.cdef 內 requires section 內的
// server 端的 interface 就是寫在 server Component.cdef 內 provides section 內的
// 若是同一個 App 內的 interfaces 的 binding,稱做 internal binding,也就是這個例子
// 若是 App 跟 App 之間的 interfaces 的 binding,則稱做 external binding
{
// binding 的寫法是 [client 端的 interface] -> [server 端的 interface]
// 由於此例子是 internal binding,所以寫法會是 clientExe.clientComponent.clientInterface -> serverExe.serverComponent.serverInterface
clientExec.clientComp.client_interface -> serverExec.serverComp.server_interface
// 這裡的 client_interface 是定義在 client 的 Component.cdef 內的 requires section
// 若是 external binding,由於 client app 理論上看不到 server app 內 component 的結構,所以應該是寫 client 的 interface 要連接到某個 App 的 interface
// 所以寫法會是:
// clientExe.clientComponent.clientInterface -> otherApp.serverInterface
// 更多細節請參閱官網 https://docs.legato.io/latest/defFilesAdef.html#defFilesAdef_bindings
}
```
#### `server_api.api`
```api
DEFINE MESSAGE_LEN = 100;
FUNCTION showMsg
(
string msg[MESSAGE_LEN] IN
);
```
#### `serverComp/Component.cdef`
```cpp
provides:
{
api:
{
// server component 提供的 API 被定義在 server_api.api
// 我們可以給這個 API 定義一個 interface 名稱,這裡命名為 server_interface
server_interface = server_api.api
// 這也是為什麼 helloIPC2App.adef 的 bindings section 的 clientExec.clientComp.client_interface -> serverExec.serverComp.server_interface 的 server_interface 的由來
// server.c 內的 function 名稱也因此是 server_interface_showMsg
// 我們也可以不自己定義一個 interface 名稱,直接用以下寫法:
server_api.api
// 這樣的話 server 端的 interface 名稱就是該 api 的檔名,也就是 server_api
// adef 內的 bindings section 就要改成:clientExec.clientComp.client_interface -> serverExec.serverComp.server_api
// server.c 內的 function 名稱也要改為 server_api_showMsg
}
}
sources:
{
server.c
}
```
#### `server.c`
```cpp
#include "legato.h"
#include "interfaces.h"
// function 名稱的組成規則是 <interface_name>_<API_name>
// interface_name 就是定義在 server Component.cdef 內 provides section 所定義的 interface 名稱
void server_interface_showMsg(const char *msg)
{
LE_INFO("Client says '%s'", msg);
}
COMPONENT_INIT
{
}
```
#### `clientComp/Component.cdef`
```cpp
requires:
{
api:
{
// client 這個 component 需要 server_api.api
// 我們可以自己重新命名 interface 名稱,這裡重新命名成 client_interface
// 這樣 client.c 內使用 server_api.api 裡面的 API 時,function 名稱會是 client_interface_<API name>
// 這也是 adef 的 bindings section 的 clientExec.clientComp.client_interface 的 client_interface 的由來
client_interface = server_api.api
// 我們也可以不自己定義 interface 名稱,直接用以下寫法:
server_api.api
// 這樣產生的 interface 名稱就會是該 .api 檔的檔名,也就是 server_api
// 這樣寫的話記得要修改 helloIPC2App.adef 內 bindings section 的 client 的 interface 名稱:
// clientExec.clientComp.server_api -> serverExec.serverComp.server_interface
// 此外,client.c 內 function 名稱也會改為 server_api_showMsg
}
}
sources:
{
client.c
}
```
#### `client.c`
```cpp
#include "legato.h"
#include "interfaces.h"
COMPONENT_INIT
{
LE_INFO("Asking server to print 'Hello, world!'");
// function 名稱是 <interface_name>_<API_name>
// interface_name 就是 client 的 Component.cdef 內 requires section 所定義的 interface 名稱
client_interface_showMsg("Hello, world!");
}
```
#### Result

### 3. 不同 App 之間的溝通
此情況僅可透過 IPC 溝通,且需要 API。這是最安全的溝通方式,也是 Legato 官方比較推薦的作法。
以下例子,server 和 client 都有各自的 App:
- Server 藉由其 `.adef` 的 `extern` section 將其想給其他 App 使用的 IPC API interface 露出來,其他 App 若想使用該 interface 則需藉由其 `.adef` 的 `bindings` section 來連接到該 interface。
- Client 藉由其 `.adef` 的 `bindings` section 使其 interface 和 server 的 interface 做連結。

#### `helloIPC3ServerApp.adef`
```cpp
executables:
{
serverExec = ( serverComp )
}
processes:
{
run:
{
serverProc = ( serverExec )
}
}
extern:
{
// 可以給 interface 設定一個別名,也可以不設定
// aliasName = exeName.componentName.interfaceName
serverInterfaceAliasName = serverExec.serverComp.server_interface
// 有設定別名的話,client 的 adef 的 bindings section 就是寫:
// clientExec.clientComp.client_interface = serverApp.serverInterfaceAliasName
// 若沒有要給別名,就這樣寫:
serverExec.serverComp.server_interface
// 這樣的話,client 的 adef 的 bindings section 就是寫:
// clientExec.clientComp.client_interface = serverApp.server_interface
}
```
#### `server_api.api`
```api
DEFINE MESSAGE_LEN = 100;
FUNCTION showMsg
(
string msg[MESSAGE_LEN] IN
);
```
#### `serverComp/Component.cdef`
```cpp
provides:
{
api:
{
// 這個 Component 有提供 API,API 定義在 server_api.api
// 可以自己設定 server 端的 interface,這裡將 server 端的 interface 設定成 server_interface
server_interface = server_api.api
// server.c 裡的 function 名稱 就是 server_interface_showMsg
// 若沒有要自己設定 server 端的 interface,這樣寫就好:
server_api.api
// 這樣 server 端的 interface 就是 server_api
// server.c 裡的 function 名稱 就是 server_api_showMsg
}
}
sources:
{
server.c
}
```
#### `server.c`
```cpp
#include "legato.h"
#include "interfaces.h"
// function name 的組成規則是 <interface_name>_<API_name>
// interface_name 就是定義在 Component.cdef 的 provides section 內的 api section 的
void server_interface_showMsg(const char *msg)
{
LE_INFO("Client says: '%s'", msg);
}
COMPONENT_INIT
{
}
```
#### `helloIPC3ClientApp.adef`
```cpp
executables:
{
clientExec = ( clientComp )
}
processes:
{
run:
{
clientProc = ( clientExec )
}
}
bindings:
{
// 由於 clientApp 會用到 serverApp 所提供的 API,所以要先將 client 的 interface 連接到 server 的 interface
// 這是 App 跟 App 間的 communication,是 external binding,所以連接的規則是:
// clientExec.clientComp.clientInterface -> serverApp.serverInterface
clientExec.clientComp.client_interface -> helloIPC3ServerApp.serverInterfaceAliasName
}
```
#### `clientComp/Component.cdef`
```cpp
requires:
{
api:
{
// client 需要用到 server_api.api
// 這裡有自己設定 client 端的 interface 名稱為 client_interface
client_interface = server_api.api
// 若沒自己設定 interface 名稱,就這樣寫:
server_api.api
// 這樣 client 端的 interface 名稱就是 server_api
}
}
sources:
{
client.c
}
```
#### `client.c`
```cpp
#include "legato.h"
#include "interfaces.h"
COMPONENT_INIT
{
LE_INFO("Asking server to print 'Hello, world!'");
// function name 的組成規則是 <interface_name>_<API_name>
// interface_name 就是寫在 Component.cdef 的 requires section 的 api section 內的 client_interface
client_interface_showMsg("Hello, world!");
}
```
#### Make App
此例子在 make app 時,記得要 include `server_api.api` 所在的 path:
```bash
~/helloIPC3$ mkapp -t simulation -i ./ helloIPC3Server/helloIPC3ServerApp.adef
~/helloIPC3$ mkapp -t simulation -i ./ helloIPC3Client/helloIPC3ClientApp.adef
```
#### Result

### Overview
由於一開始在接觸 Legato 時搞不太懂 interface name 的規則,所以自己做了以下小整理~


我們在使用 TelAF 時,若要使用 TelAF 的某個 service,就是用第三種方式 bind 到該 service 的 API interface。
譬如說若要用 Data Call 相關的 API,官網( https://docs.qualcomm.com/bundle/publicresource/topics/80-41102-2/page_c_tafDataCallSvc.html?product=1601111740010418 )就會告知說我們自己開發的 App 的 `.adef` 的 `bindings` section 要做怎樣的設定才能連結到他們 Data Call 的 service。
