summaryrefslogtreecommitdiff
path: root/src/Network/DHT/Mainline.hs
blob: 2b7db3c7cbfe3f5aa87823f92033ea9a0890a8dc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances          #-}
{-# LANGUAGE DeriveDataTypeable         #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module Network.DHT.Mainline where

import Data.Digest.CRC32C
import Control.Applicative
import Data.Maybe
import Data.Monoid
import Data.Word
import Data.IP
import Data.BEncode                    as BE
import Data.Bits
import Data.ByteString                 (ByteString)
import Data.ByteString.Base16          as Base16
import qualified Data.ByteString.Char8 as Char8
import qualified Data.ByteString       as BS
import qualified Data.ByteString.Lazy  as L
import Data.Default
import Data.LargeWord
import Data.Serialize                  as S
import Data.String
import Data.Typeable
import Network.KRPC.Message            as KRPC
import Network.RPC as RPC
import Text.PrettyPrint                as PP hiding ((<>))
import Text.PrettyPrint.HughesPJClass  hiding (($$), (<>))

nodeIdSize = finiteBitSize (undefined :: NodeId KMessageOf) `div` 8

instance BEncode (NodeId KMessageOf) where
    toBEncode (NodeId w) = toBEncode $ S.encode w
    fromBEncode bval = fromBEncode bval >>= S.decode

-- instance BEncode NodeId where TODO

-- TODO: put this somewhere appropriate
instance (Serialize a, Serialize b) => Serialize (LargeKey a b) where
   put (LargeKey lo hi) = put hi >> put lo
   get = flip LargeKey <$> get <*> get

instance Serialize (NodeId KMessageOf) where
  get = NodeId <$> get
  {-# INLINE get #-}
  put (NodeId bs) = put bs
  {-# INLINE put #-}

-- | ASCII encoded.
instance IsString (NodeId KMessageOf) where
  fromString str
    | length str == nodeIdSize     = NodeId (either error id $ S.decode (fromString str :: ByteString))
    | length str == 2 * nodeIdSize = NodeId (either error id $ S.decode (fst $ Base16.decode $ fromString str))
    |           otherwise = error "fromString: invalid NodeId length"
  {-# INLINE fromString #-}

-- | Meaningless node id, for testing purposes only.
instance Default (NodeId KMessageOf) where
  def = NodeId 0

-- | base16 encoded.
instance Pretty (NodeId KMessageOf) where
  pPrint (NodeId nid) = PP.text $ Char8.unpack $ Base16.encode $ S.encode nid

-- | KRPC 'compact list' compatible encoding: contact information for
-- nodes is encoded as a 26-byte string. Also known as "Compact node
-- info" the 20-byte Node ID in network byte order has the compact
-- IP-address/port info concatenated to the end.
instance Serialize a => Serialize (NodeInfo KMessageOf a ()) where
  get = (\a b -> NodeInfo a b ()) <$> get <*> get
  put NodeInfo {..} = put nodeId >> put nodeAddr

instance Pretty ip => Pretty (NodeInfo KMessageOf ip ()) where
  pPrint NodeInfo {..} = pPrint nodeId <> "@(" <> pPrint nodeAddr <> ")"

instance Pretty ip => Pretty [NodeInfo KMessageOf ip ()] where
  pPrint = PP.vcat . PP.punctuate "," . map pPrint


-- | Yields all 8 DHT neighborhoods available to you given a particular ip
-- address.
bep42s :: Address a => a -> NodeId KMessageOf -> [NodeId KMessageOf]
bep42s addr (NodeId r) = mapMaybe (bep42 addr) rs
 where
    rs = map (NodeId . change3bits r) [0..7]

-- change3bits :: ByteString -> Word8 -> ByteString
-- change3bits bs n = BS.snoc (BS.init bs) (BS.last bs .&. 0xF8 .|. n)

change3bits :: (Num b, Bits b) => b -> b -> b
change3bits bs n = (bs .&. complement 7) .|. n

-- | Modifies a purely random 'NodeId' to one that is related to a given
-- routable address in accordance with BEP 42.
bep42 :: Address a => a -> NodeId KMessageOf -> Maybe (NodeId KMessageOf)
bep42 addr (NodeId r)
 | Just ip <- fmap S.encode (fromAddr addr :: Maybe IPv4)
              <|> fmap S.encode (fromAddr addr :: Maybe IPv6)
    = genBucketSample' retr (NodeId $ crc $ applyMask ip) (3,0x07,0)
 | otherwise
    = Nothing
 where
    ip4mask = "\x03\x0f\x3f\xff" :: ByteString
    ip6mask = "\x01\x03\x07\x0f\x1f\x3f\x7f\xff" :: ByteString
    nbhood_select = (fromIntegral r :: Word8) .&. 7
    retr n = pure $ BS.drop (nodeIdSize - n) $ S.encode r
    crc = flip shiftL (finiteBitSize (NodeId undefined) - 32) . fromIntegral . crc32c . BS.pack
    applyMask ip = case BS.zipWith (.&.) msk ip of
                    (b:bs) -> (b .|. shiftL nbhood_select 5) : bs
                    bs     -> bs
        where msk | BS.length ip == 4  =  ip4mask
                  | otherwise          =  ip6mask



instance Envelope KMessageOf where
    type TransactionID KMessageOf = KRPC.TransactionId

    -- | Each node has a globally unique identifier known as the \"node
    -- ID.\"
    --
    --   Normally, /this/ node id should be saved between invocations
    --   of the client software.
    newtype NodeId KMessageOf = NodeId Word160
      deriving (Show, Eq, Ord, Typeable, Bits, FiniteBits)

    envelopePayload (Q q) = queryArgs q
    envelopePayload (R r) = respVals r
    envelopePayload (E _) = error "TODO: messagePayload for KError"

    envelopeTransaction (Q q) = queryId q
    envelopeTransaction (R r) = respId r
    envelopeTransaction (E e) = errorId e

    envelopeClass (Q _) = Query
    envelopeClass (R _) = Response
    envelopeClass (E _) = Error

    buildReply self addr qry response =
        (R (KResponse response (envelopeTransaction qry) (Just $ ReflectedIP addr)))

instance WireFormat BValue KMessageOf where
    type SerializableTo BValue = BEncode
    type CipherContext BValue KMessageOf = ()

    decodeHeaders _ bs = BE.decode bs >>= BE.fromBEncode
    decodePayload kmsg = mapM BE.fromBEncode kmsg

    encodeHeaders _ kmsg = L.toStrict $ BE.encode kmsg
    encodePayload msg = fmap BE.toBEncode msg