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 +4/−0
- candid.cabal +7/−5
- hcandid.hs +3/−3
- src/Codec/Candid.hs +1/−1
- src/Codec/Candid/Class.hs +1/−1
- src/Codec/Candid/Coerce.hs +1/−1
- src/Codec/Candid/Encode.hs +1/−1
- src/Codec/Candid/FieldName.hs +3/−38
- src/Codec/Candid/Hash.hs +59/−0
- src/Codec/Candid/Infer.hs +1/−1
- src/Codec/Candid/TypTable.hs +1/−1
- src/Codec/Candid/Types.hs +1/−1
- test/test.hs +4/−1
- words.txt too large to diff
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