add link to spec; html build script
[pdns-pipe-nmc.git] / NmcDom.hs
1 {-# LANGUAGE OverloadedStrings #-}
2
3 module NmcDom   ( NmcDom(..)
4                 , NmcRRService(..)
5                 , NmcRRI2p(..)
6                 , NmcRRTls(..)
7                 , NmcRRDs(..)
8                 , emptyNmcDom
9                 , mergeNmcDom
10                 ) where
11
12 import Prelude hiding (length)
13 import Control.Applicative ((<$>), (<*>), empty, pure)
14 import Data.Char
15 import Data.Text (Text, unpack)
16 import Data.List (union)
17 import Data.List.Split
18 import Data.Vector ((!), length, singleton)
19 import Data.Map (Map, unionWith)
20 import qualified Data.HashMap.Strict as H (lookup)
21 import Data.Aeson
22 import Data.Aeson.Types
23
24 -- Variant of Aeson's `.:?` that interprets a String as a
25 -- single-element list, so it is possible to have either
26 --      "ip":["1.2.3.4"]
27 -- or
28 --      "ip":"1.2.3.4"
29 -- with the same result.
30 (.:/) :: (FromJSON a) => Object -> Text -> Parser (Maybe a)
31 obj .:/ key = case H.lookup key obj of
32                Nothing -> pure Nothing
33                Just v  -> case v of
34                         String s -> parseJSON $ Array (singleton v)
35                         _        -> parseJSON v
36
37 class Mergeable a where
38         merge :: a -> a -> a -- bias towads second arg
39
40 instance (Ord k, Mergeable a) => Mergeable (Map k a) where
41         merge mx my = unionWith merge my mx
42
43 -- Alas, the following is not possible in Haskell :-(
44 -- instance Mergeable String where
45 --         merge _ b = b
46
47 instance Mergeable Value where
48         merge _ b = b
49
50 instance Mergeable a => Mergeable (Maybe a) where
51         merge (Just x) (Just y) = Just (merge x y)
52         merge Nothing  (Just y) = Just y
53         merge (Just x) Nothing  = Just x
54         merge Nothing  Nothing  = Nothing
55
56 instance Eq a => Mergeable [a] where
57         merge xs ys = union xs ys
58
59 data NmcRRService = NmcRRService
60                         { srvName       :: String
61                         , srvProto      :: String
62                         , srvPrio       :: Int
63                         , srvWeight     :: Int
64                         , srvPort       :: Int
65                         , srvHost       :: String
66                         } deriving (Show, Eq)
67
68 instance FromJSON NmcRRService where
69         parseJSON (Array a) =
70                 if length a == 6 then NmcRRService
71                         <$> parseJSON (a ! 0)
72                         <*> parseJSON (a ! 1)
73                         <*> parseJSON (a ! 2)
74                         <*> parseJSON (a ! 3)
75                         <*> parseJSON (a ! 4)
76                         <*> parseJSON (a ! 5)
77                 else empty
78         parseJSON _ = empty
79
80 instance Mergeable NmcRRService where
81         merge _ b = b
82
83 data NmcRRI2p = NmcRRI2p
84                         { i2pDestination :: Maybe String
85                         , i2pName        :: Maybe String
86                         , i2pB32         :: Maybe String
87                         } deriving (Show, Eq)
88
89 instance FromJSON NmcRRI2p where
90         parseJSON (Object o) = NmcRRI2p
91                 <$> o .:? "destination"
92                 <*> o .:? "name"
93                 <*> o .:? "b32"
94         parseJSON _ = empty
95
96 instance Mergeable NmcRRI2p where
97         merge _ b = b
98
99 data NmcRRTls = NmcRRTls
100                         { tlsMatchType  :: Int -- 0:exact 1:sha256 2:sha512
101                         , tlsMatchValue :: String
102                         , tlsIncSubdoms :: Int -- 1:enforce on subdoms 0:no
103                         } deriving (Show, Eq)
104
105 instance FromJSON NmcRRTls where
106         parseJSON (Array a) =
107                 if length a == 3 then NmcRRTls
108                         <$> parseJSON (a ! 0)
109                         <*> parseJSON (a ! 1)
110                         <*> parseJSON (a ! 2)
111                 else empty
112         parseJSON _ = empty
113
114 instance Mergeable NmcRRTls where
115         merge _ b = b
116
117 data NmcRRDs = NmcRRDs
118                         { dsKeyTag      :: Int
119                         , dsAlgo        :: Int
120                         , dsHashType    :: Int
121                         , dsHashValue   :: String
122                         } deriving (Show, Eq)
123
124 instance FromJSON NmcRRDs where
125         parseJSON (Array a) =
126                 if length a == 4 then NmcRRDs
127                         <$> parseJSON (a ! 0)
128                         <*> parseJSON (a ! 1)
129                         <*> parseJSON (a ! 2)
130                         <*> parseJSON (a ! 3)
131                 else empty
132         parseJSON _ = empty
133
134 instance Mergeable NmcRRDs where
135         merge _ b = b
136
137 data NmcDom = NmcDom    { domService     :: Maybe [NmcRRService]
138                         , domIp          :: Maybe [String]
139                         , domIp6         :: Maybe [String]
140                         , domTor         :: Maybe String
141                         , domI2p         :: Maybe NmcRRI2p
142                         , domFreenet     :: Maybe String
143                         , domAlias       :: Maybe String
144                         , domTranslate   :: Maybe String
145                         , domEmail       :: Maybe String
146                         , domLoc         :: Maybe String
147                         , domInfo        :: Maybe Value
148                         , domNs          :: Maybe [String]
149                         , domDelegate    :: Maybe String
150                         , domImport      :: Maybe [String]
151                         , domMap         :: Maybe (Map String NmcDom)
152                         , domFingerprint :: Maybe [String]
153                         , domTls         :: Maybe (Map String
154                                                     (Map String [NmcRRTls]))
155                         , domDs          :: Maybe [NmcRRDs]
156                         , domMx          :: Maybe [String] -- Synthetic
157                         , domSrv         :: Maybe [String] -- Synthetic
158                         } deriving (Show, Eq)
159
160 instance FromJSON NmcDom where
161         -- Wherever we expect a domain object, there may be a string
162         -- containing IPv4 address. Interpret it as such.
163         -- Question: shall we try to recognize IPv6 addresses too?
164         parseJSON (String s) =
165                  return $ if isIPv4 s'
166                             then emptyNmcDom { domIp = Just [s'] }
167                             else emptyNmcDom
168                           where
169                             s' = unpack s
170                             isIPv4 x = all isNibble $ splitOn "." x
171                             isNibble x =
172                               if all isDigit x then (read x :: Int) < 256
173                               else False
174         parseJSON (Object o) = NmcDom
175                 <$> o .:? "service"
176                 <*> o .:/ "ip"
177                 <*> o .:/ "ip6"
178                 <*> o .:? "tor"
179                 <*> o .:? "i2p"
180                 <*> o .:? "freenet"
181                 <*> o .:? "alias"
182                 <*> o .:? "translate"
183                 <*> o .:? "email"
184                 <*> o .:? "loc"
185                 <*> o .:? "info"
186                 <*> o .:/ "ns"
187                 <*> o .:? "delegate"
188                 <*> o .:/ "import"
189                 <*> o .:? "map"
190                 <*> o .:/ "fingerprint"
191                 <*> o .:? "tls"
192                 <*> o .:? "ds"
193                 <*> return Nothing -- domMx not parsed
194                 <*> return Nothing -- domSrv not parsed
195         parseJSON _ = empty
196
197 instance Mergeable NmcDom where
198         merge sub dom = dom     { domService =     mergelm domService
199                                 , domIp =          mergelm domIp
200                                 , domIp6 =         mergelm domIp6
201                                 , domTor =         choose  domTor
202                                 , domI2p =         mergelm domI2p
203                                 , domFreenet =     choose  domFreenet
204                                 , domAlias =       choose  domAlias
205                                 , domTranslate =   choose  domTranslate
206                                 , domEmail =       choose  domEmail
207                                 , domLoc =         choose  domLoc
208                                 , domInfo =        mergelm domInfo
209                                 , domNs =          mergelm domNs
210                                 , domDelegate =    mergelm domDelegate
211                                 , domImport =      mergelm domImport
212                                 , domMap =         mergelm domMap
213                                 , domFingerprint = mergelm domFingerprint
214                                 , domTls =         mergelm domTls
215                                 , domDs =          mergelm domDs
216                                 , domMx =          mergelm domMx
217                                 , domSrv =         mergelm domSrv
218                                 }
219           where
220                 mergelm x = merge (x sub) (x dom)
221 -- Because it is not possible to define instance of merge for Strings,
222 -- we have to treat string elements separately, otherwise strings are
223 -- 'unioned' along with the rest of lists. Ugly, but alternatives are worse.
224                 choose field = case field dom of
225                         Nothing -> field sub
226                         Just x  -> Just x
227
228 mergeNmcDom :: NmcDom -> NmcDom -> NmcDom
229 mergeNmcDom = merge
230
231 emptyNmcDom = NmcDom Nothing Nothing Nothing Nothing Nothing Nothing
232                      Nothing Nothing Nothing Nothing Nothing Nothing
233                      Nothing Nothing Nothing Nothing Nothing Nothing
234                      Nothing Nothing