Add ad hoc WiFi communication function

Change-Id: I6ec986708bfa2dcdf1cc2ed121f249fabd99ac86
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index d2b0a45..6e87640 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -1,6 +1,15 @@
 Release Notes
 =============
 
+## Version 0.2.4
+
+- Introduce function to quickly enable local NDN communication using Ad Hoc WiFi
+
+- Fix a small bug in `ndn` command-line tool
+
+- The bundled NDN stack binaries moved to `NDN.app/Contents/Helpers` folder to
+  respect macOS code signing conventions.
+
 ## Version 0.2.3
 
 - Correct names of tray menu items
diff --git a/make-osx-bundle.py b/make-osx-bundle.py
index dfbbbe4..db61242 100755
--- a/make-osx-bundle.py
+++ b/make-osx-bundle.py
@@ -370,6 +370,7 @@
   parser.add_option('-r', '--release', dest='release', help='Build a release. This determines the version number of the release.')
   parser.add_option('-s', '--snapshot', dest='snapshot', help='Build a snapshot release. This determines the \'snapshot version\'.')
   parser.add_option('-g', '--git', dest='git', help='Build a snapshot release. Use the git revision number as the \'snapshot version\'.', action='store_true', default=False)
+  parser.add_option('--no-dmg', dest='no_dmg', action='store_true', default=False, help='''Disable creation of DMG''')
   parser.add_option('--codesign', dest='codesign', help='Identity to use for code signing. (If not set, no code signing will occur)')
 
   options, args = parser.parse_args()
@@ -405,17 +406,18 @@
     codesign(a.bundle)
     print ''
 
-  # Create diskimage
-  title = "NDN-%s" % ver
-  fn = "build/%s.dmg" % title
-  d = DiskImage(fn, title)
-  d.symlink('/Applications', '/Applications')
-  d.copy('build/NDN.app', '/NDN.app')
-  d.create()
+  if not options.no_dmg:
+    # Create diskimage
+    title = "NDN-%s" % ver
+    fn = "build/%s.dmg" % title
+    d = DiskImage(fn, title)
+    d.symlink('/Applications', '/Applications')
+    d.copy('build/NDN.app', '/NDN.app')
+    d.create()
 
-  if options.codesign:
-    print ' * Signing .dmg with identity `%s\'' % options.codesign
-    codesign(fn)
-    print ''
+    if options.codesign:
+      print ' * Signing .dmg with identity `%s\'' % options.codesign
+      codesign(fn)
+      print ''
 
 Popen('tail -n +3 RELEASE_NOTES.md | pandoc -f markdown -t html > build/release-notes-%s.html' % ver, shell=True).wait()
diff --git a/ndn-control-center.xml b/ndn-control-center.xml
index 2436d76..ca26bf1 100644
--- a/ndn-control-center.xml
+++ b/ndn-control-center.xml
@@ -34,5 +34,11 @@
 <ns0:minimumSystemVersion>10.12.0</ns0:minimumSystemVersion>
 <enclosure length="27659751" type="application/octet-stream" url="https://named-data.net/binaries/NDN-Control-Center/NDN-0.2.3.dmg" ns0:dsaSignature="MC4CFQCLben2hhIZr01C9qo84yBs9fQaWwIVAJ0Amf0kXPDHqxD/Ce5AC1awzpgZ" ns0:shortVersionString="0.2.3" ns0:version="0.2.3" />
 <ns0:releaseNotesLink>https://named-data.net/binaries/NDN-Control-Center/release-notes-0.2.3.html</ns0:releaseNotesLink></item>
+<item>
+<title>Version 0.2.4 (ndn-cxx version 0.5.1-34-g1709aa7, NFD version 0.5.1-38-gd396b61, ndn-tools version 0.4)</title>
+<pubDate>Sat, 18 Mar 2017 19:05:28 -0700</pubDate>
+<ns0:minimumSystemVersion>10.12.0</ns0:minimumSystemVersion>
+<enclosure length="27751651" type="application/octet-stream" url="https://named-data.net/binaries/NDN-Control-Center/NDN-0.2.4.dmg" ns0:dsaSignature="MC0CFFu7Gz4ue3ESbVfA3/5ak1BsDsevAhUAlJO7SdLhWK8ZtW2SvoA8yomDwIg=" ns0:shortVersionString="0.2.4" ns0:version="0.2.4" />
+<ns0:releaseNotesLink>https://named-data.net/binaries/NDN-Control-Center/release-notes-0.2.4.html</ns0:releaseNotesLink></item>
 </channel>
-</rss>
\ No newline at end of file
+</rss>
diff --git a/res/icon-connected-adhoc-black.png b/res/icon-connected-adhoc-black.png
new file mode 100644
index 0000000..3251fd9
--- /dev/null
+++ b/res/icon-connected-adhoc-black.png
Binary files differ
diff --git a/src/main.cpp b/src/main.cpp
index 1dcee21..b3144c7 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -40,7 +40,6 @@
 public:
   Ncc()
     : m_isActive(true)
-    , m_localhopFibEntry(Name("/localhop/nfd"))
     , m_face(nullptr, m_keyChain)
     , m_controller(m_face, m_keyChain)
     , m_scheduler(m_face.getIoService())
@@ -103,12 +102,17 @@
   onFibStatusRetrieved(const std::vector<nfd::FibEntry>& status)
   {
     bool isConnectedToHub = false;
+    bool isConnectedToAdhoc = false;
     for (auto const& fibEntry : status) {
       if (fibEntry.getPrefix() == m_localhopFibEntry) {
         isConnectedToHub = true;
       }
+      else if (fibEntry.getPrefix() == m_adhocFibEntry) {
+        isConnectedToAdhoc = true;
+      }
     }
     emit m_tray.connectivityUpdate(isConnectedToHub);
+    emit m_tray.adhocUpdate(isConnectedToAdhoc);
   }
 
   void
@@ -141,7 +145,8 @@
 private:
   volatile bool m_isActive;
   boost::thread m_nfdThread;
-  const Name m_localhopFibEntry;
+  const Name m_localhopFibEntry = "/localhop/nfd";
+  const Name m_adhocFibEntry = "/adhoc";
 
   KeyChain m_keyChain;
   Face m_face;
diff --git a/src/osx-adhoc.hpp b/src/osx-adhoc.hpp
new file mode 100644
index 0000000..10019dd
--- /dev/null
+++ b/src/osx-adhoc.hpp
@@ -0,0 +1,70 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2017, Regents of the University of California,
+ *
+ * This file is part of NFD Control Center.  See AUTHORS.md for complete list of NFD
+ * authors and contributors.
+ *
+ * NFD Control Center is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD Control Center is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with NFD
+ * Control Center, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NCC_OSX_ADHOC_HPP
+#define NCC_OSX_ADHOC_HPP
+
+#include "config.hpp"
+
+#include <ndn-cxx/face.hpp>
+#include <ndn-cxx/mgmt/nfd/controller.hpp>
+#include <ndn-cxx/mgmt/nfd/face-monitor.hpp>
+
+#ifdef OSX_BUILD
+#define ADHOC_SUPPORTED 1
+#endif // OSX_BUILD
+
+#ifdef ADHOC_SUPPORTED
+
+namespace ndn {
+namespace ncc {
+
+class Adhoc
+{
+public:
+  Adhoc(Face& face, KeyChain& keychain);
+
+  ~Adhoc();
+
+  bool
+  createAdhoc();
+
+  void
+  onNotification(const ndn::nfd::FaceEventNotification& notification);
+
+  void
+  destroyAdhoc();
+
+private:
+  void
+  unregisterRoutes(uint16_t origin);
+
+private:
+  Face& m_face;
+  KeyChain& m_keyChain;
+  nfd::Controller* m_controller;
+  nfd::FaceMonitor* m_faceMonitor;
+};
+
+} // namespace ncc
+} // namespace ndn
+
+#endif // ADHOC_SUPPORTED
+
+#endif // NCC_OSX_ADHOC_HPP
diff --git a/src/osx-adhoc.mm b/src/osx-adhoc.mm
new file mode 100644
index 0000000..fcaf146
--- /dev/null
+++ b/src/osx-adhoc.mm
@@ -0,0 +1,142 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2017, Regents of the University of California,
+ *
+ * This file is part of NFD Control Center.  See AUTHORS.md for complete list of NFD
+ * authors and contributors.
+ *
+ * NFD Control Center is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD Control Center is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with NFD
+ * Control Center, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "osx-adhoc.hpp"
+
+#include <ndn-cxx/name.hpp>
+#include <ndn-cxx/mgmt/nfd/control-parameters.hpp>
+
+#ifdef OSX_BUILD
+
+#import <CoreWLAN/CoreWLAN.h>
+#import <CoreWLAN/CoreWLANConstants.h>
+#import <CoreWLAN/CWInterface.h>
+#import <CoreWLAN/CWWiFiClient.h>
+#import <CoreWLAN/CoreWLANTypes.h>
+
+namespace ndn {
+namespace ncc {
+
+const std::string adhocUri = "udp4://224.0.23.170:56363";
+const std::set<Name> adhocPrefixes = {"/", "/adhoc"};
+const NSUInteger g_channel = 11;
+const uint16_t ROUTE_ORIGIN_NCC = 67;
+
+Adhoc::Adhoc(Face& face, KeyChain& keychain)
+  : m_face(face)
+  , m_keyChain(keychain)
+  , m_controller(new nfd::Controller(m_face, m_keyChain))
+  , m_faceMonitor(new nfd::FaceMonitor(m_face))
+{
+  m_faceMonitor->onNotification.connect(bind(&Adhoc::onNotification, this, _1));
+}
+
+Adhoc::~Adhoc()
+{
+  delete m_faceMonitor;
+  delete m_controller;
+}
+
+bool
+Adhoc::createAdhoc()
+{
+  m_faceMonitor->start();
+
+  NSString* networkName =
+    [[NSString alloc] initWithCString:"NDNdirect" encoding:NSASCIIStringEncoding];
+  NSString* passphrase =
+    [[NSString alloc] initWithCString:"NDNhello" encoding:NSASCIIStringEncoding];
+  NSArray* airportInterfaces = [CWWiFiClient interfaceNames];
+  NSString* interfaceName = [airportInterfaces objectAtIndex:0];
+  CWWiFiClient* wifiInterfaces = [[CWWiFiClient alloc] init];
+  CWInterface* airport = [wifiInterfaces interfaceWithName:interfaceName];
+
+  NSError* error = nil;
+  NSData* data = [networkName dataUsingEncoding:NSUTF8StringEncoding];
+  BOOL created = [airport startIBSSModeWithSSID:data
+                                       security:kCWIBSSModeSecurityNone
+                                        channel:g_channel
+                                       password:passphrase
+                                          error:&error];
+
+  if (!created) {
+    return false;
+  }
+  else {
+    unregisterRoutes(ndn::nfd::ROUTE_ORIGIN_AUTOCONF);
+    return true;
+  }
+}
+
+void
+Adhoc::destroyAdhoc()
+{
+  unregisterRoutes(ROUTE_ORIGIN_NCC);
+  m_faceMonitor->stop();
+
+  NSArray* airportInterfaces = [CWWiFiClient interfaceNames];
+  NSString* interfaceName = [airportInterfaces objectAtIndex:0];
+  CWWiFiClient* wifiInterfaces = [[CWWiFiClient alloc] init];
+  CWInterface* airport = [wifiInterfaces interfaceWithName:interfaceName];
+  [airport disassociate];
+
+  NSError* err = nil;
+
+  [airport setPower:NO error:&err];
+  [NSThread sleepForTimeInterval: 3.0];
+  [airport setPower:YES error:&err];
+}
+
+void
+Adhoc::onNotification(const ndn::nfd::FaceEventNotification& notification)
+{
+  if (notification.getKind() == ndn::nfd::FACE_EVENT_CREATED &&
+      notification.getRemoteUri() == adhocUri &&
+      notification.getLinkType() == ndn::nfd::LINK_TYPE_MULTI_ACCESS) {
+    for (const auto& prefix : adhocPrefixes) {
+      ndn::nfd::ControlParameters params;
+      params
+        .setName(prefix)
+        .setFaceId(notification.getFaceId())
+        .setOrigin(ROUTE_ORIGIN_NCC);
+      m_controller->start<ndn::nfd::RibRegisterCommand>(params, nullptr, nullptr);
+    }
+  }
+}
+
+void
+Adhoc::unregisterRoutes(uint16_t origin)
+{
+  m_controller->fetch<ndn::nfd::RibDataset>([this, origin] (const std::vector<nfd::RibEntry>& ribs) {
+      for (const auto& rib : ribs) {
+        for (const auto& route : rib.getRoutes()) {
+          if (route.getOrigin() == origin) {
+            ndn::nfd::ControlParameters params;
+            params.setFaceId(route.getFaceId());
+            m_controller->start<ndn::nfd::FaceDestroyCommand>(params, nullptr, nullptr);
+          }
+        }
+      }
+    }, nullptr);
+}
+
+} // namespace ncc
+} // namespace ndn
+
+#endif // OSX_BUILD
diff --git a/src/qml.qrc b/src/qml.qrc
index a64901f..229d0fe 100644
--- a/src/qml.qrc
+++ b/src/qml.qrc
@@ -11,5 +11,6 @@
         <file>../res/icon-connected-black.png</file>
         <file>../res/icon-disconnected-black.png</file>
         <file>../res/icon-connected-status-black.png</file>
+        <file>../res/icon-connected-adhoc-black.png</file>
     </qresource>
 </RCC>
diff --git a/src/tray-menu.cpp b/src/tray-menu.cpp
index de90134..cf3b5b8 100644
--- a/src/tray-menu.cpp
+++ b/src/tray-menu.cpp
@@ -31,6 +31,7 @@
 #define CONNECT_ICON ":/res/icon-connected-black.png"
 #define DISCONNECT_ICON ":/res/icon-disconnected-black.png"
 #define CONNECT_STATUS_ICON ":/res/icon-connected-status-black.png"
+#define CONNECT_ADHOC_ICON ":/res/icon-connected-adhoc-black.png"
 
 #include <Security/Authorization.h>
 #include <Security/AuthorizationTags.h>
@@ -62,6 +63,7 @@
   : m_context(context)
   , m_isNfdRunning(false)
   , m_isConnectedToHub(false)
+  , m_isConnectedToAdhoc(false)
   , m_menu(new QMenu(this))
   , m_entryPref(new QAction("Preferences...", m_menu))
   , m_entryStatus(new QAction("Status...", m_menu))
@@ -70,6 +72,7 @@
   , m_settings(new QSettings())
 #ifdef OSX_BUILD
   , m_entryEnableCli(new QAction("Install command-line tools...", m_menu))
+  , m_entryAdhoc(new QAction("Enable ad hoc Wi-Fi...", m_menu))
   , m_checkForUpdates(new QAction("Check for updates", m_menu))
   , m_sparkle(NCC_APPCAST)
 #endif
@@ -77,6 +80,7 @@
   , m_keyViewerDialog(new ncc::KeyViewerDialog)
   , m_face(face)
   , m_keyChain(keyChain)
+  , m_adhoc(m_face, m_keyChain)
   , m_statusViewer(new StatusViewer(m_face, m_keyChain))
 {
   connect(m_entryPref, SIGNAL(triggered()), this, SIGNAL(showApp()));
@@ -84,10 +88,9 @@
   connect(m_entrySec, SIGNAL(triggered()), m_keyViewerDialog, SLOT(present()));
   connect(m_entryQuit, SIGNAL(triggered()), this, SLOT(quitApp()));
 
-  connect(this, SIGNAL(nfdActivityUpdate(bool)), this, SLOT(updateNfdActivityIcon(bool)),
-          Qt::QueuedConnection);
-  connect(this, SIGNAL(connectivityUpdate(bool)), this, SLOT(updateConnectivity(bool)),
-          Qt::QueuedConnection);
+  connect(this, SIGNAL(nfdActivityUpdate(bool)), this, SLOT(updateNfdActivityIcon(bool)), Qt::QueuedConnection);
+  connect(this, SIGNAL(connectivityUpdate(bool)), this, SLOT(updateConnectivity(bool)), Qt::QueuedConnection);
+  connect(this, SIGNAL(adhocUpdate(bool)), this, SLOT(updateAdhoc(bool)), Qt::QueuedConnection);
 
   QString nccVersion = QString(NCC_VERSION) + " (ndn-cxx: " + NDN_CXX_VERSION_BUILD_STRING +
     ", NFD: " + NFD_VERSION_BUILD_STRING +
@@ -103,6 +106,11 @@
   m_menu->addAction(m_entryPref);
 
 #ifdef OSX_BUILD
+  m_entryAdhoc->setChecked(false);
+  m_entryAdhoc->setCheckable(true);
+  connect(m_entryAdhoc, SIGNAL(triggered()), this, SLOT(onAdhocChange()));
+  m_menu->addAction(m_entryAdhoc);
+
   connect(m_entryEnableCli, SIGNAL(triggered()), this, SLOT(enableCli()));
   m_menu->addAction(m_entryEnableCli);
 
@@ -314,6 +322,45 @@
   m_settings->setValue("ENABLE_SHUTDOWN", isEnabled);
 }
 
+///////////////////////////////////////////////////////////////////////////////
+// Ad hoc WiFi communication
+
+void
+TrayMenu::onAdhocChange()
+{
+#ifdef ADHOC_SUPPORTED
+  if (m_entryAdhoc->isChecked()) {
+    QMessageBox* msgBox = new QMessageBox();
+    msgBox->setText("WARNING: Wi-Fi will be disconnected!");
+    msgBox->setIcon(QMessageBox::Warning);
+    msgBox->setInformativeText(
+      "Are you sure that you are OK with that?");
+    msgBox->setStandardButtons(QMessageBox::Yes | QMessageBox::No);
+    msgBox->setDefaultButton(QMessageBox::No);
+    int ret = msgBox->exec();
+
+    if (ret == QMessageBox::No) {
+      m_entryAdhoc->setChecked(false);
+    }
+    else {
+      stopNdnAutoConfig();
+      m_entryAdhoc->setText("Adhoc Wi-Fi is enabled");
+      m_adhoc.createAdhoc();
+      std::cerr << "ad hoc is on!" << std::endl;
+    }
+  }
+  else {
+    m_entryAdhoc->setText("Enable Adhoc Wi-Fi...");
+    m_adhoc.destroyAdhoc();
+    std::cerr << "ad hos is off!" << std::endl;
+    // a trick in DestroyAdhoc ensures that WiFi will be reconnected to a default WiFi
+    if (isNdnAutoConfigEnabled()) {
+      startNdnAutoConfig();
+    }
+  }
+#endif
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // Misc
 
@@ -361,13 +408,16 @@
   m_isNfdRunning = isActive;
 
   if (isActive) {
-    if(m_isConnectedToHub) {
+    if (m_isConnectedToHub) {
       m_tray->setIcon(QIcon(CONNECT_STATUS_ICON));
     }
+    else if (m_isConnectedToAdhoc) {
+      m_tray->setIcon(QIcon(CONNECT_ADHOC_ICON));
+    }
     else {
       m_tray->setIcon(QIcon(CONNECT_ICON));
     }
-    if (isNdnAutoConfigEnabled()) {
+    if (!m_entryAdhoc->isChecked() && isNdnAutoConfigEnabled()) {
       startNdnAutoConfig();
     }
   }
@@ -383,6 +433,12 @@
 }
 
 void
+TrayMenu::updateAdhoc(bool isConnectedToAdhoc)
+{
+  m_isConnectedToAdhoc = isConnectedToAdhoc;
+}
+
+void
 TrayMenu::enableCli()
 {
 #ifdef OSX_BUILD
diff --git a/src/tray-menu.hpp b/src/tray-menu.hpp
index 1159a1d..d71804b 100644
--- a/src/tray-menu.hpp
+++ b/src/tray-menu.hpp
@@ -33,6 +33,7 @@
 #include <QtWidgets/QSystemTrayIcon>
 #include <QtWidgets/QAction>
 #include <QtWidgets/QMenu>
+#include <QtWidgets/QMessageBox>
 
 #include <QtQml/QQmlContext>
 
@@ -41,8 +42,11 @@
 
 #ifdef OSX_BUILD
 #include "osx-auto-update-sparkle.hpp"
+#include "osx-adhoc.hpp"
 #endif // OSX_BUILD
 
+#include <thread>
+
 namespace ndn {
 
 class Face;
@@ -63,6 +67,9 @@
   void
   connectivityUpdate(bool isConnectedToHub);
 
+  void
+  adhocUpdate(bool isConnectedToAdhoc);
+
 public:
   explicit
   TrayMenu(QQmlContext* context, Face& face, KeyChain& keyChain);
@@ -125,6 +132,9 @@
   updateConnectivity(bool isConnectedToHub);
 
   void
+  updateAdhoc(bool isConnectedToAdhoc);
+
+  void
   enableCli();
 
   void
@@ -135,6 +145,9 @@
 
 #ifdef OSX_BUILD
   void
+  onAdhocChange();
+
+  void
   checkForUpdates();
 #endif // OSX_BUILD
 
@@ -145,6 +158,7 @@
   QQmlContext* m_context;
   bool m_isNfdRunning;
   bool m_isConnectedToHub;
+  bool m_isConnectedToAdhoc;
   QSystemTrayIcon* m_tray;
   QMenu* m_menu;
   QAction* m_entryPref;
@@ -155,6 +169,7 @@
   QString m_ndnAutoConfigMsg;
 #ifdef OSX_BUILD
   QAction* m_entryEnableCli;
+  QAction* m_entryAdhoc;
   QAction* m_checkForUpdates;
   OsxAutoUpdateSparkle m_sparkle;
 #endif // OSX_BUILD
@@ -164,6 +179,7 @@
   ncc::KeyViewerDialog* m_keyViewerDialog;
   Face& m_face;
   KeyChain& m_keyChain;
+  Adhoc m_adhoc;
   StatusViewer* m_statusViewer;
 };
 
diff --git a/wscript b/wscript
index 33f7684..18d8b78 100644
--- a/wscript
+++ b/wscript
@@ -1,5 +1,5 @@
 # -*- Mode: python; py-indent-offset: 4; indent-tabs-mode: nil; coding: utf-8; -*-
-VERSION='0.2.3'
+VERSION='0.2.4'
 APPNAME='ndn-control-center'
 APPCAST='https://named-data.net/binaries/NDN-Control-Center/ndn-control-center.xml'