packages feed

sockaddr 0.0.1 → 0.1.0

raw patch · 5 files changed

+186/−78 lines, 5 filesdep +hspecdep +sockaddrdep −byteorderdep ~basedep ~bytestringdep ~networkPVP ok

version bump matches the API change (PVP)

Dependencies added: hspec, sockaddr

Dependencies removed: byteorder

Dependency ranges changed: base, bytestring, network

API changes (from Hackage documentation)

Files

+ ChangeLog.md view
@@ -0,0 +1,15 @@+# ChangeLog for sockaddr++## 0.1.0++The output format of IPv6 addresses is changed.++- IPv6 addresses are compressed according to RFC 5952.+- Non-zero scope IDs of IPv6 addresses are appended, e.g. `fe80::1%4`.+- Using `Builder` instead of `printf`.+- Dropping the dependency on `byteorder`.+- Adding a test suite.++## 0.0.1++- Initial release.
Network/SockAddr.hs view
@@ -1,76 +1,94 @@--- | Converting an address in 'SockAddr'.+-- | Converting the address part of 'SockAddr' (without port).  module Network.SockAddr (     showSockAddr   , showSockAddrBS   ) where -import Data.Bits (shift, (.&.))+import Data.Bits (shiftR) import Data.ByteString (ByteString)-import qualified Data.ByteString.Char8 as BS (pack)-import Network.Socket (SockAddr(..), HostAddress, HostAddress6)-import System.ByteOrder-import Text.Printf--isReversed :: Bool-isReversed = byteOrder == LittleEndian+import qualified Data.ByteString.Builder as B+import qualified Data.ByteString.Char8 as C8+import qualified Data.ByteString.Lazy as BL+import Data.List (intersperse)+import Data.Word (Word16, Word8)+import Network.Socket (+    HostAddress6,+    SockAddr (..),+    hostAddress6ToTuple,+    hostAddressToTuple,+ )  ---------------------------------------------------------------- --- | Convert 'SockAddr' to 'String'. If the address is---   an IPv4-embedded IPv6 address, the IPv4 is extracted.+-- | Convert the address part of 'SockAddr' to 'String'.+--   The port number is not included. If the address is+--   an IPv4-mapped IPv6 address (@::ffff:0:0/96@),+--   it is shown as IPv4. A non-zero scope ID of IPv6 is appended+--   as a number, e.g. @fe80::1%4@.+--   For 'SockAddrUnix', @"unknownSocket"@ is returned+--   so that its path is not exposed. -- -- >>> import Network.Socket--- >>> as <- getAddrInfo (Just defaultHints) (Just "example.org") (Just "http")--- >>> map (showSockAddr.addrAddress) as--- ["93.184.216.119","93.184.216.119","2606:2800:220:6d:26bf:1447:1097:aa7","2606:2800:220:6d:26bf:1447:1097:aa7"]+-- >>> showSockAddr $ SockAddrInet 80 (tupleToHostAddress (192,0,2,1))+-- "192.0.2.1"+-- >>> showSockAddr $ SockAddrInet6 80 0 (tupleToHostAddress6 (0x2001,0xdb8,1,2,3,4,5,6)) 0+-- "2001:db8:1:2:3:4:5:6"+-- >>> showSockAddr $ SockAddrInet6 80 0 (tupleToHostAddress6 (0x2001,0xdb8,0,0,1,0,0,1)) 0+-- "2001:db8::1:0:0:1" showSockAddr :: SockAddr -> String-showSockAddr (SockAddrInet _ addr4)                       = showIPv4 addr4 isReversed-showSockAddr (SockAddrInet6 _ _ (0,0,0x0000ffff,addr4) _) = showIPv4 addr4 False-showSockAddr (SockAddrInet6 _ _ (0,0,0,1) _)              = "::1"-showSockAddr (SockAddrInet6 _ _ addr6 _)                  = showIPv6 addr6-showSockAddr _                                            = "unknownSocket"+showSockAddr = C8.unpack . showSockAddrBS -----------------------------------------------------------------+-- | Convert the address part of 'SockAddr' to 'ByteString'.+--   The port number is not included. If the address is+--   an IPv4-mapped IPv6 address (@::ffff:0:0/96@),+--   it is shown as IPv4. A non-zero scope ID of IPv6 is appended+--   as a number, e.g. @fe80::1%4@.+--   For 'SockAddrUnix', @"unknownSocket"@ is returned+--   so that its path is not exposed.+--+-- >>> import Network.Socket+-- >>> showSockAddrBS $ SockAddrInet6 80 0 (tupleToHostAddress6 (0,0,0,0,0,0,0,1)) 0+-- "::1"+showSockAddrBS :: SockAddr -> ByteString+showSockAddrBS = BL.toStrict . B.toLazyByteString . sockAddrBuilder --- HostAddress is network byte order.-showIPv4 :: HostAddress -> Bool-> String-showIPv4 w32 reversed-  | reversed  = show b1 ++ "." ++ show b2 ++ "." ++ show b3 ++ "." ++ show b4-  | otherwise = show b4 ++ "." ++ show b3 ++ "." ++ show b2 ++ "." ++ show b1-  where-    t1 = w32-    t2 = shift t1 (-8)-    t3 = shift t2 (-8)-    t4 = shift t3 (-8)-    b1 = t1 .&. 0x000000ff-    b2 = t2 .&. 0x000000ff-    b3 = t3 .&. 0x000000ff-    b4 = t4 .&. 0x000000ff+---------------------------------------------------------------- --- HostAddress6 is host byte order.-showIPv6 :: HostAddress6 -> String-showIPv6 (w1,w2,w3,w4) =-    printf "%x:%x:%x:%x:%x:%x:%x:%x" s1 s2 s3 s4 s5 s6 s7 s8+sockAddrBuilder :: SockAddr -> B.Builder+sockAddrBuilder (SockAddrInet _ addr4) = buildIPv4 $ hostAddressToTuple addr4+sockAddrBuilder (SockAddrInet6 _ _ addr6 scope) = case hostAddress6ToTuple addr6 of+    (0, 0, 0, 0, 0, 0xffff, h, l) -> buildIPv4 (hi h, lo h, hi l, lo l)+    _ -> buildIPv6 addr6 <> zone   where-    (s1,s2) = split16 w1-    (s3,s4) = split16 w2-    (s5,s6) = split16 w3-    (s7,s8) = split16 w4-    split16 w = (h1,h2)-      where-        h1 = shift w (-16) .&. 0x0000ffff-        h2 = w .&. 0x0000ffff+    zone+        | scope == 0 = mempty+        | otherwise = B.char7 '%' <> B.word32Dec scope+    hi w = fromIntegral (w `shiftR` 8)+    lo w = fromIntegral w+sockAddrBuilder (SockAddrUnix _) = B.string7 "unknownSocket" -----------------------------------------------------------------+buildIPv4 :: (Word8, Word8, Word8, Word8) -> B.Builder+buildIPv4 (a, b, c, d) = mconcat $ intersperse (B.char7 '.') $ map B.word8Dec [a, b, c, d] --- | Convert 'SockAddr' to 'ByteString'. If the address is---   an IPv4-embedded IPv6 address, the IPv4 is extracted.------ >>> import Network.Socket--- >>> as <- getAddrInfo (Just defaultHints) (Just "localhost") (Just "http")--- >>> map (showSockAddrBS.addrAddress) as--- ["127.0.0.1","::1","fe80:0:0:0:0:0:0:1"]+-- RFC 5952: the longest (first if tied) run of two or more+-- zero fields is compressed to "::".+buildIPv6 :: HostAddress6 -> B.Builder+buildIPv6 addr6 = case longestZeros ws of+    Just (i, n) -> hexes (take i ws) <> B.string7 "::" <> hexes (drop (i + n) ws)+    Nothing -> hexes ws+  where+    (a, b, c, d, e, f, g, h) = hostAddress6ToTuple addr6+    ws = [a, b, c, d, e, f, g, h]+    hexes = mconcat . intersperse (B.char7 ':') . map B.word16Hex -showSockAddrBS :: SockAddr -> ByteString-showSockAddrBS = BS.pack . showSockAddr+-- Returns (start index, length) of the zero run to be compressed.+longestZeros :: [Word16] -> Maybe (Int, Int)+longestZeros ws = case runs of+    [] -> Nothing+    _ -> Just $ foldr1 (\x y -> if snd y > snd x then y else x) runs+  where+    runs = [(i, n) | (i, n) <- go 0 ws, n >= 2]+    go _ [] = []+    go i xs@(0 : _) = let n = length (takeWhile (== 0) xs) in (i, n) : go (i + n) (drop n xs)+    go i (_ : xs) = go (i + 1) xs
+ README.md view
@@ -0,0 +1,10 @@+# sockaddr++Converting the address part of `SockAddr` to `String` or `ByteString`.++## Testing++```+cabal test+cabal repl --with-compiler=doctest+```
sockaddr.cabal view
@@ -1,24 +1,44 @@-Name:                   sockaddr-Version:                0.0.1-Author:                 Kazu Yamamoto <kazu@iij.ad.jp>-Maintainer:             Kazu Yamamoto <kazu@iij.ad.jp>-License:                BSD3-License-File:           LICENSE-Synopsis:               Printing SockAddr-Description:            Printing SockAddr-Category:               Network-Cabal-Version:          >= 1.10-Build-Type:             Simple+cabal-version:      2.4+name:               sockaddr+version:            0.1.0+license:            BSD-3-Clause+license-file:       LICENSE+author:             Kazu Yamamoto <kazu@iij.ad.jp>+maintainer:         Kazu Yamamoto <kazu@iij.ad.jp>+synopsis:           Printing SockAddr+description:+    Converting the address part of 'SockAddr' to 'String' or 'ByteString'. -Library-  Default-Language:     Haskell2010-  GHC-Options:          -Wall-  Exposed-Modules:      Network.SockAddr-  Build-Depends:        base >= 4 && < 5-                      , byteorder-                      , bytestring-                      , network+category:           Network+build-type:         Simple+tested-with:+    GHC == 9.2 || == 9.4 || == 9.6 || == 9.8 || == 9.10 || == 9.12+extra-doc-files:+    ChangeLog.md+    README.md -Source-Repository head-  Type:                 git-  Location:             https://github.com/kazu-yamamoto/sockaddr+source-repository head+    type:     git+    location: https://github.com/kazu-yamamoto/sockaddr++library+    exposed-modules:  Network.SockAddr+    default-language: Haskell2010+    ghc-options:      -Wall+    build-depends:+        base >=4 && <5,+        bytestring >=0.10 && <0.13,+        network >=3.0 && <3.3++test-suite spec+    type:               exitcode-stdio-1.0+    main-is:            Spec.hs+    hs-source-dirs:     test+    default-language:   Haskell2010+    default-extensions: OverloadedStrings+    ghc-options:        -Wall+    build-depends:+        base,+        hspec,+        network,+        sockaddr
+ test/Spec.hs view
@@ -0,0 +1,45 @@+module Main (main) where++import Data.Word (Word16, Word8)+import Network.SockAddr+import Network.Socket+import Test.Hspec++v4 :: (Word8, Word8, Word8, Word8) -> SockAddr+v4 t = SockAddrInet 80 (tupleToHostAddress t)++v6 :: (Word16, Word16, Word16, Word16, Word16, Word16, Word16, Word16) -> SockAddr+v6 t = SockAddrInet6 80 0 (tupleToHostAddress6 t) 0++main :: IO ()+main = hspec $ do+    describe "showSockAddr" $ do+        it "shows IPv4" $ do+            showSockAddr (v4 (127, 0, 0, 1)) `shouldBe` "127.0.0.1"+            showSockAddr (v4 (192, 0, 2, 255)) `shouldBe` "192.0.2.255"+        it "shows IPv4-mapped IPv6 as IPv4" $+            showSockAddr (v6 (0, 0, 0, 0, 0, 0xffff, 0xc000, 0x0201))+                `shouldBe` "192.0.2.1"+        it "shows IPv6 loopback" $+            showSockAddr (v6 (0, 0, 0, 0, 0, 0, 0, 1)) `shouldBe` "::1"+        it "shows IPv6" $+            showSockAddr (v6 (0x2001, 0xdb8, 1, 2, 3, 4, 5, 6))+                `shouldBe` "2001:db8:1:2:3:4:5:6"+        it "compresses zeros (RFC 5952)" $ do+            showSockAddr (v6 (0, 0, 0, 0, 0, 0, 0, 0)) `shouldBe` "::"+            showSockAddr (v6 (0xfe80, 0, 0, 0, 0, 0, 0, 1)) `shouldBe` "fe80::1"+            showSockAddr (v6 (0x2001, 0xdb8, 0, 0, 0, 0, 0, 0)) `shouldBe` "2001:db8::"+            showSockAddr (v6 (0x2001, 0xdb8, 0, 1, 1, 1, 1, 1)) `shouldBe` "2001:db8:0:1:1:1:1:1"+            showSockAddr (v6 (0x2001, 0, 0, 1, 0, 0, 0, 1)) `shouldBe` "2001:0:0:1::1"+            showSockAddr (v6 (0x2001, 0xdb8, 0, 0, 1, 0, 0, 1)) `shouldBe` "2001:db8::1:0:0:1"+            showSockAddr (v6 (0x2001, 0xdb8, 0xABCD, 0, 0, 0, 0, 1)) `shouldBe` "2001:db8:abcd::1"+        it "appends non-zero scope ID" $+            showSockAddr (SockAddrInet6 80 0 (tupleToHostAddress6 (0xfe80, 0, 0, 0, 0, 0, 0, 0x1234)) 4)+                `shouldBe` "fe80::1234%4"+        it "does not expose Unix domain socket path" $+            showSockAddr (SockAddrUnix "/tmp/sock") `shouldBe` "unknownSocket"+    describe "showSockAddrBS" $ do+        it "agrees with showSockAddr" $+            showSockAddrBS (v4 (192, 0, 2, 1)) `shouldBe` "192.0.2.1"+        it "does not expose Unix domain socket path" $+            showSockAddrBS (SockAddrUnix "/tmp/sock") `shouldBe` "unknownSocket"