packages feed

candid 0.2 → 0.3

raw patch · 14 files changed

+87/−54 lines, 14 filesdep +file-embeddep ~prettyprinterPVP ok

version bump matches the API change (PVP)

Dependencies added: file-embed

Dependency ranges changed: prettyprinter

API changes (from Hackage documentation)

Files

CHANGELOG.md view
@@ -1,5 +1,9 @@ # Revision history for haskell-candid +## 0.3 -- 2021-10-01++* Candid pretty-printing: Try to invert field names using word list+ ## 0.2 -- 2021-06-17  * Guess field named when only the hash is known
candid.cabal view
@@ -1,11 +1,11 @@ cabal-version:      >=1.10 name:               candid-version:            0.2+version:            0.3 license:            Apache license-file:       LICENSE maintainer:         mail@joachim-breitner.de author:             Joachim Breitner-homepage:           https://github.com/dfinity/candid+homepage:           https://github.com/nomeata/haskell-candid synopsis:           Candid integration description:     This package brings the Candid interface definition language to Haskell,@@ -17,7 +17,7 @@  category:           Codec build-type:         Simple-extra-source-files: CHANGELOG.md+extra-source-files: CHANGELOG.md words.txt  library     exposed-modules:@@ -35,6 +35,7 @@         Codec.Candid.Data         Codec.Candid.Types         Codec.Candid.FieldName+        Codec.Candid.Hash         Codec.Candid.TypTable         Codec.Candid.Decode         Codec.Candid.EncodeTextual@@ -63,10 +64,11 @@         unordered-containers >=0.2.10.0 && <0.3,         row-types > 1.0.0.0 && < 1.1,         constraints >=0.12 && <0.14,-        prettyprinter >=1.6.2 && <1.8,+        prettyprinter >=1.7 && <1.8,         template-haskell >=2.14.0.0 && <2.17,         base32 >=0.1.1.2 && <0.3,-        split >=0.2.3.4 && <0.3+        split >=0.2.3.4 && <0.3,+        file-embed  executable hcandid     main-is:          hcandid.hs
hcandid.hs view
@@ -9,8 +9,8 @@ import Data.Char import System.IO import System.Exit-import Data.Text.Prettyprint.Doc-import Data.Text.Prettyprint.Doc.Util+import Prettyprinter+import Prettyprinter.Util  err :: String -> IO b err s = hPutStr stderr s >> exitFailure@@ -37,7 +37,7 @@ decodeCandid :: BS.ByteString -> IO () decodeCandid b = case decodeVals b of   Left msg -> err msg-  Right vs -> putDocW 80 (pretty vs) >> putStrLn ""+  Right (_, vs) -> putDocW 80 (pretty vs) >> putStrLn ""  encodeCandid :: String -> IO () encodeCandid txt = case parseValues txt of
src/Codec/Candid.hs view
@@ -154,7 +154,7 @@ -- >>> import Data.Text (Text) -- >>> import qualified Data.Text as T -- >>> import Data.Void (Void)--- >>> import Data.Text.Prettyprint.Doc (pretty)+-- >>> import Prettyprinter (pretty) -- >>> import qualified Data.ByteString.Lazy.Char8 as BS -- >>> :set -XScopedTypeVariables 
src/Codec/Candid/Class.hs view
@@ -42,7 +42,7 @@ import Data.Word import Data.Int import Data.Void-import Data.Text.Prettyprint.Doc+import Prettyprinter import Language.Haskell.TH (mkName, tupleDataName) import Language.Haskell.TH.Lib   ( appT, tupleT, varT, litT, strTyLit
src/Codec/Candid/Coerce.hs view
@@ -11,7 +11,7 @@   )   where -import Data.Text.Prettyprint.Doc+import Prettyprinter import qualified Data.Vector as V import qualified Data.ByteString.Lazy as BS import qualified Data.Map as M
src/Codec/Candid/Encode.hs view
@@ -20,7 +20,7 @@ import Data.Void import Control.Monad.RWS.Lazy import Data.Serialize.LEB128-import Data.Text.Prettyprint.Doc+import Prettyprinter  import Codec.Candid.Data import Codec.Candid.TypTable
src/Codec/Candid/FieldName.hs view
@@ -14,17 +14,15 @@   ) where  import qualified Data.Text as T-import qualified Data.Text.Encoding as T-import qualified Data.ByteString.Lazy as BS-import Data.Text.Prettyprint.Doc+import Prettyprinter import Data.String-import Data.Maybe import Data.Word-import Data.Char import Numeric.Natural import Data.Function import Text.Read (readMaybe) +import Codec.Candid.Hash+ -- | A type for a Candid field name. Essentially a 'Word32' with maybe a textual label attached data FieldName = FieldName     { fieldHash :: Word32 -- ^ Extract the raw field hash value@@ -39,39 +37,6 @@ -- | Create a 'FieldName' from the raw hash hashedField :: Word32 -> FieldName hashedField h = FieldName h Nothing---- | The Candid field label hashing algorithm-candidHash :: T.Text -> Word32-candidHash s = BS.foldl (\h c -> h * 223 + fromIntegral c) 0 $ BS.fromStrict $ T.encodeUtf8 s---- | Inversion of the Candid field label hash-invertHash :: Word32 -> Maybe T.Text-invertHash w32 | w32 < 32 = Nothing-    -- leave small numbers alone, tend to be tuple indicies-invertHash w32 = listToMaybe guesses-  where-    x = fromIntegral w32 :: Word64-    chars = ['a'..'z'] ++ ['_']-    ords = 0 : map (fromIntegral . ord) chars-    non_mod x = x - (x `mod` 2^(32::Int))-    guesses =-        [ T.pack $ reverse guess-        | c8 <- ords, c7 <- ords, c6 <- ords, c5 <- ords-        -- It seems that 8 characters are enough to invert anything-        -- (based on quickchecking)-        -- Set up so that short guesses come first-        , let high_chars = c5 * 223^(4::Int) + c6 * 223^(5::Int) + c7 * 223^(6::Int) + c8 * 223^(7::Int)-        , let guess = simple $ x + non_mod high_chars-        , all (`elem` chars) guess-        ]--    -- inverts the Hash if the hash was created without modulos-    -- returns string in reverse order-    simple :: Word64 -> String-    simple 0 = ""-    simple x = chr (fromIntegral b) : simple a-      where (a, b) = x `divMod` 223-  instance Eq FieldName where     (==) = (==) `on` fieldHash
+ src/Codec/Candid/Hash.hs view
@@ -0,0 +1,59 @@+{-# LANGUAGE TemplateHaskell #-}+-- | The hash algorithm used for Candid field names+--+-- Also includes a function that tries to reverse the hash, first using an+-- English word list, and then a brute force approach.+module Codec.Candid.Hash+  ( candidHash+  , invertHash+  ) where++import qualified Data.Text as T+import qualified Data.Text.Encoding as T+import qualified Data.ByteString.Lazy as BS+import qualified Data.IntMap as M+import Data.Maybe+import Data.Char+import Data.Word+import Data.FileEmbed++-- | The Candid field label hashing algorithm+candidHash :: T.Text -> Word32+candidHash s = BS.foldl (\h c -> h * 223 + fromIntegral c) 0 $ BS.fromStrict $ T.encodeUtf8 s++-- | Inversion of the Candid field label hash+invertHash :: Word32 -> Maybe T.Text+invertHash w32 | w32 < 32 = Nothing+    -- leave small numbers alone, tend to be tuple indicies+invertHash w32 | Just t <- M.lookup (fromIntegral w32) m  = Just t+    -- try the word list+invertHash w32 = listToMaybe guesses+  where+    x = fromIntegral w32 :: Word64+    chars = ['a'..'z'] ++ ['_']+    ords = 0 : map (fromIntegral . ord) chars+    non_mod x = x - (x `mod` 2^(32::Int))+    guesses =+        [ T.pack $ reverse guess+        | c8 <- ords, c7 <- ords, c6 <- ords, c5 <- ords+        -- It seems that 8 characters are enough to invert anything+        -- (based on quickchecking)+        -- Set up so that short guesses come first+        , let high_chars = c5 * 223^(4::Int) + c6 * 223^(5::Int) + c7 * 223^(6::Int) + c8 * 223^(7::Int)+        , let guess = simple $ x + non_mod high_chars+        , all (`elem` chars) guess+        ]++    -- inverts the Hash if the hash was created without modulos+    -- returns string in reverse order+    simple :: Word64 -> String+    simple 0 = ""+    simple x = chr (fromIntegral b) : simple a+      where (a, b) = x `divMod` 223++-- Word list obtained from https://github.com/dwyl/english-words+ws :: T.Text+ws = $(embedStringFile "words.txt")++m :: M.IntMap T.Text+m = M.fromList [ (fromIntegral (candidHash w), w) | w <- T.lines ws]
src/Codec/Candid/Infer.hs view
@@ -6,7 +6,7 @@ import Control.Monad import Data.Void import Data.List-import Data.Text.Prettyprint.Doc+import Prettyprinter  import Codec.Candid.Types 
src/Codec/Candid/TypTable.hs view
@@ -16,7 +16,7 @@ import qualified Data.Map as M import Control.Monad.State.Lazy import Data.Void-import Data.Text.Prettyprint.Doc+import Prettyprinter import Data.DList (singleton, DList) import Data.Graph import Data.Foldable
src/Codec/Candid/Types.hs view
@@ -15,7 +15,7 @@ import Data.Char import Numeric -import Data.Text.Prettyprint.Doc+import Prettyprinter  import Codec.Candid.Data import Codec.Candid.FieldName
test/test.hs view
@@ -36,7 +36,7 @@ import GHC.Word import Numeric.Natural import GHC.Generics (Generic)-import Data.Text.Prettyprint.Doc+import Prettyprinter import Data.Row import Data.Proxy import qualified Data.Row.Records as R@@ -411,6 +411,9 @@         QC.forAll (QC.choose (0,4)) $ \len ->         QC.forAll (T.pack <$> QC.vectorOf len (QC.elements ('_':['a'..'z']))) $ \s ->         candidHash s >= 32 QC.==>+        invertHash (candidHash s) QC.=== Just s+    , QC.testProperty "long dictionary name" $+        let s = "precriticized" in         invertHash (candidHash s) QC.=== Just s     , QC.testProperty "all hashes find something" $         QC.forAll QC.arbitraryBoundedIntegral $ \w ->
+ words.txt view

file too large to diff