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 +15/−0
- Network/SockAddr.hs +74/−56
- README.md +10/−0
- sockaddr.cabal +42/−22
- test/Spec.hs +45/−0
+ 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"