TimeService e .Net Micro Framework : importanza della data ed dell’ora nell’IoT

Nell’ambito dell’Internet of Things e soprattutto quando abbiamo intenzione di proteggere i dati trasmessi da un sistema embedded da occhi indiscreti attraverso gli algoritmi di criptazione (AES, DES, 3DES, RSA, …) e con relativi protocolli di rete (SSL, TLS, DTLS, …), assume un ruolo fondamentale l’impostazione della data ed dell’ora sulla nostra board.

Infatti, gli algoritmi di criptazione si basano fortemente sui generatori di numeri pseudocasuali (PRNG, Pseudo-Random Number Generator), la cui inizializzazione prevede molto spesso l’utilizzo della data ed dell’ora corrente come “seme” della generazione. Essendo generatori pseudo-casuali e non reali è importante che ad ogni avvio il “seme” risulti diverso altrimenti la sequenza generata è la medesima dell’avvio precedente e quindi prevedibile.

Inoltre, nel caso in cui dobbiamo accedere ad un servizio nel cloud per il quale si rende necessaria l’autenticazione attraverso un token (es. accesso SAS, Shared Access Signature, al Microsoft Azure Service Bus), è importante che la richiesta contenga anche un timestamp relativo alla scadenza del token (es. nel caso di un SWT, Simple Web Toke); il timestamp va ovviamente calcolato in base alle proprie esigenze e rispetto ad una data ed ora correttamente impostati nel sistema.

RTC ed (S)NTP server

Le modalità con cui è possibile impostare ed aggiornare la data e l’ora in un sistema embedded sono tipicamente due :

  • L’utilizzo di un RTC (Real Time Clock) che può essere a bordo del microcontrollore oppure una periferica esterna collegata a quest’ultimo mediante un opportuno protocollo (in molti casi I2C);
  • La connessione ad un (S)NTP server (Simple Network Time Protocol) e la relativa sincronizzazione con quest’ultimo con una certa cadenza oppure su richiesta esplicita;

Nel primo caso, l’RTC ha il compito di salvare e fornire al sistema la data e l’ora aggiornati anche con successivi riavvii; nel secondo caso, il sistema si connette al server per ricavare una data ed ora aggiornati senza avere però la possibilità di ritrovarsi con le informazioni corrette al successivo riavvio.

Ovviamente le due modalità possono anche essere utilizzate insieme : al primo avvio il sistema si sincronizza con un server (S)NTP e salva la data nell’RTC a disposizione. All’avvio successivo, essendo l’RTC dotato tipicamente di una batteria al litio, il sistema potrà chiedere ad esso la data e l’ora senza la necessità di una connessione al server (S)NTP. In un qualsiasi momento, si può richiedere la sincronizzazione con relativo aggiornamento della data ed ora del server con l’RTC in locale.

TimeService e .Net Micro Framework

Sviluppando con il .Net Framework, siamo tutti abituati a ricavare la data e l’ora utilizzando la proprietà DateTime.Now; se eseguiamo questa operazione all’avvio su una board con il .Net Micro Framework non avremo sicuramente la data e l’ora corretti !!

L’utilizzo di un RTC non viene fornito nativamente dal framework ma per fortuna abbiamo a disposizione la classe TimeService che ci aiuta nell’utilizzo di un (S)NTP server.

Questa classe mette a disposizione una serie di proprietà, metodi ed eventi i cui principali sono :

  • Settings : proprietà del tipo (TimeServiceSettings) che permette di definire le principali impostazioni del servizio (indirizzi di un server primario e secondario a cui connettersi, intervallo di refresh, sincronizzazione forzata ad ogni avvio, …);
  • SystemTimeChangedTimeSyncFailed : eventi sollevati rispettivamente quando la data e l’ora sono cambiati a seguito di una corretta sincronizzazione oppure quando la sincronizzazione è fallita (es. mancata connessione);
  • SetTimeZoneOffset : metodo per l’impostare l’offset della nostra timezone rispetto a UTC/GMT;
  • Start/Stop : metodi per avviare ed arrestare il servizio di sincronizzazione;
  • UpdateNow : metodo per forzare una sincronizzazione immediata;

Ovviamente, queste rappresentano solo una minima parte di tutte le caratteristiche e funzionalità della classe TimeService.

Una strana anomalia sui settings : un occhio al codice nativo

La classe statica TimeService espone tutte le impostazioni mediante la proprietà Settings che a sua volta contiene una serie di proprietà per le impostazioni stesse. Per poterle impostare, saremo portati ad eseguire un codice di questo tipo

TimeService.Settings.RefreshTime = 10;
TimeService.Settings.ForceSyncAtWakeUp = true;

I valori di default per le proprietà RefreshTime e ForceSyncAtWakeUp sono 50000 e false. Se proviamo ad eseguire il codice appena visto, vedremo che subito dopo le due istruzioni, i valori non sono assolutamente cambiati in 10 e true. Perchè ?

TimeServiceSettings

Il motivo di questo comportamente è l’implementazione nativa del “get” della proprietà Settings. Nel codice sorgente del .Net Micro Framework troviamo :

HRESULT Library_spot_Time_native_Microsoft_SPOT_Time_TimeService::get_Settings___STATIC__MicrosoftSPOTTimeTimeServiceSettings( CLR_RT_StackFrame& stack )
{
 TINYCLR_HEADER(); 

 TimeService_Settings settings;
 CLR_RT_HeapBlock& top = stack.PushValueAndClear();
 CLR_RT_HeapBlock* managedSettings = NULL;

 TINYCLR_CHECK_HRESULT(TimeService_LoadSettings(&settings)); 

 TINYCLR_CHECK_HRESULT(g_CLR_RT_ExecutionEngine.NewObjectFromIndex( top, g_CLR_RT_WellKnownTypes.m_TimeServiceSettings ));
 managedSettings = top.Dereference();

 managedSettings[ ManagedSettings::FIELD__PrimaryServerIP ].SetInteger( settings.PrimaryServerIP );
 managedSettings[ ManagedSettings::FIELD__AlternateServerIP ].SetInteger( settings.AlternateServerIP );
 managedSettings[ ManagedSettings::FIELD__RefreshTime ].SetInteger( settings.RefreshTime );
 managedSettings[ ManagedSettings::FIELD__Tolerance ].SetInteger( settings.Tolerance );
 managedSettings[ ManagedSettings::FIELD__ForceSyncAtWakeUp ].SetBoolean( 0 != (settings.Flags & TimeService_Settings_Flags_ForceSyncAtWakeUp) );
 managedSettings[ ManagedSettings::FIELD__AutoDayLightSavings ].SetBoolean( 0 != (settings.Flags & TimeService_Settings_Flags_AutoDST) );

 TINYCLR_NOCLEANUP();
}

Come possiamo notare, la funzione crea una variabile locale “settings” all’interno della quale carica le impostazioni globali. Successivamente, essa copia i campi della variabile locale alla variabile managed da ritornare al nostro codice (attraverso lo stack e per riferimento) … ciò vuol dire che non stiamo impostando i settings globali della classe TimeService ma una copia di una variabile locale di una funzione ! Non so dirvi se può essere considerato un bug o un comportamento voluto ma per impostare i settings correttamente è necessario creare una istanza della classe TimeServiceSettings ed assegnarla alla proprietà Settings di TimeService.

TimeServiceSettings settings = new TimeServiceSettings();
settings.RefreshTime = 10; // every 10 seconds
settings.ForceSyncAtWakeUp = true;
TimeService.Settings = settings;

Un esempio completo

In definitiva, per poter utilizzare in maniera corretta la classe TimeService, possiamo utilizzare il seguente codice.

void ethernetJ11D_NetworkUp(GTM.Module.NetworkModule sender, GTM.Module.NetworkModule.NetworkState state)
{
     TimeServiceSettings settings = new TimeServiceSettings();
     settings.RefreshTime = 10; // every 10 seconds
     settings.ForceSyncAtWakeUp = true;
            
     TimeService.SystemTimeChanged += TimeService_SystemTimeChanged;
     TimeService.TimeSyncFailed += TimeService_TimeSyncFailed;
     TimeService.SetTimeZoneOffset(60);

     IPHostEntry hostEntry = Dns.GetHostEntry("time.nist.gov");
     IPAddress[] address = hostEntry.AddressList;
     if (address != null)
         settings.PrimaryServer = address[0].GetAddressBytes();
                
     hostEntry = Dns.GetHostEntry("time.windows.com");
     address = hostEntry.AddressList;
     if (address != null)
         settings.AlternateServer = address[0].GetAddressBytes();
                
     TimeService.Settings = settings;

     TimeService.Start();
}

void TimeService_TimeSyncFailed(object sender, TimeSyncFailedEventArgs e)
{
     Debug.Print("DateTime Sync Failed");
}

void TimeService_SystemTimeChanged(object sender, SystemTimeChangedEventArgs e)
{
     Debug.Print("DateTime = " + DateTime.Now.ToString());
}

Sulla base dell’intervallo di tempo impostato in RefreshTime, verrà sollevato periodicamente l’evento SystemTimeChanged potremmo essere certi che la proprietà DateTime.Now sarà quella correttamente sincronizzata con il server.

Va sottolineato che la classe TimeService basa il suo funzionamento su una parte di codice nativo che è incluso nel firmware delle board della GHI Electronics (FEZ Spider, FEZ Raptor, …) ma non nel firmware del Netduino, per il quale è necessario l’utilizzo di una classe di terze parti che implementi un client (S)NTP.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s