packages feed

network-unexceptional (empty) → 0.1.0.0

raw patch · 6 files changed

+206/−0 lines, 6 filesdep +basedep +byteslicedep +error-codes

Dependencies added: base, byteslice, error-codes, network, posix-api, primitive

Files

+ CHANGELOG.md view
@@ -0,0 +1,5 @@+# Revision history for network-unexceptional++## 0.1.0.0 -- YYYY-mm-dd++* First version.
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2023, Andrew Martin++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 Andrew Martin 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.
+ network-unexceptional.cabal view
@@ -0,0 +1,27 @@+cabal-version: 3.0+name: network-unexceptional+version: 0.1.0.0+synopsis: Network functions that do not throw exceptions+license: BSD-3-Clause+license-file: LICENSE+author: Andrew Martin+maintainer: andrew.thaddeus@gmail.com+copyright: 2023 Andrew Martin+build-type: Simple+extra-doc-files: CHANGELOG.md++library+  ghc-options: -O2 -Wall+  exposed-modules:+    Network.Unexceptional.Bytes+    Network.Unexceptional.Chunks+    Network.Unexceptional.MutableBytes+  build-depends:+    , base >=4.17 && <5+    , posix-api >=0.6.0.1+    , error-codes >=0.1.1+    , byteslice >=0.2.8+    , network >=3.1+    , primitive >=0.8+  hs-source-dirs: src+  default-language: Haskell2010
+ src/Network/Unexceptional/Bytes.hs view
@@ -0,0 +1,68 @@+{-# language BangPatterns #-}+{-# language DuplicateRecordFields #-}+{-# language PatternSynonyms #-}+{-# language LambdaCase #-}+{-# language NamedFieldPuns #-}++module Network.Unexceptional.Bytes+  ( send+  , receive+  ) where++import System.Posix.Types (Fd(Fd))+import Foreign.C.Error (Errno)+import Foreign.C.Types (CInt)+import Network.Socket (Socket)+import Data.Bytes.Types (Bytes(Bytes),MutableBytes(MutableBytes))+import Foreign.C.Error.Pattern (pattern EWOULDBLOCK,pattern EAGAIN)+import Data.Array.Byte (ByteArray)+import GHC.Conc (threadWaitRead, threadWaitWrite)+import Control.Monad (when)++import qualified Posix.Socket as X+import qualified Linux.Socket as X+import qualified Data.Bytes.Types+import qualified Network.Socket as S+import qualified Data.Primitive as PM+import qualified Network.Unexceptional.MutableBytes as MB++-- | Send the entire byte sequence. This call POSIX @send@ in a loop+-- until all of the bytes have been sent.+send ::+     Socket+  -> Bytes+  -> IO (Either Errno ())+send s Bytes{array,offset,length=len} = S.withFdSocket s $ \fd ->+  -- We attempt the first send without testing if the socket is in+  -- ready for writes. This is because it is uncommon for the transmit+  -- buffer to already be full.+  sendLoop (Fd fd) array offset len++-- does not wait for file descriptor to be ready+sendLoop :: Fd -> ByteArray -> Int -> Int -> IO (Either Errno ())+sendLoop !fd !arr !off !len =+  X.uninterruptibleSendByteArray fd arr off (fromIntegral len) (X.noSignal <> X.dontWait) >>= \case+    Left e -> if e == EAGAIN || e == EWOULDBLOCK+      then do+        threadWaitWrite fd+        sendLoop fd arr off len+      else pure (Left e)+    Right sentSzC ->+      let sentSz = fromIntegral sentSzC :: Int+       in case compare sentSz len of+            EQ -> pure (Right ())+            LT -> sendLoop fd arr (off + sentSz) (len - sentSz)+            GT -> fail "Network.Unexceptional.Bytes.sendAll: send claimed to send too many bytes"++receive :: +     Socket+  -> Int -- ^ Maximum number of bytes to receive+  -> IO (Either Errno Bytes)+receive s n = do+  dst <- PM.newByteArray n+  MB.receive s (MutableBytes dst 0 n) >>= \case+    Left e -> pure (Left e)+    Right m -> do+      when (m < n) (PM.shrinkMutableByteArray dst m)+      dst' <- PM.unsafeFreezeByteArray dst +      pure (Right (Bytes dst' 0 m))
+ src/Network/Unexceptional/Chunks.hs view
@@ -0,0 +1,24 @@+{-# language BangPatterns #-}+{-# language DuplicateRecordFields #-}+{-# language PatternSynonyms #-}+{-# language LambdaCase #-}+{-# language NamedFieldPuns #-}++module Network.Unexceptional.Chunks+  ( send+  ) where++import Foreign.C.Error (Errno)+import Network.Socket (Socket)+import Data.Bytes.Chunks (Chunks)++import qualified Data.Bytes.Chunks as Chunks+import qualified Network.Unexceptional.Bytes as NB++-- | Send the entire byte sequence. This call POSIX @send@ in a loop+-- until all of the bytes have been sent.+send ::+     Socket+  -> Chunks+  -> IO (Either Errno ())+send !s cs = NB.send s (Chunks.concat cs)
+ src/Network/Unexceptional/MutableBytes.hs view
@@ -0,0 +1,52 @@+{-# language BangPatterns #-}+{-# language DuplicateRecordFields #-}+{-# language PatternSynonyms #-}+{-# language LambdaCase #-}+{-# language NamedFieldPuns #-}++module Network.Unexceptional.MutableBytes+  ( receive+  ) where++import Data.Bytes.Types (MutableBytes(MutableBytes))+import Data.Primitive (MutableByteArray)+import Foreign.C.Error (Errno)+import Foreign.C.Error.Pattern (pattern EWOULDBLOCK,pattern EAGAIN)+import GHC.Conc (threadWaitRead)+import GHC.Exts (RealWorld)+import Network.Socket (Socket)+import System.Posix.Types (Fd(Fd))++import qualified Data.Bytes.Types+import qualified Linux.Socket as X+import qualified Network.Socket as S+import qualified Posix.Socket as X++-- | Receive bytes from a socket. Receives at most N bytes, where N+-- is the size of the buffer. Returns the number of bytes that were+-- actually received.+receive ::+     Socket+  -> MutableBytes RealWorld -- ^ Slice of a buffer+  -> IO (Either Errno Int)+receive s MutableBytes{array,offset,length=len} = S.withFdSocket s $ \fd ->+  -- We attempt the first send without testing if the socket is in+  -- ready for writes. This is because it is uncommon for the transmit+  -- buffer to already be full.+  receiveLoop (Fd fd) array offset len++-- Does not wait for file descriptor to be ready. Only performs+-- a single successful recv syscall+receiveLoop :: Fd -> MutableByteArray RealWorld -> Int -> Int -> IO (Either Errno Int)+receiveLoop !fd !arr !off !len =+  X.uninterruptibleReceiveMutableByteArray fd arr off (fromIntegral len) X.dontWait >>= \case+    Left e -> if e == EAGAIN || e == EWOULDBLOCK+      then do+        threadWaitRead fd+        receiveLoop fd arr off len+      else pure (Left e)+    Right recvSzC ->+      let recvSz = fromIntegral recvSzC :: Int+       in case compare recvSz len of+            GT -> fail "Network.Unexceptional.Bytes.receive: recv claimed to receive too many bytes"+            _ -> pure (Right recvSz)