449 lines
28 KiB
Markdown
449 lines
28 KiB
Markdown
# Protocollo TCN {#sec:tcn-protocol}
|
|
|
|
## Descrizione del protocollo
|
|
|
|
Il protocollo TCN [@TCNCoalitionTCN2020], Temporary Contact Number, è un protocollo di tracciamento dei contatti decentralizzato, ideato dalla TCN Coalition. Quest'ultima è una comunità di persone che durante l'emergenza COVID-19 hanno sviluppato questo protocollo per lo sviluppo di applicazioni per notificare l'esposizione al contagio.
|
|
Il protocollo non richiede informazioni personali ed è compatibile con autorità sanitarie.
|
|
I dispositivi degli utenti inviano un identificativo agli utenti vicini tramite Bluetooth e successivamente, se un utente è risultato positivo al contagio, può notificarlo ai suo contatti.
|
|
|
|
Per aumentare la scalabilità la TCN Coalition ha scelto di non generare randomicamente tcn, ma di generarli in modo deterministico da un seed.
|
|
In questo modo si riduce la dimensione del report da inviare, in quanto contiene solo le informazione necessarie per calcolare i tcn e non l'elenco intero di tcn.
|
|
L'utente può caricare diversi report di dimensioni minori in modo da non caricare l'intera cronologia dei contatti.
|
|
|
|
### Chiavi di autorizzazione e verifica
|
|
|
|
L'user agent crea una chiave di autorizzazione *rak* (report authorization key) e una chiave di verifica *rvk* (report verification key) attraverso la curva ellittica *Ed25519*.
|
|
|
|
A partire dalla rak è possibile ricavare la chiave di contatto iniziale tck_0.
|
|
|
|
```
|
|
tck_0 ← H_tck(rak)
|
|
```
|
|
|
|
dove `H_tck` è una funzione di hash con 256 bit di output che utilizza come separatore di dominio la rappresentazione in byte della stringa *H_TCK*.
|
|
|
|
### Chiave temporanea di contatto {#sec:tck}
|
|
|
|
Ogni report può contenere al massimo $2^{16}$ chiavi di contatto.
|
|
|
|
Partendo da un tcn può essere ricavato il prossimo come mostrato di seguito:
|
|
|
|
```
|
|
tck_i ← H_tck(rvk || tck_{i-1})
|
|
```
|
|
|
|
dove `||` indica una concatenazione.
|
|
|
|
### Numeri temporanei di contatto {#sec:tcn}
|
|
|
|
Per ogni tck viene generato un unico numero di contatto temporaneo (*tcn*) come mostrato di seguito:
|
|
|
|
```
|
|
tcn_i ← H_tcn(le_u16(i) || tck_i)
|
|
```
|
|
dove `H_tcn` è una funzione di Hash con 128 bit di output che utilizza come separatore di dominio la rappresentazione in byte della stringa *H_TCN* .
|
|
|
|
Dalla chiave rak è possibile generare la chiave rvk e la chiave tck_0, dalle quali è possibile generare le successive tck e quindi tutti i successivi tcn.
|
|
|
|
### Report {#sec:report}
|
|
|
|
Quando viene richiesto di caricare i dati dell'utente, è possibile eseguire l'upload di un report compatto. Infatti grazie alla derivazione deterministica, è possibile inviare tutti gli identificativi utilizzati nel periodo `j1` e `j2`, caricando unicamente la `rvk`, `tck_{j1-1}`, e i due indici `j1` e `j2`.
|
|
Di seguito è illustrato come costruire il report:
|
|
|
|
```
|
|
report ← rvk || tck_{j1-1} || le_u16(j1) || le_u16(j2) || memo
|
|
```
|
|
|
|
dove `memo` è una stringa di byte di lunghezza variabile che va da 2 a 257 byte.
|
|
Inoltre si utilizza il rak per produrre una firma `sig` per il report e verrà inviata al server la concatenazione di sig e report.
|
|
|
|
Poiché la rvk è contenuta all'interno del report, chiunque può verificare l'integrità del report e ricavare i vari tcn.
|
|
|
|
## Implementazione del protocollo per la JVM
|
|
|
|
Al fine di utilizzare il protocollo precedentemente descritto all'interno dell'applicazione Android, ne è stato sviluppato un'implementazione per la *Java Virtual Machine*.
|
|
La gestione della coppia di chiavi derivate dalla curva ellittica *Ed25519* è stata affidata alla libreria ***ed25519-elisabeth*** [@CryptographycafeEd25519elisabeth2020].
|
|
Le chiavi private e pubbliche prodotte sono state *wrappate* rispettivamente nelle classi `ReportAuthorizationKey` e `ReportVerificationKey`.
|
|
Questa scelta non solo ha permesso di utilizzare nomi dal maggiore significato rispetto al dominio applicativo, ma anche di nascondere l'implementazione della curva *Ed25519* in modo da disaccoppiare l'interfaccia della libreria crittografica da quella utilizzata per il protocollo TCN.
|
|
|
|
Come illustrato nella @sec:tck, a partire dalla chiave di autorizzazione è possibile ricavare la `TemporaryContactKey` iniziale[^tck-0].
|
|
Ciò può essere fatto attraverso la funzione `baseTemporaryContactKey()`, la cui implementazione è riportata nel @lst:tck-0.
|
|
Invece, partendo da una chiave di contatto generica, è possibile ricavare la successiva chiave di contatto attraverso il metodo `nextTemporaryContactKey()` riportato nel listato @lst:next-tck.
|
|
|
|
[^tck-0]: La chiave temporanea iniziale viene ricavata a partire dalla sola *rak* e da essa non è generato nessun numero temporaneo di contatto.
|
|
|
|
``` {.kotlin #lst:tck-0 caption="Derivazione della prima tck."}
|
|
fun baseTemporaryContactKey(): TemporaryContactKey {
|
|
val hmac = MessageDigest.getInstance("SHA-256").apply {
|
|
update(Const.H_TCK_DOMAIN_SEPARATOR)
|
|
update(key.toByteArray())
|
|
}
|
|
|
|
return TemporaryContactKey.createFromByteArray(
|
|
hmac.digest(),
|
|
0
|
|
)
|
|
}
|
|
```
|
|
|
|
``` {.kotlin #lst:next-tck caption="Generazione della prossima tck."}
|
|
fun nextTemporaryContactKey(
|
|
rvk: ReportVerificationKey
|
|
): TemporaryContactKey {
|
|
val hmac = MessageDigest.getInstance("SHA-256").apply {
|
|
update(Const.H_TCK_DOMAIN_SEPARATOR)
|
|
update(rvk.toByteArray())
|
|
update(key)
|
|
}
|
|
|
|
return TemporaryContactKey(
|
|
hmac.digest(),
|
|
index.inc()
|
|
)
|
|
}
|
|
```
|
|
|
|
Sempre a partire dalla tck è possibile ricavare il numero di contatto temporaneo (tcn) e da esso settare il campo UUID utilizzato per il broadcast del beacon bluetooth.
|
|
Questa operazione può essere eseguita mediante la funzione `deriveTemporaryContactNumber()` la cui implementazione è stata riportata nel @lst:derive-tcn.
|
|
|
|
``` {.kotlin #lst:derive-tcn caption="Derivazione del numero di contatto temporaneo."}
|
|
fun deriveTemporaryContactNumber(): TemporaryContactNumber {
|
|
val hmac = MessageDigest.getInstance("SHA-256").apply {
|
|
update(Const.H_TCN_DOMAIN_SEPARATOR)
|
|
update(index.toLeByteArray())
|
|
update(key)
|
|
}
|
|
|
|
return TemporaryContactNumber(
|
|
hmac.digest().sliceArray(0 until 16),
|
|
index
|
|
)
|
|
}
|
|
```
|
|
|
|
Un'altra componente fondamentale dell'implementazione del protocollo TCN è la classe `Report`, infatti tramite essa è possibile generare il report firmato che poi sarà inviato al server.
|
|
Inoltre questa classe mette a disposizione una serie di funzioni di utilità come `generateContactNumbers()` e `toReportData()` che facilitano l'estrazione delle informazioni contenute all'interno del report.
|
|
Infine la classe report fornisce anche un metodo statico, `readReportDataFromByteArray()`, attraverso il quale altre componenti applicative sono in grado di recuperare le informazioni di un report contenute all'interno di un array di bytes.
|
|
L'implementazione di questo metodo è riportata nel listato @lst:read-report.
|
|
|
|
``` {.kotlin #lst:read-report caption="Lettura dei dati di un report da un ByteArray."}
|
|
fun readReportDataFromByteArray(bytes: ByteArray): ReportData {
|
|
val buffer = ByteBuffer.wrap(bytes).apply {
|
|
order(ByteOrder.LITTLE_ENDIAN)
|
|
}
|
|
|
|
val rvk = ReportVerificationKey
|
|
.createFromByteArray(buffer.read(32))
|
|
val tckBytes = buffer.read(32)
|
|
val from = buffer.short
|
|
val until = buffer.short
|
|
val memoType = buffer.get()
|
|
val memoData = String(buffer.read(buffer.get().toInt()))
|
|
|
|
return ReportData(
|
|
rvk,
|
|
TemporaryContactKey
|
|
.createFromByteArray(tckBytes, from.dec()),
|
|
from,
|
|
until,
|
|
memoData
|
|
)
|
|
}
|
|
```
|
|
|
|
# Applicazione
|
|
|
|
L'applicazione permette di tracciare i contatti degli utenti attraverso l'impiego del Bluetooth Low Energy (BLE).
|
|
In particolare lo smartphone di ogni utente esegue il broadcast e la scansione di beacon bluetooth.
|
|
In questo modo quando due utenti entrano nel raggio di azione del bluetooth il contatto verrà memorizzato sui rispettivi dispositivi.
|
|
|
|
L'applicazione prevede differenti modalità di funzionamento, ognuna delle quali garantisce un diverso livello di privacy.
|
|
Nella modalità di funzionamento ***A*** ogni qual volta si verifica un contatto l'applicazione si occupa di notificare immediatamente l'evento al server in modo tale che esso possa essere aggiunto al database remoto.
|
|
Questa modalità è quella meno *privacy friendly* in quanto la comunicazione avviene in *real-time* e all'interno del messaggio scambiato viene riportato sia il *tcn* dell'utente sia quello della persona incontrata.
|
|
|
|
La modalità ***B*** prevede lo scambio delle stesse informazioni previste per la modalità precedente, ma solo se richiesto dalle autorità sanitarie.
|
|
In questo modo non solo si evita che i dati siano catturati dal server in *real-time*, ma si espongono le informazioni dell'utente solo quando queste sono strettamente necessarie.
|
|
Sia in questa modalità, che nella precedente si è scelto di non ruotare i *tcn* identificativi degli utenti in modo da facilitare la generazione del grafo sul server.
|
|
Questa soluzione può mettere a repentaglio la privacy degli utenti ed essere sfruttata da *avversari* per ottenere informazioni sulle abitudini degli utilizzatori[^catena-negozi].
|
|
|
|
[^catena-negozi]: Per esempio una catena di negozi attraverso l'impiego di uno scanner bluetooth potrebbe ricostruire la *fedeltà* degli utenti, conoscere i settori del negozio preferiti ecc.
|
|
|
|
L'ultima modalità, la ***C***, è quella che tutela maggiormente la privacy degli utilizzatori attraverso due accorgimenti:
|
|
|
|
- Rotazione dei *tcn*
|
|
- Matching locale
|
|
|
|
La generazione dei *tcn* avviene attraverso una derivazione deterministica come visto nella @sec:tcn-protocol, in modo tale da avere lo stesso livello di privacy di una soluzione randomica, ma con una migliore scalabilità.
|
|
Mentre il matching locale permette di condividere il minor numero di informazioni possibili e solo quando questo è strettamente necessario.
|
|
Infatti in questa modalità l'applicazione carica le informazioni sul server solo in seguito alla richiesta delle autorità sanitarie.
|
|
Inoltre a differenza delle prime due modalità è previsto l'upload unicamente dei *tcn* che il dispositivo ha assunto nel tempo, in questo modo il server non è in grado di conoscere o ricavare i contatti avuti dall'utente.
|
|
|
|
## API covid di Apple e Google
|
|
|
|
La prima scelta progettuale che abbiamo dovuto affrontare ha riguardato l'utilizzo o meno dell'API messa a disposizione da Apple [@AppleGooglePartner] e Google [@NotificheDiEsposizione].
|
|
Infatti i due giganti californiani hanno sviluppato in modo congiunto un protocollo che permettesse di tracciare i contatti degli utenti dei due sistemi operativi mobili più diffusi.
|
|
Questo protocollo ricorda per molte caratteristiche il protocollo TCN illustrato precedentemente.
|
|
Entrambi i protocolli ricavano gli identificativi (*rolling proximity identifier* nel protocollo di Apple e Google) in modo deterministico a partire da delle chiavi (*temporary exposure keys*).
|
|
I due protocolli differiscono nel numero di identificativi ricavati da ciascuna chiave; come visto nella @sec:tcn il protocollo TCN ricava un unico tcn per chiave, mentre l'altro protocollo permette di ricavare un numero imprecisato di identificativi.
|
|
Un'ulteriore differenza risiede nella derivazioni delle chiavi, che nel protocollo TCN è deterministica, mentre in quello di Apple e Google non è specificato.
|
|
|
|
Oltre al protocollo, le due società, hanno fornito agli sviluppatori un'API che permettesse alle applicazioni di utilizzare il protocollo descritto nel paragrafo precedente.
|
|
Sfortunatamente l'API non ci avrebbe permesso di implementare tutte e tre le modalità di funzionamento, ma solo l'ultima che si basa su un matching locale.
|
|
Questa caratteristica, unita alle regole stringenti di utilizzo imposte della libreria[^regole-api-apple-google] ci ha condotto alla scelta di implementare le varie funzionalità in maniera indipendente.
|
|
|
|
[^regole-api-apple-google]: Le due società californiane consentono lo sviluppo di applicazioni che fanno uso della loro API solo ad enti governativi e impongo un numero massimo di applicazioni per nazione pari ad uno.
|
|
Dalla documentazione ufficiale non è ben chiaro se questi vincoli riguardino solo la pubblicazione sugli store o anche lo sviluppo.
|
|
|
|
## Interazione via Bluetooth
|
|
|
|
L'interazione tra l'hardware bluetooth del dispositivo e l'applicazione è stata gestita attraverso l'impiego della libreria *Android Beacon Library* [@AndroidBeaconLibrary] che permette di gestire più facilmente le operazioni con beacon bluetooth.
|
|
Inoltre per rendere l'applicazione più funzionale, e quindi garantirne il funzionamento anche in background o a schermo spento è stato utilizzato un *foreground service* [@ServicesOverview], che consente di mantenere in *primo piano* le operazioni di trasmissione e scansione anche quando l'applicazione non lo è.
|
|
Data la natura variegata di Android, le diverse implementazioni del sistema operativo adoperate dai vari produttori non si comportano sempre nello stesso modo, motivo per il quale alcuni dispositivi tenderanno a terminare, o mettere in pausa ugualmente l'applicazione[^dont-kill-my-app].
|
|
Potendo operare unicamente nello spazio utente non è stato possibile superare questi limiti.
|
|
|
|
[^dont-kill-my-app]: Molti produttori Android per aumentare la durata della batteria dei propri dispositivi tendono a stoppare e ridurre le funzionalità delle applicazioni. Maggiori dettagli possono essere trovati al seguente link \url{https://dontkillmyapp.com}.
|
|
|
|
Tutte le funzionalità legate al bluetooth sono state *incapsulate* all'interno della classe `BluetoothManager`.
|
|
L'interfaccia di questa classe espone due metodi, `startService()` e `stopService()` che consentono di avviare e stoppare sia la scansione che la trasmissione del beacon.
|
|
Poiché queste operazioni vanno ad interagire con le funzionalità del sistema operativo, l'istanza di questa classe deve essere collegata ad un oggetto di tipo `Context`.
|
|
Si è scelto di collegare l'oggetto `BluetoothManager` all'*application* e non ad una `Activity` in quanto i servizi devono essere utilizzati anche quando non sono presenti *activity* in *foreground*.
|
|
Per questo motivo è stata sviluppata anche una classe `BluetoothApplication` che va ad estendere le funzionalità di `Application` e fornisce a sua volta due metodi di start e stop che vanno a richiamare quelli esposti da `BluetoothManager` in modo tale da rendere possibile il controllo dei servizi legati al bluetooth anche da altre componenti dell'applicazione.
|
|
|
|
I beacon bluetooth gestiti dall'applicazione sono caratterizzati dal seguente formato `s:0-1=2a,i:2-17` dove il parametro `s` specifica i bit destinati al tipo di servizio e il suo valore, mentre il parametro `i` specifica i bit destinati all'UUID.
|
|
|
|
### Trasmissione
|
|
|
|
Il dispositivo dell'utente deve eseguire il **broadcast** di un beacon bluetooth contenete nel campo UUID il *tcn* identificativo.
|
|
Questa operazione è stata svolta attraverso la classe `BeaconTransmitter` messa a disposizione dalla *Android Beacon Library*.
|
|
Inoltre per la modalità di funzionamento *C* è stato necessario prevedere un meccanismo di rotazione delle chiavi di contatto.
|
|
Questa rotazione viene settata attraverso la funzione `rotateTCN()` (@lst:rotate-tcn) che sfrutta un `Handler` per programmare la rotazione del *tcn*.
|
|
|
|
``` {.kotlin #lst:rotate-tcn caption="Codice necessario alla rotazione del tcn."}
|
|
private fun rotateTCN() {
|
|
val advertiseHandler = Handler()
|
|
val changeTCN: Runnable = object : Runnable {
|
|
override fun run() {
|
|
tcnManager.nextTcn()
|
|
startAdvertising()
|
|
advertiseHandler.postDelayed(
|
|
this,
|
|
TCNManager.ELAPSE_BETWEEN_NEW_TCN
|
|
)
|
|
}
|
|
}
|
|
|
|
advertiseHandler.postDelayed(
|
|
changeTCN,
|
|
TCNManager.ELAPSE_BETWEEN_NEW_TCN
|
|
)
|
|
}
|
|
```
|
|
|
|
La scelta della frequenza di *advertising* è stata dettata dai vincoli tracciati dall'API di Android [@AdvertiseSettings].
|
|
Infatti la libreria permette di trasmettere un beacon con una frequenza di 1 *Hz*, 3 *Hz* o 10 *Hz*.
|
|
Fortunatamente questi vincoli non si sono rilevati troppo limitanti infatti la frequenza di un Hertz, quindi un beacon trasmetto ogni secondo, permette di avere una buona trasmissione e di risparmiare batteria.
|
|
Inoltre in fase di scanning evita che siano registrate più interazioni nello stesso ciclo.
|
|
|
|
Sempre attraverso l'API di Android è stata settata la potenza di trasmissione del beacon.
|
|
Anche in questo caso la scelta era limitata a poche alternative:
|
|
|
|
- HIGH
|
|
- MEDIUM
|
|
- LOW
|
|
- ULTRA_LOW
|
|
|
|
Come è possibile dedurre anche dai nomi dei vari livelli, l'API non fornisce nessuna stima quantitativa[^dispositivi-non-omogenei], ma solo delle indicazioni qualitative delle intensità del segnale trasmesso.
|
|
L'individuazione del livello più adatto è stata svolta per via sperimentale utilizzando quattro dispositivi differenti[^dispositivi-usati].
|
|
I due livelli più alti sono stati immediatamente scartati in quanto permettevano di rilevare i beacon a distanze elevate cosa che avrebbe minato la bontà dell'applicazione.
|
|
Con il livello ULTRA_LOW si è notato che venivano rilevate unicamente le interazioni inferiori al metro in contesti *free space*.
|
|
Poiché l'organizzazione mondiale della sanità raccomanda una distanza di almeno un metro [@AdvicePublicCOVID19] questo livello di trasmissione non consente di rilevare contatti potenzialmente a rischio.
|
|
Per questo motivo si è scelto di utilizzare il livello LOW che permette di rilevare contatti fino a circa due metri.
|
|
|
|
[^dispositivi-non-omogenei]: D'altronde, data la natura non omogenea dei vari dispositivi Android, una stima quantitativa sarebbe stata impossibile da ottenere.
|
|
|
|
[^dispositivi-usati]: Sono stati usati i seguenti dispositivi; One Plus 3T con versione di Android 9, Xiaomi Mi 5 con Android 8, Xiaomi Mi A2 Lite con Android 10, Samsung Galaxy A5 con Android 7.
|
|
|
|
### Scansione
|
|
|
|
Le operazioni di scansione, effettuate tramite l'ausilio della classe `BeaconManager`, sono meno limitate dalle funzionalità dell'API e per questo motivo c'è stata maggiore libertà di scelta.
|
|
In particolare è stato possibile settare sia l'intervallo temporale che deve intercorrere tra una scansione e la successiva, sia la durata della singola scansione.
|
|
Si è scelto di far trascorrere un minuto tra una scansione e la prossima e di avere una scansione della durata di un secondo.
|
|
Per quanto detto già in precedenza in base ai vari dispositivi e alle varie condizioni di funzionamento l'intervallo tra una scansione e la successiva potrebbe essere più ampio rispetto a quello stabilito.
|
|
Entrambi i parametri sono stati settati tramite una costante in modo tale da poter configurare facilmente il comportamento dell'applicazione.
|
|
|
|
Quando l'applicazione rivela un beacon nelle vicinanze esso viene trasmesso ad un'ulteriore componente applicativa tramite l'impiego del `LocalBroadcastManager` [@BroadcastsOverview].
|
|
Questa componente non consuma direttamente il beacon, ma ha il compito di smistarlo ad ulteriori componenti in base alla modalità di funzionamento dell'applicazione.
|
|
|
|
Nel caso della modalità *A* il beacon viene trasmesso alla classe `NetworkReceiver` che si occupa di trasmettere il contatto al server remoto.
|
|
Mentre nel caso delle modalità *B* e *C* il beacon viene consumato dalla classe `StoreReceiver` la quale si occupa della memorizzazione permanente del contatto all'interno di un database locale.
|
|
|
|
### Stima della distanza
|
|
|
|
In base all'intensità dell segnale (***rssi***) misurato dal dispositivo ricevente è possibile ottenere una stima della distanza che intercorre tra chi invia il beacon e chi lo riceve attraverso l'@eq:distanza.
|
|
Per poter calcolare la distanza è necessario conoscere anche il valore di $n$ e $TxPower$.
|
|
$n$ è una costante che generalmente assume valori compresi tra uno e quattro e ci permette di modellare i diversi ambienti in cui si può operare.
|
|
Generalmente s'impone $n$ pari a due quando si ipotizza di lavorare in ambienti *free space*.
|
|
|
|
$$
|
|
d = 10^{\frac{TxPower - rssi}{10 \cdot n}}
|
|
$$ {#eq:distanza}
|
|
|
|
$TxPower$ è la potenza di trasmissione nominale che si misurerebbe alla distanza di un metro dalla sorgente del segnale.
|
|
Il valore di $TxPower$ deve essere precedentemente ricavato per ogni emettitore e deve essere inviato all'interno del beacon bluetooth.
|
|
Lavorando con dispositivi eterogenei tra di loro non è stato possibile calcolare in modo esatto questo valore, ma si è scelto di utilizzare il valore $-59$ poiché si adattava mediamente a tutti i dispositivi utilizzati in fase di test.
|
|
|
|
## Memorizzazione ID
|
|
|
|
In base alla modalità di funzionamento l'applicazione deve memorizzare diversi tipi di dati.
|
|
La gestione della persistenza è stata realizzata attraverso la libreria ***Room*** [@RoomPersistenceLibrary], una componente di *Jetpack* [@AndroidJetpackAndroid] la suite di librerie supportate da *Google*.
|
|
*Room* fornisce un layer astratto che permette di operare più facilmente con il database *SQLite* sottostante.
|
|
|
|
La memorizzazione dei contatti è avvenuta tramite lo schema riportato nel @lst:contact-data.
|
|
Questi dati vengono conservati solo nella modalità di funzionamento *B* e *C* poiché nella modalità *A* il contatto viene comunicato immediatamente al server per cui non è necessaria una memorizzazione locale.
|
|
Per il timestamp si è scelto di utilizzare un `Long` che misura i secondi trascorsi dalla *epoch unix* e che fa riferimento al *Greenwich Mean Time* (GMT).
|
|
|
|
``` {.markdown #lst:contact-data caption="Schema utilizzato per la memorizzazione dei dati di contatto."}
|
|
- `id`: Int [PrimaryKey | AutoGenerate]
|
|
- `uuidReceiver`: String
|
|
- `uuidSender`: String
|
|
- `rssi`: Int
|
|
- `txPower`: Int
|
|
- `timestamp`: Long
|
|
```
|
|
|
|
Nella modalità *C* è necessario memorizzare anche le tck utilizzate nel corso del tempo (si veda la @sec:tck per maggiori dettagli).
|
|
Oltre alla memorizzazione della tck, tramite un array di byte, è necessario memorizzare anche l'indice associato ad essa e il timestamp di primo utilizzo.
|
|
Lo schema della tabella utilizzata per la memorizzazione di queste informazioni è riportato nel @lst:tck-data.
|
|
Anche in questo caso, per quanto riguarda il timestamp, restano valide le considerazioni fatte in precedenza.
|
|
|
|
``` {.markdown #lst:tck-data caption="Schema utilizzato per la memorizzazione delle tck."}
|
|
- `index`: Short [PrimaryKey]
|
|
- `timestamp`: Long,
|
|
- `tck`: ByteArray
|
|
```
|
|
|
|
Questi dati persistenti sono stati acceduti mediante l'utilizzo di due *Data Access Object* (DAO).
|
|
Le interfacce dei DAO utilizzate sono riportate nei listati -@lst:dao-contact e -@lst:dao-tcn.
|
|
|
|
Oltre alla memorizzazione sul database sono state impiegate anche le *shared preferences* [@KeyvalueData] per memorizzare la modalità di funzionamento scelta dall'utente, la *rak*, l'ultima *tck* e il *tcn* associato ad essa.
|
|
|
|
``` {.kotlin #lst:dao-contact caption="Interfaccia ContactDataDao."}
|
|
@Dao
|
|
interface ContactDataDao {
|
|
@Query("SELECT * FROM contact_data")
|
|
suspend fun getAllContactData(): List<ContactData>
|
|
|
|
@Query("SELECT * FROM contact_data WHERE timestamp >= :baseline")
|
|
suspend fun getNewerThan(baseline: Long): List<ContactData>
|
|
|
|
@Insert
|
|
suspend fun insert(cn: ContactData)
|
|
}
|
|
```
|
|
|
|
``` {.kotlin #lst:dao-tcn caption="Interfaccia TCNDataDao."}
|
|
@Dao
|
|
interface TCNDataDao {
|
|
@Query("SELECT * FROM tcn_data")
|
|
suspend fun getAllTCNData(): List<TCNData>
|
|
|
|
@Query("SELECT * FROM tcn_data WHERE `index` == :index")
|
|
suspend fun getByIndex(index: Short): TCNData
|
|
|
|
@Insert
|
|
suspend fun insert(tcnData: TCNData)
|
|
}
|
|
```
|
|
|
|
## Comunicazione report
|
|
|
|
La comunicazione con il server avviene mediante un broker MQTT fornito da un altro gruppo di studenti.
|
|
Come implementazione del client MQTT si è scelto di utilizzare *Paho* [@EclipsePahoMQTT], un client realizzato da Eclipse.
|
|
Questa libreria oltre a fornire un client MQTT per la JVM fornisce anche un *service* per Android che permette di sollevare lo sviluppatore da alcuni dettagli implementativi.
|
|
|
|
L'applicazione, all'interno dell'architettura, svolge il ruolo di *publisher* e si occupa della pubblicazione di tre tipologie di messaggi:
|
|
|
|
- ***Messaggi di contatto***:
|
|
utilizzati sia nella modalità *A* che nella *B*, permettono di notificare al server un contatto tra due utenti.
|
|
Nel caso della modalità *A* viene svolto un invio in *real-time*, mentre nella configurazione *B* è manuale e viene eseguito solo quando l'utente risulta essere contagiato.
|
|
Il contenuto di questi messaggi coincide con la rappresentazione JSON dei dati di contatto illustrati nel @lst:contact-data.
|
|
- ***Messaggi di report***:
|
|
utilizzati esclusivamente nella modalità *C* e sono inviati manualmente dall'utente quando risulta essere positivo.
|
|
Questi messaggi trasportano come *payload* la rappresentazione esadecimale del report TCN firmato discusso nella @sec:report e vengono inviati solo quando l'*upload* è richiesto dall'utente.
|
|
- ***Messaggi di esposizione***:
|
|
contengono l'identificativo associato al dispositivo e permettono di notificare lo stato di contagiato al server.
|
|
Questi messaggi sono inviati unicamente dalla modalità *A* poiché nella modalità *B* e *C* il caricamento dei dati locali di contatto implica lo stato di contagiato.
|
|
|
|
Anche in questo caso si è scelto di *wrappare* la libreria utilizzata all'interno di una classe sviluppata in proprio.
|
|
Poiché l'unica funzionalità di nostro interesse è la *publish* è stato necessario scrivere un'unica funzione statica che si occupa di eseguire quest'operazione.
|
|
Questa funzione, riportata nel @lst:mqtt-publish, permette di specificare il topic e il contenuto del messaggio da pubblicare.
|
|
Inoltre attraverso una *lambda expression* è possibile specificare come comportarsi in caso di errori.
|
|
Sempre dal @lst:mqtt-publish è possibile notare l'utilizzo del parametro `cleanSession` impostato a `false` e l'utilizzo dei *QoS* a livello uno.
|
|
Queste scelte sono state necessarie per garantire un corretto funzionamento con il resto dell'infrastruttura.
|
|
|
|
``` {.kotlin #lst:mqtt-publish caption="Funzione wrap che consente la publicazione di un messaggio MQTT."}
|
|
fun publish(
|
|
context: Context,
|
|
topic: String,
|
|
payload: ByteArray,
|
|
onFailure: () -> Unit = LOG_ERROR
|
|
) {
|
|
val clientId = MqttClient.generateClientId()
|
|
val client = MqttAndroidClient(
|
|
context.applicationContext,
|
|
BROKER_URL,
|
|
clientId
|
|
)
|
|
|
|
val option = MqttConnectOptions().apply {
|
|
isCleanSession = false
|
|
}
|
|
|
|
client.connect(option)
|
|
.actionCallback = object: IMqttActionListener {
|
|
override fun onSuccess(asyncActionToken: IMqttToken?) {
|
|
val msg = MqttMessage(payload).apply {
|
|
qos = 1
|
|
}
|
|
|
|
client.publish(topic, msg).apply {
|
|
actionCallback = Disconnect(client)
|
|
}
|
|
}
|
|
|
|
override fun onFailure(
|
|
asyncActionToken: IMqttToken?,
|
|
exception: Throwable?
|
|
) {
|
|
onFailure()
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
Le funzionalità di rete sono state testate attraverso l'impiego di un broker pubblico (`tcp://broker.hivemq.com:1883`) ed un'istanza locale di *ActiveMQ Artemis* inoltre è stato utilizzato il topic `exposed-test-topic` per la pubblicazione dei messaggi.
|
|
Entrambi i parametri sono stati settati tramite delle costanti e quindi possono essere modificati facilmente.
|
|
|
|
## Interfaccia utente
|
|
|
|
L'applicazione opera prevalentemente in background, ma comunque è dotata di una serie di elementi grafici che consentono all'utente di interagire con essa.
|
|
Quando l'applicazione viene avviata per la prima volta l'utente ha la possibilità di scegliere la modalità di funzionamento che desidera utilizzare (si veda @fig:ui-welcome).
|
|
Una volta compiuta questa scelta viene chiesto all'utente di concedere l'accesso alla posizione.
|
|
Sebbene l'applicazione non utilizzi il GPS o altri strumenti di posizionamento ciò si rende necessario al fine di abilitare la scansione dei beacon BLE anche in background.
|
|
|
|
![Schermata di benvenuto.](fig/welcome.jpg){#fig:ui-welcome width=130}
|
|
|
|
La schermata principale dell'applicazione, riportata in @fig:ui-main, si compone di tre elementi:
|
|
|
|
- `TextView` che indica la modalità di funzionamento.
|
|
- `Button` *start/stop* che consente di avviare o stoppare il servizio bluetooth.
|
|
Questo pulsante viene abilitato unicamente se sono stati concessi i permessi di accesso alla posizione.
|
|
- `Button` *exposed* consente all'utente di raggiungere l'activity, @fig:ui-upload, attraverso la quale è possibile comunicare al server il proprio stato di contagiato.
|
|
Nel caso della modalità *B* e *C* vengono caricati sul server anche i dati di contatto presenti sul dispositivo.
|
|
|
|
![Schermata principale.](fig/main.jpg){#fig:ui-main width=130}
|
|
|
|
![Schermata per l'upload.](fig/upload.jpg){#fig:ui-upload width=130}
|
|
|
|
# Riferimenti
|