Tag Archives: iOS

NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9814)

En informatique, les problèmes les plus simples sont parfois les plus bizarres à comprendre et c’est ce qui m’est arrivé il y a 2 jours sur un prototype Xamarin.iOS sur laquelle je travaille.

L’application est on ne peut plus classique puisqu’elle fait simplement des appels à des webservices pour afficher des données sous forme d’une liste.

Alors que tout fonctionne correctement au début, en passant les webservices de HTTP à HTTPS, l’erreur, dont la description est très parlante (NSURLSession/NSURLConnection HTTP load failed kCFStreamErrorDomainSSL, -9814), se produit de manière subite.

La première réaction a été de se dire que j’ai oublié de configurer ATS (App Transport Security) dans le fichier Info.plist de l’application. Après vérification, ce n’est pas le cas puisque la configuration autorise tous les appels comme indiqué ci-dessous.

<key>NSAppTransportSecurity</key>
  <dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
  </dict>
</key>

Après recherche dans la documentation officielle Apple, le code 9814 signifie que le certificat SSL est expiré mais ce n’est pas le cas puisque celui-ci expire en 2019.

D’où peut donc bien venir cette erreur dans ce cas ?

Après plus d’une heure à tourner en rond, en cherchant sur StackOverflow et toutes les sources classiques d’aide pour un développeur, une illumination m’est apparue : “Vérifions quelle date est utilisée par mon périphérique actuellement”.

BINGO !!! Le périphérique qui n’a apparemment jamais réussi à se synchroniser avec une source de temps (ou défini manuellement dans les paramètres du système), utilise une date en Janvier 1970.

L’erreur ne signifie pas dans ce cas que le certificat est réellement expiré, mais qu’il n’a pas réussi à être validé dans la séquence normale de validation des communications HTTPS.

Je partage donc cette petite mésaventure qui vous évitera de perdre du temps, simplement parce que le périphérique utilisé n’était pas à l’heure. Après vérification, la même erreur s’applique sur tvOS si jamais vous travaillez sur cette plateforme.

Reconnaissance vocale et analyse de texte dans vos applications Xamarin.iOS

Bien qu’existante depuis très longtemps dans l’univers informatique, la reconnaissance vocale est une fonctionnalité encore peu utilisée au sein des applications (aussi bien sur mobile que sur desktop).

Depuis l’annonce de Siri par Apple en 2011, les assistants personnels ont le vent en poupe et une nouvelle étape a été franchie sur iOS 10 lors de la WWDC 2016 puisque le moteur de reconnaissance vocal utilisé par Siri est désormais accessible aux développeurs.

Voyons comment utiliser celui-ci pour intégrer la reconnaissance vocale au sein d’une application Xamarin.iOS, et comment le coupler à des APIs plus anciennes mais relativement méconnues, pour faire de l’analyse de texte.

Speech Recognition API au service des développeurs

Comme nous l’évoquions précédemment, Apple a rendu disponible dans iOS 10 le moteur qui propulse Siri pour la reconnaissance vocale. Pour cela, une nouvelle classe est à la disposition des développeurs : SFSpeechRecognizer.

Ce qu’il est important de noter pour utiliser cette classe, c’est qu’une connexion réseau est nécessaire car tout ce qui sera enregistré via le microphone, sera envoyé à Apple pour que les serveurs qui propulsent Siri, traitent et analysent les informations avant de vous renvoyer la transcription du texte.

Cette phase de communication avec les serveurs Apple a lieu tout au long du processus de reconnaissance vocale (vous récupérez la transcription au fil de l’eau), ce qui fait que si vous utilisez la fonctionnalité trop souvent, cela a tendance à consommer beaucoup de données (sur votre forfait 3G/4G) et d’énergie sur la batterie de votre téléphone/tablette.

C’est pourquoi Apple impose certaines limitations (ex: 1mn maximum pour chaque enregistrement audio) et a également mis en place des quotas d’utilisation permettant à une application d’effectuer un nombre limité de requêtes par jour (un message d’erreur est renvoyé si le quota est dépassé).

Comme pour tout ce qui touche à la vie privée chez Apple, avant de pouvoir utiliser cette API, il est nécessaire de demander l’autorisation à l’utilisateur et de réagir en conséquence selon la réponse qu’il donnera :

SFSpeechRecognizer.RequestAuthorization((SFSpeechRecognizerAuthorizationStatus status) =>
{
  switch (status)
  {
    case SFSpeechRecognizerAuthorizationStatus.Authorized:
      break;
    case SFSpeechRecognizerAuthorizationStatus.Denied:
      break;
    case SFSpeechRecognizerAuthorizationStatus.NotDetermined:
      break;
    case SFSpeechRecognizerAuthorizationStatus.Restricted:
      break;
  }
});

Pour que tout ceci fonctionne sans soucis, il est également nécessaire d’ajouter 2 lignes dans le fichier Info.plist, afin d’indiquer à l’application que vous avez besoin d’utiliser la reconnaissance vocale et le microphone.

Speech Recognition - Info.plist

Maintenant que les bases sont posées pour pouvoir utiliser les composants nécessaires, il ne reste plus qu’à écrire le code. Celui-ci repose sur 3 classes importantes :

  • AVAudioEngine ==> Le moteur audio permettant d’enregistrer ce que l’utilisateur dira via le microphone de son téléphone ou de sa tablette
  • SFSpeechRecognizer ==> Le moteur de reconnaissance vocal qui communiquera avec les serveurs Apple
  • SFSpeechAudioBufferRecognitionRequest ==> Le buffer audio qui servira à stocker les données renvoyées par le moteur audio avant d’être envoyées chez Apple pour analyse

Un exemple de code étant toujours plus parlant qu’un long discours, voici ci-dessous le code associé à notre application de démonstration, qui initie l’enregistrement quand l’utilisateur tape sur un bouton, et met fin à l’enregistrement quand l’utilisateur relâche ce même bouton.

private AVAudioEngine AudioEngine;
private SFSpeechRecognizer SpeechRecognizer;
private SFSpeechAudioBufferRecognitionRequest LiveSpeechRequest;
private SFSpeechRecognitionTask RecognitionTask;
private string sentence;

partial void StartSpeechRecognition(UIButton sender)
{
  SpeechRecognizer = new SFSpeechRecognizer(NSLocale.FromLocaleIdentifier("fr"));
  if (!SpeechRecognizer.Available)
    return;

  AudioEngine = new AVAudioEngine();
  if (AudioEngine.InputNode == null)
    return;

  NSError audioError;
  LiveSpeechRequest = new SFSpeechAudioBufferRecognitionRequest();

  var recordingFormat = AudioEngine.InputNode.GetBusOutputFormat(0);
  AudioEngine.InputNode.InstallTapOnBus(0, 1024, recordingFormat, (AVAudioPcmBuffer buffer, AVAudioTime when) => LiveSpeechRequest.Append(buffer));
  AudioEngine.Prepare();
  AudioEngine.StartAndReturnError(out audioError);
  if (audioError != null)
    return;

  RecognitionTask = SpeechRecognizer.GetRecognitionTask(LiveSpeechRequest, (SFSpeechRecognitionResult result, NSError taskError) =>
  {
    if (taskError != null)
      return;

    if (!result.Final)
    {
      SpeechLabel.Text = result.BestTranscription.FormattedString;
      return;
    }
 
    sentence = result.BestTranscription.FormattedString;
    ParseSpeechRecognition();
  });
}

partial void StopSpeechRecognition(UIButton sender)
{
  AudioEngine?.Stop();
  AudioEngine?.InputNode?.RemoveTapOnBus(0);
  LiveSpeechRequest?.EndAudio();
}

Outre l’initialisation du moteur audio via la définition de paramètres sur la source d’enregistrement (AudioEngine.InputNode), la partie importante repose sur la fonction de “callback” passée à la méthode GetRecognitionTask, qui permet de récupérer la transcription du texte (result.BestTranscription.FormattedString) au fil de l’eau, et de savoir si la reconnaissance est terminée ou non (result.Final).

Une fois tous les éléments assemblés et en réalisant un peu de cosmétique autour de l’interface graphique, cela donne le résultat visible dans la vidéo ci-dessous :

Comme on peut le voir dans cette vidéo, avec juste quelques lignes de code, il est possible d’intégrer la reconnaissance vocale dans une application. Mais hormis si l’on souhaite juste permettre à l’utilisateur de remplir des zones de texte sans rien saisir au clavier, il serait plus intéressant de pouvoir analyser ce texte.

NSLinguisticTagger : Une classe méconnue mais très pratique

C’est à ce moment qu’une classe relativement méconnue surgit, bien qu’elle existe dans iOS depuis la version 5.0. Cette classe porte un nom relativement explicite : NSLinguisticTagger.

Celle classe est capable, à partir d’un texte plus ou moins long, de “tagger” celui-ci, c’est-à-dire d’identifier tous ses composants (noms, pronoms, verbes, adverbes, déterminants, etc…) mais également d’identifier certains éléments marquants (noms de lieux, noms de sociétés…). Malheureusement cette dernière possibilité ne fonctionne qu’en Anglais.

Différentes langues sont supportées par NSLinguisticTagger (Anglais, Français, Allemand…) afin de couvrir les langues les plus fréquemment rencontrées sur iOS.

Comment fonctionne tout ceci ? C’est relativement simple comme on peut le voir dans l’exemple de code ci-dessous :

private void ParseSpeechRecognition()
{
  var options = NSLinguisticTaggerOptions.OmitWhitespace | NSLinguisticTaggerOptions.OmitPunctuation | NSLinguisticTaggerOptions.JoinNames;
  var schemes = NSLinguisticTagger.GetAvailableTagSchemesForLanguage("fr");
  var tagger = new NSLinguisticTagger(schemes, options);
  tagger.AnalysisString = sentence;
  tagger.EnumerateTagsInRange(new NSRange(0, sentence.Length), NSLinguisticTag.SchemeLexicalClass, options, HandleNSLingusticEnumerator);
}

private void HandleNSLingusticEnumerator(NSString tag, NSRange tokenRange, NSRange sentenceRange, ref bool stop)
{
  var token = sentence.Substring((int)tokenRange.Location, (int)tokenRange.Length);
  Console.WriteLine($"{token} = {tag}");
}

A partir d’une liste d’options (ignorer les espaces, ignorer la ponctuation, etc…) et d’une liste de types d’éléments analysables pour une langue (appelées “schemes”), on énumère les “tags” contenus dans le texte à analyser (AnalysisString).

La méthode permettant d’énumérer les tags du texte, prend en paramètre une fonction de “callback” qui sera appelée pour chaque tag identifié.

Si on exécute le code précédent avec la phrase utilisée dans la vidéo de notre application de démonstration, nous obtenons le résultat ci-dessus dans la fenêtre de sortie de Xamarin Studio.

2016-11-27 09:02:57.714 SpeechDemo[919:415497] C' = Pronoun
2016-11-27 09:02:57.714 SpeechDemo[919:415497] est = Verb
2016-11-27 09:02:57.714 SpeechDemo[919:415497] bientôt = Adverb
2016-11-27 09:02:57.715 SpeechDemo[919:415497] Noël = Noun
2016-11-27 09:02:57.715 SpeechDemo[919:415497] j' = Pronoun
2016-11-27 09:02:57.715 SpeechDemo[919:415497] espère = Verb
2016-11-27 09:02:57.716 SpeechDemo[919:415497] que = Conjunction
2016-11-27 09:02:57.716 SpeechDemo[919:415497] vous = Pronoun
2016-11-27 09:02:57.716 SpeechDemo[919:415497] passerez = Verb
2016-11-27 09:02:57.716 SpeechDemo[919:415497] tous = Adjective
2016-11-27 09:02:57.717 SpeechDemo[919:415497] de = Preposition
2016-11-27 09:02:57.717 SpeechDemo[919:415497] bonnes = Adjective
2016-11-27 09:02:57.717 SpeechDemo[919:415497] fêtes = Noun

Comme on le voit ci-dessus, à partir des informations renvoyées par NSLinguisticTagger, il est possible d’envisager les traitements nécessaires à la logique propre à une application (ex: extraire les noms et rechercher des valeurs particulières pour filtrer des résultats).

Pour aller encore plus loin…

Comme nous l’avons vu dans cet article, en couplant la reconnaissance vocale, désormais disponible dans iOS 10, avec un outil de taggage comme NSLinguisticTagger, de nouveaux horizons s’ouvrent pour intégrer des fonctionnalités inédites au sein des applications mobiles.

Toutefois si vous voulez aller encore plus, il existe d’autres éléments qu’il est toujours bon de connaitre, à commencer par la catégorisation de contenus.

Si vous réalisez des applications pour OSX, Apple a mis à disposition un framework appelé Latent Semantic Mapping (LSM) qui permet de catégoriser un contenu (ex: une page web, un fichier…) à partir d’une base d’autres contenus. Une session dédiée à ce framework avait été présentée lors de la WWDC 2011 si vous souhaitez en savoir plus à ce sujet.

Si vous voulez faire une analyse plus poussée du texte renvoyé par la reconnaissance vocale (ex: extraire des entités, identifier le sujet de fond d’un document, etc…) il existe différentes APIs basées sur le Machine Learning qui satisferont tous vos désirs.

On pourra citer comme exemple un projet sur lequel mise beaucoup Microsoft, à savoir Cognitive Services et plus particulièrement la brique Language Understanding Intelligent Service (LUIS). Des alternatives telles que TextRazor méritent également le coup d’oeil.

Editer un XIB avec Xcode 8 peut avoir des conséquences inattendues sur le rendu de votre application Xamarin

Apple a rendu disponible la version finale de Xcode 8 courant Septembre et comme beaucoup de développeurs, vous avez probablement effectué la mise à jour si vous voulez pouvoir tirer parti des nouveautés d’iOS 10.

Ayant déjà essuyer les plâtres de ce type de mise à jour par le passé, notamment lors des différentes montées de version de Swift (de 1.0 à 3.0), je laisse désormais passer un peu de temps pour voir si d’autres personnes rencontrent des soucis ou non, même si j’avais déjà testé de manière superficielle cette version sur une 2ème machine de développement à sa sortie.

J’ai donc appliqué le même principe avec cette dernière mise à jour, et j’ai effectué des tests un plus poussés cette semaine pour voir si une application Xamarin.iOS sur laquelle travaille mon équipe, fonctionnait toujours correctement une fois la mise à jour effectuée.

Une interface graphique relativement simple basée sur des XIB

Quand on réalise une application iOS, on peut réaliser l’interface graphique sous 3 formes, en pouvant combiner celles-ci si nécessaire :

  • Par code => Il faut être motivé si les écrans sont complexes
  • A base de XIB => La solution historique utilisée sur iOS depuis ses débuts
  • A base de storyboards => La solution désormais préconisée par Apple

Nous ne débattrons pas ici des avantages et inconvénients de chaque solution et pour l’application sur laquelle nous travaillons, nous avons préféré retenir les XIB avec également la solution par code sur certains composants.

Ci-dessous, vous trouverez l’exemple d’un écran de l’application réalisé par XIB sous Xcode 7, avec l’utilisation de contraintes pour gérer facilement les différentes tailles/résolutions de périphériques. Cet écran contient également un composant entièrement réalisé par code (le calendrier sur l’onglet “Mois”).

Xcode8 - Application avant édition #1  Xcode8 - Application avant édition #2

Ouverture du XIB sous Xcode 8 sans apporter aucune modification…

Après avoir mis à jour les différents outils (Xamarin Studio, Xcode, etc…) sur une machine de développement, j’ai tout simplement ouvert le XIB de cet écran depuis Xcode 8 afin de pouvoir tirer parti des nouveautés de l’environnement Apple (ex: la nouvelle prévisualisation sur différents périphériques).

A l’ouverture du XIB, Xcode 8 fait un certain nombre de modifications dont vous n’avez pas nécessairement conscience, et enregistre automatiquement celles-ci. Le mécanisme de synchronisation entre Xamarin Studio et Xcode fait en sorte que ces modifications soient immédiatement répercutées côté Xamarin à la prochaine compilation de l’application.

… et là c’est le drame une fois l’application compilée et exécutée

Après être retourné sous Xamarin Studio et avoir compiler et exécuter l’application, je retourne sur l’écran en question et là surprise, celui-ci ne fonctionne plus comme auparavant.

Les différences sont subtiles (disparition du liseré rouge sous l’onglet courant, disparition de certaines lignes dans le contrôle calendrier…). Vous pouvez voir ci-dessous le résultat :

Xcode8 - Application après édition #1 Xcode8 - Application après édition #2

Mais que se passe-t-il vu que nous avons juste ouvert le XIB ?

Comme je le disais précédemment, lors de l’ouverture du XIB, Xcode 8 fait quelques modifications sans vous prévenir dans la structure du fichier, dont vous pouvez voir un extrait ci-dessous grâce au client Git que nous utilisons pour nos projets (Atlasssian SourceTree).

Xcode8 - Différences dans le fichier XIB

Comme on peut le voir, certaines lignes ont été ajoutées et/ou modifiées (en vert) et d’autres lignes ont purement et simplement été supprimées (en rouge).

Parmi les lignes supprimées, on retrouve tout ce qui concerne la définition du positionnement par défaut des éléments graphiques dans la vue (les lignes commençant par <rect key=”frame” x=”…” y=”…” width=”…” height=”…” />.

Vous vous demandez peut être pourquoi un tel impact sur le rendu de l’écran ?

Et bien parce que dans celui-ci, certains éléments sont construits par code (le liseré rouge ou le contrôle calendrier) avec du code ressemblant à quelque chose comme ceci :

public override void ViewDidLoad()
{
   base.ViewDidLoad();
   ...

   _selectedBottomLayer = new CALayer();
   _selectedBottomLayer.Frame = new CGRect(0, DayButton.Bounds.Size.Height - SELECTED_LAYER_THICKNESS, DayButton.Bounds.Width, SELECTED_LAYER_THICKNESS);
   View.Layer.AddSublayer(_selectedBottomLayer);

   ...
}

Rien que des choses assez classiques pour les personnes qui font de l’iOS, mais qui ne fonctionnent plus comme auparavant à cause de la modification opérée par Xcode 8.

La suppression de la position par défaut a un impact dans le cycle de vie de construction d’une vue

Le fait que Xcode 8 aie supprimé le positionnement par défaut des éléments n’a pas d’impact sur les éléments positionnés par contraintes, mais pour les éléments positionnés par code et sans contraintes (les contraintes ne peuvent pas toujours être utilisées), cette modification a un impact très important.

Dans l’exemple précédent, le positionnement du liseré rouge est calculé dans la méthode ViewDidLoad. Le problème étant qu’au moment où cette méthode est exécutée dans le cycle de vie iOS, les contraintes appliquées sur les composants graphiques n’ont pas encore été calculées.

Là où au préalable la bouton sur lequel étaient basés les calculs avait un cadre défini comme ceci (x=0, y=0, width=106.5, height=45), il n’a désormais plus de positionnement par défaut ce qui fait qu’au moment ou la méthode ViewDidLoad s’exécute, le bouton a les caractéristiques suivantes (x=0, y=0, width=1000, height=1000).

Donc le liseré rouge est bien construit mais avec des coordonnées totalement farfelues qui le positionnent en dehors de la zone visible de l’écran. Le problème est le même pour les lignes qui ont disparues du contrôle calendrier.

Quelle est la solution pour résoudre les soucis dans ce cas ?

La solution est relativement simplement à mettre en oeuvre mais peut nécessiter de revoir complètement le cycle de vie qui a été utilisé dans les différents écrans.

Puisque le calcul de positionnement ne peut être effectué correctement au moment du ViewDidLoad, il suffit de le faire à un moment plus opportun.

Il n’y a pas une réponse qui pourra répondre à tous les cas possibles que vous pourriez rencontrer mais iOS propose différentes méthodes à surcharger dans lesquelles vous pouvez effectuer les traitements nécessaires à vos besoins.

Parmi les méthodes classiques que l’on a l’habitude d’utiliser pour effectuer les traitements nécessaires à des calculs de positionnement, on retrouve celles-ci (liste non exhaustive) :

public override void UpdateViewConstraints()
{
}

public override void ViewDidAppear(bool animated)
{
}

public override void ViewDidLayoutSubviews()
{
}

public override void ViewWillAppear(bool animated)
{
}

public override void ViewWillLayoutSubviews()
{
}

En conclusion, même si dans la plupart des cas la montée de version de Xcode n’aura pas d’impact sur vos applications, il peut arriver que dans certains cas cela ne soit pas si transparent que cela.

Que Xcode 8 supprime des données du XIB est une chose qui parait assez logique, certainement pour privilégier le système de contraintes créé par Apple, mais le fait que cela change le comportement et l’affichage d’une application qui fonctionnait sans soucis jusque là, est plus dérangeant sur le fond.

On ne répètera jamais assez qu’avant de faire une montée de version quelle qu’elle soit (outils de développement, packages NuGet…), la mise en place de tests approfondis (unitaires, graphiques, etc…) pour vérifier tous les impacts est un élément crucial pour la qualité de vos applications.

Introducing the new HttpClient implementation in Xamarin.iOS

In the upcoming days/weeks, Xamarin will release a whole set of new versions for its products to develop cross-platform mobile applications.

One of the most important (from my perspective) is Xamarin Studio 6.0 which will include lots of new features. If you want more information about those features, you can find a complete list on this page.

Today, I would like to talk about one of them which allows you to choose which implementation you want to use for network communications for your application iOS.

When you want to write cross-platform mobile applications, you avoid to use platform-specific APIs such as NSURLSession in iOS. Instead, you will prefer to use APIs that are available on all platforms such as the HttpClient class available in the .NET framework.

While this solution has many benefits for reusing your code on all platforms, the performance is worse than using specific API to the platform.

Fortunately, Xamarin Studio 6.0 (available on the Beta channel at this time) will introduce an option to use the underlying native APIs (NSURLSession, CFNetwork…) above HttpClient.

To activate this option, you just have to edit your Xamarin.iOS project settings and to go to the iOS Build section. On the screenshot below, you can see that 2 new options are available :

  • HttpClient Implementation
  • SSL / TLS Implementation

By default, your project will use the .NET implementation but you can switch to native APIs.

Xamarin Studio 6.0 - Choose your HttpClient implementation

There are many choices available and if you want more details, leave your cursor on the “blue I” on the right side of the selection lists. Below, the details about choices available for the HttpClient implementation.

Xamarin Studio 6.0 - HttpClient implementation tooltip

As mentioned in this tooltip, the usage of native APIs will result of better performance, support for latest standards (e.g. TLS 1.2) and smaller executable size.

An SSL error has occurred when trying to use the Outlook REST API on iOS 9 or OSX 10.11 (El Capitan)

In a previous article, we talked about an issue when you try to stream an Office 365 Videos from Azure Media Services in your application running on iOS 9.

It seems that Microsoft is actually doing some modifications on the infrastructure of the Office 365 platform, especially around the SSL certificates, because today I’m facing the same error when using the Outlook REST API.

I’m trying to call the following URL which should return the last 10 messages stored in the inbox  of the authenticated user :

https://outlook.office365.com/api/v2.0/Me/MailFolders('Inbox')/Messages

But I get the following error message, which indicates there’s an issue with SSL certificates :

NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9802)

Error Domain=NSURLErrorDomain Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made."
UserInfo={NSLocalizedDescription=An SSL error has occurred and a secure connection to the server cannot be made., NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, _kCFStreamErrorDomainKey=3, NSUnderlyingError=0x7d96cd90 {Error Domain=kCFErrorDomainCFNetwork Code=-1200 "(null)" UserInfo={_kCFStreamPropertySSLClientCertificateState=0, _kCFNetworkCFStreamSSLErrorOriginalValue=-9802, _kCFStreamErrorCodeKey=-9802, _kCFStreamErrorDomainKey=3, kCFStreamPropertySSLPeerTrust=<SecTrustRef: 0x7afb9d50>, kCFStreamPropertySSLPeerCertificates=<CFArray 0x7ae63490 [0xa97098]>{type = immutable, count = 2, values = (
 0 : <cert(0x7afb8570) s: outlook.com i: Microsoft IT SSL SHA1>
 1 : <cert(0x7afb96d0) s: Microsoft IT SSL SHA1 i: Baltimore CyberTrust Root>
)}}}, _kCFStreamErrorCodeKey=-9802, NSErrorFailingURLStringKey=https://outlook.office365.com/api/v2.0/Me/MailFolders('Inbox')/Messages, NSErrorPeerCertificateChainKey=<CFArray 0x7ae63490 [0xa97098]>{type = immutable, count = 2, values = (
 0 : <cert(0x7afb8570) s: outlook.com i: Microsoft IT SSL SHA1>
 1 : <cert(0x7afb96d0) s: Microsoft IT SSL SHA1 i: Baltimore CyberTrust Root>
)}, NSErrorClientCertificateStateKey=0, NSURLErrorFailingURLPeerTrustErrorKey=<SecTrustRef: 0x7afb9d50>, NSErrorFailingURLKey=https://outlook.office365.com/api/v2.0/Me/MailFolders('Inbox')/Messages}

The cause of this error is still the same as explained in our previous article : App Transport Security (ATS).

To solve the issue, you need to add some lines in your Info.plist to configure ATS to ignore the SSL error.

<key>NSAppTransportSecurity</key>
<dict>
  <key>NSExceptionDomains<key>
  <dict>
    <key>outlook.office365.com</key>
    <dict>
      <key>NSExceptionRequiresForwardSecrecy</key>
      <false/>
    </dict>
  </dict>
</dict>

You can also edit this file using the editor in Xcode rather than manually edit the XML file :

Office365 Outlook REST API - App Transport Security

NSURLSession/NSURLConnection HTTP load failed when trying to stream an Office 365 Video on iOS 9

Yesterday, I was playing with Office 365 Video and I wanted to be able to play/stream videos from Office 365 into an iOS app.

The first step to be able to read a video was to retrieve the streaming URL of that video in Azure Media Services (which is used behind Office 365 to offer streaming capabilities).

It’s pretty simple using the Office 365 REST API :

https://tenant.sharepoint.com/portals/hub/_api/VideoService/Channels(guid'01234567-abcd-cded-1234-1234567890ab')/Videos(guid'01234567-abcd-cded-1234-1234567890ab')/GetPlaybackUrl(0)

When you execute that request, you retrieve the URL to play/stream the video from Azure Media Services in HLS format (HTTP Live Streaming). This URL should look something like :

https://cvprdb302v.cloudvideo.azure.net/api/ManifestProxy?playbackUrl=https://cdn-cvprdb302m01.streaming.mediaservices.windows.net/.../Manifest(format=m3u8-aapl)&token=...

To play/stream the video into an iOS app, you can use the built-in video player (AVPlayer) which is compatible with HLS format. To do that, you just have to write few lines of code :

let URL = NSURL(string: "...")  // Put here the URL retrieved from GetPlaybackUrl
let playerViewController = AVPlayerViewController()
playerViewController.player = AVPlayer(URL: URL!)
presentViewController(playerViewController, animated: true, completion: nil)
playerViewController.player?.play()

If you run this code on iOS 8, there’s no problem and everything should work fine. But if you run the same code on iOS 9, it should throw an exception like that :

NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9802)

You have to wonder why the same code don’t work on the last version of iOS. The answer is quite simple : App Transport Security (ATS).

In iOS 9, there are new security mechanisms that have been implemented to ensure that, when an app uses network communications, the traffic is encrypted to ensure confidentiality.

But if you look the URL of the video, it uses HTTPS so the traffic is encrypted. Then why an exception was thrown ? It seems that the ciphers used to encrypt the traffic are considered as unsafe by iOS 9.

What to do to be able to play/stream the video in that case ? It’s possible to configure how ATS works by modifying the info.plist file of your app.

There are many ways to do that and the most easiest is to add the following key/values :

<key>NSAppTransportSecurity</key>
<dict>
  <key>NSAllowsArbitraryLoads</key>
  <true/>
</dict>

For in-house apps or development purposes, it’s very useful but not secured because ATS is completely disabled. If you read Apple’s documentation, it also could be a reason to reject your app if you want to publish it on the AppStore.

If you want to manage more precisely which URLs can be used without problems with ATS, you have to add exception domains in your info.plist as you can see above :

<key>NSAppTransportSecurity</key>
<dict>
  <key>NSExceptionDomains</key>
  <dict>
    <key>mediaservices.windows.net</key>
    <dict>
      <key>NSIncludesSubdomains</key>
      <true/>
      <key>NSExceptionRequiresForwardSecrecy</key>
      <false/>
    </dict>
    <key>cloudvideo.azure.net</key>
    <dict>
      <key>NSIncludesSubdomains</key>
      <true/>
      <key>NSExceptionRequiresForwardSecrecy</key>
      <false/>
 </dict>
</dict>

With these settings, we configure ATS to allow more ciphers to encrypt HTTPS traffic, for two domains (and all sub-domains) used by Azure Media Services to stream videos.

If you prefer to use the editor available in Xcode to edit your info.plist, rather than manually editing the XML file, you just have to add key/values like :

Office365 Video - App Transport Security

Now if we execute our app, we are able to play/stream the video without any exceptions. 😉