├── Makefile
├── README
├── Soutei.cabal
├── Soutei
├── Assertions.hs
├── Check.hs
├── Client.hs
├── FBackTrack.hs
├── FBackTrackT.hs
├── GBBFS.hs
├── GBBFS1.hs
├── Lirs.hs
├── Logic.hs
├── Parsec.hs
├── Sexpr.hs
├── Soutei.hs
└── Syntax.hs
├── demo
├── metcast-channels
│ ├── Makefile
│ ├── doc
│ │ ├── 00.html
│ │ ├── 01.html
│ │ ├── 06.html
│ │ ├── 07.html
│ │ ├── 08.html
│ │ ├── 09.html
│ │ ├── 11.html
│ │ ├── Makefile
│ │ ├── index.html
│ │ ├── notes
│ │ ├── soutei.fig
│ │ └── soutei.png
│ ├── soutei-metcast-demo-init.txt
│ └── soutei-metcast-tests.hs
└── rbac
│ ├── local
│ ├── SOUTEI
│ └── assertions
│ │ ├── TPS-report-owner
│ │ ├── app-owner
│ │ ├── hr
│ │ └── system
│ ├── run-test.sh
│ └── system
├── doc
├── Auth-transport.html
├── Auth-transport.scm
├── Auth-use-cases.html
├── Auth-use-cases.scm
├── Authorization.html
├── Authorization.scm
├── index.html
└── soutei-sourceforge.scm
├── soutei-cli.hs
├── soutei-pipelined.hs
└── soutei-server.hs
/Makefile:
--------------------------------------------------------------------------------
1 | # Compiling Soutei library and sample applications
2 |
3 | GHC := ghc
4 | GHC_MAKE = $(GHC) --make
5 | GHC_OPTS := -O2
6 | GHC_INCLUDES = `$(GHC) --print-libdir`/include
7 |
8 |
9 | PROGS := soutei-server soutei-cli
10 |
11 | SOURCES := $(wildcard Soutei/*.hs)
12 | MODULES := $(subst /,.,$(patsubst %.hs,%,$(SOURCES)))
13 | OBJECTS := $(patsubst %.hs,$(BUILD_DIR)/objs/%.o,$(SOURCES))
14 | HC_CMD := $(GHC_MAKE) $(GHC_OPTS) -I$(GHC_INCLUDES)
15 |
16 | all: $(PROGS)
17 |
18 | test:
19 | cd demo/metcast-channels/ && $(MAKE) test
20 |
21 | clean::
22 | cd demo/metcast-channels/ && $(MAKE) clean
23 |
24 |
25 | %: %.hs
26 | $(HC_CMD) -o $@ $*.hs
27 |
28 | clean::
29 | rm -f *.hi *.o $(PROGS)
30 | rm -f Soutei/*.hi
31 | rm -f Soutei/*.o
32 |
33 | soutei-pipelined: soutei-pipelined.hs
34 | ghc -O2 --make \
35 | soutei-pipelined.hs -o $@
36 |
37 |
38 | # The following targets require the FCGI library or Metcast
39 | # They are present for information only
40 | #
41 | # FCGIP=XXXSet-FGCI-library-here
42 | # soutei-fcgi: soutei-fcgi.hs
43 | # ghc -O2 --make -I$(FCGIP)/fcgi/include -i$(FCGIP) \
44 | # $(FCGIP)/fcgi/fcgiapp.o $(FCGIP)/fcgi/os_unix.o \
45 | # soutei-fcgi.hs -o $@
46 | #
47 | # soutei-mux: soutei-mux.hs
48 | # ghc -O2 --make -i$(FCGIP) $(FCGIP)/System/sys_open.o \
49 | # soutei-mux.hs -o $@
50 |
--------------------------------------------------------------------------------
/README:
--------------------------------------------------------------------------------
1 | Soutei, a logic-based trust-management system
2 | Andrew Pimlott and Oleg Kiselyov
3 |
4 | Soutei is a trust-management system, a dialect of Binder, for access
5 | control in distributed systems. Soutei policies and credentials are
6 | written in a declarative logic-based security language and thus
7 | constitute distributed logic programs. Soutei policies are modular,
8 | concise, and readable. They support policy verification, and, despite
9 | the simplicity of the language, express role- and attribute-based
10 | access control lists, and conditional delegation.
11 |
12 |
13 | Documentation:
14 | - Andrew Pimlott and Oleg Kiselyov:
15 | Soutei, a logic-based trust-management system (system description)
16 | The paper presented at FLOPS 2006, 8th International Symposium on
17 | Functional and Logic Programming. Fuji-Susono, Japan, April 24-26, 2006.
18 | The paper is published in Springer's Lecture Notes in Computer Science 3945,
19 | pp. 130-145, 2006.
20 | http://dx.doi.org/10.1007/11737414
21 | http://okmij.org/ftp/papers/Soutei.pdf
22 |
23 | - Specification, use cases and design notes
24 | http://soutei.sf.net/doc/
25 |
26 |
27 | The source code is under GPL 2 license.
28 |
29 | MANIFEST
30 |
31 | Soutei/ The Soutei library (The Soutei `package')
32 |
33 | Sample Soutei applications
34 | soutei-server.hs Soutei TCP server
35 | soutei-cli.hs A bare-bone client for the soutei-server
36 | (useful for testing)
37 |
38 | Demonstrations
39 | demo/rbac/ Modelling access control lists and role-based
40 | access control in Soutei
41 | See the Soutei paper, section `Soutei by example'
42 | demo/rbac/run-test.sh The file to run the tests
43 | demo/rbac/demo/rbac/system The system policy
44 | demo/rbac/demo/rbac/local/assertions/ Other policies (HR, app-owner, etc)
45 |
46 |
47 | demo/metcast-channels/ Extensive regression test suite for Soutei along
48 | the lines of an actual demonstration,
49 | given in June 2005.
50 | See Section `A real-life use case' in the Soutei
51 | paper.
52 | demo/metcast-channels/Makefile do make test
53 | demo/metcast-channels/doc/ The slides of the test and the presentation
54 | script
55 |
--------------------------------------------------------------------------------
/Soutei.cabal:
--------------------------------------------------------------------------------
1 | Cabal-Version: >= 1.14
2 | Name: Soutei
3 | Version: 2.1
4 | Synopsis: a logic-based trust-management system
5 | Description:
6 | SOUTEI is a trust-management system, a dialect of Binder, for access
7 | control in distributed systems. Soutei policies and credentials are written
8 | in a declarative logic-based security language and thus constitute
9 | distributed logic programs. Soutei policies are modular, concise, and
10 | readable. They support policy verification, and, despite the simplicity of
11 | the language, express role- and attribute-based access control lists, and
12 | conditional delegation.
13 | http://soutei.sourceforge.net/
14 | http://github.com/mstone/soutei
15 | License: GPLv2
16 | Author: Oleg Kiselyov , Andrew Pimlott
17 | Maintainer: Michael Stone
18 | Stability: experimental
19 | Build-Type: Simple
20 |
21 | Library
22 | Exposed-Modules: Soutei.Soutei
23 | Soutei.Assertions,
24 | Soutei.Check,
25 | Soutei.Client,
26 | Soutei.FBackTrack,
27 | Soutei.FBackTrackT,
28 | Soutei.GBBFS,
29 | Soutei.GBBFS1,
30 | Soutei.Lirs,
31 | Soutei.Logic,
32 | Soutei.Parsec,
33 | Soutei.Sexpr,
34 | Soutei.Syntax
35 | Build-Depends: base >= 4.0,
36 | containers -any,
37 | directory -any,
38 | mtl -any,
39 | network -any,
40 | old-locale -any,
41 | old-time -any,
42 | parallel -any,
43 | parsec -any,
44 | QuickCheck == 2.*,
45 | random -any
46 | GHC-Options: -Wall -funbox-strict-fields
47 | GHC-Prof-Options: -prof -auto-all
48 | Default-Language: Haskell2010
49 |
50 | Executable soutei-server
51 | Main-Is: soutei-server.hs
52 | Other-Modules: Soutei.Soutei
53 | Build-Depends: base >= 4.0,
54 | containers -any,
55 | directory -any,
56 | mtl -any,
57 | network -any,
58 | old-locale -any,
59 | old-time -any,
60 | parallel -any,
61 | parsec -any,
62 | QuickCheck == 2.*,
63 | random -any
64 | GHC-Options: -Wall -funbox-strict-fields -rtsopts -threaded
65 | GHC-Prof-Options: -prof -auto-all
66 | Default-Language: Haskell2010
67 |
68 | Executable soutei-cli
69 | Main-Is: soutei-server.hs
70 | Other-Modules: Soutei.Soutei
71 | Build-Depends: base >= 4.0,
72 | containers -any,
73 | directory -any,
74 | mtl -any,
75 | network -any,
76 | old-locale -any,
77 | old-time -any,
78 | parallel -any,
79 | parsec -any,
80 | QuickCheck == 2.*,
81 | random -any
82 | GHC-Options: -Wall -funbox-strict-fields -rtsopts -threaded
83 | GHC-Prof-Options: -prof -auto-all
84 | Default-Language: Haskell2010
85 |
86 | Test-Suite soutei-metcast-tests
87 | Type: exitcode-stdio-1.0
88 | Main-is: demo/metcast-channels/soutei-metcast-tests.hs
89 | Build-Depends: base >= 4.0,
90 | containers -any,
91 | directory -any,
92 | mtl -any,
93 | network -any,
94 | old-locale -any,
95 | old-time -any,
96 | parallel -any,
97 | parsec -any,
98 | QuickCheck == 2.*,
99 | random -any
100 | GHC-Options: -Wall -funbox-strict-fields -rtsopts -threaded
101 | Default-Language: Haskell2010
102 |
--------------------------------------------------------------------------------
/Soutei/Assertions.hs:
--------------------------------------------------------------------------------
1 | -- $HeadURL: https://svn.metnet.navy.mil/svn/metcast/Mserver/trunk/soutei/haskell/Soutei/Assertions.hs $
2 | -- $Id: Assertions.hs 2947 2012-09-14 08:26:08Z oleg.kiselyov $
3 | -- svn propset svn:keywords "HeadURL Id" filename
4 |
5 | -- Routines for loading, storing, and running queries against assertions.
6 | -- Primary interface is imperative because the memory index must be kept in
7 | -- sync with the disk; functional alternatives end in F.
8 |
9 | module Soutei.Assertions (
10 | Assertions, AssertionsF,
11 | fromDataDir, fromDataDirWithActions,
12 | loadSysCtx, putCtx, putCtxRules, query, queryResults,
13 | emptyF, loadSysCtxF, putCtxF, putCtxRulesF, queryF, queryResultsF
14 | ) where
15 |
16 | import Prelude hiding (lookup)
17 | import Control.Monad
18 | import Control.Monad.Identity
19 | import Control.Monad.Trans
20 | import Data.Char (ord, isAlphaNum)
21 | import Data.Map (Map, empty, lookup, insert, delete, union, fromList)
22 | import Data.Maybe (fromMaybe, fromJust)
23 | import Data.Version (Version(..), showVersion, parseVersion)
24 | import Numeric (showHex)
25 | import System.Directory
26 | import System.IO
27 | import System.IO.Error as IO
28 | import System.IO.Unsafe (unsafePerformIO)
29 | import Text.ParserCombinators.ReadP (readP_to_S, string)
30 |
31 | import Soutei.Check
32 | import Soutei.FBackTrackT (Stream, runM)
33 | import qualified Soutei.Lirs as Lirs
34 | import Soutei.Logic (Query, runQuery, false,
35 | CtxQuery, PredQuery, ArgsQuery,
36 | prove, proveResults, clausesQuery, funcQuery)
37 | import Soutei.Parsec (parseM)
38 | import Soutei.Soutei
39 | import Soutei.Syntax (top, assertionL)
40 |
41 | data Assertions = Assertions {
42 | put :: Const -> CtxIndex IO -> Maybe String -> IO (),
43 | get :: Const -> IO (CtxIndex IO),
44 | app :: AppPrims IO
45 | }
46 | data AssertionsF = AssertionsF {
47 | index :: Index,
48 | appF :: AppPrims Identity
49 | }
50 | type Index = Map Const (CtxIndex Identity)
51 | type CtxIndex m = Map Pred (ArgsQuery m)
52 | type AppPrims m = Map Pred (ArgsQuery m, [ArgMode])
53 |
54 | fromDataDir :: (IOError -> IO a) -> FilePath -> IO Assertions
55 | fromDataDir err dataDir = do
56 | initDataDir dataDir
57 | let storage = Lirs.Storage (storeA dataDir)
58 | (\ctx -> do s <- loadA dataDir ctx
59 | rules <- parseA "data store" s
60 | uncheckedIndexRules rules
61 | `IO.catch` \e -> err e >> return empty)
62 | lirs <- Lirs.new storage 990 1000
63 | return (Assertions (Lirs.put lirs) (Lirs.get lirs) stdAppPrims)
64 |
65 | fromDataDirWithActions ::
66 | (IOError -> IO a) ->
67 | (Const -> IO (Maybe String)) -> -- read action
68 | (Const -> (Maybe String) -> IO ()) -> -- write action
69 | IO Assertions
70 | fromDataDirWithActions err readA writeA = do
71 | let storage = Lirs.Storage (writeA)
72 | (\ctx -> do s <- readA ctx
73 | rules <- parseA "data store" s
74 | uncheckedIndexRules rules
75 | `IO.catch` \e -> err e >> return empty)
76 | lirs <- Lirs.new storage 990 1000
77 | return (Assertions (Lirs.put lirs) (Lirs.get lirs) stdAppPrims)
78 |
79 | emptyF :: AssertionsF
80 | emptyF = AssertionsF empty stdAppPrims
81 |
82 | loadSysCtx :: FilePath -> Assertions -> IO ()
83 | loadSysCtx initFile as = do
84 | s <- readInitFile initFile
85 | putCtx initFile sysCtx (Just s) as
86 |
87 | loadSysCtxF :: FilePath -> AssertionsF -> IO AssertionsF
88 | loadSysCtxF initFile as = do
89 | s <- readInitFile initFile
90 | putCtxF initFile sysCtx (Just s) as
91 |
92 | readInitFile :: FilePath -> IO String
93 | readInitFile initFile =
94 | doesFileExist initFile >>= \b -> if b
95 | then readFile initFile
96 | else fail ("system-assertion " ++ initFile ++ " not found")
97 |
98 | -- Parse and add (compile and store) an assertion for a context
99 | putCtx :: String -> Const -> Maybe String -> Assertions -> IO ()
100 | putCtx source ctx s as = do rules <- parseA source s
101 | putCtx' ctx s rules as
102 |
103 | putCtxRules :: Const -> Maybe [SynRule] -> Assertions -> IO ()
104 | putCtxRules ctx rules as = let s = liftM (concatMap show) rules
105 | in putCtx' ctx s (fromMaybe [] rules) as
106 |
107 | putCtx' :: Const -> Maybe String -> [SynRule] -> Assertions -> IO ()
108 | putCtx' ctx s rules as = do
109 | ctxIndex <- indexRules (fmap snd (app as)) rules
110 | put as ctx ctxIndex s
111 |
112 | putCtxF :: Monad m => String -> Const -> (Maybe String) ->
113 | AssertionsF -> m AssertionsF
114 | putCtxF source ctx s as = do rules <- parseA source s
115 | putCtxF' ctx rules as
116 |
117 | putCtxRulesF :: Monad m => Const -> Maybe [SynRule] -> AssertionsF ->
118 | m AssertionsF
119 | putCtxRulesF ctx rules as = putCtxF' ctx (fromMaybe [] rules) as
120 |
121 | putCtxF' :: Monad m => Const -> [SynRule] -> AssertionsF -> m AssertionsF
122 | putCtxF' ctx [] as = return as{index=delete ctx (index as)}
123 | putCtxF' ctx rules as = do ctxIdx <- indexRules (fmap snd (appF as)) rules
124 | return as{index=insert ctx ctxIdx (index as)}
125 |
126 | parseA :: Monad m => String -> Maybe String -> m [SynRule]
127 | parseA source Nothing = return []
128 | parseA source (Just s) = parseM (top assertionL) source s
129 |
130 | indexRules :: (Monad m, Monad m') => CtxModes -> [SynRule] -> m (CtxIndex m')
131 | indexRules appModes rules = liftM compilePreds (checkRules appModes rules)
132 |
133 | uncheckedIndexRules :: (Monad m, Monad m') => [SynRule] -> m (CtxIndex m')
134 | uncheckedIndexRules facts = liftM compilePreds (groupRules facts)
135 |
136 | compilePreds :: Monad m => Map Pred [SynRule] -> CtxIndex m
137 | compilePreds preds = ctxIdx where
138 | ctxIdx = fmap compile preds
139 | compile clauses = clausesQuery clauses predQuery
140 | predQuery pred = fromJust (lookup pred ctxIdx)
141 |
142 | query :: Maybe Int -> Assertions -> [Fact] -> SynHeadAtom -> IO Bool
143 | query t as facts goal = mkCtxQ as facts >>= query' t goal
144 |
145 | queryF :: Monad m => Maybe Int -> AssertionsF -> [Fact] -> SynHeadAtom -> m Bool
146 | queryF t as facts goal = liftM (runIdentity . query' t goal) (mkCtxQF as facts)
147 |
148 | queryResults :: Maybe Int -> Assertions -> [Fact] -> SynHeadAtom -> IO [Fact]
149 | queryResults t as facts goal = mkCtxQ as facts >>= queryResults' t goal
150 |
151 | queryResultsF :: Monad m => Maybe Int -> AssertionsF -> [Fact] -> SynHeadAtom ->
152 | m [Fact]
153 | queryResultsF t as facts goal = liftM (runIdentity . queryResults' t goal)
154 | (mkCtxQF as facts)
155 |
156 | query' :: Monad m => Maybe Int -> SynHeadAtom -> CtxQuery m -> m Bool
157 | query' t goal ctxQ = let q = prove sysCtx goal ctxQ
158 | in liftM (not . null) (runM t (Just 1) (runQuery q))
159 |
160 | queryResults' :: Monad m => Maybe Int -> SynHeadAtom -> CtxQuery m -> m [Fact]
161 | queryResults' t goal ctxQ = let q = proveResults sysCtx goal ctxQ
162 | in liftM (map fst) (runM t Nothing (runQuery q))
163 |
164 | mkCtxQ :: Monad m => Assertions -> [Fact] -> m (CtxQuery IO)
165 | mkCtxQ as facts = do appIdx <- mkAppIdx (fmap fst (app as)) facts
166 | return (asQuery as appIdx)
167 |
168 | mkCtxQF :: Monad m => AssertionsF -> [Fact] -> m (CtxQuery Identity)
169 | mkCtxQF as facts = do appIdx <- mkAppIdx (fmap fst (appF as)) facts
170 | return (idxQuery (insert appCtx appIdx (index as)))
171 |
172 | mkAppIdx :: (Monad m, Monad m') => CtxIndex m' -> [Fact] -> m (CtxIndex m')
173 | mkAppIdx appPrims facts = let rules = map (flip Rule [] . factToAtom) facts
174 | in liftM (union appPrims) (uncheckedIndexRules rules)
175 |
176 | asQuery :: Assertions -> CtxIndex IO -> CtxQuery IO
177 | asQuery as appIdx ctx pred args frame
178 | | ctx == appCtx = ctxIdxQ appIdx
179 | | otherwise = \s -> do ctxIdx <- liftIO (get as ctx)
180 | ctxIdxQ ctxIdx s
181 | where
182 | ctxIdxQ ctxIdx = case lookup pred ctxIdx of
183 | Just q -> q args (asQuery as appIdx) frame
184 | Nothing -> false
185 |
186 | idxQuery :: Index -> CtxQuery Identity
187 | idxQuery idx ctx pred args frame = fromMaybe false $ do
188 | ctxIdx <- lookup ctx idx
189 | q <- lookup pred ctxIdx
190 | return (q args (idxQuery idx) frame)
191 |
192 | -- primitives
193 |
194 | ipofQuery :: Monad m => (ArgsQuery m, [ArgMode])
195 | ipofQuery = (funcQuery ipof', [R L, R S]) where
196 | ipof' [SIP4Addr addr, SIP4Net net] = ip4of addr net
197 | ipof' _ = False
198 |
199 | neqQuery :: Monad m => (ArgsQuery m, [ArgMode])
200 | neqQuery = (funcQuery (\[x1, x2] -> x1 /= x2), [R S, R S])
201 |
202 | stdAppPrims :: Monad m => AppPrims m
203 | stdAppPrims = fromList [(Pred "ip-of" 2, ipofQuery),
204 | (Pred "neq" 2, neqQuery)]
205 |
206 | -- storage
207 |
208 | initDataDir :: FilePath -> IO ()
209 | initDataDir dataDir = let markerFile = markerFilename dataDir in
210 | doesDirectoryExist dataDir >>= \b -> if b
211 | then doesFileExist markerFile >>= \b -> if b
212 | then do marker <- readFile markerFile
213 | checkMarker dataDir marker
214 | return ()
215 | else do ents <- getDirectoryContents dataDir
216 | if (null (filter (\ent -> ent /= "." && ent /= "..") ents))
217 | then do writeFile markerFile marker
218 | createDirectory (assertionsDir dataDir)
219 | else fail (dataDir ++ " is not a Soutei data directory")
220 | else fail (dataDir ++ " is not a directory")
221 |
222 | storeA :: FilePath -> Const -> (Maybe String) -> IO ()
223 | storeA dataDir ctx = store where
224 | store Nothing = do b <- doesFileExist ctxFile
225 | when b (removeFile ctxFile)
226 | store (Just s) = do writeFile tmpFile s
227 | renameFile tmpFile ctxFile
228 | tmpFile = tmpFilename dataDir
229 | ctxFile = ctxFilename dataDir ctx
230 |
231 | loadA :: FilePath -> Const -> IO (Maybe String)
232 | loadA dataDir ctx = let ctxFile = ctxFilename dataDir ctx in do
233 | b <- doesFileExist ctxFile
234 | if b
235 | then liftM Just (readFile ctxFile)
236 | else return Nothing
237 |
238 | marker = magic ++ showVersion dataDirVersion ++ "\n"
239 | checkMarker dataDir s = case readP_to_S p s of
240 | [(Version [major, minor] [], "")] ->
241 | if major > dataDirMajor
242 | then fail (dataDir ++ " is newer than Soutei")
243 | else return (major, minor)
244 | _ -> fail (dataDir ++ " is not a Soutei data directory (corrupt SOUTEI)")
245 | where
246 | p = do string magic
247 | v <- parseVersion
248 | string "\n"
249 | return v
250 | magic = "Soutei data directory, version "
251 | dataDirVersion = Version [dataDirMajor, dataDirMinor] []
252 | dataDirMajor = 0
253 | dataDirMinor = 0
254 |
255 | markerFilename :: FilePath -> FilePath
256 | markerFilename dataDir = dataDir ++ "/SOUTEI"
257 |
258 | tmpFilename :: FilePath -> FilePath
259 | tmpFilename dataDir = dataDir ++ "/.tmp"
260 |
261 | ctxFilename :: FilePath -> Const -> FilePath
262 | ctxFilename dataDir ctx = assertionsDir dataDir ++ "/" ++ encode (show ctx)
263 |
264 | assertionsDir :: FilePath -> FilePath
265 | assertionsDir dataDir = dataDir ++ "/assertions"
266 |
267 | encode :: String -> String
268 | encode = concatMap encode' where
269 | encode' ch | isAlphaNum ch || ch `elem` "!#$&'()+,-.;=@_" = [ch]
270 | | otherwise = '%' : showHex (ord ch) ";"
271 |
272 |
--------------------------------------------------------------------------------
/Soutei/Check.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies #-}
2 | {-# LANGUAGE TypeSynonymInstances, FlexibleInstances #-}
3 | -- $HeadURL: https://svn.metnet.navy.mil/svn/metcast/Mserver/trunk/soutei/haskell/Soutei/Check.hs $
4 | -- $Id: Check.hs 2947 2012-09-14 08:26:08Z oleg.kiselyov $
5 | -- svn propset svn:keywords "HeadURL Id" filename
6 |
7 | module Soutei.Check (
8 | ArgMode(..), TermMode(..), CtxModes,
9 | checkRules, groupRules
10 | ) where
11 |
12 | import Prelude hiding (lookup)
13 | import Control.Monad.Reader
14 | import Control.Monad.Writer
15 | import Data.List (transpose)
16 | import Data.Map (Map, empty, lookup, member, insert, insertWith, fromList)
17 |
18 | import Soutei.Parsec (uncheckedParse)
19 | import Soutei.Soutei
20 | import Soutei.Syntax
21 |
22 | type CtxModes = Map Pred [ArgMode]
23 |
24 | checkRules :: Monad m => CtxModes -> [SynRule] -> m (Map Pred [SynRule])
25 | checkRules appModes rules = do (m, preds) <- checkErrs (groupRules' rules)
26 | checkErrs (checkUndefined m preds)
27 | checkErrs (modeCheck appModes preds)
28 | return m
29 |
30 | groupRules :: Monad m => [SynRule] -> m (Map Pred [SynRule])
31 | groupRules rules = liftM fst (checkErrs (groupRules' rules))
32 |
33 | groupRules' :: [SynRule] -> Check (Map Pred [SynRule], [(Pred, [SynRule])])
34 | groupRules' rules = group (empty, []) rules where
35 | group a [] = return a
36 | group (m, preds) (rule@(Rule (Atom _ pred _) _) : rules) = do
37 | when (member pred m)
38 | (err ("multiple definitions of " ++ show pred))
39 | groupPred (rule:) rules
40 | where
41 | groupPred l (rule@(Rule (Atom _ pred' _) _) : rules)
42 | | pred' == pred = groupPred (l . (rule:)) rules
43 | groupPred l rules = let clauses = l []
44 | in group (insert pred clauses m,
45 | (pred, clauses):preds) rules
46 |
47 | checkUndefined :: Map Pred [SynRule] -> [(Pred, [SynRule])] -> Check ()
48 | checkUndefined m preds = mapEnterClauseM_ (mapEnterM_ checkRule) preds where
49 | checkRule (Rule h b) = mapEnterM_ checkAtom b
50 | checkAtom (Atom NothingCtx pred _) =
51 | when (not (member pred m))
52 | (errPos ("undefined predicate " ++ show pred))
53 | checkAtom _ = return ()
54 |
55 | -- mode checker
56 |
57 | data ArgMode = R TermMode | P TermMode deriving Eq
58 | data TermMode = L | S deriving (Eq, Ord)
59 |
60 | instance Ord ArgMode where
61 | R m `compare` R m' = m' `compare` m
62 | R _ `compare` P _ = LT
63 | P _ `compare` R _ = GT
64 | P m `compare` P m' = m `compare` m'
65 |
66 | instance Show TermMode where
67 | showsPrec _ L = ("range limited"++)
68 | showsPrec _ S = ("statically range limited"++)
69 |
70 | type ModeEnv = Map SynVar TermMode
71 |
72 | {-
73 | At the top level, we pass in a data structure that maps built-in predicates onto their
74 | argument modes; this never changes.
75 |
76 | For each predicate:
77 | First, mode-check each clause:
78 | For each body atom:
79 | Look at the context for the atom:
80 | * If it's "application", then either the predicate is
81 | built-in (so we look up its argument modes) or it's not (so
82 | we assign all arguments the mode (P S): we're saying that in
83 | the application namespace, all predicates force their
84 | arguments to be statically limited.)
85 | * If the context is something that's not "application", then
86 | we assign all arguments the mode (P L): in any namespaces other
87 | than "application", all predicates bind their arguments (but not
88 | necessarily to a statically-known value).
89 | * If there is no context, then as in the "application" rule, we
90 | look up the predicate in the data structure that tells us the modes
91 | for predicates in the current context; if it's found, then we know
92 | the argument modes, and if not, we assign all arguments (P L).
93 |
94 | Now that we have a mode for each argument, for each (arg, mode) pair,
95 | we do the following checks:
96 | * If arg is an anonymous variable, and the mode requires anything,
97 | we report an error because an anonymous variable can't have a mode.
98 | * If arg is a named variable and the mode is provided, then we
99 | insert (arg, mode) into the environment if mode is a refinement on
100 | whatever mode arg is already bound to in the environment.
101 | * If arg is a named variable and the mode is required, then we look
102 | up arg in the environment and check that the mode that's already in
103 | the environment contains at least as much information as mode does.
104 |
105 | Second, determine the modes for the head, using the information we just
106 | computed about the variables in the body. The goal is to ensure that
107 | every argument in the head provides some mode.
108 | We create a list of modes corresponding to each argument in the head:
109 | * If the argument is a constant, then the mode is (P S): that is, this
110 | rule provides a statically known value for that argument.
111 | * If the argument is an anonymous variable, then we report an error,
112 | because we can't have anonymous variables in the head: an anonymous
113 | variable can't provide a mode.
114 | * If the argument is a named variable, then look up its mode and indicate
115 | that it provides that mode. If the mode isn't found in the environment,
116 | then we report an error.
117 |
118 | Third, now that for each predicate, we have a mode list for each of the rules
119 | that combine it, we combine these lists together so that we have a single mode
120 | list for each predicate, by taking the lower bound of each element in the list
121 | over all of the defining rules.
122 |
123 | Now that we know the argument modes for each predicate,
124 | iterate with this new information, until none of the modes
125 | for any of the predicates change.
126 |
127 | -}
128 |
129 | modeCheck :: CtxModes -> [(Pred, [SynRule])] -> Check CtxModes
130 | modeCheck appModes preds = iter (iterEnterClause addPred preds)
131 | where
132 | iter :: (CtxModes -> Check CtxModes) -> Check CtxModes
133 | iter f = iter' empty where
134 | iter' m = let (m', errs) = runWriter (f m)
135 | in if m' == m then tell errs >> return m
136 | else iter' m'
137 |
138 | addPred :: (Pred, [SynRule]) -> CtxModes -> CheckPos PredPos CtxModes
139 | addPred (pred, clauses) m = liftM (addPredMode pred m) (checkPred m clauses)
140 |
141 | addPredMode :: Pred -> CtxModes -> Maybe [ArgMode] -> CtxModes
142 | addPredMode pred m (Just argModes) = insert pred argModes m
143 | addPredMode _ m Nothing = m
144 |
145 | checkPred :: CtxModes -> [SynRule] -> CheckPos PredPos (Maybe [ArgMode])
146 | checkPred modes clauses = liftM lwb (mapEnterM checkRule clauses)
147 | where
148 | lwb :: [Maybe [ArgMode]] -> Maybe [ArgMode]
149 | lwb clauseModes = fmap (map minimum . transpose) (sequence clauseModes)
150 |
151 | checkRule :: SynRule -> CheckPos ClausePos (Maybe [ArgMode])
152 | checkRule (Rule (Atom NoCtx _ args) b) = do
153 | e <- iterEnter checkAtom b empty
154 | liftM sequence (mapEnterM (checkArg e) args)
155 | where
156 | checkArg :: ModeEnv -> SynTerm -> CheckPos HeadArgPos (Maybe ArgMode)
157 | checkArg e t@(Val _) = return (Just (P S))
158 | checkArg e t@(Var Anon) = noMode (show t ++ " in head")
159 | checkArg e t@(Var (Named v)) = case lookup v e of
160 | Just m -> return (Just (P m))
161 | Nothing -> noMode ("Can not derive mode for head term " ++ show t)
162 | noMode msg = errPos msg >> return Nothing
163 |
164 | checkAtom :: SynBodyAtom -> ModeEnv -> CheckPos BodyPos ModeEnv
165 | checkAtom (Atom ctx pred args) e =
166 | let argModes = case ctx of
167 | JustCtx (Val (SString "application")) ->
168 | case lookup pred appModes of
169 | Just argModes -> argModes
170 | Nothing -> repeat (P S)
171 | NothingCtx ->
172 | case lookup pred modes of
173 | Just argModes -> argModes
174 | Nothing -> repeat (P L)
175 | _ -> repeat (P L)
176 | in case ctx of
177 | JustCtx ctx -> withReaderT enterBodyCtx (checkArg ctx (R L) e)
178 | NothingCtx -> return e
179 | >>= iterEnter' checkArg (zip args argModes)
180 | where
181 | checkArg :: Show pos => SynTerm -> ArgMode -> ModeEnv ->
182 | CheckPos pos ModeEnv
183 | checkArg t@(Val _) _ e = return e
184 | checkArg t@(Var Anon) (P _) e = return e
185 | checkArg t@(Var Anon) (R m) e = expected t Nothing m >> return e
186 | checkArg t@(Var (Named v)) (P m) e = return (insertWith max v m e)
187 | checkArg t@(Var (Named v)) (R m) e = case lookup v e of
188 | Just m' | m' >= m -> return e
189 | m' -> expected t m' m >> return e
190 | expected :: Show pos => SynTerm -> Maybe TermMode -> TermMode ->
191 | CheckPos pos ()
192 | expected t m m' = errPos (show t ++ showMaybe m ++ " found where " ++
193 | show m' ++ " expected")
194 | showMaybe Nothing = " (mode unknown)"
195 | showMaybe (Just x) = " (" ++ show x ++ ")"
196 |
197 | -- A monad that helps with error reporting.
198 | -- Position stuff is a mess.
199 |
200 | type Check a = Writer Errors a
201 | type CheckPos pos a = ReaderT pos (Writer Errors) a
202 | type Errors = [String] -> [String]
203 |
204 | data PredPos = PredPos Pred
205 | data ClausePos = ClausePos PredPos Int
206 | data HeadArgPos = HeadArgPos ClausePos Int
207 | data BodyPos = BodyPos ClausePos Int
208 | data BodyCtxPos = BodyCtxPos BodyPos
209 | data BodyArgPos = BodyArgPos BodyPos Int
210 |
211 | instance Show PredPos where
212 | showsPrec _ (PredPos pred) = shows pred
213 | instance Show ClausePos where
214 | showsPrec _ (ClausePos p n) = shows p . (", clause "++) . shows n
215 | instance Show HeadArgPos where
216 | showsPrec _ (HeadArgPos p n) = shows p . (", arg "++) . shows n
217 | instance Show BodyPos where
218 | showsPrec _ (BodyPos p n) = shows p . (", atom "++) . shows n
219 | instance Show BodyCtxPos where
220 | showsPrec _ (BodyCtxPos p) = shows p . (", context"++)
221 | instance Show BodyArgPos where
222 | showsPrec _ (BodyArgPos p n) = shows p . (", arg "++) . shows n
223 |
224 | class Enter pos pos' obj | pos' -> pos obj, pos obj -> pos' where
225 | enter :: pos -> Int -> pos'
226 |
227 | instance Enter PredPos ClausePos SynRule where enter = ClausePos
228 | instance Enter ClausePos HeadArgPos (SynTerm) where enter = HeadArgPos
229 | instance Enter ClausePos BodyPos (SynBodyAtom) where enter = BodyPos
230 | instance Enter BodyPos BodyArgPos (SynTerm) where enter = BodyArgPos
231 |
232 | enterPred = PredPos
233 | enterBodyCtx = BodyCtxPos
234 |
235 | mapEnter :: Enter pos pos' obj => (obj -> CheckPos pos' a) -> [obj] ->
236 | [CheckPos pos a]
237 | mapEnter f xs = zipWith body [1..] xs where
238 | body n x = withReaderT (\pos -> enter pos n) (f x)
239 | mapEnterM f xs = sequence (mapEnter f xs)
240 | mapEnterM_ f xs = sequence_ (mapEnter f xs)
241 |
242 | iterEnter :: Enter pos pos' obj => (obj -> a -> CheckPos pos' a) -> [obj] ->
243 | (a -> CheckPos pos a)
244 | iterEnter f xs = iter (zipWith body [1..] xs) where
245 | body n x z = withReaderT (\pos -> enter pos n) (f x z)
246 |
247 | iterEnter' :: Enter pos pos' obj => (obj -> b -> a -> CheckPos pos' a) ->
248 | [(obj, b)] -> (a -> CheckPos pos a)
249 | iterEnter' f xs = iter (zipWith body [1..] xs) where
250 | body n (x, y) z = withReaderT (\pos -> enter pos n) (f x y z)
251 |
252 | mapEnterClause :: ([SynRule] -> CheckPos PredPos a) ->
253 | [(Pred, [SynRule])] -> [Check a]
254 | mapEnterClause f preds = map body preds where
255 | body (pred, clauses) = runReaderT (f clauses) (enterPred pred)
256 | mapEnterClauseM f preds = sequence (mapEnterClause f preds)
257 | mapEnterClauseM_ f preds = sequence_ (mapEnterClause f preds)
258 |
259 | iterEnterClause :: ((Pred, [SynRule]) -> a -> CheckPos PredPos a) ->
260 | [(Pred, [SynRule])] -> a -> Check a
261 | iterEnterClause f preds = iter (map body preds) where
262 | body p@(pred, _) z = runReaderT (f p z) (enterPred pred)
263 |
264 | err :: String -> Check ()
265 | err msg = tell (msg :)
266 |
267 | errPos :: Show pos => String -> CheckPos pos ()
268 | errPos msg = ask >>= \pos -> tell ((show pos ++ ": " ++ msg) :)
269 |
270 | checkErrs :: Monad m => Check a -> m a
271 | checkErrs m = let (r, errs) = runWriter m in case errs [] of
272 | [] -> return r
273 | errs -> fail (foldr1 (\s1 s2 -> s1 ++ "\n" ++ s2) errs)
274 |
275 | iter :: Monad m => [a -> m a] -> (a -> m a)
276 | iter = foldr (\f g x -> f x >>= g) return
277 |
278 | -- tests
279 |
280 | undefT = (1, ["foo(a) :- bar(a)."])
281 | anonHeadT = (1, ["foo(?)."])
282 | unusedHeadT = (1, ["foo(?a)."])
283 | rsT = (1, ["foo(1) :- application says rs(?a)."])
284 | rsT' = (2, ["foo(?a) :- application says rs(?a)."])
285 | rsAnonT = (1, ["foo(1) :- application says rs(?)."])
286 | rlT = (1, ["foo(1) :- application says rl(?a)."])
287 | rlT' = (2, ["foo(?a) :- application says rl(?a)."])
288 | rlAnonT = (1, ["foo(1) :- application says rl(?)."])
289 | saysT = (1, ["foo(1) :- ?a says foo(1)."])
290 | saysAnonT = (1, ["foo(1) :- ? says foo(1)."])
291 | saysOkT = (0, ["foo(1) :- someone says foo(?a),\
292 | \?a says foo(1)."])
293 | rsFactT = (0, ["foo(1).",
294 | "bar(?a) :- foo(?a), application says rs(?a)."])
295 | rsAppFactT = (0, ["bar(?a) :- application says foo(?a),\
296 | \application says rs(?a)."])
297 | rlRuleT = (0, ["bar(?a) :- someone says foo(?a),\
298 | \application says rl(?a)."])
299 | rsRuleT = (1, ["bar(?a) :- someone says foo(?a),\
300 | \application says rs(?a)."])
301 | rsChainT = (0, ["foo(1).",
302 | "bar(?a) :- foo(?a).",
303 | "baz(?a) :- bar(?a), application says rs(?a)."])
304 | multiT = (1, ["foo(1).",
305 | "foo(?x) :- foo(?x).",
306 | "bar(?a) :- foo(?a), application says rs(?a)."])
307 | improveT = (0, ["foo(1).",
308 | "bar(?a) :- bar(?a), foo(?a).",
309 | "baz(?a) :- bar(?a), application says rs(?a)."])
310 | deproveT = (0, ["foo(1).",
311 | "bar(?a) :- foo(?a), bar(?a).",
312 | "baz(?a) :- bar(?a), application says rs(?a)."])
313 |
314 | test = do assertErrs anonHeadT
315 | assertErrs unusedHeadT
316 | assertErrs rsT
317 | assertErrs rsT'
318 | assertErrs rsAnonT
319 | assertErrs rlT
320 | assertErrs rlT'
321 | assertErrs rlAnonT
322 | assertErrs saysT
323 | assertErrs saysAnonT
324 | assertErrs saysOkT
325 | assertErrs rsFactT
326 | assertErrs rsAppFactT
327 | assertErrs rlRuleT
328 | assertErrs rsRuleT
329 | assertErrs rsChainT
330 | assertErrs multiT
331 | assertErrs improveT
332 | assertErrs deproveT
333 |
334 | assertErrs :: (Int, [String]) -> IO ()
335 | assertErrs (n, ps) = assert (length (testCheck ps) == n)
336 |
337 | assert :: Bool -> IO ()
338 | assert True = putStrLn "assertion passed"
339 | assert False = fail "assertion failed"
340 |
341 | testCheck :: [String] -> [String]
342 | testCheck s = let rules = uncheckedParse assertionL (concat s)
343 | appModes = fromList [(Pred "rs" 1, [R S]),
344 | (Pred "rl" 1, [R L])]
345 | (_, errs) = runWriter $ do (m, preds) <- groupRules' rules
346 | checkUndefined m preds
347 | modeCheck appModes preds
348 | in errs []
349 |
350 |
--------------------------------------------------------------------------------
/Soutei/Client.hs:
--------------------------------------------------------------------------------
1 | module Soutei.Client (
2 | ServiceAddress(..), parseServiceAddress,
3 | queryRemote
4 | ) where
5 |
6 | import Control.Monad.Error () -- just for instance Monad Either
7 | import Data.Maybe (fromMaybe)
8 | import Network
9 | import System.IO
10 |
11 | import Soutei.Sexpr as Sexpr
12 | import Soutei.Soutei as Soutei
13 | import Soutei.Syntax
14 |
15 | data ServiceAddress = HostPort HostName PortID
16 |
17 | parseServiceAddress :: Monad m => String -> m ServiceAddress
18 | parseServiceAddress s =
19 | let (host, port) = break (== ':') s
20 | in case port of
21 | ':':port -> case reads port of
22 | [(port,"")] -> return (HostPort host (PortNumber (fromInteger port)))
23 | _ -> return (HostPort host (Service port))
24 | _ -> fail "cannot parse host:port"
25 |
26 | instance Read ServiceAddress where
27 | readsPrec _ s = map (\r -> (r, "")) (eitherToList (parseServiceAddress s))
28 |
29 | eitherToList :: Either String a -> [a]
30 | eitherToList (Left _) = []
31 | eitherToList (Right x) = [x]
32 |
33 | queryRemote :: ServiceAddress -> String -> [Fact] -> Goal -> IO Bool
34 | queryRemote (HostPort host port) id facts goal =
35 | let q = Sexpr.fromList (Sexpr.Atom (Val (SString id)) :
36 | Sexpr.Atom (Val (SString "query")) :
37 | atomToSexpr goal :
38 | map (atomToSexpr . factToAtom) facts)
39 | in do h <- connectTo host port
40 | hPrint h q
41 | hFlush h
42 | r <- hGetContents h
43 | fromMaybe (fail "Unexpected reply from Soutei")
44 | (lookup r [("(" ++ id ++ " #t)\n", return True),
45 | ("(" ++ id ++ " #f)\n", return False)])
46 |
47 | atomToSexpr :: HeadAtom v -> Sexpr.Sexpr (Term v)
48 | atomToSexpr (Soutei.Atom NoCtx (Pred pred arity) args) =
49 | Sexpr.fromAtomList (Val (SString pred):args)
50 |
51 | t = let facts = [Soutei.Atom NoCtx (Pred "foo" 1) [Val (SString "bar")]]
52 | goal = Soutei.Atom NoCtx (Pred "may" 1) [Val (SString "HTTP")]
53 | in queryRemote (read "localhost:1500") "test" facts goal
54 |
--------------------------------------------------------------------------------
/Soutei/FBackTrack.hs:
--------------------------------------------------------------------------------
1 | {-# OPTIONS -fglasgow-exts #-}
2 |
3 | -- Simple Fair back-tracking monad
4 | -- Based on the Scheme code book-si, `Stream implementation, with incomplete'
5 | -- as of Feb 18, 2005
6 |
7 | module Soutei.FBackTrack where
8 |
9 | import Control.Monad
10 |
11 | data Stream a = Nil | One a | Choice a (Stream a) | Incomplete (Stream a)
12 | | IncompleteR (Stream a)
13 | deriving Show
14 |
15 | instance Monad Stream where
16 | return = One
17 |
18 | Nil >>= f = Nil
19 | One a >>= f = f a
20 | Choice a r >>= f = f a `mplus` (Incomplete (r >>= f))
21 | {-
22 | Incomplete i >>= f = case i of
23 | Incomplete j -> Incomplete (j >>= f)
24 | _ -> i >>= f
25 | -}
26 | IncompleteR i >>= f = IncompleteR (i >>= f)
27 | Incomplete i >>= f = Incomplete (i >>= f)
28 |
29 | instance MonadPlus Stream where
30 | mzero = Nil
31 |
32 | mplus Nil r' = Incomplete r'
33 | mplus (One a) r' = Choice a r'
34 | mplus (Choice a r) r' = Choice a (mplus r' r) -- interleaving!
35 | --mplus (Incomplete i) r' = Incomplete (mplus i r')
36 | mplus r@(Incomplete i) r' =
37 | case r' of
38 | Nil -> r
39 | One b -> Choice b i
40 | Choice b r' -> Choice b (mplus i r')
41 | -- Choice _ _ -> Incomplete (mplus r' i)
42 | Incomplete j -> (Incomplete (mplus i j))
43 | IncompleteR j -> IncompleteR (mplus i j)
44 | mplus r@(IncompleteR i) r' = IncompleteR (mplus r' i)
45 |
46 | -- run the Monad, to a specific depth
47 | runM :: Maybe Int -> Stream a -> [a]
48 | runM _ Nil = []
49 | runM _ (One a) = [a]
50 | runM d (Choice a r) = a : (runM d r)
51 | runM (Just 0) (Incomplete r) = [] -- exhausted depth
52 | runM (Just 0) (IncompleteR r) = [] -- exhausted depth
53 | runM d (Incomplete r) = runM (liftM pred d) r
54 | runM d (IncompleteR r) = runM (liftM pred d) r
55 |
56 | -- Don't try the following with the regular List monad or List comprehension!
57 | -- That would diverge instantly: all `i', `j', and `k' are infinite
58 | -- streams
59 |
60 | pythagorean_triples :: MonadPlus m => m (Int,Int,Int)
61 | pythagorean_triples =
62 | let number = (return 0) `mplus` (number >>= (return . (+1))) in
63 | do
64 | i <- number
65 | guard $ i > 0
66 | j <- number
67 | guard $ j > 0
68 | k <- number
69 | guard $ k > 0
70 | guard $ i*i + j*j == k*k
71 | return (i,j,k)
72 |
73 | test = take 7 $ runM Nothing pythagorean_triples
74 |
75 | -- even more fun
76 |
77 | pythagorean_triples2 :: Stream (Int,Int,Int)
78 | pythagorean_triples2 =
79 | let number = (IncompleteR number >>= \n -> return (n+1)) `mplus` return 0 in
80 | do i <- number
81 | j <- number
82 | k <- number
83 | guard $ i*i + j*j == k*k
84 | return (i,j,k)
85 |
86 | test2 = take 7 $ filter (\(x,y,_) -> x*y/=0) $ runM Nothing pythagorean_triples2
87 |
88 | -- test different branching strategies
89 | -- note--ensure that structures aren't shared
90 |
91 | -- m = y (m + m + ... + m)
92 | t1 n = y (msum (map ($ n) (replicate n t1)))
93 |
94 | -- m = (y m + y m + ... + y m)
95 | t2 n = msum (map (y . ($ n)) (replicate n t2))
96 |
97 | -- m = y (m + y (m + y (... + y m)...))
98 | t3 n = y (foldr1 (\m m' -> mplus m (y m')) (map ($ n) (replicate n t3)))
99 | t3' n = foldr (\m m' -> y (mplus m m')) mzero (map ($ n) (replicate n t3'))
100 |
101 | -- m = y $ y $ ... $ y (m + m + ... + m)
102 | t4 n = foldr1 (.) (replicate n y) (msum (map ($ n) (replicate n t4)))
103 |
104 | -- m = y $ y $ ... $ y (m + m + ... + m) (~ ln n y's)
105 | t5 n = let n' = 1 + floor (log (fromIntegral n))
106 | t n = foldr1 (.) (replicate n' y) (msum (map ($ n) (replicate n t)))
107 | in t n
108 |
109 | y = IncompleteR
110 |
--------------------------------------------------------------------------------
/Soutei/FBackTrackT.hs:
--------------------------------------------------------------------------------
1 | {- Haskell98! -}
2 |
3 | -- $HeadURL: https://svn.metnet.navy.mil/svn/metcast/Mserver/trunk/soutei/haskell/Soutei/FBackTrackT.hs $
4 | -- $Id: FBackTrackT.hs 2926 2012-09-07 04:43:30Z oleg.kiselyov $
5 | -- svn propset svn:keywords "HeadURL Id" filename
6 |
7 | -- Simple Fair back-tracking monad TRANSFORMER
8 | -- Made by `transforming' the stand-alone monad from FBackTrack.hs,
9 | -- which, in turn, is based on the Scheme code book-si,
10 | -- `Stream implementation, with incomplete' as of Feb 18, 2005
11 | --
12 | -- The transformatiion from a stand-alone Stream monad to a monad transformer
13 | -- is not at all similar to the trick described in Ralf Hinze's ICFP'00 paper,
14 | -- Deriving backtracking monad transformers.
15 |
16 | -- $Id: FBackTrackT.hs 2926 2012-09-07 04:43:30Z oleg.kiselyov $
17 |
18 | module Soutei.FBackTrackT (Stream, yield, runM) where
19 |
20 | import Control.Monad
21 | import Control.Monad.Trans
22 | import Control.Monad.Identity
23 |
24 | data StreamE m a = Nil | One a |
25 | Choice a (Stream m a) |
26 | Incomplete (Stream m a)
27 |
28 | newtype Stream m a = Stream{unStream :: m (StreamE m a)}
29 |
30 |
31 | instance Monad m => Monad (Stream m) where
32 | return = Stream . return . One
33 |
34 | m >>= f = Stream (unStream m >>= bind)
35 | where
36 | bind Nil = return Nil
37 | bind (One a) = unStream $ f a
38 | bind (Choice a r) = unStream $ f a `mplus` (yield (r >>= f))
39 | bind (Incomplete i) = return $ Incomplete (i >>= f)
40 |
41 | yield :: Monad m => Stream m a -> Stream m a
42 | yield = Stream . return . Incomplete
43 |
44 | instance Monad m => MonadPlus (Stream m) where
45 | mzero = Stream $ return Nil
46 |
47 | mplus m1 m2 = Stream (unStream m1 >>= mplus')
48 | where
49 | mplus' Nil = return $ Incomplete m2
50 | mplus' (One a) = return $ Choice a m2
51 | mplus' (Choice a r) = return $ Choice a (mplus m2 r) -- interleaving!
52 | --mplus' (Incomplete i) = return $ Incomplete (mplus i m2)
53 | mplus' r@(Incomplete i) = unStream m2 >>= \r' ->
54 | case r' of
55 | Nil -> return r
56 | One b -> return $ Choice b i
57 | Choice b r' -> return $ Choice b (mplus i r')
58 | -- Choice _ _ -> Incomplete (mplus r' i)
59 | Incomplete j -> return $ Incomplete $ Stream $ return $ Incomplete (mplus i j)
60 |
61 |
62 | instance MonadTrans Stream where
63 | lift m = Stream (m >>= return . One)
64 |
65 | instance MonadIO m => MonadIO (Stream m) where
66 | liftIO = lift . liftIO
67 |
68 |
69 | -- run the Monad, to a specific depth, and give at most
70 | -- specified number of answers. The monad `m' may be strict (like IO),
71 | -- so we can't count on the laziness of the `[a]'
72 | runM :: Monad m => Maybe Int -> Maybe Int -> Stream m a -> m [a]
73 | runM _ (Just 0) _ = return [] -- out of breadth
74 | runM d b m = unStream m >>= runM' d b
75 | runM' _ _ Nil = return []
76 | runM' _ _ (One a) = return [a]
77 | runM' d b (Choice a r) = do t <- runM d (liftM pred b) r; return (a:t)
78 | runM' (Just 0) _ (Incomplete r) = return [] -- exhausted depth
79 | runM' d b (Incomplete r) = runM (liftM pred d) b r
80 |
81 |
82 | -- Don't try the following with the regular List monad or List comprehension!
83 | -- That would diverge instantly: all `i', `j', and `k' are infinite
84 | -- streams
85 |
86 | pythagorean_triples :: MonadPlus m => m (Int,Int,Int)
87 | pythagorean_triples =
88 | let number = (return 0) `mplus` (number >>= return . succ) in
89 | do
90 | i <- number
91 | guard $ i > 0
92 | j <- number
93 | guard $ j > 0
94 | k <- number
95 | guard $ k > 0
96 | guard $ i*i + j*j == k*k
97 | return (i,j,k)
98 |
99 | -- If you run this in GHCi, you can see that Indetity is a lazy monad
100 | -- and IO is strict: evaluating `test' prints the answers as they are computed.
101 | -- OTH, testio runs silently for a while and then prints all the answers
102 | -- at once
103 | test = runIdentity $ runM Nothing (Just 7) pythagorean_triples
104 | testio = runM Nothing (Just 7) pythagorean_triples >>= print
105 |
106 |
107 | -- The following code is not in general MonadPlus: it uses Incomplete
108 | -- explicitly. But it supports left recursion! Note that in OCaml, for example,
109 | -- we _must_ include that Incomplete data constructor to make
110 | -- the recursive definition well-formed.
111 | -- The code does *not* get stuck in the generation of primitive tuples
112 | -- like (0,1,1), (0,2,2), (0,3,3) etc.
113 | pythagorean_triples' :: Monad m => Stream m (Int,Int,Int)
114 | pythagorean_triples' =
115 | let number = (yield number >>= return . succ) `mplus` return 0 in
116 | do
117 | i <- number
118 | j <- number
119 | k <- number
120 | guard $ i*i + j*j == k*k
121 | return (i,j,k)
122 |
123 | test' = runIdentity $ runM Nothing (Just 27) pythagorean_triples'
124 | testio' = runM Nothing (Just 27) pythagorean_triples' >>= print
125 |
126 | pythagorean_triples'' :: Stream IO (Int,Int,Int)
127 | pythagorean_triples'' =
128 | let number = (yield number >>= return . succ) `mplus` return 0 in
129 | do
130 | i <- number
131 | j <- number
132 | k <- number
133 | liftIO $ print (i,j,k)
134 | guard $ i*i + j*j == k*k
135 | return (i,j,k)
136 |
137 | testio'' = runM Nothing (Just 7) pythagorean_triples'' >>= print
138 |
139 | -- a serious test of left recursion (due to Will Byrd)
140 | flaz x = yield (flaz x) `mplus` (yield (flaz x) `mplus`
141 | if x == 5 then return x else mzero)
142 | test_flaz = runIdentity $ runM Nothing (Just 15) (flaz 5)
143 |
--------------------------------------------------------------------------------
/Soutei/GBBFS.hs:
--------------------------------------------------------------------------------
1 | {-# OPTIONS -fglasgow-exts #-}
2 |
3 | -- A monad TRANSFORMER for BFS or DFS traversal
4 | -- The function dfs'or'bfs2 below determines which is which
5 |
6 | module Soutei.GBBFS (Stream, yield, runM) where
7 |
8 | import Control.Monad
9 | import Control.Monad.Trans
10 | import Control.Monad.Identity
11 |
12 | import Data.IORef -- for tests
13 |
14 | --import Debug.Trace -- for tests
15 | import System.IO.Unsafe -- because Debug.Trace.trace doesn't work after interr
16 | import System.Mem.StableName -- for tests
17 | trace str x = unsafePerformIO (putStrLn str) `seq` x
18 |
19 |
20 | -- The following two lines choose DFS or BFS traversal
21 | -- dfs'or'bfs2 r1 r2 = r1 ++ r2
22 | dfs'or'bfs2 r1 r2 = r2 ++ r1
23 |
24 |
25 | newtype Monad m => Stream m a = Stream{unStream :: m (StreamE m a)}
26 | type StreamE m a = (Maybe a, [Stream m a])
27 |
28 | instance Monad m => Monad (Stream m) where
29 | return x = Stream (return (Just x, []))
30 |
31 | m >>= f = Stream (unStream m >>= bind)
32 | where
33 | bind (ans, r) = return $ let q' = map (>>=f) r
34 | in (Nothing, maybe q' (\x -> f x : q') ans)
35 |
36 |
37 | instance Monad m => MonadPlus (Stream m) where
38 | mzero = Stream (return (Nothing, []))
39 |
40 | mplus m1 m2 = Stream (unStream m1 >>= mplus')
41 | where
42 | mplus' (ans, r) = return $ (ans, m2:r)
43 |
44 | instance MonadTrans Stream where
45 | lift m = Stream (m >>= \x -> return (Just x, []))
46 |
47 | instance MonadIO m => MonadIO (Stream m) where
48 | liftIO = lift . liftIO
49 |
50 | yield :: Monad m => Stream m a -> Stream m a
51 | yield = mplus mzero
52 |
53 | -- run the Monad, to a specific depth, and give at most
54 | -- specified number of answers. The monad `m' may be strict (like IO),
55 | -- so we can't count on the laziness of the `[a]'
56 | runM :: Monad m => Maybe Int -> Maybe Int -> Stream m a -> m [a]
57 | runM d b m = runM' d b [m]
58 | runM' d b m | trace ("Queue size " ++ show (length m)) False = undefined
59 | runM' _ (Just 0) _ = return [] -- out of breadth
60 | runM' _ _ [] = return [] -- finished
61 | runM' d b (m:r) = unStream m >>= runM'' d b r
62 | runM'' _ _ [] (Nothing,[]) = return []
63 | runM'' _ _ [] (Just a, []) = return [a]
64 | runM'' d b r' (Just a, r) =
65 | do t <- runM' d (liftM pred b) (dfs'or'bfs2 r r'); return (a:t)
66 | runM'' (Just 0) _ _ (Nothing, r) = return [] -- exhausted depth
67 | runM'' d b r' (Nothing, r) = runM' (liftM pred d) b (dfs'or'bfs2 r r')
68 |
69 | ------------------------------------------------------------------------
70 | -- Tests
71 |
72 | -- Testing that order solutions are generated
73 |
74 | nat :: MonadPlus m => m Int
75 | nat = return 0 `mplus` (nat >>= return . succ)
76 |
77 | testn0 = runIdentity $ runM Nothing (Just 10) nat
78 | testn1 = runIdentity $ runM Nothing (Just 32)
79 | (nat >>= \i -> nat >>= \j -> return (i,j))
80 |
81 | {- For the true BFS, the solutions are generated in the order
82 | i+j=const
83 | *GBBFS> testn1
84 | [(0,0),(0,1),(1,0),(0,2),(1,1),(2,0),(0,3),(1,2),(2,1),(3,0),
85 | (0,4),(1,3),(2,2),(3,1),(4,0),(0,5),(1,4),(2,3),(3,2),(4,1),(5,0)]
86 | -}
87 |
88 | -- This test shows that effects associated with the solutions are
89 | -- NOT duplicated
90 | -- Compare with the corresponding test in FBIDFS.hs
91 | natio :: Stream IO Int
92 | natio = do n <- natio'; liftIO $ print n; return n
93 | where natio' = return 0 `mplus` (natio' >>= return . succ)
94 |
95 | testn2 = runM Nothing (Just 5) natio >>= print
96 |
97 | -- Don't try the following with the regular List monad or List comprehension!
98 | -- That would diverge instantly: all `i', `j', and `k' are infinite
99 | -- streams
100 |
101 | pythagorean_triples :: MonadPlus m => m (Int,Int,Int)
102 | pythagorean_triples =
103 | let number = (return 0) `mplus` (number >>= return . succ) in
104 | do
105 | i <- number
106 | guard $ i > 0
107 | j <- number
108 | guard $ j > 0
109 | k <- number
110 | guard $ k > 0
111 | guard $ i*i + j*j == k*k
112 | return (i,j,k)
113 |
114 | -- If you run this in GHCi, you can see that Indetity is a lazy monad
115 | -- and IO is strict: evaluating `test' prints the answers as they are computed.
116 | -- OTH, testio runs silently for a while and then prints all the answers
117 | -- at once
118 | test = runIdentity $ runM Nothing (Just 7) pythagorean_triples
119 | testio = runM Nothing (Just 7) pythagorean_triples >>= print
120 |
121 |
122 | -- The following code is not in general MonadPlus: it uses Incomplete
123 | -- explicitly. But it supports left recursion! Note that in OCaml, for example,
124 | -- we _must_ include that Incomplete data constructor to make
125 | -- the recursive definition well-formed.
126 | -- The code does *not* get stuck in the generation of primitive tuples
127 | -- like (0,1,1), (0,2,2), (0,3,3) etc.
128 | pythagorean_triples' :: Monad m => Stream m (Int,Int,Int)
129 | pythagorean_triples' =
130 | let number = (yield number >>= return . succ) `mplus` return 0 in
131 | do
132 | i <- number
133 | j <- number
134 | k <- number
135 | guard $ i*i + j*j == k*k
136 | return (i,j,k)
137 |
138 | test' = runIdentity $ runM Nothing (Just 27) pythagorean_triples'
139 | testio' = runM Nothing (Just 27) pythagorean_triples' >>= print
140 |
141 | pythagorean_triples'' :: Stream IO (Int,Int,Int)
142 | pythagorean_triples'' =
143 | let number = (yield number >>= return . succ) `mplus` return 0 in
144 | do
145 | i <- number
146 | j <- number
147 | k <- number
148 | liftIO $ print (i,j,k)
149 | guard $ i*i + j*j == k*k
150 | return (i,j,k)
151 |
152 | testio'' = runM Nothing (Just 7) pythagorean_triples'' >>= print
153 |
154 | -- a serious test of left recursion (due to Will Byrd)
155 | flaz x = yield (flaz x) `mplus` (yield (flaz x) `mplus`
156 | if x == 5 then return x else mzero)
157 | test_flaz = runIdentity $ runM Nothing (Just 15) (flaz 5)
158 |
159 |
160 | test_gt0 :: Stream IO Int
161 | test_gt0 =
162 | do
163 | g_count <- liftIO $ newIORef 0
164 | t_count <- liftIO $ newIORef 0
165 | let clear = (liftIO $ writeIORef g_count 0) >>
166 | (liftIO $ writeIORef t_count 0)
167 | let g = do
168 | liftIO $ modifyIORef g_count succ
169 | return 0 `mplus` (g >>= \y -> return $ 3*y + 1)
170 | t x = do
171 | -- liftIO $ print $ "t: " ++ (show x)
172 | liftIO $ modifyIORef t_count succ
173 | (if x == - 10 then return x else mzero) `mplus` t (x-1)
174 | clear
175 | r <- g >>= t
176 | gc <- liftIO $ readIORef g_count
177 | tc <- liftIO $ readIORef t_count
178 | clear
179 | liftIO $ putStrLn $ "g_count: " ++ (show gc) ++
180 | ", t_count: " ++ (show tc)
181 | return r
182 |
183 | -- runM Nothing (Just 2) test_gt0 >>= print
184 |
185 | test_gt1 :: Stream IO Int
186 | test_gt1 =
187 | do
188 | g_count <- liftIO $ newIORef 0
189 | t_count <- liftIO $ newIORef 0
190 | let clear = (liftIO $ writeIORef g_count 0) >>
191 | (liftIO $ writeIORef t_count 0)
192 | let g = do
193 | liftIO $ modifyIORef g_count succ
194 | (yield g >>= \y -> return $ 3*y + 1) `mplus` return 0
195 | t x = do
196 | -- liftIO $ print $ "t: " ++ (show x)
197 | liftIO $ modifyIORef t_count succ
198 | (yield $ t (x-1)) `mplus`
199 | (if x == - 10 then return x else mzero)
200 | clear
201 | r <- g >>= t
202 | gc <- liftIO $ readIORef g_count
203 | tc <- liftIO $ readIORef t_count
204 | clear
205 | liftIO $ putStrLn $ "g_count: " ++ (show gc) ++
206 | ", t_count: " ++ (show tc)
207 | return r
208 |
209 |
210 | -- test for the scheduling order
211 | -- it seems like BFS...
212 | test_sched n = runM (Just n) Nothing (t "")
213 | where
214 | t l | trace l False = undefined
215 | t l = Stream(return (Nothing, [(mplus (t ('0':l)) (t ('1':l)))]))
216 |
217 |
218 | pythagorean_triplesi :: MonadPlus m => () -> m (Int,Int,Int)
219 | pythagorean_triplesi () =
220 | let number l = trace ("Label: " ++ l)
221 | (return 0) `mplus` (number ('R':l)>>= return . succ) in
222 | do
223 | i <- number "i"
224 | guard $ i > 0
225 | j <- number ("j" ++ (replicate i 'R'))
226 | guard $ j > 0
227 | k <- number ("k" ++ (replicate i 'R') ++ "," ++ (replicate j 'R'))
228 | guard $ k > 0
229 | guard $ i*i + j*j == k*k
230 | return (i,j,k)
231 |
232 | testi n = runM (Just n) Nothing (pythagorean_triplesi ())
233 |
--------------------------------------------------------------------------------
/Soutei/GBBFS1.hs:
--------------------------------------------------------------------------------
1 | {-# OPTIONS -fglasgow-exts #-}
2 |
3 | -- A monad TRANSFORMER for BFS or DFS traversal
4 | -- The function dfs'or'bfs2 below determines which is which
5 |
6 | module Soutei.GBBFS1 (Stream, yield, runM) where
7 |
8 | import Control.Monad
9 | import Control.Monad.Trans
10 | import Control.Monad.Identity
11 |
12 | import Data.IORef -- for tests
13 |
14 | --import Debug.Trace -- for tests
15 | import System.IO.Unsafe -- because Debug.Trace.trace doesn't work after interr
16 | import System.Mem.StableName -- for tests
17 | trace str x = unsafePerformIO (putStrLn str) `seq` x
18 |
19 |
20 | -- The following two lines choose DFS or BFS traversal
21 | -- dfs'or'bfs2 r1 r2 = r1 ++ r2
22 | dfs'or'bfs2 r1 r2 = r2 ++ r1
23 |
24 |
25 | newtype Monad m => Stream m a = Stream{unStream :: m (StreamE m a)}
26 | type StreamE m a = (Maybe a, [Stream m a])
27 |
28 | instance Monad m => Monad (Stream m) where
29 | return x = Stream (return (Just x, []))
30 |
31 | m >>= f = Stream (unStream m >>= bind)
32 | where
33 | -- the following was an absolutely critical update to reduce the
34 | -- queue size. Cf with the corresponding bind method of GBBFS.hs
35 | bind (Nothing, r) = return (Nothing, map (>>=f) r)
36 | bind (Just x, r) = let q' = map (>>=f) r
37 | in unStream (mplus (f x)
38 | (Stream (return (Nothing, q'))))
39 |
40 |
41 | instance Monad m => MonadPlus (Stream m) where
42 | mzero = Stream (return (Nothing, []))
43 |
44 | mplus m1 m2 = Stream (unStream m1 >>= mplus')
45 | where
46 | mplus' (Nothing,r@(_:_)) = unStream m2 >>= mplus'' r
47 | mplus' (ans, r) = return $ (ans, m2:r)
48 | -- the following was critical for the fairness test_shed
49 | mplus'' r (ans,r') = return $ (ans, r ++ r')
50 |
51 |
52 |
53 | instance MonadTrans Stream where
54 | lift m = Stream (m >>= \x -> return (Just x, []))
55 |
56 | instance MonadIO m => MonadIO (Stream m) where
57 | liftIO = lift . liftIO
58 |
59 | yield :: Monad m => Stream m a -> Stream m a
60 | yield = mplus mzero
61 |
62 | -- run the Monad, to a specific depth, and give at most
63 | -- specified number of answers. The monad `m' may be strict (like IO),
64 | -- so we can't count on the laziness of the `[a]'
65 | runM :: Monad m => Maybe Int -> Maybe Int -> Stream m a -> m [a]
66 | runM d b m = runM' d b [m]
67 | -- runM' d b m | trace ("Queue size " ++ show (length m)) False = undefined
68 | runM' _ (Just 0) _ = return [] -- out of breadth
69 | runM' _ _ [] = return [] -- finished
70 | runM' d b (m:r) = unStream m >>= runM'' d b r
71 | runM'' _ _ [] (Nothing,[]) = return []
72 | runM'' _ _ [] (Just a, []) = return [a]
73 | runM'' d b r' (Just a, r) =
74 | do t <- runM' d (liftM pred b) (dfs'or'bfs2 r r'); return (a:t)
75 | runM'' (Just 0) _ _ (Nothing, r) = return [] -- exhausted depth
76 | runM'' d b r' (Nothing, r) = runM' (liftM pred d) b (dfs'or'bfs2 r r')
77 |
78 | ------------------------------------------------------------------------
79 | -- Tests
80 |
81 | -- Testing that order solutions are generated
82 |
83 | nat :: MonadPlus m => m Int
84 | nat = return 0 `mplus` (nat >>= return . succ)
85 |
86 | testn0 = runIdentity $ runM Nothing (Just 10) nat
87 | testn1 = runIdentity $ runM Nothing (Just 32)
88 | (nat >>= \i -> nat >>= \j -> return (i,j))
89 |
90 | {- For the true BFS, the solutions are generated in the order
91 | i+j=const
92 | *GBBFS> testn1
93 | [(0,0),(0,1),(1,0),(0,2),(1,1),(2,0),(0,3),(1,2),(2,1),(3,0),
94 | (0,4),(1,3),(2,2),(3,1),(4,0),(0,5),(1,4),(2,3),(3,2),(4,1),(5,0)]
95 | -}
96 |
97 | -- This test shows that effects associated with the solutions are
98 | -- NOT duplicated
99 | -- Compare with the corresponding test in FBIDFS.hs
100 | natio :: Stream IO Int
101 | natio = do n <- natio'; liftIO $ print n; return n
102 | where natio' = return 0 `mplus` (natio' >>= return . succ)
103 |
104 | testn2 = runM Nothing (Just 5) natio >>= print
105 |
106 | -- Don't try the following with the regular List monad or List comprehension!
107 | -- That would diverge instantly: all `i', `j', and `k' are infinite
108 | -- streams
109 |
110 | pythagorean_triples :: MonadPlus m => m (Int,Int,Int)
111 | pythagorean_triples =
112 | let number = (return 0) `mplus` (number >>= return . succ) in
113 | do
114 | i <- number
115 | guard $ i > 0
116 | j <- number
117 | guard $ j > 0
118 | k <- number
119 | guard $ k > 0
120 | guard $ i*i + j*j == k*k
121 | return (i,j,k)
122 |
123 | -- If you run this in GHCi, you can see that Indetity is a lazy monad
124 | -- and IO is strict: evaluating `test' prints the answers as they are computed.
125 | -- OTH, testio runs silently for a while and then prints all the answers
126 | -- at once
127 | test = runIdentity $ runM Nothing (Just 7) pythagorean_triples
128 | testio = runM Nothing (Just 7) pythagorean_triples >>= print
129 |
130 |
131 | -- The following code is not in general MonadPlus: it uses Incomplete
132 | -- explicitly. But it supports left recursion! Note that in OCaml, for example,
133 | -- we _must_ include that Incomplete data constructor to make
134 | -- the recursive definition well-formed.
135 | -- The code does *not* get stuck in the generation of primitive tuples
136 | -- like (0,1,1), (0,2,2), (0,3,3) etc.
137 | pythagorean_triples' :: Monad m => Stream m (Int,Int,Int)
138 | pythagorean_triples' =
139 | let number = (yield number >>= return . succ) `mplus` return 0 in
140 | do
141 | i <- number
142 | j <- number
143 | k <- number
144 | guard $ i*i + j*j == k*k
145 | return (i,j,k)
146 |
147 | test' = runIdentity $ runM Nothing (Just 27) pythagorean_triples'
148 | testio' = runM Nothing (Just 27) pythagorean_triples' >>= print
149 |
150 | pythagorean_triples'' :: Stream IO (Int,Int,Int)
151 | pythagorean_triples'' =
152 | let number = (yield number >>= return . succ) `mplus` return 0 in
153 | do
154 | i <- number
155 | j <- number
156 | k <- number
157 | liftIO $ print (i,j,k)
158 | guard $ i*i + j*j == k*k
159 | return (i,j,k)
160 |
161 | testio'' = runM Nothing (Just 7) pythagorean_triples'' >>= print
162 |
163 | -- a serious test of left recursion (due to Will Byrd)
164 | flaz x = yield (flaz x) `mplus` (yield (flaz x) `mplus`
165 | if x == 5 then return x else mzero)
166 | test_flaz = runIdentity $ runM Nothing (Just 15) (flaz 5)
167 |
168 |
169 | test_gt0 :: Stream IO Int
170 | test_gt0 =
171 | do
172 | g_count <- liftIO $ newIORef 0
173 | t_count <- liftIO $ newIORef 0
174 | let clear = (liftIO $ writeIORef g_count 0) >>
175 | (liftIO $ writeIORef t_count 0)
176 | let g = do
177 | liftIO $ modifyIORef g_count succ
178 | return 0 `mplus` (g >>= \y -> return $ 3*y + 1)
179 | t x = do
180 | -- liftIO $ print $ "t: " ++ (show x)
181 | liftIO $ modifyIORef t_count succ
182 | (if x == - 10 then return x else mzero) `mplus` t (x-1)
183 | clear
184 | r <- g >>= t
185 | gc <- liftIO $ readIORef g_count
186 | tc <- liftIO $ readIORef t_count
187 | clear
188 | liftIO $ putStrLn $ "g_count: " ++ (show gc) ++
189 | ", t_count: " ++ (show tc)
190 | return r
191 |
192 | -- runM Nothing (Just 2) test_gt0 >>= print
193 |
194 | test_gt1 :: Stream IO Int
195 | test_gt1 =
196 | do
197 | g_count <- liftIO $ newIORef 0
198 | t_count <- liftIO $ newIORef 0
199 | let clear = (liftIO $ writeIORef g_count 0) >>
200 | (liftIO $ writeIORef t_count 0)
201 | let g = do
202 | liftIO $ modifyIORef g_count succ
203 | (yield g >>= \y -> return $ 3*y + 1) `mplus` return 0
204 | t x = do
205 | -- liftIO $ print $ "t: " ++ (show x)
206 | liftIO $ modifyIORef t_count succ
207 | (yield $ t (x-1)) `mplus`
208 | (if x == - 10 then return x else mzero)
209 | clear
210 | r <- g >>= t
211 | gc <- liftIO $ readIORef g_count
212 | tc <- liftIO $ readIORef t_count
213 | clear
214 | liftIO $ putStrLn $ "g_count: " ++ (show gc) ++
215 | ", t_count: " ++ (show tc)
216 | return r
217 |
218 |
219 | -- test for the scheduling order
220 | -- it seems like BFS...
221 | test_sched n = runM (Just n) Nothing (t "")
222 | where
223 | t l | trace l False = undefined
224 | t l = Stream(return (Nothing, [(mplus (t ('0':l)) (t ('1':l)))]))
225 |
226 |
227 | pythagorean_triplesi :: MonadPlus m => () -> m (Int,Int,Int)
228 | pythagorean_triplesi () =
229 | let number l = trace ("Label: " ++ l)
230 | (return 0) `mplus` (number ('R':l)>>= return . succ) in
231 | do
232 | i <- number "i"
233 | guard $ i > 0
234 | j <- number ("j" ++ (replicate i 'R'))
235 | guard $ j > 0
236 | k <- number ("k" ++ (replicate i 'R') ++ "," ++ (replicate j 'R'))
237 | guard $ k > 0
238 | guard $ i*i + j*j == k*k
239 | return (i,j,k)
240 |
241 | testi n = runM (Just n) Nothing (pythagorean_triplesi ())
242 |
--------------------------------------------------------------------------------
/Soutei/Lirs.hs:
--------------------------------------------------------------------------------
1 | -- $HeadURL: https://svn.metnet.navy.mil/svn/metcast/Mserver/trunk/soutei/haskell/Soutei/Lirs.hs $
2 | -- $Id: Lirs.hs 2935 2012-09-12 00:42:48Z oleg.kiselyov $
3 | -- svn propset svn:keywords "HeadURL Id" filename
4 |
5 | -- LIRS: http://parapet.ee.princeton.edu/~sigm2002/papers/p31-jiang.pdf
6 | -- Basic idea: Maintain an LRU list, marking some entries "hot", based on
7 | -- the distance between the last two accesses. Maintain a separate list of
8 | -- "cold" entries, from which we evict.
9 |
10 | module Soutei.Lirs (
11 | Lirs, Storage(..), new, get, put
12 | ) where
13 |
14 | import Prelude hiding (last, lookup)
15 | import Control.Monad
16 | import Data.IORef
17 | import Data.Map (Map, empty, lookup, insert, delete, elems, size)
18 | import Data.Maybe (fromMaybe)
19 | import System.Random (randomRIO)
20 |
21 | data Lirs k v sv = Lirs {
22 | cache :: IORef (Map k (Entry k v)),
23 | lru :: List (Entry k v),
24 | cold :: List (Entry k v),
25 | storage :: Storage k v sv,
26 | maxHot, maxSize :: Int
27 | -- TODO: limit the number of entries, ie size of lru
28 | }
29 | data Storage k v sv = Storage {
30 | store :: k -> sv -> IO (),
31 | load :: k -> IO v
32 | }
33 | data Entry k v = Entry {
34 | key :: k,
35 | status :: IORef (Status k v)
36 | }
37 | data Status k v = Hot (LruElem k v) v
38 | | Cold (Maybe (LruElem k v)) (ColdElem k v) v
39 | | Out (LruElem k v)
40 | type LirsElem k v = Elem (Entry k v)
41 | type LruElem k v = LirsElem k v
42 | type ColdElem k v = LirsElem k v
43 |
44 | printLirs lirs = do
45 | m <- readIORef (cache lirs)
46 | mapM (\e -> liftM ((,) (key e)) (readIORef (status e))) (elems m) >>= print
47 | toHList (lru lirs) >>= print . map key
48 | toHList (cold lirs) >>= print . map key
49 |
50 | instance Show (Status k v) where
51 | showsPrec _ (Hot _ _) = ("hot" ++)
52 | showsPrec _ (Cold _ _ _) = ("cold" ++)
53 | showsPrec _ (Out _) = ("out" ++)
54 |
55 | new :: Storage k v sv -> Int -> Int -> IO (Lirs k v sv)
56 | new s maxHot maxSize = do when (maxHot < 1 || maxSize < 1 || maxSize < maxHot)
57 | (fail "bad sizes")
58 | cache <- newIORef empty
59 | lruList <- newList
60 | coldList <- newList
61 | return (Lirs cache lruList coldList s maxHot maxSize)
62 |
63 | -- This doesn't count as a hit, but keep if the cache is not yet full.
64 | put :: Ord k => Lirs k v sv -> k -> v -> sv -> IO ()
65 | put lirs k v sv = do
66 | (store (storage lirs)) k sv
67 | m <- readIORef (cache lirs)
68 | case lookup k m of
69 | Just entry -> modifyIORef (status entry) (setVal v)
70 | Nothing -> if size m < maxHot lirs
71 | then newHot lirs k v append
72 | else if size m < maxSize lirs
73 | then newCold lirs k v Nothing append
74 | else return ()
75 | where
76 | setVal v (Hot lruElem _) = Hot lruElem v
77 | setVal v (Cold lruElem coldElem _) = Cold lruElem coldElem v
78 | setVal v s@(Out _) = s
79 |
80 | get :: Ord k => Lirs k v sv -> k -> IO v
81 | get lirs k = liftM fst (get' lirs k)
82 |
83 | get' lirs k = do
84 | m <- readIORef (cache lirs)
85 | case lookup k m of
86 | Just entry -> do
87 | lruElem' <- prepend (lru lirs) entry
88 | readIORef (status entry) >>= \s -> case s of
89 | Hot lruElem v -> do
90 | remove (lru lirs) lruElem
91 | writeIORef (status entry) (Hot lruElem' v)
92 | prune lirs
93 | return (v, True)
94 | Cold (Just lruElem) coldElem v -> do
95 | remove (lru lirs) lruElem
96 | remove (cold lirs) coldElem
97 | writeIORef (status entry) (Hot lruElem' v)
98 | demoteHot lirs
99 | return (v, True)
100 | Cold Nothing coldElem v -> do
101 | remove (cold lirs) coldElem
102 | coldElem' <- prepend (cold lirs) entry
103 | writeIORef (status entry) (Cold (Just lruElem') coldElem' v)
104 | return (v, True)
105 | Out lruElem -> do
106 | v <- load (storage lirs) k
107 | remove (lru lirs) lruElem
108 | writeIORef (status entry) (Hot lruElem' v)
109 | demoteHot lirs
110 | evictCold lirs
111 | return (v, False)
112 | Nothing -> do v <- load (storage lirs) k
113 | if size m < maxHot lirs
114 | then newHot lirs k v prepend
115 | else if size m < maxSize lirs
116 | then newCold lirs k v (Just prepend) prepend
117 | else do newCold lirs k v (Just prepend) prepend
118 | evictCold lirs
119 | return (v, False)
120 |
121 | newHot lirs k v lruIns = do
122 | status <- newIORef undefined
123 | let entry = Entry k status
124 | lruElem <- lruIns (lru lirs) entry
125 | writeIORef status (Hot lruElem v)
126 | modifyIORef (cache lirs) (insert k entry)
127 |
128 | newCold lirs k v lruIns coldIns = do
129 | status <- newIORef undefined
130 | let entry = Entry k status
131 | lruElem <- case lruIns of
132 | Just ins -> liftM Just (ins (lru lirs) entry)
133 | Nothing -> return Nothing
134 | coldElem <- coldIns (cold lirs) entry
135 | writeIORef status (Cold lruElem coldElem v)
136 | modifyIORef (cache lirs) (insert k entry)
137 |
138 | forget lirs k = modifyIORef (cache lirs) (delete k)
139 |
140 | demoteHot lirs = do
141 | Just lruElem@Elem{elemData = entry} <- readIORef (last (lru lirs))
142 | remove (lru lirs) lruElem
143 | coldElem <- prepend (cold lirs) entry
144 | modifyIORef (status entry) (\(Hot _ v) -> Cold Nothing coldElem v)
145 | prune lirs
146 |
147 | evictCold lirs = do
148 | Just coldElem@Elem{elemData = entry} <- readIORef (last (cold lirs))
149 | remove (cold lirs) coldElem
150 | readIORef (status entry) >>= \s -> case s of
151 | Cold (Just lruElem) _ v -> writeIORef (status entry) (Out lruElem)
152 | Cold Nothing _ _ -> forget lirs (key entry)
153 |
154 | prune lirs = do
155 | Just lruElem@Elem{elemData = entry} <- readIORef (last (lru lirs))
156 | readIORef (status entry) >>= \s -> case s of
157 | Hot _ _ -> return ()
158 | Cold _ coldElem v -> do remove (lru lirs) lruElem
159 | writeIORef (status entry) (Cold Nothing coldElem v)
160 | prune lirs
161 | Out _ -> do remove (lru lirs) lruElem
162 | forget lirs (key entry)
163 | prune lirs
164 |
165 | -- mutable doubly-linked list
166 |
167 | data Elem a = Elem {
168 | elemData :: a,
169 | prev, next :: IORef (Maybe (Elem a))
170 | }
171 | data List a = List { first, last :: (IORef (Maybe (Elem a))) }
172 |
173 | newList :: IO (List a)
174 | newList = liftM2 List (newIORef Nothing) (newIORef Nothing)
175 |
176 | toHList :: List a -> IO [a]
177 | toHList l = walk (first l) where
178 | walk p = readIORef p >>= \e -> case e of
179 | Just e -> liftM (elemData e :) (walk (next e))
180 | Nothing -> return []
181 |
182 | remove :: List a -> Elem a -> IO ()
183 | remove l e = do
184 | p <- readIORef (prev e)
185 | n <- readIORef (next e)
186 | case p of
187 | Just p -> writeIORef (next p) n
188 | Nothing -> writeIORef (first l) n
189 | case n of
190 | Just n -> writeIORef (prev n) p
191 | Nothing -> writeIORef (last l) p
192 |
193 | prepend :: List a -> a -> IO (Elem a)
194 | prepend l x = do
195 | firstElem <- readIORef (first l)
196 | elem <- liftM2 (Elem x) (newIORef Nothing) (newIORef firstElem)
197 | case firstElem of
198 | Just f -> writeIORef (prev f) (Just elem)
199 | Nothing -> writeIORef (last l) (Just elem)
200 | writeIORef (first l) (Just elem)
201 | return elem
202 |
203 | append :: List a -> a -> IO (Elem a)
204 | append l x = do
205 | lastElem <- readIORef (last l)
206 | elem <- liftM2 (Elem x) (newIORef lastElem) (newIORef Nothing)
207 | case lastElem of
208 | Just l -> writeIORef (next l) (Just elem)
209 | Nothing -> writeIORef (first l) (Just elem)
210 | writeIORef (last l) (Just elem)
211 | return elem
212 |
213 | -- tests
214 |
215 | testStorage = Storage (\k v -> return ()) (\k -> return ())
216 | testLirs = new testStorage
217 |
218 | testPaper = do
219 | lirs <- testLirs 2 3
220 | let g n = get lirs n >> printLirs lirs
221 | g 1 -- [(1,hot)] [1] []
222 | g 4 -- [(1,hot),(4,hot)] [4,1] []
223 | g 2 -- [(1,hot),(2,cold),(4,hot)] [2,4,1] [2]
224 | g 3 -- [(1,hot),(2,out),(3,cold),(4,hot)] [3,2,4,1] [3]
225 | g 2 -- [(1,cold),(2,hot),(3,out),(4,hot)] [2,3,4] [1]
226 | g 1 -- [(1,cold),(2,hot),(3,out),(4,hot)] [1,2,3,4] [1]
227 | g 4 -- [(1,cold),(2,hot),(4,hot)] [4,1,2] [1]
228 | g 1 -- [(1,hot),(2,cold),(4,hot)] [1,4] [2]
229 | g 5 -- [(1,hot),(4,hot),(5,cold)] [5,1,4] [5]
230 |
231 | testCyclic workingSize maxHot maxSize reps = do
232 | lirs <- testLirs maxHot maxSize
233 | let g n = liftM snd (get' lirs n)
234 | mapM (\i -> g i) [1..workingSize]
235 | flip mapM_ [1..reps] $ \n -> do
236 | mapM_ (\i -> assert (show i) (g i)) [1..maxHot]
237 | mapM_ (\i -> assertNot (show i) (g i)) [maxHot+1..workingSize]
238 | print n
239 | tc = testCyclic 10001 9900 10000 100
240 |
241 | assert :: String -> IO Bool -> IO ()
242 | assert msg io = io >>= \r -> when (not r) (fail ("assertion failed: " ++ msg))
243 | assertNot msg io = assert msg (liftM not io)
244 |
245 | testRandom workingSize maxHot maxSize num = do
246 | lirs <- testLirs maxHot maxSize
247 | replicateM_ num $ do
248 | i <- randomRIO (1::Int, workingSize)
249 | get lirs i
250 | tr = testRandom 10000 9900 10000 100000000
251 |
--------------------------------------------------------------------------------
/Soutei/Parsec.hs:
--------------------------------------------------------------------------------
1 | -- helpers for Parsec
2 |
3 | module Soutei.Parsec where
4 |
5 | import Text.ParserCombinators.Parsec
6 | import Control.Monad.Identity
7 |
8 | junk p = p >> return ()
9 |
10 | parseM :: Monad m => Parser a -> FilePath -> String -> m a
11 | parseM p f s = handleM (parse p f s)
12 |
13 | parseFromFileM :: Parser a -> FilePath -> IO a
14 | parseFromFileM p f = parseFromFile p f >>= handleM
15 |
16 | handleM (Left e) = fail (show e)
17 | handleM (Right x) = return x
18 |
19 | uncheckedParse :: Parser a -> String -> a
20 | uncheckedParse p s = runIdentity (parseM p "string" s)
21 |
--------------------------------------------------------------------------------
/Soutei/Sexpr.hs:
--------------------------------------------------------------------------------
1 | module Soutei.Sexpr (
2 | Sexpr(..),
3 | sexpr, cons, whiteSpace,
4 | toList, toAtomList, fromList, fromAtomList
5 | ) where
6 |
7 | import Control.Monad
8 | import Text.ParserCombinators.Parsec hiding (newline)
9 |
10 | data Sexpr a = Atom a
11 | | Cons (Sexpr a) (Sexpr a)
12 | | Nil
13 | deriving (Eq)
14 |
15 | instance Show a => Show (Sexpr a) where
16 | showsPrec _ (Atom x) = shows x
17 | showsPrec _ l@(Cons x y) = showParen True (shows x . showsTail y) where
18 | showsTail (Atom x) = (" . "++) . shows x
19 | showsTail (Cons x y) = (' ':) . shows x . showsTail y
20 | showsTail Nil = id
21 | showsPrec _ Nil = ("()"++)
22 |
23 | -- do not eat trailing whitespace, because we want to process a request from
24 | -- a lazy stream (eg socket) as soon as we see the closing paren.
25 | sexpr :: Parser a -> Parser (Sexpr a)
26 | sexpr p = liftM Atom p
27 | <|> cons p
28 | sexprL p = lexeme (sexpr p)
29 |
30 | cons :: Parser a -> Parser (Sexpr a)
31 | cons p = between (lexeme (string "(")) (string ")") tailL where
32 | tailL = do dotL
33 | sexprL p
34 | -- require a space between consecutive atoms
35 | <|> liftM2 Cons (liftM Atom p)
36 | ( (whiteSpace1 >> tailL)
37 | <|> liftM2 Cons (consL p) tailL
38 | <|> return Nil)
39 | <|> liftM2 Cons (consL p) tailL
40 | <|> return Nil
41 | consL p = lexeme (cons p)
42 |
43 | lexeme p = do r <- p
44 | whiteSpace
45 | return r
46 | whiteSpace = many space
47 | whiteSpace1 = many1 space
48 | dotL = lexeme (string ".")
49 |
50 | toList :: Monad m => Sexpr a -> m [Sexpr a]
51 | toList Nil = return []
52 | toList (Cons x xs) = liftM (x:) (toList xs)
53 | toList (Atom x) = fail "toList: not a pure list"
54 |
55 | toAtomList :: Monad m => Sexpr a -> m [a]
56 | toAtomList s = toList s >>= mapM toAtom where
57 | toAtom (Atom x) = return x
58 | toAtom _ = fail "toAtomList: element is not an atom"
59 |
60 | fromList :: [Sexpr a] -> Sexpr a
61 | fromList = foldr Cons Nil
62 |
63 | fromAtomList :: [a] -> Sexpr a
64 | fromAtomList = fromList . map Atom
65 |
--------------------------------------------------------------------------------
/Soutei/Soutei.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE EmptyDataDecls #-}
2 |
3 | -- $HeadURL: https://svn.metnet.navy.mil/svn/metcast/Mserver/trunk/soutei/haskell/Soutei/Soutei.hs $
4 | -- $Id: Soutei.hs 2929 2012-09-11 03:42:25Z oleg.kiselyov $
5 | -- svn propset svn:keywords "HeadURL Id" filename
6 |
7 | module Soutei.Soutei where
8 |
9 | import Control.Monad
10 | import Data.Bits
11 | import Data.Word
12 | import Test.QuickCheck (Arbitrary(..), choose, oneof, sized, Property, forAll)
13 |
14 | -- The Soutei language
15 |
16 | data Const = SString String
17 | | SNumber !Integer
18 | | SIP4Addr !IP4Addr
19 | | SIP4Net !IP4Net
20 | deriving (Eq, Ord)
21 | newtype ConstC v = ConstC Const
22 | data IP4Addr = IP4Addr !Word32
23 | deriving (Eq, Ord)
24 | data IP4Net = IP4Net !IP4Addr !Int
25 | deriving (Eq, Ord)
26 | data Pred = Pred String !Int
27 | deriving (Eq, Ord)
28 | newtype SynVar = SynVar String
29 | deriving (Eq, Ord)
30 | data Var v = Anon
31 | | Named v
32 | deriving Eq
33 | -- No data constructor--used to guarantee a fact has no variables
34 | data NoVar
35 | data Term v = Var v
36 | | Val Const
37 | deriving Eq
38 | -- c is the container type constructor of context (NoCtx or MaybeCtx)
39 | data Atom c v = Atom {
40 | ctx :: c (Term v),
41 | pred :: Pred,
42 | args :: [Term v] }
43 | type HeadAtom v = Atom NoCtx v
44 | type BodyAtom v = Atom MaybeCtx v
45 | data NoCtx a = NoCtx
46 | data MaybeCtx a = JustCtx a
47 | | NothingCtx
48 | data Rule v = Rule (HeadAtom v) [BodyAtom v]
49 | type Fact = Atom NoCtx NoVar
50 | type SynTerm = Term (Var SynVar)
51 | type SynAtom c = Atom c (Var SynVar)
52 | type SynHeadAtom = HeadAtom (Var SynVar)
53 | type SynBodyAtom = BodyAtom (Var SynVar)
54 | type SynRule = Rule (Var SynVar)
55 | type Goal = SynHeadAtom
56 |
57 | atomToFact :: Monad m => Atom NoCtx v -> m Fact
58 | atomToFact atom = fmapM (\_ -> fail "facts may not have variables") atom
59 |
60 | factToAtom :: Fact -> Atom NoCtx v
61 | factToAtom fact = fmap undefined fact
62 |
63 | class FunctorM f where
64 | fmapM :: Monad m => (a -> m b) -> f a -> m (f b)
65 |
66 | instance FunctorM Maybe where
67 | fmapM f Nothing = return Nothing
68 | fmapM f (Just x) = f x >>= return . Just
69 |
70 | instance Functor Term where
71 | fmap f (Var v) = Var (f v)
72 | fmap f (Val x) = Val x
73 | instance FunctorM Term where
74 | fmapM f (Var v) = liftM Var (f v)
75 | fmapM f (Val x) = return (Val x)
76 |
77 | instance Functor c => Functor (Atom c) where
78 | fmap f (Atom ctx pred args) = Atom (fmap (fmap f) ctx)
79 | pred
80 | (map (fmap f) args)
81 | instance FunctorM c => FunctorM (Atom c) where
82 | fmapM f (Atom ctx pred args) = do
83 | ctx' <- fmapM (fmapM f) ctx
84 | args' <- mapM (fmapM f) args
85 | return (Atom ctx' pred args')
86 |
87 | instance Functor Rule where
88 | fmap f (Rule h b) = Rule (fmap f h) (map (fmap f) b)
89 | instance FunctorM Rule where
90 | fmapM f (Rule h b) = liftM2 Rule (fmapM f h) (mapM (fmapM f) b)
91 |
92 | instance Functor NoCtx where
93 | fmap f NoCtx = NoCtx
94 | instance FunctorM NoCtx where
95 | fmapM f NoCtx = return NoCtx
96 |
97 | instance Functor MaybeCtx where
98 | fmap f (JustCtx x) = JustCtx (f x)
99 | fmap f NothingCtx = NothingCtx
100 | instance FunctorM MaybeCtx where
101 | fmapM f (JustCtx x) = liftM JustCtx (f x)
102 | fmapM f NothingCtx = return NothingCtx
103 |
104 | sysCtx = SString "system"
105 | appCtx = SString "application"
106 |
107 | fact :: String -> [Const] -> Atom NoCtx v
108 | fact pred args = Atom NoCtx (Pred pred (length args)) (map Val args)
109 |
110 | goal :: String -> [Const] -> Goal
111 | goal pred args = Atom NoCtx (Pred pred (length args)) (map Val args)
112 |
113 | -- IPAddr/IPNet
114 |
115 | bytesToIP4Addr :: [Word8] -> IP4Addr
116 | bytesToIP4Addr = IP4Addr . bytesToBits 4
117 |
118 | ip4AddrToBytes :: IP4Addr -> [Word8]
119 | ip4AddrToBytes (IP4Addr addr) = bitsToBytes 4 addr
120 |
121 | bytesToBits :: Bits a => Int -> [Word8] -> a
122 | bytesToBits = toBits 0 where
123 | toBits acc 0 [] = acc
124 | toBits acc n (b:bs) = toBits (shiftL acc 8 .|. fromIntegral b) (n-1) bs
125 |
126 | bitsToBytes :: (Bits a, Integral a) => Int -> a -> [Word8]
127 | bitsToBytes n x = [fromIntegral (shiftR x ((n-i)*8) .&. 255) | i <- [1..n]]
128 |
129 | ip4of :: IP4Addr -> IP4Net -> Bool
130 | ip4of (IP4Addr addr) (IP4Net (IP4Addr netAddr) netBits) =
131 | let mask = complement (shiftL 1 (32 - netBits) - 1)
132 | in addr .&. mask == netAddr .&. mask
133 |
134 | -- QuickCheck
135 |
136 | -- only generate strings for simplicity
137 | instance Arbitrary Const where
138 | arbitrary = liftM SString (logSized (\n -> liftM show (choose (0, n))))
139 |
140 | instance Arbitrary v => Arbitrary (Var v) where
141 | arbitrary = oneof [return Anon, liftM Named arbitrary]
142 |
143 | instance Arbitrary v => Arbitrary (Term v) where
144 | arbitrary = oneof [liftM Var arbitrary, liftM Val arbitrary]
145 |
146 | logSized f = sized (\n -> f (floor (log (fromIntegral (n+1)) / log 2) :: Int))
147 |
148 | prop_ip4of :: Word32 -> Property
149 | prop_ip4of addr = forAll (choose (0,32)) $ \netBits ->
150 | forAll (choose (0,31)) $ \flipBit ->
151 | ip4of (IP4Addr (addr `complementBit` flipBit))
152 | (IP4Net (IP4Addr addr) netBits) ==
153 | (netBits + flipBit < 32)
154 |
155 |
--------------------------------------------------------------------------------
/Soutei/Syntax.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE UndecidableInstances #-}
2 |
3 | -- $HeadURL: https://svn.metnet.navy.mil/svn/metcast/Mserver/trunk/soutei/haskell/Soutei/Syntax.hs $
4 | -- $Id: Syntax.hs 2947 2012-09-14 08:26:08Z oleg.kiselyov $
5 | -- svn propset svn:keywords "HeadURL Id" filename
6 |
7 | module Soutei.Syntax (
8 | top, assertionL, headL, term, const
9 | ) where
10 |
11 | import Prelude hiding (head, const)
12 | import Control.Monad
13 | import Data.Bits
14 | import Data.Monoid (mconcat)
15 | import Text.ParserCombinators.Parsec hiding (newline)
16 |
17 | import Soutei.Parsec
18 | import Soutei.Soutei
19 |
20 | -- show
21 |
22 | instance Show Const where
23 | -- if it can be parsed as a symbol, show it as a symbol (cosmetic)
24 | showsPrec _ (SString s) = case parseM symbol "" s of
25 | Just s' | s' == s -> (s++)
26 | _ -> shows s
27 | showsPrec _ (SNumber n) = shows n
28 | showsPrec _ (SIP4Addr a) = ("#p"++) . shows a
29 | showsPrec _ (SIP4Net n) = ("#n"++) . shows n
30 |
31 | instance Show (ConstC v) where
32 | showsPrec _ (ConstC x) = shows x
33 |
34 | instance Show IP4Addr where
35 | showsPrec _ addr = ip4AddrToBytes addr `showsSep` "."
36 |
37 | instance Show IP4Net where
38 | showsPrec _ (IP4Net addr bits) = shows addr . ('/':) . shows bits
39 |
40 | instance Show Pred where
41 | showsPrec _ (Pred pred arity) = (pred++) . ('/':) . shows arity
42 |
43 | instance Show SynVar where
44 | showsPrec _ (SynVar v) = (v++)
45 |
46 | instance Show v => Show (Var v) where
47 | showsPrec _ Anon = ('?':)
48 | showsPrec _ (Named v) = ('?':) . shows v
49 |
50 | instance Show v => Show (Term v) where
51 | showsPrec _ (Val x) = shows x
52 | showsPrec _ (Var v) = shows v
53 |
54 | instance (Show (c (Term v)), Show v) => Show (Atom c v) where
55 | showsPrec _ (Atom ctx (Pred pred arity) args) =
56 | shows ctx . (pred++) . showParen True (args `showsSep` ", ")
57 |
58 | instance Show v => Show (Rule v) where
59 | showsPrec _ (Rule a []) = shows a . (".\n"++)
60 | showsPrec _ (Rule a as) = shows a . (" :-\n "++) .
61 | (as `showsSep` ",\n ") . (".\n"++)
62 | showList rules = mconcat (map shows rules)
63 |
64 | instance Show (NoCtx a) where
65 | showsPrec _ NoCtx = id
66 |
67 | instance Show a => Show (MaybeCtx a) where
68 | showsPrec _ (JustCtx a) = shows a . (" says "++)
69 | showsPrec _ (NothingCtx)= id
70 |
71 | showsSep :: Show a => [a] -> String -> ShowS
72 | showsSep [x] _ = shows x
73 | showsSep (x:xs) s = shows x . (s++) . showsSep xs s
74 |
75 | -- parser
76 |
77 | -- L suffix means a lexeme parser (eats trailing space); sort of annoying,
78 | -- but I prefer to be explicit, because the distinction sometimes matters.
79 |
80 | assertionL :: Parser [SynRule]
81 | assertionL = many statementL
82 | statementL :: Parser SynRule
83 | statementL = do h <- headL
84 | b <- do inCaseL
85 | bodyL
86 | <|>
87 | return []
88 | stopL
89 | return (Rule h b)
90 | headL :: Parser SynHeadAtom
91 | headL = predAtomL NoCtx
92 | bodyL :: Parser [SynBodyAtom]
93 | bodyL = atomL `sepBy` commaL
94 | predAtomL :: c SynTerm -> Parser (SynAtom c)
95 | predAtomL ctx = do pred <- symbolL
96 | args <- parensL (termL `sepBy` commaL)
97 | return (Atom ctx (Pred pred (length args)) args)
98 | ctxAtomL :: Parser SynBodyAtom
99 | ctxAtomL = do ctx <- term
100 | whiteSpace1
101 | says
102 | whiteSpace1
103 | predAtomL (JustCtx ctx)
104 | atomL :: Parser SynBodyAtom
105 | atomL = try (predAtomL NothingCtx)
106 | <|> ctxAtomL
107 | termL :: Parser SynTerm
108 | termL = lexeme term
109 | term :: Parser SynTerm
110 | term = liftM Var var
111 | <|> liftM Val const
112 |
113 | -- lexemes
114 |
115 | top p = between whiteSpace eof p
116 | lexeme p = do r <- p
117 | whiteSpace
118 | return r
119 | space' = junk space <|> junk comment
120 | whiteSpace = many space'
121 | whiteSpace1 = many1 space'
122 | comment = do char ';'
123 | manyTill anyChar (newline <|> eof)
124 | > "comment"
125 | newline = junk (char '\n')
126 | <|> junk (try (string "\r\n"))
127 | <|> junk (char '\r')
128 |
129 | stopL = lexeme (string ".")
130 | inCaseL = lexeme (string ":-")
131 | commaL = lexeme (string ",")
132 | parensL p = between (lexeme (string "(")) (lexeme (string ")")) p
133 | says = string "says"
134 |
135 | var = do char '?'
136 | liftM (Named . SynVar) symbol <|> return Anon
137 | symbolL = lexeme symbol
138 | symbol = do c <- symbolStart
139 | s <- many symbolChar
140 | return (c:s)
141 | > "symbol"
142 | -- based on Scheme, except disallow leading '?', and allow '@"
143 | symbolStart = letter <|> oneOf "!$%&*/:<=>@~_^"
144 | symbolChar = symbolStart <|> digit <|> oneOf ".+-?"
145 | > "symbol character"
146 | const = liftM SString qString
147 | <|> liftM SString symbol
148 | <|> liftM SNumber number
149 | <|> liftM SIP4Addr ip4Addr
150 | <|> liftM SIP4Net ip4Net
151 | > "constant"
152 | qString = do char '"'
153 | manyTill ch (char '"') where
154 | ch = do char '\\'
155 | c <- anyChar
156 | case c of
157 | 'n' -> return '\n'
158 | '"' -> return '"'
159 | '\\' -> return '\\'
160 | otherwise -> fail "bad escape sequence"
161 | <|> anyChar
162 | number = natural
163 | ip4Addr = do try (string "#p")
164 | ip4Addr'
165 | ip4Net = do try (string "#n")
166 | addr <- ip4Addr'
167 | char '/'
168 | bits <- ip4NetBits
169 | return (IP4Net addr bits)
170 | ip4Addr' = liftM bytesToIP4Addr (ip4AddrByte `sepBy4` char '.')
171 | ip4AddrByte = liftM fromIntegral (naturalBet 0 255 "ip address octet")
172 | ip4NetBits = liftM fromIntegral (naturalBet 1 32 "ip address range bits")
173 | natural :: Parser Integer
174 | natural = liftM read (many1 digit)
175 | naturalBet n1 n2 what = do n <- natural
176 | when (n < n1 || n > n2)
177 | (fail ("bad " ++ what ++ " " ++ show n ++
178 | ", not between " ++ show n1 ++ " and "
179 | ++ show n2))
180 | return n
181 |
182 | sepByN :: Int -> Parser a -> Parser b -> Parser [a]
183 | sepByN n p sep = sb n where
184 | sb n = liftM2 (:) p (sb' (n-1))
185 | sb' 0 = return []
186 | sb' n = sep >> sb n
187 | sepBy4 = sepByN 4
188 |
189 | -- tests
190 |
191 | testParser :: String -> IO ()
192 | testParser s = do a' <- parseM assertionL "test" s
193 | let s' = show a'
194 | if (s == s')
195 | then putStrLn "parsed"
196 | else fail ("parse error: " ++ show s ++ " -> " ++ show s')
197 |
198 | testParserError :: String -> IO ()
199 | testParserError s = case parse assertionL "test" s of
200 | Left e -> putStrLn "parse error"
201 | Right x -> fail ("should not have parsed: " ++ s)
202 |
203 | -- most tests cribbed from binder-parse.scm
204 | test = do testParser ""
205 | testParserError "may"
206 | testParserError "may(1)"
207 | testParser "may(1).\n"
208 | testParserError "may(\")"
209 | testParserError "may(?, ?, ?)"
210 | testParser "may(?, ?, ?).\n"
211 | testParser "may(?, _, ?_).\n"
212 | testParser "may(?x, \"ok x\", ?zZ).\n"
213 | testParser ("may(?x, ?Y, ?) :-\n" ++
214 | " application says ip(?ip).\n")
215 | testParser ("may(2, ?Y, ?) :-\n" ++
216 | " ?Y says ip(?ip, #p10.0.0.1),\n" ++
217 | " ok(?ip).\n")
218 |
--------------------------------------------------------------------------------
/demo/metcast-channels/Makefile:
--------------------------------------------------------------------------------
1 | # Run the regression test suite for Soutei or for the
2 | # Metcast Channels + Soutei combo
3 |
4 | # The Soutei engine root
5 | ROOT=../../
6 |
7 | GHC := ghc
8 | GHC_MAKE = $(GHC) --make
9 | GHC_OPTS := -O2
10 | GHC_INCLUDES = `$(GHC) --print-libdir`/include
11 |
12 | HC_CMD := $(GHC_MAKE) $(GHC_OPTS) -I$(GHC_INCLUDES) -i$(ROOT)
13 |
14 | test: soutei-metcast-tests
15 | ./soutei-metcast-tests 10 10
16 |
17 | soutei-metcast-tests: soutei-metcast-tests.hs
18 | $(HC_CMD) $^ -o $@
19 |
20 | clean::
21 | rm -f soutei-metcast-tests
22 |
23 |
24 | clean::
25 | rm -f *.hi *.o $(PROGS)
26 | rm -f Soutei/*.hi
27 | rm -f Soutei/*.o
28 |
--------------------------------------------------------------------------------
/demo/metcast-channels/doc/00.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Soutei-Channels Demonstration:
4 | Introduction to Soutei
5 |
6 |
7 |
Introduction to Soutei
8 |
9 |
Soutei is Policy Decision Point (PDP). It offers advice
10 | about what actions are permitted, according to a policy. Enforcement of
11 | policy is left to a Policy Enforcement Point (PEP).
12 |
13 |
Design goals:
14 |
15 |
16 |
Universal policy language and authorization service
17 |
We demonstrate Soutei integrated with Navy Enterprise Single Sign-On
10 | (NESSO) and Metcast Channels. We include examples of
11 | delegation and Risk Adaptable Access Control
12 | (RAdAC).
13 |
14 |
15 |
16 |
Our initial policy states that:
17 |
18 |
19 |
sam.sysadmin may delegate permission to create channels.
20 |
The creator of a channel may delegate access to his channel.
21 |
All access is delegated to the emergency policy.
22 |
cam.create creates the DustSensorData channel and grants himself full
10 | access to the channel. He also allows don.delegate to grant read
11 | permission to US citizens.
joe.canada cannot see the channel, even though don.delegate's policy
10 | attempted to let him, because the delegation only applies to US
11 | citizens.
This page discusses various ways of making Soutei assertions
12 | available to the decision engine: by reading a local configuration
13 | file, by querying trusted databases and LDAP servers, or by delivering
14 | the assertions in HTTP headers and in Public-key and Attribute X.509
15 | certificates.
Soutei assertions may be included in a public-key X.509
26 | certificate (PKC) [RFC3280] or in a X.509 attribute
27 | certificate [RFC3281].
28 |
29 |
There is a distinct advantage of including Soutei assertions in a
30 | public-key certificate intended for establishing a TLS
31 | (HTTPS, SSL) connection with the application server. In that case, we
32 | get certificate transport and validation ``for free'', performed as
33 | a part of the TLS handshake. In case of HTTPS, the web server should
34 | be configured to request a client certificate during the
35 | handshake. The web server will challenge the validity of the
36 | certificate, according to the TLS protocol. If the certificate is found
37 | valid and authentic, the web server establishes the TLS connection and
38 | accepts the HTTP request. The web server will give to the application
39 | server (i.e., the Metcast server) the certificate in the PEM-encoded
40 | format, as the content of the environment variable SSL_CLIENT_CERT. An option ExportCertData must be
41 | included in SSLOptions directive of the SSL engine
42 | portion of the Apache configuration file. The Soutei assertion should
43 | be placed, as an octet string, in the field authInfo of
44 | an Attribute structure SvceAuthInfo of a
45 | sequence SubjectDirectoryAttributes of the v3 certificate
46 | extensions field. A private v3 certificate extension may also be
47 | defined for the purpose of holding assertions. Because the assertion
48 | is a part of a signed and validated certificate, we can trust the
49 | assertion without any further checks.
50 |
51 |
Including Soutei assertions in a public-key certificate however
52 | can be problematic from the logistic point of view. Certificates
53 | issued to an end user (e.g., Common Access Card certificates)
54 | typically have restrictions on their usage: the bit cA is
55 | turned off and the certificates may be invalid for key
56 | encipherment. Therefore, an end user may not issue his own
57 | certificates to delegate his privileges to applications or other users
58 | via Soutei assertions. The user must ask a Certifying Authority (CA)
59 | to issue him a certificate with the appropriate Soutei assertions. The
60 | latter is quite an involved process. Furthermore, it is argued in [RFC3281] that CA are wrong entities to issue authorization
61 | statements.
62 |
63 |
The task of issuing authorization statements properly belongs
64 | to attribute authorities, as described in [RFC3281]. Authorization statements are placed into attribute X.509
65 | certificates. According to [RFC3281], an attribute
66 | certificate is a structure similar to public key certificates. Whereas
67 | the latter binds an identity of a subject and his public key, an
68 | attribute certificate serves to assign authorization attributes to the
69 | certificate holder. The attributes may include group membership, role,
70 | security clearance, etc.
71 |
72 |
Some people constantly confuse public-key certificates (PKC) and
73 | attribute certificates (AC). An analogy may make the distinction
74 | clear. A PKC can be considered to be like a passport: it identifies
75 | the holder, tends to last for a long time, and should not be trivial
76 | to obtain. An AC is more like an entry visa: it is typically issued
77 | by a different authority and does not last for as long a time. As
78 | acquiring an entry visa typically requires presenting a passport,
79 | getting a visa can be a simpler process [RFC3281].
80 |
81 |
Attribute certificates seem therefore appropriate vehicles for
82 | Soutei assertions. An end user may issue attribute certificates for
83 | his own applications. Attribute certificates are short-lived and ideal
84 | for such a delegation purpose. Furthermore, the profile in [RFC3281] explicitly states that an attribute certificate issuer
85 | must not be a CA: an attribute certificate issuer's public-key
86 | certificate must not contain a basicConstraints extension
87 | with the cA boolean set to TRUE. Not only
88 | end users may issue attribute certificates -- they are the only
89 | entities that may do so.
90 |
91 |
Before processing an assertion found in an attribute certificate,
92 | the server must validate the certificate as described in Section 5 of
93 | [RFC3281]. If the field Holder of the
94 | certificate identifies the holder by name or by a certificate
95 | reference, the identity of the holder must match the identity of the
96 | authenticated client; see Section 4.2.2 of RFC3281 for more detail.
97 | The holder of the certificate may also be an empty sequence. The assertion
98 | found in such a certificate is eligible for caching. Certificates with
99 | the empty holder name are employed for delegation.
100 |
101 |
A Soutei assertion is placed into one attribute of an attribute
102 | certificate. The certificate may include other attributes. It seems
103 | that the most appropriate attribute for Soutei assertions is
104 | SvceAuthInfo, described in Section 4.4.1 or RFC3281:
105 | ``This attribute provides information that can be presented by the AC
106 | verifier to be interpreted and authenticated by a separate application
107 | within the target system.''
The Soutei verifier is such a separate application. The Soutei
115 | assertion should be placed into the field authInfo. The
116 | fields service and ident are currently
117 | unused and should be set to the empty value of the type directoryName. We should mention that a Group attribute (Section
118 | 4.4.4 of RFC3281) seems also an appropriate attribute to hold Soutei
119 | assertions. We may in the future register an attribute object identifier
120 | specifically for Soutei assertions.
121 |
When an attribute certificate is imported into Soutei, the subject
122 | identity of the issuer (taken from the field issuerName
123 | of the certificate) serves as a context identifier for the imported
124 | assertion. The subject identity is generally a SHA-1 hash computed
125 | from the name of the issuer or found in the SubjectKeyIdentifier extension of the public-key certificate of
126 | the issuer. The extension takes precedence, if it exists.
127 |
128 |
The problem with attribute certificates is transporting them from
129 | a client to the server. Can we still piggy-back on TLS for
130 | transporting and validating attribute certificates? It seems that
131 | OpenSSL might do that for us. This issue requires further
132 | investigation.
133 |
134 |
HTTP headers and Soutei
135 |
136 |
Soutei assertions may also be delivered in HTTP headers. The
137 | headers take precedence over the TLS-based transport of
138 | certificates. We introduce two kinds of headers. Only one kind must be
139 | present in a HTTP session.
140 |
141 |
Attribute-assertion header: X-X509-AC. The contents of
142 | the header is an attribute certificate (described above), DER- and
143 | Base64- encoded.
144 |
145 |
Signed-assertion-header: X-BAssertion. The contents of
146 | the header is a cryptographically signed message [RFC3369]
147 | with the content-type text/x-bassertion. The signer of
148 | the message is considered to be the issuer of the assertion.
[RFC3280] R. Housley, W. Polk, W. Ford, D. Solo. Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile. RFC 3280, Standards Track. April 2002.
161 | <http://www.rfc-editor.org/rfc/rfc3280.txt>
193 |
--------------------------------------------------------------------------------
/doc/Auth-transport.scm:
--------------------------------------------------------------------------------
1 | (define Content
2 | '(html:begin
3 | (Header
4 | (title "Transport of Soutei assertions")
5 | (description "Discussion of various ways to bring assertions to
6 | the Soutei engine.")
7 | (Date-Revision-yyyymmdd "20040723")
8 | (Date-Creation-yyyymmdd "20040722")
9 | (keywords "Binder, Soutei, Keynote, X.509, Attribute certificate")
10 | (AuthorAddress "oleg-at-okmij.org")
11 | (Author "Oleg Kiselyov")
12 | (rcs-id "$Id: Auth-transport.scm,v 1.2 2004/10/15 02:46:02 oleg Exp oleg $")
13 | (long-title "Transport of Soutei assertions")
14 | (Links
15 | (start "Authorization.html"
16 | (title "Authorization and Role-Based Access control"))
17 | (contents "Authorization.html")
18 | (prev "Auth-use-cases.html")
19 | (next "Authorization.html")
20 | (top "index.html")
21 | (home "http://soutei.sf.net/")))
22 |
23 | (body
24 | (navbar)
25 | (page-title)
26 |
27 |
28 | (p "This page discusses various ways of making Soutei assertions
29 | available to the decision engine: by reading a local configuration
30 | file, by querying trusted databases and LDAP servers, or by delivering
31 | the assertions in HTTP headers and in Public-key and Attribute X.509
32 | certificates.")
33 |
34 | (TOC)
35 |
36 | (Section 2 "X.509" " Certificates and Soutei")
37 |
38 | (p "Soutei assertions may be included in a public-key X.509
39 | certificate (PKC) " (cite "RFC3280") " or in a X.509 attribute
40 | certificate " (cite "RFC3281") ".")
41 |
42 | (p "There is a distinct advantage of including Soutei assertions in a
43 | public-key certificate intended for establishing a TLS
44 | \(HTTPS, SSL) connection with the application server. In that case, we
45 | get certificate transport and validation ``for free'', performed as
46 | a part of the TLS handshake. In case of HTTPS, the web server should
47 | be configured to request a client certificate during the
48 | handshake. The web server will challenge the validity of the
49 | certificate, according to the TLS protocol. If the certificate is found
50 | valid and authentic, the web server establishes the TLS connection and
51 | accepts the HTTP request. The web server will give to the application
52 | server (i.e., the Metcast server) the certificate in the PEM-encoded
53 | format, as the content of the environment variable " (code
54 | "SSL_CLIENT_CERT") ". An option " (code "ExportCertData") " must be
55 | included in " (code "SSLOptions") " directive of the SSL engine
56 | portion of the Apache configuration file. The Soutei assertion should
57 | be placed, as an octet string, in the field " (code "authInfo") " of
58 | an " (code "Attribute") " structure " (code "SvceAuthInfo") " of a
59 | sequence " (code "SubjectDirectoryAttributes") " of the v3 certificate
60 | extensions field. A private v3 certificate extension may also be
61 | defined for the purpose of holding assertions. Because the assertion
62 | is a part of a signed and validated certificate, we can trust the
63 | assertion without any further checks.")
64 |
65 | (p "Including Soutei assertions in a public-key certificate however
66 | can be problematic from the logistic point of view. Certificates
67 | issued to an end user (e.g., Common Access Card certificates)
68 | typically have restrictions on their usage: the bit " (code "cA") " is
69 | turned off and the certificates may be invalid for key
70 | encipherment. Therefore, an end user may not issue his own
71 | certificates to delegate his privileges to applications or other users
72 | via Soutei assertions. The user must ask a Certifying Authority (CA)
73 | to issue him a certificate with the appropriate Soutei assertions. The
74 | latter is quite an involved process. Furthermore, it is argued in "
75 | (cite "RFC3281") " that CA are wrong entities to issue authorization
76 | statements.")
77 |
78 | (p "The task of issuing authorization statements properly belongs
79 | to attribute authorities, as described in " (cite "RFC3281")
80 | ". Authorization statements are placed into attribute X.509
81 | certificates. According to " (cite "RFC3281") ", an attribute
82 | certificate is a structure similar to public key certificates. Whereas
83 | the latter binds an identity of a subject and his public key, an
84 | attribute certificate serves to assign authorization attributes to the
85 | certificate holder. The attributes may include group membership, role,
86 | security clearance, etc.")
87 | (blockquote
88 | "Some people constantly confuse public-key certificates (PKC) and
89 | attribute certificates (AC). An analogy may make the distinction
90 | clear. A PKC can be considered to be like a passport: it identifies
91 | the holder, tends to last for a long time, and should not be trivial
92 | to obtain. An AC is more like an entry visa: it is typically issued
93 | by a different authority and does not last for as long a time. As
94 | acquiring an entry visa typically requires presenting a passport,
95 | getting a visa can be a simpler process " (cite "RFC3281") ".")
96 |
97 | (p "Attribute certificates seem therefore appropriate vehicles for
98 | Soutei assertions. An end user may issue attribute certificates for
99 | his own applications. Attribute certificates are short-lived and ideal
100 | for such a delegation purpose. Furthermore, the profile in " (cite
101 | "RFC3281") " explicitly states that an attribute certificate issuer
102 | must not be a CA: an attribute certificate issuer's public-key
103 | certificate must not contain a " (code "basicConstraints") " extension
104 | with the " (code "cA") " boolean set to " (code "TRUE") ". Not only
105 | end users may issue attribute certificates -- they are the only
106 | entities that may do so.")
107 |
108 | (p "Before processing an assertion found in an attribute certificate,
109 | the server must validate the certificate as described in Section 5 of
110 | " (cite "RFC3281") ". If the field " (code "Holder") " of the
111 | certificate identifies the holder by name or by a certificate
112 | reference, the identity of the holder must match the identity of the
113 | authenticated client; see Section 4.2.2 of RFC3281 for more detail.
114 | The holder of the certificate may also be an empty sequence. The assertion
115 | found in such a certificate is eligible for caching. Certificates with
116 | the empty holder name are employed for delegation.")
117 |
118 | (p "A Soutei assertion is placed into one attribute of an attribute
119 | certificate. The certificate may include other attributes. It seems
120 | that the most appropriate attribute for Soutei assertions is
121 | " (code "SvceAuthInfo") ", described in Section 4.4.1 or RFC3281:
122 | ``This attribute provides information that can be presented by the AC
123 | verifier to be interpreted and authenticated by a separate application
124 | within the target system.''")
125 |
126 | (verbatim
127 | "id-aca OBJECT IDENTIFIER ::= { id-pkix 10 }"
128 | "id-aca-authenticationInfo OBJECT IDENTIFIER ::= { id-aca 1 }"
129 | "SvceAuthInfo ::= SEQUENCE {"
130 | " service GeneralName,"
131 | " ident GeneralName,"
132 | " authInfo OCTET STRING OPTIONAL }"
133 | )
134 |
135 | "The Soutei verifier is such a separate application. The Soutei
136 | assertion should be placed into the field " (code "authInfo") ". The
137 | fields " (code "service") " and " (code "ident") " are currently
138 | unused and should be set to the empty value of the type " (code
139 | "directoryName") ". We should mention that a Group attribute (Section
140 | 4.4.4 of RFC3281) seems also an appropriate attribute to hold Soutei
141 | assertions. We may in the future register an attribute object identifier
142 | specifically for Soutei assertions."
143 |
144 | ; Place service into Soutei's application context as 'service_info'?
145 |
146 | (p
147 | "When an attribute certificate is imported into Soutei, the subject
148 | identity of the issuer (taken from the field " (code "issuerName") "
149 | of the certificate) serves as a context identifier for the imported
150 | assertion. The subject identity is generally a SHA-1 hash computed
151 | from the name of the issuer or found in the " (code
152 | "SubjectKeyIdentifier") " extension of the public-key certificate of
153 | the issuer. The extension takes precedence, if it exists.")
154 |
155 | (p "The problem with attribute certificates is transporting them from
156 | a client to the server. Can we still piggy-back on TLS for
157 | transporting and validating attribute certificates? It seems that
158 | OpenSSL might do that for us. This issue requires further
159 | investigation.")
160 |
161 | (Section 2 "HTTP" " headers and Soutei")
162 |
163 | (p "Soutei assertions may also be delivered in HTTP headers. The
164 | headers take precedence over the TLS-based transport of
165 | certificates. We introduce two kinds of headers. Only one kind must be
166 | present in a HTTP session.")
167 |
168 | (p "Attribute-assertion header: " (code "X-X509-AC") ". The contents of
169 | the header is an attribute certificate (described above), DER- and
170 | Base64- encoded.")
171 |
172 | (p "Signed-assertion-header: " (code "X-BAssertion") ". The contents of
173 | the header is a cryptographically signed message " (cite "RFC3369") "
174 | with the content-type " (code "text/x-bassertion") ". The signer of
175 | the message is considered to be the issuer of the assertion.")
176 |
177 |
178 | (hr)
179 |
180 | (Section 2 "References")
181 |
182 | (bibitem "Binder" "Binder"
183 | "J. DeTreville. Binder, a logic-based security language. "
184 | "IEEE Security and Privacy, 2002. "
185 | (URL "http://research.microsoft.com/research/pubs/view.aspx?tr_id=545"))
186 |
187 |
188 | (bibitem "PKI-Tutorial" "PKI-Tutorial"
189 | "Peter Gutmann. "
190 | "Everything you never wanted to know about PKI but have been forced
191 | to find out. "
192 | (URL "http://www.cs.auckland.ac.nz/~pgut001/pubs/pkitutorial.pdf"))
193 |
194 |
195 | (bibitem "RFC3280" "RFC3280"
196 | "R. Housley, W. Polk, W. Ford, D. Solo. "
197 | "Internet X.509 Public Key Infrastructure "
198 | "Certificate and Certificate Revocation List (CRL) Profile. "
199 | "RFC 3280, Standards Track. April 2002."
200 | (URL "http://www.rfc-editor.org/rfc/rfc3280.txt")
201 | )
202 |
203 | (bibitem "RFC3281" "RFC3281"
204 | "S. Farrell and R. Housley. "
205 | "An Internet Attribute Certificate Profile for Authorization. "
206 | "RFC 3281, Standards Track. April 2002."
207 | (URL "http://www.rfc-editor.org/rfc/rfc3281.txt")
208 | )
209 |
210 | (bibitem "RFC3369" "RFC3369"
211 | "R. Housley. "
212 | "Cryptographic Message Syntax (CMS). "
213 | "RFC 3369. Standards Track. August 2002. "
214 | (URL "http://www.rfc-editor.org/rfc/rfc3369.txt")
215 | )
216 |
217 | (bibitem "NIST-PKI" "NIST-PKI"
218 | "NIST PKI Program"
219 | (URL "http://csrc.nist.gov/pki/")
220 | )
221 |
222 | ; ASN.1 References
223 |
224 | (bibitem "ASN1-Guide" "ASN1-Guide"
225 | "Burton S. Kaliski Jr. "
226 | "A Layman's Guide to a Subset of ASN.1, BER, and DER. "
227 | "An RSA Laboratories Technical Note. "
228 | "Revised November 1, 1993. "
229 | (URL "http://citeseer.nj.nec.com/47302.html")
230 | ; http://luca.ntop.org/Teaching/Appunti/asn1.html
231 | )
232 |
233 | ; ASN.1 Misuse, a paper by Carl M. Ellison
234 | ; Other ASN.1 papers
235 | ; http://www.oss.com/asn1/booksintro.html
236 | ; http://www.larmouth.demon.co.uk/tutorials/tagging/index.htm
237 | ; http://www.oss.com/
238 |
239 | (bibitem "X509-Style" "X509-Style"
240 | "Peter Gutmann. "
241 | "X.509 Style Guide. "
242 | "October 2000. "
243 | (URL "http://www.cs.auckland.ac.nz/~pgut001/pubs/x509guide.txt"))
244 |
245 |
246 | (footer)
247 |
248 | )))
249 |
250 |
251 | ;========================================================================
252 | ; HTML generation
253 |
254 | (define common-rules-here
255 | (generic-web-rules Content
256 | `(
257 | ; A reference to a group of test cases
258 | ; (group-ref target . title)
259 | ; If title is given, generate a regular
260 | ; title
261 | ; Otherwise, use target as the title
262 | (group-ref
263 | . ,(lambda (tag target . title)
264 | (list "" (if (pair? title) title target)
265 | "" nl)))
266 |
267 | ; A category of use cases
268 | ; (Category key . title)
269 | (Category
270 | . ,(lambda (tag key . title)
271 | (list "
Most of the access control systems discussed below belong to a
26 | class of trust-management systems. RFC 2704 succinctly describes these
27 | systems as follows [RFC2704]:
28 |
29 |
30 |
A trust-management system provides standard, general-purpose
31 | mechanisms for specifying application security policies and
32 | credentials. Trust-management credentials describe a specific
33 | delegation of trust and subsume the role of public key certificates;
34 | unlike traditional certificates, which bind keys to names, credentials
35 | can bind keys directly to the authorization to perform specific
36 | tasks.
37 | ...
38 |
39 |
Trust management unifies the notions of security policy,
40 | credentials, access control, and authorization. An application that
41 | uses a trust-management system can simply ask the compliance checker
42 | whether a requested action should be allowed. Furthermore, policies
43 | and credentials are written in standard languages that are shared by
44 | all trust-managed applications; the security configuration mechanism
45 | for one application carries exactly the same syntactic and semantic
46 | structure as that of another, even when the semantics of the
47 | applications themselves are quite different.
48 |
49 |
Trust-management policies are easy to distribute across networks,
50 | helping to avoid the need for application-specific distributed policy
51 | configuration mechanisms, access control lists, and certificate
52 | parsers and interpreters.
53 |
54 |
55 |
KeyNote
56 |
57 |
KeyNote [KeyNote] [RFC2704] is one particular
58 | framework and a language to build trust-management systems. In
59 | KeyNote, principals are identified by names, which can be
60 | cryptographic keys. Policies and credentials are called `assertions':
61 | typically cryptographically signed statements describing trusted
62 | actions and conditions that yield a policy compliance value. The
63 | latter is often a binary value (e.g., grant and deny); a range of restricted access permissions may also be
64 | specified. A principal may issue an assertion delegating authorization
65 | to perform (a subset) of actions to other principals. Top-level assertions
66 | are usually stored locally. Others are fetched from remote authorities
67 | or delivered by clients. In the latter cases, the assertions should be
68 | signed.
69 |
70 |
Particularly attractive properties of KeyNote are an ability of
71 | principals to delegate a subset of their privileges to other
72 | principals, and an ability to express authorization conditions as
73 | logical formulas. A condition is a logical proposition over attributes
74 | whose values can be strings, integers, and floating-point numbers. The
75 | values of the attributes are provided by an application that requests
76 | an authorization advice. The conditions can express, for example, that
77 | a particular file is accessible for reading only within a specific
78 | time window and only if the request is vouched for by at least two
79 | trusted administrators. Examples at the end of RFC2704 are quite
80 | illustrative.
81 |
82 |
KeyNote is a mature system. There is a reference implementation and
83 | several others. KeyNote is a part of a secure distributed file system
84 | [DisCFS] and of OpenBSD's IPSEC stack. Apache-SSL can also
85 | use KeyNote. The KeyNote page
86 | [KeyNote] lists other real-world applications of the
87 | system. Google search for KeyNote trust management yields
88 | quite a few links.
89 |
90 |
One of the important properties of the KeyNote system is its
91 | monotonicity: access permissions never decrease as more security
92 | assertions are made available to the system. That is, KeyNote will
93 | never authorize an action only because some crucial assertion was not
94 | delivered to the system in time. We should note however that the
95 | monotonicity property, however beneficial, precludes using KeyNote
96 | assertions for revocation. Revocation of privileges must be handled in
97 | some other way (e.g., through expiration of certificates).
98 |
99 |
The monotonicity property (adding certificates may only increase
100 | the trust value) seems to be sound: it is guaranteed by the fact that
101 | the Licensees: field of an assertion uses monotone
102 | operators (&&, || and k-of),
103 | and the Conditions: field cannot refer to other
104 | certificates.
105 |
106 |
The notion of an application scope provides some kind of scoping of
107 | attributes. The calling application is responsible for dereferencing
108 | attributes -- either by passing a dictionary or providing a look-up
109 | function (call-back). To the KeyNote system, the values of attributes
110 | and the bindings themselves are immutable. KeyNote provides for
111 | indirect references (e.g., $foo refers to an attribute
112 | whose name is in the attribute foo).
113 |
114 |
Keynote problems
115 |
116 |
The KeyNote system is not without problems.
117 |
118 |
119 |
RFC2704 says, ``Attribute names may be given literally or
120 | calculated from string expressions and may be recursively
121 | dereferenced.'' It is not clear if self-references or cyclical
122 | references are expressible. If they are, a non-termination of a
123 | policy decision becomes an issue.
124 |
125 |
Type conversion seems quite sloppy. Attribute values are
126 | strings; a user may request a conversion of a string to an integer or
127 | an IEEE floating-point number. If the conversion fails, no error is
128 | reported but the conversion result is assumed to be 0. Likewise,
129 | dereferencing an unbound attribute reports no error but yields the
130 | empty string instead.
131 |
132 |
Local attributes (defined in an assertion itself) override
133 | application-supplied attributes. However, if the name of a local
134 | attribute is mis-spelled (or mis-cased -- names are case-sensitive),
135 | trouble ensues. No error is reported but the overriding fails. The
136 | error becomes especially insidious if a mis-spelled name is used as an
137 | indirect attribute name. The latter fact may cause a wrong value used
138 | in a condition formula, and consequently, authorizing an action that
139 | should have been denied.
140 |
141 |
A design decision making a numeric conversion failure yield 0 is a
142 | security concern. Here's the example from RFC2704 itself:
Here @ is a string-to-integer conversion
148 | operator. Let us suppose that user_id was meant to be
149 | 65535 but by mistake was 65535-. The
150 | conversion fails, @user_id yields the value of
151 | zero, which triggers the answer full_access. A client
152 | would be given an authorization for the full access when no access
153 | should have been granted. This security concern becomes especially
154 | serious if the values of the attributes are accepted from clients,
155 | without exhaustive checking.
156 |
157 |
It has been proven [RTC] that an analysis of all
158 | requests authorized by a set of assertions is undecidable. In fact,
159 | we cannot in generally tell if a policy with a single assertion
160 | authorizes any request at all. It is therefore impossible in general
161 | to analyze the effect of security assertions, e.g., to verify global
162 | policy constraints.
163 |
164 |
165 |
166 |
Binder system
167 |
168 |
Binder is a logic-based security language: an extension of
169 | Datalog. Binder was introduced in a paper [Binder]. Google
170 | search for ``Binder security language'' offers many links to that
171 | paper -- but no real applications or implementations. In that respect,
172 | KeyNote is more developed. On the other hand, Binder is developed by
173 | an experienced security researcher, has a clean design and sound
174 | logical foundations [Logic-AC].
175 |
176 |
A security statement in Binder is a logical program written in a
177 | subset of Prolog without function symbols (i.e., Datalog). Binder
178 | extends Datalog [Datalog] with the notion of a context and
179 | a distinguished relation says. A statement in Binder can
180 | be a simple fact, e.g., can(john_smith,read,resource_r)
181 | or a rule, e.g., can(X,read,resource_r) :-
182 | employee(X,bigco). One rule like that replaces a great number
183 | of conventional access control list items. Security statements in Binder
184 | are therefore concise. Binder can easily express role-based access
185 | control, delegation, and quite complex security policies, for example
186 | [Binder]:
The first statement in the above certificate is a rule stating that
195 | any employee of a BigCo may read resource_r provided such an
196 | action is approved by his boss. The other three statements are facts
197 | about employees of BigCo and the approval action. More examples along
198 | with their detailed descriptions can be found in the Binder paper [Binder].
199 |
Granting access to a resource in Binder is deriving
200 | an atom that asserts such a permission, e.g., an atom can(john_smith,read,resource_r) in the example. The
201 | derivation constitutes a logical proof of the
202 | permission. The proof can be generated by a service, in polynomial
203 | time. Alternatively, a client can generate a proof and submit it with
204 | the request. The service needs merely to check the proof. The latter
205 | approach distributes the load of authorization computations and helps
206 | prevent denial-of-service attacks.
207 |
208 |
Binder programs do not contain negation. Therefore, Binder is
209 | monotonic: adding more statements can only make more atoms
210 | provable. In other words, we cannot cause elevated access permissions
211 | by withholding statements.
212 |
213 |
Binder is specifically designed for a distributed computing
214 | environment. Each authorization service has its own Binder context. A
215 | context with a set of facts and rules can be exported into a signed
216 | certificate and transmitted to another service. Statements in an
217 | exported context are marked with the identity of the exporting service
218 | using the quotation form says. A service can import a
219 | context and use the context's statements in local proofs if the local
220 | service trusts the remote one. The trust relationship is itself
221 | expressed as a set of Binder statements.
222 |
223 |
Identities of Binder principals -- for instance, identities of the
224 | exporting services -- are represented by cryptographic keys. The latter
225 | may be encoded in the format described in [RFC2792]. One may
226 | bind a local name to a cryptographic key for easy reference, e.g.,
227 | [Binder]:
The local context with its name bigco_hr can be exported
233 | in turn. This feature lets Binder simulate the linked name spaces of
234 | SDSI/SPKI, but without built-in language support.
235 |
The paper [Binder] states the following distinguished
236 | features of the system:
237 |
238 |
239 |
A statement in Binder can be translated into a declarative,
240 | stand-alone English sentence.
241 |
242 |
Binder programs can explicitly define new, application-specific
243 | predicates, which can act as lemmas in proofs. Predicates can be
244 | defined recursively. Rich proofs are allowed.
245 |
246 |
Certificates may contain arbitrary facts and rules, over local,
247 | application-specific -- or remote and quoted predicates. Certificates
248 | can be safely interpreted outside their exporting context.
249 |
250 |
Binder statements can appear in certificates, in policies, in
251 | ACLs, and elsewhere, and these statements can inter-operate freely.
252 |
253 |
Binder queries are decidable in polynomial time.
254 |
255 |
256 |
Section 7 of the paper [Binder] compares Binder with
257 | X.509 Certificates, SDSI and SPKI, PolicyMaker, KeyNote, SD3 and
258 | similar logic-based security languages, and digital rights management
259 | languages. The paper shows that none of those systems possesses all five
260 | key Binder properties.
261 |
262 |
263 |
SAML
264 |
265 |
SAML is a Security Assertion Markup Language [SAML].
266 | SAML seems to be more a certificate format and a certificate transport
267 | format than a trust management language.
268 |
269 |
It seems that DecisionType of a SAML assertion only specifies
270 | Permit, Deny and Indeterminate. KeyNote provides for far more variety
271 | of decisions. The conditions on the assertion are also far less
272 | expressive: NotBefore, NotOnOrAfter, <AudienceRestrictionCondition>,
273 | <DoNotCacheCondition>.
274 |
275 |
RTC: Datalog with constraints
276 |
277 |
Ninghui Li and John C. Mitchell have proposed a family of
278 | declarative trust-management languages based on Datalog with
279 | constraints [RTC].
280 |
281 |
Policy checking
282 |
283 |
An access control system advises an application if an action
284 | requested by a particular principal is consistent with a security
285 | policy. We may also need to check if the security policy itself is
286 | consistent, that is, if it actually protects valuable resources. In a
287 | policy with many rules, the overall effect may be difficult to
288 | see. Unpleasant surprises do happen in practice:
289 |
290 |
Firewalls that rely on chained rule sets are vulnerable to
291 | cascade failures -- a change in one rule can have an effect on every
292 | rule that follows it. I've seen systems that relied on a firewall to
293 | block services that were only supposed to be available on the local
294 | network, but which were made available to the entire Internet due to
295 | the unforeseen results of a firewall rule change. [Firewalls] (p. 35)
296 |
297 |
The first quantitative study of firewall configuration errors
298 | [Firewall-errors] found the results dismal. ``Only one of the
299 | 37 firewalls exhibited just a single misconfiguration. All the others
300 | could have been easily penetrated by both unsophisticated attackers
301 | and mindless automatic worms.''
302 |
303 |
To prevent such unforeseen results we need to check policy's
304 | invariants and consistency. Unfortunately, many access control systems
305 | have quite low expressivity, which results in a large set of
306 | rules. For example, SELinux policy has around 50,000 statements. We
307 | need automated tools to verify policies. The tools must be built on
308 | firm logical foundations. Because the policy check is an off-line
309 | process (executed only when the policy is updated), the performance of
310 | the tools is not of prime importance.
311 |
312 |
Unfortunately, some of the access control systems such as KeyNote
313 | have not been designed with policy checking in mind: in general,
314 | policy checking in KeyNote is undecidable [RTC].
315 |
316 |
One real-life example of policy checking is testing that SELinux
317 | policies are consistent with the trusted computer base requirements:
318 | `Analyzing Integrity Protection in the SELinux Example Policy' by
319 | Trent Jaeger, Reiner Sailer, Xiaolan Zhang presented at USENIX
320 | Security Symposium 2003 [VALI]. The authors have developed
321 | a Gokyo policy analysis tool, which seems to rely on a human-aided
322 | exhaustive search. No inference seem to be present. In fact, the words
323 | `infer' and `formal' are not even mentioned, and the word `logic'
324 | occurs only in the title of two referenced papers. It is not clear how
325 | the tool itself was verified -- if it was at all. Perhaps the stress
326 | must be on rigor rather than on the development of visual tools.
327 |
328 |
329 |
Datalog
330 |
331 |
Datalog seems to be the foundation for many logic-based access
332 | control languages and systems.
333 |
334 |
Introduction to datalog, top-down and bottom-up strategies,
335 | and Herbrand interpretation:
336 |
337 |
Computational Intelligence, a logical approach. D. Poole,
338 | A. Mackworth and R. Goebel.Oxford University Press, 1998. ISBN
339 | 0-19-510270-3. Chapter 2
XSB: An efficient logical system with top-down and bottom-up
373 | strategies. XSB can evaluate according to Well-Founded Semantics
374 | through full SLG resolution.
A disjunctive Datalog system DLV is the very first system
383 | supporting full disjunctive logic programming with answer set
384 | semantics. It supports answer set semantics for full disjunctive
385 | logic programs with negation, integrity constraints, queries, and
386 | arithmetic built-ins.
A graph coloring problem in the tutorial illustrates the
397 | advantages of DLV. The problem is to find out if a given map of
398 | countries can be colored with three colors. No two neighbor countries
399 | should have the same color. The map, of Mid-Western U.S. states in the
400 | example, as represented as a set of nodes and arcs. Arcs connect
401 | neighboring states.
The first statement is a disjunctive rule that guesses a
413 | coloring. The second statement expresses the strong constraint that
414 | deletes all those colorings that do not satisfy our requirements (that
415 | there may be no arc between two nodes of equal color). DLV solves the
416 | problem rather efficiently.
417 |
418 |
[RFC2792] M. Blaze, J. Ioannidis, A. Keromytis. DSA and RSA Key and Signature Encoding for the
427 | KeyNote Trust Management System.RFC 2792. March 2000.
428 | <http://www.rfc-editor.org/rfc/rfc2792.txt>
[RTC] Ninghui Li, J.C. Mitchell. Datalog with constraints: a foundation for trust management languages. Proc. PADL2003: Practical Aspects of Declarative Languages. LNCS 2562, pp. 58-73.