diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2013, Stephen Blackheath
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Stephen Blackheath nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Network/Bluetooth.hs b/Network/Bluetooth.hs
new file mode 100644
--- /dev/null
+++ b/Network/Bluetooth.hs
@@ -0,0 +1,13 @@
+-- | Simple Bluetooth API for Windows and Linux (bluez).
+--
+-- You must use 'Network.withSocketsDo' at the start of your program
+-- for Windows compatibility.
+module Network.Bluetooth (
+        module Network.Bluetooth.Adapter,
+        module Network.Bluetooth.Device
+    ) where
+
+import Network.Bluetooth.Adapter
+import Network.Bluetooth.Device
+import qualified Network (withSocketsDo)
+
diff --git a/Network/Bluetooth/Adapter.hsc b/Network/Bluetooth/Adapter.hsc
new file mode 100644
--- /dev/null
+++ b/Network/Bluetooth/Adapter.hsc
@@ -0,0 +1,149 @@
+{-# LANGUAGE ForeignFunctionInterface, EmptyDataDecls #-}
+module Network.Bluetooth.Adapter (
+        allAdapters,
+        defaultAdapter,
+        discover,
+        module Network.Bluetooth.Types
+    ) where
+
+#if defined(mingw32_HOST_OS)
+#include <windows.h>
+#else
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#endif
+#include <stddef.h>
+
+import Network.Bluetooth.Device
+import Network.Bluetooth.Types
+#if defined(mingw32_HOST_OS)
+import Network.Bluetooth.Win32
+#endif
+import qualified Data.ByteString.Internal as BI
+
+import Control.Applicative
+import Control.Exception
+import Control.Monad
+import Data.Bits
+import Data.IORef
+import Data.Word
+import Foreign
+import Foreign.C
+#if defined(mingw32_HOST_OS)
+import System.Win32.Types
+#endif
+
+#if !defined(mingw32_HOST_OS)
+foreign import ccall safe "hci_for_each_dev" hci_for_each_dev
+    :: CInt -> FunPtr (CInt -> CInt -> CLong -> IO CInt) -> CLong -> IO ()
+
+foreign import ccall safe "wrapper" mkVisitDev
+    ::            (CInt -> CInt -> CLong -> IO CInt) ->
+       IO (FunPtr (CInt -> CInt -> CLong -> IO CInt))
+
+foreign import ccall safe "hci_open_dev" hci_open_dev
+    :: CInt -> IO CInt
+#endif
+
+#if !defined(mingw32_HOST_OS)
+openDev :: CInt -> IO Adapter
+openDev dev_id = do
+    ret <- hci_open_dev dev_id
+    if ret < 0 then do
+        errno@(Errno errno_) <- getErrno
+        if errno == eINTR
+            then openDev dev_id
+            else do
+                err <- peekCString (strerror errno_)
+                throwIO $ BluetoothException "openDev" err
+      else
+        pure $ Adapter dev_id ret
+#endif
+
+allAdapters :: IO [Adapter]
+#if defined(mingw32_HOST_OS)
+allAdapters = return [Adapter]
+#else
+allAdapters = do
+    devsRef <- newIORef []
+    cb <- mkVisitDev $ \_ dev_id _ -> do
+        modifyIORef devsRef (dev_id:)
+        pure 0
+    hci_for_each_dev (#const HCI_UP) cb 0
+      `finally`
+        freeHaskellFunPtr cb
+    dev_ids <- reverse <$> readIORef devsRef
+    mapM openDev dev_ids
+#endif
+
+#if !defined(mingw32_HOST_OS)
+foreign import ccall unsafe "hci_get_route" hci_get_route
+    :: Ptr BluetoothAddr -> IO CInt
+#endif
+
+defaultAdapter :: IO (Maybe Adapter)
+#if defined(mingw32_HOST_OS)
+defaultAdapter = return $ Just Adapter
+#else
+defaultAdapter = do
+    ret <- hci_get_route nullPtr
+    if ret < 0 then do
+        errno@(Errno errno_) <- getErrno
+        if errno == eINTR
+            then defaultAdapter
+          else if errno == eNODEV
+            then pure Nothing
+            else do
+                err <- peekCString (strerror errno_)
+                throwIO $ BluetoothException "defaultAdapter" err
+      else
+        Just <$> openDev ret
+#endif
+
+#if !defined(mingw32_HOST_OS)
+foreign import ccall safe "hci_inquiry" hci_inquiry
+    :: CInt -> CInt -> CInt -> Ptr Word8 -> Ptr (Ptr InquiryInfo) -> CLong -> IO CInt
+
+data InquiryInfo = InquiryInfo {
+        iiAddr :: BluetoothAddr
+    }
+    deriving Show
+
+instance Storable InquiryInfo where
+    sizeOf _ = (#const sizeof(inquiry_info))
+    alignment _ = alignment (undefined :: Word64)
+    peek p = InquiryInfo <$> peek (p `plusPtr` (#const offsetof(inquiry_info, bdaddr)))
+    poke _ _ = fail "InquiryInfo.poke not defined"
+#endif
+
+discover :: Adapter -> IO [Device]
+#if defined(mingw32_HOST_OS)
+discover a = map (Device a . fst) <$> discover' a flags
+  where
+    flags = (#const LUP_CONTAINERS) .|.
+            (#const LUP_RETURN_ADDR) .|.
+            (#const LUP_FLUSHCACHE)
+#else
+discover a@(Adapter dev_id _) = go 0  -- (#const IREQ_CACHE_FLUSH)
+  where
+    go flags = do
+        mDevices <- allocaArray 255 $ \ppDevs -> do
+            poke ppDevs nullPtr
+            n <- hci_inquiry dev_id 8 255 nullPtr ppDevs flags
+            if n < 0 then do
+                errno@(Errno errno_) <- getErrno
+                if errno == eINTR
+                    then pure Nothing
+                    else do
+                        err <- peekCString (strerror errno_)
+                        throwIO $ BluetoothException "discover" err
+              else do
+                pDevs <- peek ppDevs
+                iis <- peekArray (fromIntegral n) pDevs
+                free pDevs
+                pure $ Just $ map (Device a . iiAddr) iis
+        case mDevices of
+            Just devices -> pure devices
+            Nothing      -> go 0  -- eINTR ? Retry.
+#endif
+
diff --git a/Network/Bluetooth/Device.hsc b/Network/Bluetooth/Device.hsc
new file mode 100644
--- /dev/null
+++ b/Network/Bluetooth/Device.hsc
@@ -0,0 +1,187 @@
+{-# LANGUAGE ForeignFunctionInterface #-}
+module Network.Bluetooth.Device (
+        Device(..),
+        deviceName,
+        RFCOMMSocket,
+        openRFCOMM,
+        recvRFCOMM,
+        sendRFCOMM,
+        sendAllRFCOMM,
+        closeRFCOMM,
+        module Network.Bluetooth.Types
+    ) where
+
+#if defined(mingw32_HOST_OS)
+#include <windows.h>
+#else
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/rfcomm.h>
+#endif
+#include <stddef.h>
+
+import Network.Bluetooth.Types
+#if defined(mingw32_HOST_OS)
+import Network.Bluetooth.Win32
+#endif
+import Network.Socket
+import qualified Network.Socket.ByteString as NB
+
+import Control.Applicative
+import Control.Exception
+import Control.Monad
+import Data.ByteString (ByteString)
+import qualified Data.ByteString as B
+import qualified Data.ByteString.Internal as B
+import qualified Data.ByteString.Unsafe as B
+import Data.IORef
+import Data.List (lookup)
+import Data.Word
+import Foreign
+import Foreign.C
+import GHC.Conc (threadWaitWrite)
+#if defined(mingw32_HOST_OS)
+import System.Win32.Types
+#endif
+
+
+data Device = Device Adapter BluetoothAddr deriving (Eq, Ord, Show)
+
+foreign import ccall safe "hci_read_remote_name" hci_read_remote_name
+    :: CInt -> Ptr BluetoothAddr -> CInt -> Ptr CChar -> CInt -> IO CInt
+
+deviceName :: Device -> IO ByteString
+#if defined(mingw32_HOST_OS)
+deviceName dev@(Device a addr) = do
+    devs <- discover' a flags
+    case join $ lookup addr devs of
+        Just name -> return name
+        Nothing -> throwIO $ BluetoothException "deviceName" "device has no name"
+  where
+    flags = (#const LUP_CONTAINERS) .|.
+            (#const LUP_RETURN_ADDR) .|.
+            (#const LUP_RETURN_NAME)
+#else
+deviceName dev@(Device (Adapter _ dd) (BluetoothAddr bs)) = do
+    retRef <- newIORef 0
+    bs0 <- B.create maxLen $ \buf -> do
+        ret <- B.unsafeUseAsCString bs $ \cs ->
+            hci_read_remote_name dd (castPtr cs) (fromIntegral maxLen) (castPtr buf) 0
+        writeIORef retRef ret
+    ret <- readIORef retRef
+    if ret < 0 then do
+        errno@(Errno errno_) <- getErrno
+        if errno == eINTR
+            then deviceName dev
+            else do
+                err <- peekCString (strerror errno_)
+                throwIO $ BluetoothException "deviceName" err
+      else
+        return $ B.takeWhile (/= 0) bs0
+  where
+    maxLen = 255
+#endif
+
+data RFCOMMSocket = RFCOMMSocket Socket
+
+#if !defined(mingw32_HOST_OS)
+data SockAddrBTH = SockAddrBTH Word16 ByteString Word8
+
+sockAddrBTH :: BluetoothAddr -> Word8 -> SockAddrBTH
+sockAddrBTH (BluetoothAddr bs) port = SockAddrBTH (#const AF_BLUETOOTH) bs port
+
+instance Storable SockAddrBTH where
+    sizeOf _ = (#const sizeof(struct sockaddr_rc))
+    alignment _ = alignment (undefined :: Word64)
+    peek _ = fail "SockAddrBTH.peek not defined"
+    poke p (SockAddrBTH family bdaddr channel) = do
+        let p_family = p `plusPtr` (#const offsetof(struct sockaddr_rc, rc_family)) :: Ptr ()
+            p_bdaddr = p `plusPtr` (#const offsetof(struct sockaddr_rc, rc_bdaddr))
+            p_channel = p `plusPtr` (#const offsetof(struct sockaddr_rc, rc_channel))
+        case (#const sizeof(sa_family_t)) of
+            1 -> poke (castPtr p_family) (fromIntegral family :: Word8)
+            2 -> poke (castPtr p_family) (fromIntegral family :: Word16)
+            4 -> poke (castPtr p_family) (fromIntegral family :: Word32)
+            sz -> fail $ "SockAddrBTH.poke can't handle size "++show sz
+        B.unsafeUseAsCString bdaddr $ \c_bdaddr ->
+            B.memcpy p_bdaddr (castPtr c_bdaddr) (B.length bdaddr)
+        poke p_channel channel
+#endif
+
+#if defined(mingw32_HOST_OS)
+foreign import stdcall unsafe "connect"
+#else
+foreign import ccall unsafe "connect"
+#endif
+  c_connect :: CInt -> Ptr SockAddrBTH -> CInt -> IO CInt
+
+#if defined(mingw32_HOST_OS)
+foreign import stdcall unsafe "socket"
+  c_socket :: CInt -> CInt -> CInt -> IO CInt
+#endif
+
+openRFCOMM :: Device -> Word8 -> IO RFCOMMSocket
+openRFCOMM dev@(Device _ addr) channel = do
+#if defined(mingw32_HOST_OS)
+    s <- do
+        fd <- c_socket (fromIntegral aF_BTH) (#const SOCK_STREAM) bTHPROTO_RFCOMM
+        mkSocket fd AF_BLUETOOTH Stream bTHPROTO_RFCOMM NotConnected
+#else
+    s <- socket AF_BLUETOOTH Stream (#const BTPROTO_RFCOMM)
+    setSocketOption s ReusePort 1
+#endif
+    connect s `onException` sClose s
+  where
+    connect s = do
+        let fd = fdSocket s
+        ret <- alloca $ \p_sarc -> do
+            poke p_sarc (sockAddrBTH addr channel)
+            c_connect fd p_sarc (fromIntegral $ sizeOf (undefined :: SockAddrBTH))
+        if ret < 0 then do
+            errno@(Errno errno_) <- getErrno
+            case errno of
+                _ | errno == eINTR -> connect s
+                _ | errno == eOK -> return $ RFCOMMSocket s
+                _ | errno == eINPROGRESS -> do
+                    threadWaitWrite (fromIntegral fd)
+                    errno@(Errno errno_) <- Errno . fromIntegral <$> getSocketOption s SoError
+                    if errno == eOK
+                        then return $ RFCOMMSocket s
+                        else do
+#if defined(mingw32_HOST_OS)
+                            err <- getLastError
+                            throwIO =<< BluetoothException "openRFCOMM" <$> (peekTString =<< getErrorMessage err)
+#else
+                            err <- peekCString (strerror errno_)
+                            throwIO $ BluetoothException "openRFCOMM" err
+#endif
+                _ -> do
+#if defined(mingw32_HOST_OS)
+                    err <- getLastError
+                    throwIO =<< BluetoothException "openRFCOMM" <$> (peekTString =<< getErrorMessage err)
+#else
+                    err <- peekCString (strerror errno_)
+                    throwIO $ BluetoothException "openRFCOMM" err
+#endif
+          else
+            return $ RFCOMMSocket s
+
+recvRFCOMM :: RFCOMMSocket -> Int -> IO ByteString
+recvRFCOMM (RFCOMMSocket s) n = NB.recv s n
+  `catch` \exc ->
+     throwIO (BluetoothException "recvRFCOMM" (show (exc :: IOException)))
+
+sendRFCOMM :: RFCOMMSocket -> ByteString -> IO Int
+sendRFCOMM (RFCOMMSocket s) txt = NB.send s txt
+  `catch` \exc ->
+     throwIO (BluetoothException "sendRFCOMM" (show (exc :: IOException)))
+
+sendAllRFCOMM :: RFCOMMSocket -> ByteString -> IO ()
+sendAllRFCOMM (RFCOMMSocket s) txt = NB.sendAll s txt
+  `catch` \exc ->
+     throwIO (BluetoothException "sendAllRFCOMM" (show (exc :: IOException)))
+
+closeRFCOMM :: RFCOMMSocket -> IO ()
+closeRFCOMM (RFCOMMSocket s) = sClose s
+  `catch` \exc ->
+     throwIO (BluetoothException "close" (show (exc :: IOException)))
diff --git a/Network/Bluetooth/Types.hsc b/Network/Bluetooth/Types.hsc
new file mode 100644
--- /dev/null
+++ b/Network/Bluetooth/Types.hsc
@@ -0,0 +1,67 @@
+{-# LANGUAGE DeriveDataTypeable #-}
+module Network.Bluetooth.Types where
+
+#if defined(mingw32_HOST_OS)
+#include <windows.h>
+#else
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#endif
+#include <stddef.h>
+
+import Control.Applicative
+import Control.Exception
+import Data.ByteString (ByteString)
+import qualified Data.ByteString as B
+import qualified Data.ByteString.Internal as BI
+import Data.Char
+import Data.List
+import Data.Typeable
+import Foreign
+import Foreign.C
+import Numeric
+
+
+#if !defined(mingw32_HOST_OS)
+foreign import ccall unsafe "strerror" strerror
+    :: CInt -> CString
+#endif
+
+#if defined(mingw32_HOST_OS)
+data Adapter = Adapter deriving (Eq, Ord, Show)
+#else
+data Adapter = Adapter CInt CInt deriving (Eq, Ord, Show)
+#endif
+
+data BluetoothException = BluetoothException String String deriving (Show, Typeable)
+instance Exception BluetoothException
+
+newtype BluetoothAddr = BluetoothAddr ByteString deriving (Eq, Ord)
+
+instance Show BluetoothAddr where
+    show (BluetoothAddr bs) = intercalate ":" $ map (\x -> dig2 $ showHex x "") $ reverse $ B.unpack bs
+      where
+        dig2 = map toUpper . reverse . take 2 . reverse . ('0':) 
+
+instance Read BluetoothAddr where
+    readsPrec _ t = go t (6 :: Int) []
+      where
+        go t n acc = case readHex t of
+            (x, t'):_ -> case (n, t') of
+                (1, _)       -> [(BluetoothAddr (B.pack (x:acc)), t')]
+                (_, ':':t'') -> go t'' (n-1) (x:acc)
+                _ -> []
+            _ -> []
+
+instance Storable BluetoothAddr where
+#if defined(mingw32_HOST_OS)
+    sizeOf _ = 8
+#else
+    sizeOf _ = (#const sizeof(bdaddr_t))
+#endif
+    alignment _ = alignment (undefined :: Word64)
+    peek p = BluetoothAddr . B.pack <$> peekArray 6 (castPtr p)
+    poke p (BluetoothAddr bs) = do
+        BI.memset (castPtr p) 0 (fromIntegral $ sizeOf (undefined :: BluetoothAddr)) 
+        pokeArray (castPtr p) (B.unpack bs)
+
diff --git a/Network/Bluetooth/Win32.hsc b/Network/Bluetooth/Win32.hsc
new file mode 100644
--- /dev/null
+++ b/Network/Bluetooth/Win32.hsc
@@ -0,0 +1,169 @@
+module Network.Bluetooth.Win32 where
+
+#include <windows.h>
+
+import Network.Bluetooth.Types
+
+import Data.ByteString (ByteString)
+import qualified Data.ByteString.Char8 as C
+import qualified Data.ByteString.Internal as BI
+import Control.Applicative
+import Control.Exception
+import Foreign
+import Foreign.C
+import System.Win32.Types
+
+
+data SockAddrBTH = SockAddrBTH {
+    bthFamily :: USHORT,
+    bthAddr   :: BluetoothAddr,
+    bthPort   :: LONG
+  }
+  deriving Show
+
+sockAddrBTH :: BluetoothAddr -> Word8 -> SockAddrBTH
+sockAddrBTH addr port = SockAddrBTH aF_BTH addr (fromIntegral port)
+
+instance Storable SockAddrBTH where
+    sizeOf _ = 30
+    alignment _ = alignment (undefined :: Word64)
+    poke p bth = do
+        BI.memset (castPtr p) 0 (fromIntegral $ sizeOf bth)
+        poke (p `plusPtr` 0) (bthFamily bth)
+        poke (p `plusPtr` 2) (bthAddr bth)
+        poke (p `plusPtr` 26) (bthPort bth)
+    peek p = SockAddrBTH <$> peek (p `plusPtr` 0)
+                         <*> peek (p `plusPtr` 2)
+                         <*> peek (p `plusPtr` 26)
+
+data SOCKET_ADDRESS sa = SOCKET_ADDRESS {
+    saSockaddr :: Ptr sa,
+    saSockaddrLength :: INT
+  }
+  deriving Show
+
+instance Storable (SOCKET_ADDRESS sa) where
+    sizeOf _ = (#const sizeof(SOCKET_ADDRESS))
+    alignment _ = alignment (undefined :: Word64)
+    poke _ _ = fail "SOCKET_ADDRESS.poke not defined"
+    peek p = SOCKET_ADDRESS <$> peek (p `plusPtr` (#const offsetof(SOCKET_ADDRESS,lpSockaddr)))
+                            <*> peek (p `plusPtr` (#const offsetof(SOCKET_ADDRESS,iSockaddrLength)))
+
+data CSADDR_INFO sa = CSADDR_INFO {
+    csaLocalAddr  :: SOCKET_ADDRESS sa,
+    csaRemoteAddr :: SOCKET_ADDRESS sa
+  }
+  deriving Show
+
+instance Storable sa => Storable (CSADDR_INFO sa) where
+    sizeOf _ = (#const sizeof(CSADDR_INFO))
+    alignment _ = alignment (undefined :: Word64)
+    poke _ _ = fail "CSADDR_INFO.poke not defined"
+    peek p = CSADDR_INFO <$> peek (p `plusPtr` (#const offsetof(CSADDR_INFO,LocalAddr)))
+                         <*> peek (p `plusPtr` (#const offsetof(CSADDR_INFO,RemoteAddr)))
+
+data WSAQUERYSET sa = WSAQUERYSET {
+    qsSize                :: DWORD,
+    qsServiceInstanceName :: CString,
+    qsNameSpace           :: DWORD,
+    qsNumberOfCsAddrs     :: DWORD,
+    qsCsAddrs             :: Ptr (CSADDR_INFO sa),
+    qsBlob                :: Ptr Word8
+  }
+  deriving Show
+
+instance Storable (WSAQUERYSET sa) where
+    sizeOf _ = (#const sizeof(WSAQUERYSET))
+    alignment _ = alignment (undefined :: Word64)
+    poke p qs = do
+        BI.memset (castPtr p) 0 (fromIntegral $ sizeOf qs)
+        poke (p `plusPtr` (#const offsetof(WSAQUERYSET,dwSize))) (qsSize qs)
+        poke (p `plusPtr` (#const offsetof(WSAQUERYSET,lpszServiceInstanceName))) (qsServiceInstanceName qs)
+        poke (p `plusPtr` (#const offsetof(WSAQUERYSET,dwNameSpace))) (qsNameSpace qs)
+        poke (p `plusPtr` (#const offsetof(WSAQUERYSET,dwNumberOfCsAddrs))) (qsNumberOfCsAddrs qs)
+        poke (p `plusPtr` (#const offsetof(WSAQUERYSET,lpcsaBuffer))) (qsCsAddrs qs)
+        poke (p `plusPtr` (#const offsetof(WSAQUERYSET,lpBlob))) (qsBlob qs)
+    peek p =
+        WSAQUERYSET <$> peek (p `plusPtr` (#const offsetof(WSAQUERYSET,dwSize)))
+                    <*> peek (p `plusPtr` (#const offsetof(WSAQUERYSET,lpszServiceInstanceName)))
+                    <*> peek (p `plusPtr` (#const offsetof(WSAQUERYSET,dwNameSpace))) 
+                    <*> peek (p `plusPtr` (#const offsetof(WSAQUERYSET,dwNumberOfCsAddrs)))
+                    <*> peek (p `plusPtr` (#const offsetof(WSAQUERYSET,lpcsaBuffer)))
+                    <*> peek (p `plusPtr` (#const offsetof(WSAQUERYSET,lpBlob)))
+
+foreign import stdcall safe "WSALookupServiceBeginA" wsaLookupServiceBegin
+    :: Ptr (WSAQUERYSET SockAddrBTH) -> DWORD -> Ptr HANDLE -> IO CInt
+foreign import stdcall safe "WSALookupServiceNextA" wsaLookupServiceNext
+    :: HANDLE -> DWORD -> Ptr DWORD -> Ptr (WSAQUERYSET SockAddrBTH) -> IO CInt
+foreign import stdcall safe "WSALookupServiceEnd" wsaLookupServiceEnd
+    :: HANDLE -> IO CInt
+
+-- | The Bluetooth namespace
+nS_BTH :: DWORD
+nS_BTH = 16
+
+aF_BTH :: USHORT
+aF_BTH = 32
+
+wsaServiceNotFound :: ErrCode
+wsaServiceNotFound = 10108
+
+wsaENoMore :: ErrCode
+wsaENoMore = 10110
+
+bTHPROTO_RFCOMM :: CInt
+bTHPROTO_RFCOMM = 0x0003
+
+discover' :: Adapter -> DWORD -> IO [(BluetoothAddr, Maybe ByteString)]
+discover' a flags = alloca $ \pqs -> alloca $ \ph -> do
+    poke pqs $ WSAQUERYSET {
+        qsSize            = fromIntegral $ sizeOf (undefined :: WSAQUERYSET SockAddrBTH),
+        qsServiceInstanceName = nullPtr,
+        qsNameSpace       = nS_BTH,
+        qsNumberOfCsAddrs = 0,
+        qsCsAddrs         = nullPtr,
+        qsBlob            = nullPtr
+      }
+    ret <- wsaLookupServiceBegin pqs flags ph
+    none <- if ret < 0 then do
+        err <- getLastError
+        if err == wsaServiceNotFound  -- This error means that there are no devices
+            then pure True
+            else throwIO =<< BluetoothException "discover" <$> (peekTString =<< getErrorMessage err)
+      else
+        pure False
+    if none then
+        return []
+      else do
+        h <- peek ph
+        do
+            let bufSize = 5000
+            alloca $ \pResults -> allocaBytes bufSize $ \buf -> alloca $ \pdwSize -> do
+                poke pResults $ WSAQUERYSET {
+                    qsSize            = fromIntegral $ sizeOf (undefined :: WSAQUERYSET SockAddrBTH),
+                    qsServiceInstanceName = nullPtr,
+                    qsNameSpace       = nS_BTH,
+                    qsNumberOfCsAddrs = 0,
+                    qsCsAddrs         = nullPtr,
+                    qsBlob            = nullPtr
+                  }
+                let loop acc = do
+                        poke pdwSize (fromIntegral bufSize)
+                        ret <- wsaLookupServiceNext h flags pdwSize pResults
+                        if ret < 0 then do
+                            err <- getLastError
+                            if err == wsaENoMore
+                                then pure $ reverse acc
+                                else throwIO =<< BluetoothException "discover" <$> (peekTString =<< getErrorMessage err)
+                          else do
+                            results <- peek pResults
+                            csAddrs <- peekArray (fromIntegral $ qsNumberOfCsAddrs results) (qsCsAddrs results)
+                            addrs <- mapM (peek . saSockaddr . csaRemoteAddr) csAddrs
+                            serviceName <- if qsServiceInstanceName results == nullPtr
+                                then pure Nothing
+                                else Just . C.pack <$> peekCString (qsServiceInstanceName results)
+                            loop $ reverse (map (\addr -> (bthAddr addr, serviceName)) addrs) ++ acc
+                loop []
+          `finally`
+            wsaLookupServiceEnd h
+
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/examples/discover.hs b/examples/discover.hs
new file mode 100644
--- /dev/null
+++ b/examples/discover.hs
@@ -0,0 +1,24 @@
+{-# LANGUAGE ScopedTypeVariables #-}
+import Network.Bluetooth
+import Network.Socket (withSocketsDo)
+import Control.Applicative
+import Control.Exception
+import Control.Monad (mapM_)
+import System.IO
+
+main = withSocketsDo $ do
+    mAdapter <- defaultAdapter
+    case mAdapter of
+        Just adapter -> do
+            devs <- discover adapter
+            mapM_ printDev devs
+        Nothing -> hPutStrLn stderr "no local bluetooth adapter found"
+  where
+    printDev dev@(Device _ addr) = do
+        putStr $ show addr ++ " "
+        hFlush stdout
+        name <- (Just <$> deviceName dev)
+            `catch` \(exc :: BluetoothException) -> return Nothing
+        print name
+        hFlush stdout
+
diff --git a/simple-bluetooth.cabal b/simple-bluetooth.cabal
new file mode 100644
--- /dev/null
+++ b/simple-bluetooth.cabal
@@ -0,0 +1,40 @@
+-- Initial bluetooth.cabal generated by cabal init.  For further 
+-- documentation, see http://haskell.org/cabal/users-guide/
+
+name:                simple-bluetooth
+version:             0.1.0.0
+synopsis:            Simple Bluetooth API for Windows and Linux (bluez)
+description:         You must use 'Network.withSocketsDo' at the start of your program
+                     for Windows compatibility.
+license:             BSD3
+license-file:        LICENSE
+author:              Stephen Blackheath
+maintainer:          docks.cattlemen.stephen@blacksapphire.com
+-- copyright:           
+category:            Network
+build-type:          Simple
+extra-source-files:  examples/discover.hs
+cabal-version:       >=1.10
+
+library
+  exposed-modules:   
+      Network.Bluetooth  
+      Network.Bluetooth.Adapter
+      Network.Bluetooth.Device
+  other-modules:       
+      Network.Bluetooth.Types
+  if os(windows)
+    other-modules:
+      Network.Bluetooth.Win32
+  build-depends:       base >=4.5 && <5,
+                       network,
+                       bytestring
+  if os(windows)
+    build-depends: Win32
+  default-language:    Haskell2010
+  if os(windows)
+    extra-libraries:   ws2_32
+    include-dirs: Network/Bluetooth/
+  else
+    extra-libraries:   bluetooth
+
