packages feed

hcoap 0.1.1.0 → 0.1.2.0

raw patch · 10 files changed

+193/−66 lines, 10 filesdep +dnsdep +iproutedep +network-uriPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: dns, iproute, network-uri

API changes (from Hackage documentation)

+ Network.CoAP.Client: [doRawRequest] :: Client -> Endpoint -> Request -> IO Response
- Network.CoAP.Client: Client :: (Endpoint -> Request -> IO Response) -> IO () -> Client
+ Network.CoAP.Client: Client :: (URI -> Request -> IO Response) -> (Endpoint -> Request -> IO Response) -> IO () -> Client
- Network.CoAP.Client: [doRequest] :: Client -> Endpoint -> Request -> IO Response
+ Network.CoAP.Client: [doRequest] :: Client -> URI -> Request -> IO Response
- Network.CoAP.Message: Message :: MessageVersion -> MessageType -> MessageCode -> MessageId -> Token -> [Option] -> Maybe Payload -> Message
+ Network.CoAP.Message: Message :: !MessageVersion -> !MessageType -> !MessageCode -> !MessageId -> !Token -> ![Option] -> !(Maybe Payload) -> Message
- Network.CoAP.Message: [messageCode] :: Message -> MessageCode
+ Network.CoAP.Message: [messageCode] :: Message -> !MessageCode
- Network.CoAP.Message: [messageId] :: Message -> MessageId
+ Network.CoAP.Message: [messageId] :: Message -> !MessageId
- Network.CoAP.Message: [messageOptions] :: Message -> [Option]
+ Network.CoAP.Message: [messageOptions] :: Message -> ![Option]
- Network.CoAP.Message: [messagePayload] :: Message -> Maybe Payload
+ Network.CoAP.Message: [messagePayload] :: Message -> !(Maybe Payload)
- Network.CoAP.Message: [messageToken] :: Message -> Token
+ Network.CoAP.Message: [messageToken] :: Message -> !Token
- Network.CoAP.Message: [messageType] :: Message -> MessageType
+ Network.CoAP.Message: [messageType] :: Message -> !MessageType
- Network.CoAP.Message: [messageVersion] :: Message -> MessageVersion
+ Network.CoAP.Message: [messageVersion] :: Message -> !MessageVersion
- Network.CoAP.Message: decode :: ByteString -> Message
+ Network.CoAP.Message: decode :: ByteString -> Either String Message

Files

README.md view
@@ -5,3 +5,29 @@ The library is split into a high-level API in Network.CoAP.Server and Network.CoAP.Client, and a lower layer API in Network.CoAP.Message for working directly with CoAP messages.  [![Build Status](https://travis-ci.org/lulf/hcoap.svg?branch=master)](https://travis-ci.org/lulf/hcoap)++## Example server+    main =+      withSocketsDo $ do+        sock <- socket AF_INET6 Datagram defaultProtocol+        bindSocket sock (SockAddrInet6 5683 0 iN6ADDR_ANY 0)+        server <- createServer (createUDPTransport sock) (\(request, _) -> do+          let payload = Just (B.pack "{\"value\":\"foo\"}")+          return (Response Content [ContentFormat ApplicationJson] payload))+        runServer server++## Example client+    main = do+      let request = Request { requestMethod = GET+                            , requestOptions = []+                            , requestPayload = Nothing+                            , requestReliable = True }+    withSocketsDo $ do+      sock <- socket AF_INET6 Datagram defaultProtocol+      bindSocket sock (SockAddrInet6 0 0 iN6ADDR_ANY 0)+      let transport = createUDPTransport sock+      client <- createClient transport+      uri <- parseURI "coap://[::1]:5683/hello"+      response <- doRequest client uri request+      putStrLn ("Got response: " ++ show response)+      return ()
example-client/Main.hs view
@@ -1,21 +1,35 @@ import Network.CoAP.Client import Network.CoAP.Transport import Network.Socket+import Network.URI+import System.Environment  import qualified Data.ByteString.Char8 as B  main :: IO () main = do-  let request = Request { requestMethod = GET-                        , requestOptions = [UriPath (B.pack "hello")]+  args <- getArgs+  if length args < 2+  then printUsage+  else runClient args++runClient :: [String] -> IO ()+runClient (method:uriStr:_) = do+  let request = Request { requestMethod = read method :: Method+                        , requestOptions = []                         , requestPayload = Nothing                         , requestReliable = True }   withSocketsDo $ do     sock <- socket AF_INET6 Datagram defaultProtocol-    bindSocket sock (SockAddrInet6 12345 0 iN6ADDR_ANY 0)+    bindSocket sock (SockAddrInet6 0 0 iN6ADDR_ANY 0)     let transport = createUDPTransport sock-    let dest = SockAddrInet6 5683 0 (0, 0, 0, 0) 0     client <- createClient transport-    response <- doRequest client dest request+    let (Just uri) = parseURI uriStr+    response <- doRequest client uri request     putStrLn ("Got response: " ++ show response)     return ()++printUsage :: IO ()+printUsage = do+  pname <- getProgName+  putStrLn ("Usage: " ++ pname ++ " <method> <uri>")
hcoap.cabal view
@@ -1,6 +1,6 @@ copyright:           Copyright (c) 2016, Ulf Lilleengen name:                hcoap-version:             0.1.1.0+version:             0.1.2.0 synopsis:            Library for handling CoAP protocol license:             BSD3 license-file:        LICENSE@@ -21,7 +21,7 @@  library   exposed-modules: Network.CoAP.Server, Network.CoAP.Client, Network.CoAP.Message, Network.CoAP.Transport-  other-modules: Network.CoAP.Types, Network.CoAP.MessageCodec, Network.CoAP.Messaging+  other-modules: Network.CoAP.Types, Network.CoAP.MessageCodec, Network.CoAP.Messaging, Network.CoAP.Internal    build-depends:       base >= 4 && < 5,                        bytestring >= 0.10 && < 0.11,@@ -31,7 +31,10 @@                        mtl >= 2.2 && < 2.3,                        time >= 1.5 && < 1.6,                        random >= 1.1 && < 1.2,-                       async >= 2.1 && < 2.2+                       async >= 2.1 && < 2.2,+                       network-uri >= 2.6 && < 3,+                       iproute >= 1.7 && < 2,+                       dns >= 2 && < 3   hs-source-dirs:      src   default-language:    Haskell2010 @@ -57,6 +60,7 @@   build-depends:       base,                        network,                        bytestring,+                       network-uri,                        hcoap   default-language:    Haskell2010 @@ -67,6 +71,7 @@   build-depends:       base,                        network,                        bytestring,+                       network-uri,                        hcoap   default-language:    Haskell2010 
src/Network/CoAP/Client.hs view
@@ -7,9 +7,10 @@ The CoAP client API is intended to provide the minimal building block needed for sending CoAP requests. The API exposes CoAP request and response types and handles all internal messaging details of the CoAP protocol.  Example:+ @-  client <- createClient (createUDPTransport socket)-  doRequest client (SockAddrInet 5683 0) (Request GET [UriPath path] Nothing True)+    client <- createClient (createUDPTransport socket)+    doRawRequest client (SockAddrInet 5683 0) (Request GET [UriPath path] Nothing True) @ -} module Network.CoAP.Client@@ -30,13 +31,17 @@ import Control.Concurrent.Async import Control.Concurrent.STM import Network.Socket-import Data.ByteString+import Data.ByteString hiding (putStrLn) import Data.Word import System.Random+import Network.URI+import Network.CoAP.Internal                      -- | A client that can perform CoAP requests.-data Client = Client { -- | Send a CoAP request to a given endpoint. Returns a CoAP response.-                       doRequest :: Endpoint -> Request -> IO Response+data Client = Client { -- | Send a CoAP request to a given endpoint represented by an URI. Returns a CoAP response.+                       doRequest :: URI -> Request -> IO Response+                       -- | Send a CoAP request to a given endpoint. Returns a CoAP response.+                     , doRawRequest :: Endpoint -> Request -> IO Response                        -- | Stop a client. Ensures that no threads are running and that messaging                        -- layer is shut down.                      , shutdownClient :: IO () }@@ -48,6 +53,7 @@   state <- createMessagingState transport   msgThreads <- startMessaging state   return Client { doRequest = doRequestInternal state+                , doRawRequest = doRawRequestInternal state                 , shutdownClient = stopClient state msgThreads }  -- | Shuts down the internal messaging threads and stops the client@@ -63,9 +69,14 @@   next <- generateToken (len - 1)   return (tkn:next) -doRequestInternal :: MessagingState -> Endpoint -> Request -> IO Response-doRequestInternal state dest (Request method options payload reliable) = do+doRequestInternal :: MessagingState -> URI -> Request -> IO Response+doRequestInternal state uri (Request method options payload reliable) = do+  dest <- createEndpoint uri+  let newOpts = createOpts uri+  doRawRequestInternal state dest (Request method (options ++ newOpts) payload reliable) +doRawRequestInternal :: MessagingState -> Endpoint -> Request -> IO Response+doRawRequestInternal state dest (Request method options payload reliable) = do   tokenLen <- randomRIO (0, 8)   token <- generateToken tokenLen   let msg = Message { messageVersion = 1
+ src/Network/CoAP/Internal.hs view
@@ -0,0 +1,65 @@+module Network.CoAP.Internal+( createEndpoint+, createOpts+) where++import Network.DNS.Lookup+import Network.DNS.Resolver+import Data.ByteString.Char8+import Network.URI+import Data.IP+import Network.Socket+import Network.CoAP.Types++createIPv4Endpoint :: [IPv4] -> PortNumber -> Endpoint+createIPv4Endpoint (addr:_) port = SockAddrInet port (toHostAddress addr)++createIPv6Endpoint :: [IPv6] -> PortNumber -> Endpoint+createIPv6Endpoint (addr:_) port = SockAddrInet6 port 0 (toHostAddress6 addr) 0++stripBrackets :: String -> String+stripBrackets [] = []+stripBrackets [']'] = []+stripBrackets ('[':rest) = stripBrackets rest+stripBrackets (h:t) = h:stripBrackets t++parsePort :: String -> Int+parsePort [] = 5683+parsePort (':':rest) = parsePort rest+parsePort s = read s++createEndpoint :: URI -> IO Endpoint+createEndpoint uri = do+  let (Just auth) = uriAuthority uri+      hname = stripBrackets (uriRegName auth)+      port = fromIntegral (parsePort (uriPort auth))+  if isIPv6address hname+  then return (createIPv6Endpoint [read hname :: IPv6] port)+  else if isIPv4address hname+       then return (createIPv4Endpoint [read hname :: IPv4] port)+       else resolveEndpoint hname port++resolveEndpoint :: String -> PortNumber -> IO Endpoint+resolveEndpoint hostName port = do+  let hname = pack hostName+  rs <- makeResolvSeed defaultResolvConf+  ipv6Addr <- withResolver rs (`lookupAAAA` hname)+  case ipv6Addr of+    Left _ -> do+      ipv4Addr <- withResolver rs (`lookupA` hname)+      case ipv4Addr of+        Left _ -> error "Unable to resolve hostname"+        Right addr -> return (createIPv4Endpoint addr port)+    Right addr -> return (createIPv6Endpoint addr port)++parsePath :: String -> String+parsePath [] = []+parsePath ('/':rest) = rest+parsePath any = any++createOpts :: URI -> [Option]+createOpts uri =+  let (Just auth) = uriAuthority uri+      path = parsePath (uriPath uri)+      port = parsePort (uriPort auth)+   in [UriPath (pack path), UriPort port]
src/Network/CoAP/MessageCodec.hs view
@@ -165,8 +165,12 @@                  , messagePayload = payload }  -- | Decode a CoAP message according to the specification.-decode :: BS.ByteString -> Message-decode msg = runGet getMessage (fromStrict msg)+decode :: BS.ByteString -> Either String Message+decode msg =+  let result = runGetOrFail getMessage (fromStrict msg)+   in case result of+        Left (_, _, errorStr) -> Left errorStr+        Right (_, _, parsedMsg) -> Right parsedMsg  encodeType :: MessageType -> Word8 encodeType CON = 0@@ -259,20 +263,16 @@ encodeOption (Size1 value)              = (60, encodeOptionInt value)  encodeOptionPart :: Int -> Word8-encodeOptionPart value =-  if value > 268-  then 14-  else if value > 12-       then 13-       else fromIntegral value+encodeOptionPart value+  | value > 268 = 14+  | value > 12  = 13+  | otherwise   = fromIntegral value  putOptionPart :: Word8 -> Int -> Put-putOptionPart valueLen value = do-  if valueLen == 13-  then putWord8 (fromIntegral (value - 13))-  else if valueLen == 14-       then putWord16be (fromIntegral (value - 269))-       else return ()+putOptionPart valueLen value+  | valueLen == 13 = putWord8 (fromIntegral (value - 13))+  | valueLen == 14 = putWord16be (fromIntegral (value - 269))+  | otherwise      = return ()  putOption :: Int -> BS.ByteString -> Put putOption nextDelta optValue = do
src/Network/CoAP/Messaging.hs view
@@ -30,12 +30,10 @@                                  , replyTimeout    :: Double                                   , retransmitCount :: Integer } deriving (Show) -cmpMessageState a b =-  messageId (message (messageContext a)) == messageId (message (messageContext b)) &&-  srcEndpoint (messageContext a) == srcEndpoint (messageContext b)  instance Eq MessageState where-  (==) = cmpMessageState+  (==) a b = messageId (message (messageContext a)) == messageId (message (messageContext b)) &&+             srcEndpoint (messageContext a) == srcEndpoint (messageContext b)  -- A message store contains an inbound and outbound list of messages that needs to be ACKed type MessageList = [MessageState]@@ -127,13 +125,11 @@ takeMessageByIdAndOrigin :: MessageId -> Endpoint -> TVar MessageList -> STM (Maybe MessageState) takeMessageByIdAndOrigin msgId origin = takeMessageMatching (\x -> (origin == dstEndpoint (messageContext x)) && (msgId == messageId (message (messageContext x)))) -recvLoop :: MessagingState -> IO ()-recvLoop state@(MessagingState transport store) = do-  {-putStrLn "Waiting for UDP packet"-}-  (msgData, srcEndpoint) <- recvFrom transport+recvLoopSuccess :: MessagingState -> Endpoint -> Message -> IO ()+recvLoopSuccess state@(MessagingState transport store) srcEndpoint message = do   dstEndpoint <- localEndpoint transport   now <- getCurrentTime-  let message = decode msgData+   let messageCtx = MessageContext { message = message                                   , srcEndpoint = srcEndpoint                                   , dstEndpoint = dstEndpoint }@@ -147,6 +143,15 @@   atomically (when (msgType == ACK) (do _ <- takeMessageByIdAndOrigin msgId srcEndpoint (unconfirmedMessages store)                                         return ()))   atomically (when (msgCode /= CodeEmpty) (queueMessage messageState (incomingMessages store)))++recvLoopError :: String -> IO ()+recvLoopError err = putStrLn ("Error parsing message: " ++ show err ++ ", skipping")++recvLoop :: MessagingState -> IO ()+recvLoop state@(MessagingState transport _) = do+  {-putStrLn "Waiting for UDP packet"-}+  (msgData, srcEndpoint) <- recvFrom transport+  either recvLoopError (recvLoopSuccess state srcEndpoint) (decode msgData)   recvLoop state    sendLoop :: MessagingState -> IO ()
src/Network/CoAP/Server.hs view
@@ -7,11 +7,12 @@ The CoAP server API is intended to provide the minimal building block needed for creating CoAP servers. The API exposes CoAP requests and response types and handles all internal messaging details of the CoAP protocol.  Example:+ @-  server <- createServer (createUDPTransport socket) (\(req, endpoint) = do-    let payload = Just (B.pack ("{\"msg\":\"Hello, Client\"}"))-    return (Response Content [ContentFormat ApplicationJson] payload))-  runServer server+    server <- createServer (createUDPTransport socket) (\(req, endpoint) = do+      let payload = Just (B.pack ("{\"msg\":\"Hello, Client\"}"))+      return (Response Content [ContentFormat ApplicationJson] payload))+    runServer server @ -} module Network.CoAP.Server
src/Network/CoAP/Types.hs view
@@ -55,7 +55,7 @@ type Payload = BS.ByteString  -- | Request Method-data Method = GET | POST | PUT | DELETE deriving (Show, Eq)+data Method = GET | POST | PUT | DELETE deriving (Show, Eq, Read)  -- | A message with additional context on source and destination data MessageContext = MessageContext { message :: Message@@ -116,13 +116,13 @@  -- | CoAP message record. Can be encoded and decoded to ByteString. data Message = Message-  { messageVersion :: MessageVersion -  , messageType    :: MessageType-  , messageCode    :: MessageCode-  , messageId      :: MessageId-  , messageToken   :: Token-  , messageOptions :: [Option]-  , messagePayload :: Maybe Payload+  { messageVersion :: !MessageVersion +  , messageType    :: !MessageType+  , messageCode    :: !MessageCode+  , messageId      :: !MessageId+  , messageToken   :: !Token+  , messageOptions :: ![Option]+  , messagePayload :: !(Maybe Payload)   } deriving (Show)  
test/Spec.hs view
@@ -20,7 +20,7 @@  sendToChan prob chan msg endpoint = do   v <- randomRIO(0, 1)-  when (v >= prob) (putStrLn ("Message to to " ++ (show endpoint) ++ " dropped!"))+  when (v >= prob) (putStrLn ("Message to to " ++ show endpoint ++ " dropped!"))   when (v < prob) (writeChan chan msg)   return (length msg) @@ -38,10 +38,10 @@ instance Arbitrary C.Request where   arbitrary = do     pload <- arbitrary-    return (C.Request { C.requestMethod = GET-                      , C.requestOptions = []-                      , C.requestPayload = pload-                      , C.requestReliable = True })+    return C.Request { C.requestMethod = GET+                     , C.requestOptions = []+                     , C.requestPayload = pload+                     , C.requestReliable = True }  testReliability =   TestCase (do@@ -58,7 +58,7 @@     client <- C.createClient transportB      reqs <- generate (vector 10)     mapM_ (\req -> do-      response <- timeout 20000000 (C.doRequest client endpointA req)+      response <- timeout 20000000 (C.doRawRequest client endpointA req)       assertBool ("Timed out waiting for response on reliable request " ++ show req) (isJust response)       let res = fromJust response       putStrLn "Got response, checking"@@ -80,7 +80,7 @@   arbitrary = elements [PUT, GET, POST, DELETE]  instance Arbitrary ByteString where-  arbitrary = suchThat (fmap pack arbitrary) (\s -> ((length s > 0) && (length s <= 8)))+  arbitrary = suchThat (fmap pack arbitrary) (\s -> (length s > 0) && (length s <= 8))  instance Arbitrary MediaType where   arbitrary = elements [TextPlain, ApplicationLinkFormat, ApplicationXml, ApplicationOctetStream, ApplicationExi, ApplicationJson]@@ -107,19 +107,19 @@     tkn <- arbitrary     options <- arbitrary     payload <- arbitrary-    return (Message { messageVersion = 1-                    , messageType    = msgType-                    , messageCode    = CodeRequest msgMethod-                    , messageId      = msgId-                    , messageToken = tkn-                    , messageOptions = [options]-                    , messagePayload = payload })+    return Message { messageVersion = 1+                   , messageType    = msgType+                   , messageCode    = CodeRequest msgMethod+                   , messageId      = msgId+                   , messageToken = tkn+                   , messageOptions = [options]+                   , messagePayload = payload }   checkCodec msg =   let encoded = encode msg-      decoded = decode encoded-   in (show msg) == (show decoded)+      (Right decoded) = decode encoded+   in show msg == show decoded  main :: IO () main = do