Nouveaux types de communications sans fil pour iOS

De Ensiwiki
Aller à : navigation, rechercher
Nouveaux types de communications sans fil pour iOS
Projet Projet de spécialité 2A 2014
Thème Réseaux & Communications
Étudiants Audrey LEMINEUR (TEL)
Caroline THOMET (TEL)
Benjamin DAUBIE (TEL)
Date 26 Mai - 19 Juin 2014
Tuteur Franck ROUSSEAU

Introduction

Contexte

Alors que les télécommunications sont au coeur de nos sociétés, nous avons souhaité explorer de nouveaux types d’applications communicantes, totalement indépendantes de l'ensemble du réseau mobile existant. Ce projet s'inscrit dans une logique nouvelle, étudiant la faisabilité de nouveaux types de communications sur les terminaux iOS, système d'exploitation propriétaire Apple, comme l’iPhone et l’iPad.
L’idée générale du projet iOScom est de fournir un ensemble de services de communications locales inter-iPhone pour un ensemble d’utilisateurs.

Objectifs

Le projet iOScom possède un double objectif. Le premier objectif du projet est de fournir un point d’accès réseau local, éventuellement sécurisé, auto-configuré par DHCP. La configuration de celui-ci sera minimale et sera nécessaire pour réaliser l'ensemble des tests de l’application qui elle représente le coeur du projet. Les variantes de configuration étant ensuite à négocier selon l’usage réel.

L’objectif principal, quant à lui, est de fournir une application pour iPhone permettant un ensemble de services de communication.

Cette application offre trois fonctionnalités distinctes :

  • L'échange de messages textes. (sorte de "chat")
  • La possibilité de lire des fichiers audio stockés sur le téléphone
  • L'échange de fichiers audio en streaming


Nous avons choisi d'implémenter deux fonctionnements quant à ces échanges de données : l'unicast et le multicast.

Unicast : Hôte 1 <--> Hôte 2

Le principe de l'unicast est simple : la communication se fait d'un hôte vers un second hôte, comme le figure le schéma ci-dessous.


Figure 1 : Principe de l'Unicast

Multicast : Hôte 1 <--> Hôte 1,...,Hôte N

Le multicast, quant à lui, consiste en une multidiffusion. La source de diffusion envoie ses paquets à une adresse multicast (dans la plage [224.0.0.0 - 239.255.255.255]) et les utilisateurs qui souscrivent à cette adresse (ayant des adresses IP classiques distinctes) reçoivent les données émises. Le fait que les récepteurs soient abonnés à cette adresse permet à leur carte réseau de récupérer les paquets, si ceux ci n'ont pas été filtrés en amont par un routeur qui n'autorise pas le multicast. Tous ceux qui souscrivent à cette adresse multicast font parti du groupe de diffusion et peuvent envoyer, recevoir en son nom.

Figure 2 : Principe du Multicast

Streaming

Le Streaming, ou diffusion de flux, consiste en l'envoi d'un fichier (audio, vidéo, ou autre) en "direct".
Comme on peut le voir sur le schéma ci-dessous, l'iPhone A possède un fichier audio, enregistré dans sa mémoire, et procède à l'envoi de celui-ci vers l'iPhone B.
Au fur et à mesure de la réception, l'iPhone B lit ce fichier, sans avoir besoin de le stocker.

Figure 3 : Principe du Streaming

Configuration du point d'accès : le NUC

Pour configurer notre réseau local wifi, nous avons utilisé un Mini PC - Intel® NUC ayant UNIX comme système d'exploitation, que nous avons configuré comme point d'accès. De plus, nous avons utilisé un adaptateur USB WiFi TPLINK ATHEROS® pour augmenter la porté. En effet, l'émetteur wifi intégré à la carte réseau du NUC est au vu de nos tests moins puissant. Nous avons utilisé hostapd et dhcpd pour cette configuration :
Fichiers de configuration - Attention vous devez être root ou utiliser sudo pour pouvoir éditer ses fichiers dans /etc/

  • hostapd.conf & hostapd.accept
  • dhcpd.conf, activer ip_forwarding
  • script de lancement : launch.sh


Téléchargez les fichiers de configurations Fichier:NetworkConfigFiles.tar.gz
Le lancement de launch.sh crée sur l'interface wlan1 un réseau wifi nommé "experimental" (celui ci peut être renommé en allant modifier ssid dans le fichier de configuration hostapd.conf). Vu la faible porté de ce réseau et son utilisation au sein même de l'Ensimag, nous n'avons pas, dans un premier temps, souhaité le sécuriser. Nous effectuons donc juste un filtrage sur les adresses mac à la connection via le fichier hostapd.accept : toute tentative de connections par un appareil dont l'adresse mac n'est pas contenue dans ce fichier est refusée. On peut désactiver cette option dans le fichier précédent en mettant macaddr_acl=0 et gérer manuellement les adresses mac en éditant le fichier hostapd.accept, il est préférable de commenter les blocs d'adresses ajoutés pour pouvoir retirer celles que l'on ne souhaite plus autoriser par la suite. On laisse la possibilité de rajouter une clef de chiffrement en allant décommenter les lignes que nous avons pré remplie à cette effet dans le fichier hostapd.conf.

Fonctionnement général

A travers cette partie de fonctionnement général, nous allons vous présenter les parties techniques de notre projet, qui nous ont permis d'aboutir au développement de notre application une fois le réseau mis en place.
La gestion d'envois de données de notre application (texte, audio, streaming voix ...) repose sur la gestion des Socket UDP. Nous vous présentons également notre implémentation de la partie de gestion de l'audio dans ses différentes configurations.

Sockets UDP : Principe & fonctionnement

Pour réaliser des communications inter-iPhone, et de par le coté recherche de ce projet, nous avons choisi d'utiliser les Sockets UDP.
Le rôle de ce protocole est de permettre la transmission de données de manière très simple entre deux entités, chacune étant définie par une adresse IP et un numéro de port. Ce protocole, choisi pour sa simplicité d'utilisation, présente tout de même certains désavantages. En effet, UDP ne garantit pas la bonne livraison des paquets envoyés, ni que ces derniers soient reçus dans l'ordre d'envoi. Cependant, pour une première approche, et dans un contexte de réseau local (et donc non pollué par le réseau alentour), ces imprécisions seront négligeables et ce protocole sera suffisant.

Nous utilisons le framework GCDAsyncUDPSocket. Voici ci-dessous, le principe de fonctionnement de ces sockets.

  • La création d'une socket coté émetteur & bind au port voulu
   NSError *error = nil;
   if (udpSocket == nil) {
       udpSocket = [[GCDAsyncUdpSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
       if (![udpSocket bindToPort:0 error:&error])
        {
        NSLog(@"Error binding: %@", error);
        return;
        }
       if (![udpSocket beginReceiving:&error])
       {
           NSLog(@"Error receiving: %@", error);
           return;
       }
   }
  • Envoi de la donnée data
NSData *data = [msg dataUsingEncoding:NSUTF8StringEncoding];
[udpSocket sendData:data toHost:host port:port withTimeout:-1 tag:tag];
  • Mise en écoute de la socket du coté récepteur
           if (![udpSocket beginReceiving:&error])
           {
               [udpSocket close];
               NSLog(@"Error starting server (recv): %@", error);
               return;
           }
  • Fermeture de la socket
        [udpSocket close];



Notre première utilisation des sockets dans notre application permet l'envoie de messages de type sms en unicast ou multicast

Gestion de l'audio

La gestion de l'audio pour lire, envoyer ou recevoir des données a été gérée dans notre projet de manière incrémentale.
Dans un premier temps nous nous sommes simplement intéressés à la lecture d'un fichier et à son chargement dans un buffer, puis nous avons entrepris de mettre en place le flux nécessaire au streaming en parallèle de la gestion de l'enregistrement vocal à travers le microphone de l'iPhone.
En combinant ces 3 étapes, nous sommes ainsi en mesure de déployer une application de streaming vocal en temps réel.

Lecture d'un fichier Audio

La lecture d'un fichier audio est un premier passage pour envisager des fonctionnalités de partage. Nous avons utilisé le framework AVFoundation. Nous avons exploré les différentes méthodes possibles selon si l'on possède le fichier dans son intégralité sur son disque. S'il on le reçoit de l'extérieur, donc potentiellement fractionné, on verra son traitement dans la partie Streaming.

  • Jouer une musique contenue sur le disque avec un bouton dynamique play/pause
   // on initialise le fichier et on le lance
   if (!init){
       NSString *filePath = [NSString stringWithFormat:@"%@/<name>.<ext>",
                             [[NSBundle mainBundle] resourcePath]];
       NSURL *fileURL = [NSURL fileURLWithPath:filePath];
       
       player = [[AVAudioPlayer alloc] initWithContentsOfURL:fileURL error:nil];
       player.numberOfLoops = 0; //-1 pour répéter à l'infini
       player.delegate = self;
       init = YES ;
       [player play];
       [playPause setTitle: @"Pause" forState:UIControlStateNormal];
   //le fichier est joué donc on le pause
   } else if ([player isPlaying]) {
       [player pause];
       [playPause setTitle: @"Play" forState:UIControlStateNormal];
   // le fichier est en pause donc on le redémarre
   } else {
       [player play];
       [playPause setTitle: @"Pause" forState:UIControlStateNormal];
   }

Streaming Audio

  • Expérimentations avec VLC

Afin de tester la faisabilité du stream audio sur un réseau local d'Iphone, nous avons utilisé la fonctionnalité de streaming fournie par VLC. Le résultat est convaincant en unicast, nous fournissons un petit tutoriel pour aborder cette fonctionnalité Fichier:TutoVLC.pdf.

  • Jouer une musique reçue issue du streaming


Dans les deux sous-parties ci-dessous nous présentons l'architecture de notre code d'envoi et de celui d'émission.
Voici le schéma général du streaming audio.

Figure 4 : Streaming et gestion des données
Partie Emission

L'émetteur possède à sa disposition un fichier audio, stocké sur son téléphone. La première étape, avant l'envoi au travers des sockets UDP, consiste en la découpe de ce fichier audio en paquets nommés splitData dans notre code, dont un aperçu est disponible ci-dessous.

       // Decoupage en paquets de l'audioFile à envoyer
       while (numeroPaquets <= nombrePaquets) {
           
           if (sizeAudioFile - i < dataRange.length){
               dataRange.length = sizeAudioFile - i;
           }
           
           NSLog(@"i : %d",i);
           NSLog(@"Envoi paquet numeroPaquets : %d",numeroPaquets);
           
           dataRange.location = i;
           splitData = [data subdataWithRange:dataRange];
           [udpSocket sendData:splitData toHost:host port:port withTimeout:-1 tag:tag];
           
           i += (int) dataRange.length;
           numeroPaquets ++;
       }

Ces différents paquets sont alors envoyés un par un dans la Socket UDP, sur le réseau local.

Partie Réception

Coté réception, ces paquets de données sont alors reçus au travers la socket et stockés dans un tableau de données, nommé NSArray tab ici. Une fois que tab est rempli par un certain nombre de données, nous commençons à remplir les buffers. Nous avons choisi d'avoir trois buffers distincts, dans le tableau de buffers inBuffer. Cette initialisation des 3 buffers se fait en appelant "à la main" la méthode callback.
C'est alors que ces buffers sont ajoutés dans une AudioQueueRef, grâce à la méthode AudioQueueEnqueueBuffer. La queue AudioQueueRef va par la suite être lue par la méthode AudioQueueStart(queue,NULL). Une fois le buffet libéré, car lu par la méthode de lecture précédente, la méthode callback est rappelée automatiquement. C'est ainsi qu'un nouvel élément du tableau tab peut alors remplir ce buffer, devenu vide.

Pour résumer, l'ensemble des données envoyées sont tour à tour mises dans le tableau tab, puis ajoutées dans un buffer, pour qu'enfin, ce buffer soit mis en queue de l'AudioQueueRef.

Nous avons choisi d'effectuer ce stockage des données dans le tableau tab afin de pallier à un problème de synchronisation. En effet, nous avons ici deux phénomènes asynchrones : d'une part la réception des paquets UDP coté réseau, et d'autre part, la lecture du fichier audio. Ces deux procédés ont chacun une vitesse qui leur est propre. Il est donc nécessaire d'effectuer un stockage intermédiaire, afin de diminuer une possible latence.

Enregistrement à partir du microphone

Pour la gestion des enregistrements de la voix via le microphone du Smartphone, nous nous sommes aidés des framework suivantes : AudioToolbox et AVFoundation.
Nous utilisons dans un premier temps des sessions AVAudioSession qui nous permettent de gérer l'activation et l'arrêt de l'enregistrement. Elles nous permettent également de régler les paramètres de configuration audio (taille du buffer ...).
Les données enregistrées sont stockées dans des objets de type NSDictionary pour faciliter l'accès à celles-ci.
Enfin, nous avons utilisé un AVAudioRecorder pour enregistrer les données du dictionnaires sur le disque (dans un premier temps pour les tests). Pour le streaming nous n'avons pas choisi de garder cette implémentation qui utilise inutilement de la mémoire. Elle est toutefois facilement réimplémentable.

Librairies & Framework Xcode utilisés

GCDAsyncSocket : librairie réseau pour Mac et iOS utilisant une gestion asynchrone de sockets TCP ou UDP. CocoaAsyncSocket-Github Source
AVFoundation : partie audio. AVFoundation
UIKit : gestion messages d'alertes. UIAlert view

Notre application pour iPhone - Tutoriel

Dans cette partie, nous allons décrire les trois différentes fonctionnalités de notre application, en détaillant le fonctionnement utilisateur de celles-ci.

Echange de messages texte

Mode de fonctionnement

Pour nous familiariser avec l'environnement de développement en Objective C pour iOS, nous avons codé une petite interface permettant l'échange de courts messages. Un bouton à plusieurs états permet de faire les chargements nécessaires sur la vue de l'application et active les fonctionnalités associées.

  • Unicast

Quand on souhaite converser de manière simple et rapide avec une personne précise. En écoute, un port nous est donné, et il suffit d'attendre les messages. Pour envoyer, il faut remplir remplir l'adresse destination et le port destination et envoyer le message souhaité. Dans la mesure où le projet ne visait pas à connaitre qui envoie des messages et à faire de authentification poussée, nous considérons que les personnes qui utilise cette petite application le font dans un cadre limité et qu'ils sont conscients des menaces potentielles : usurpation d'identité, spam, harcèlement...

UnicastSR.png


  • Multicast émission

Quand on souhaite avertir un groupe d'usager d'un coup, il suffit d'entrer une adresse multicast (dans la plage 224.0.0.0 à 239.255.255.255 et en retirant adresses multicast reservées) ainsi qu'un port. On peut ensuite envoyé des messages pour un groupe, sur les trames émises (vues ci dessous) utilise notre adresse classique à l'émission et la destination est un groupe, ce qui masque les récepteurs.

Trame.png MultiS.png
  • Multicast réception

De même, quand on souhaite recevoir des informations au nom d'un groupe, on s'abonne à l'adresse multicast et on écoute le port souhaité. Si l'on souhaite envoyer et recevoir sur ce groupe, il faut basculer d'un menu à l'autre.

MultiR.png
  • Remarques :

Bien qu'ayant suivi les cours d'IHM cette année, les délais de temps et le cadre du projet ne nous ont pas permis de faire une étude approfondie du point de vu de l'Interaction Homme Machine. Nous nous en excusons.

Rattrapage d'erreurs utilisateurs

Afin de permettre du rattrapage d'erreurs pour un utilisateur non averti ou mal intentionné nous nous assurons à l'aide d'expressions régulières et de tests simples que les champs remplis ont le bon format:

  • adresse IPv4

Non seulement nous proposons un modèle d'adresse valide (192.168.0.0 pour l'unicast, 239.255.0.0 pour le multicast) mais nous vérifions aussi que le format de ce champ est bien dans la plage (0.0.0.0 - 255.255.255).

#define patternIP @"^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|
[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"
addrIP = [[NSRegularExpression alloc] initWithPattern:patternIP options:nil error:nil];
range = [addrIP rangeOfFirstMatchInString:host options:0 range:NSMakeRange(0, [host length])]; 
if (range.length != [host length] ^ [host length] == 0)
{
  alert.message = [NSString stringWithFormat:@"Not a valid IP address selected \n choose between 0.0.0.0 and 255.255.255.255"];
  [alert show];
  return;
}
  • port

De même pour le port, on vérifie que celui ci est un port valide et non utilisé par une autre application (cf ports réservés).

#define patternPort @"^([0-9]{1,5})$"

  • message d'erreurs
Badaddress.png Badport.png Emptymessage.png

Player Audio

Ce premier volet de l'application avec la gestion de l'Audio a pour unique but de nous aider à effectuer des tests sur le son.
Ainsi nous avons la possibilité de lire de l'Audio (à partir d'une bibliothèque limitée stockée dans l'application) en appuyant sur le bouton play. Nous pouvons arrêter cette lecture à l'aide du bouton "stop" ou encore passer à la musique suivante "next".

Sources

Ci-dessous, voici une liste récapitulative de nos sources, nécessaires à la réalisation de ce projet :