scuttlebutt-types (empty) → 0.1.0
raw patch · 12 files changed
+647/−0 lines, 12 filesdep +aesondep +basedep +base64-bytestringsetup-changed
Dependencies added: aeson, base, base64-bytestring, bytestring, bytestring-conversion, ed25519, hspec, scuttlebutt-types, text
Files
- CHANGELOG +5/−0
- LICENSE +29/−0
- README.md +23/−0
- Setup.hs +2/−0
- scuttlebutt-types.cabal +56/−0
- src/Ssb/Types.hs +7/−0
- src/Ssb/Types/Key.hs +57/−0
- src/Ssb/Types/Link.hs +26/−0
- src/Ssb/Types/Message.hs +208/−0
- test/KeySpec.hs +16/−0
- test/MessageSpec.hs +217/−0
- test/Spec.hs +1/−0
+ CHANGELOG view
@@ -0,0 +1,5 @@+scuttlebutt-types (0.1.0) unstable; urgency=low++ * First release, split off from haskell-scuttlebutt.++ -- Joey Hess <id@joeyh.name> Thu, 05 Oct 2017 12:47:48 -0400
+ LICENSE view
@@ -0,0 +1,29 @@+BSD 3-Clause License++Copyright (c) 2017, bkil.hu, Peter Ferenc Hajdu, Joey Hess+All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++* Redistributions of source code must retain the above copyright notice, this+ list of conditions and the following disclaimer.++* Redistributions in binary form must reproduce the above copyright notice,+ this list of conditions and the following disclaimer in the documentation+ and/or other materials provided with the distribution.++* Neither the name of the copyright holder nor the names of its+ contributors may be used to endorse or promote products derived from+ this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,23 @@+Secure Scuttlebutt is a database of unforgeable append-only feeds,+optimized for efficient replication for peer to peer protocols.+<https://www.scuttlebutt.nz/>++This library contains Haskell data types for common Secure Scuttlebutt+messages, including JSON serialization.++Git repositories:++* https://git.joeyh.name/git/haskell-scuttlebutt-types.git+* git-ssb repository:+ ssb://%h2FnIacUWXelN8YGY0NcShMjQCqkcDF8XVpW64LgTHE=.sha256++Contributions welcome! Please use git-ssb or email for making pull requests+and issue tracking. Also you can find fellow haskellers on the #haskell+channel on SSB, and discuss this library there.++Other Haskell Scuttlebutt resources:++* The haskell-scuttlebutt git repository is built on top of+ scuttlebutt-types, and has dreams of one day growing up to be a+ real scuttlebutt client. It git-ssb repository is here:+ ssb://%Nk2lTG8YHweKZEAfDrmzyjrkHPX1C9jHmYqc6csTCdU=.sha256
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ scuttlebutt-types.cabal view
@@ -0,0 +1,56 @@+Name: scuttlebutt-types+Version: 0.1.0+License: BSD3+License-File: LICENSE+Author: bkil.hu, Peter Ferenc Hajdu, Joey Hess+Maintainer: Joey Hess <id@joeyh.name>+Copyright: 2017 bkil.hu, Peter Ferenc Hajdu, Joey Hess+Category: Network+Build-Type: Simple+Extra-Source-Files: README.md CHANGELOG+Cabal-Version: >=1.10+Synopsis: generic types for Secure Scuttlebutt+Description:+ Secure Scuttlebutt is a database of unforgeable append-only feeds,+ optimized for efficient replication for peer to peer protocols.+ .+ This library contains data types for common Scuttlebutt messages,+ including JSON serialization.++Library+ Hs-Source-Dirs: src+ GHC-Options: -Wall -fno-warn-tabs+ Exposed-Modules:+ Ssb.Types+ Ssb.Types.Message+ Ssb.Types.Key+ Ssb.Types.Link+ Build-Depends:+ base (>= 4.7 && < 5),+ bytestring,+ bytestring-conversion,+ base64-bytestring,+ aeson,+ ed25519,+ text+ Default-Language: Haskell2010++Test-Suite scuttlebutt-types-test+ Type: exitcode-stdio-1.0+ Hs-Source-Dirs: test+ Main-Is: Spec.hs+ Build-Depends:+ base,+ scuttlebutt-types,+ hspec,+ bytestring,+ aeson+ Other-Modules:+ MessageSpec+ KeySpec+ Ghc-Options: -Wall -fno-warn-tabs -threaded -rtsopts -with-rtsopts=-N+ Default-Language: Haskell2010++source-repository head+ type: git+ location: https://git.joeyh.name/git/haskell-scuttlebutt-types.git
+ src/Ssb/Types.hs view
@@ -0,0 +1,7 @@+module Ssb.Types (+ module Ssb.Types.Message,+ module Ssb.Types.Key+) where++import Ssb.Types.Message+import Ssb.Types.Key
+ src/Ssb/Types/Key.hs view
@@ -0,0 +1,57 @@+{-# LANGUAGE DeriveGeneric, OverloadedStrings #-}++module Ssb.Types.Key (+ PublicKey(..),+ parsePublicKey,+ parseEd25519PublicKey,+ formatPublicKey,+) where++import Data.ByteString+import Data.Text.Encoding (encodeUtf8, decodeUtf8)+import qualified Data.Text as T+import qualified Data.ByteString.Base64 as B64+import qualified Data.Aeson as Aeson+import qualified Crypto.Sign.Ed25519 as Ed+import GHC.Generics+import Data.Monoid++newtype PublicKey = Ed25519PublicKey { ed25519Key :: Ed.PublicKey }+ deriving (Eq, Ord, Generic)++instance Aeson.FromJSON PublicKey where+ parseJSON = Aeson.withText "PublicKey" $ \t ->+ case parsePublicKey (encodeUtf8 t) of+ Just pk -> return pk+ Nothing -> fail ("public key decode failure: " ++ T.unpack t)++instance Aeson.ToJSON PublicKey where+ toJSON = Aeson.String . decodeUtf8 . formatPublicKey++instance Show PublicKey where+ show = T.unpack . decodeUtf8 . formatPublicKey++-- | Parses a SSB public key, which is base64 encoded, and has a prefix of+-- "@" and a suffix specifying the type of the key.+parsePublicKey :: ByteString -> Maybe PublicKey+parsePublicKey b = do+ b' <- stripPrefix "@" b+ case stripSuffix ".ed25519" b' of+ Just b'' -> parseEd25519PublicKey b''+ Nothing -> Nothing++-- | Decodes a SSB Ed25519 public key, which is base64 encoded.+parseEd25519PublicKey :: ByteString -> Maybe PublicKey+parseEd25519PublicKey b = Ed25519PublicKey <$> maybeEd25519PublicKey+ where+ decodedPublicKey = B64.decode b+ maybeEd25519PublicKey = either+ (const Nothing)+ (Just . Ed.PublicKey)+ decodedPublicKey++-- | Formats a SSB public key to a base64 encoded string with a prefix of+-- "@" and suffix specifying the type of the key.+formatPublicKey :: PublicKey -> ByteString+formatPublicKey (Ed25519PublicKey { ed25519Key = k }) =+ "@" <> B64.encode (Ed.unPublicKey k) <> ".ed25519"
+ src/Ssb/Types/Link.hs view
@@ -0,0 +1,26 @@+-- | TODO newtypes for everything++module Ssb.Types.Link where++import Ssb.Types.Key++import Data.Aeson++-- | A link to a message.+type MessageLink = String++-- | A link to a feed.+newtype FeedLink = FeedLink { unFeedLink :: PublicKey }+ deriving (Show, Eq, Ord)++instance FromJSON FeedLink where+ parseJSON d = FeedLink <$> parseJSON d++instance ToJSON FeedLink where+ toJSON = toJSON . unFeedLink++-- | A link to a blob.+type BlobLink = String++-- | A link to a message, feed, or blob.+type GenericLink = String
+ src/Ssb/Types/Message.hs view
@@ -0,0 +1,208 @@+{-# LANGUAGE DeriveGeneric, OverloadedStrings, FlexibleContexts #-}++module Ssb.Types.Message (+ Signature,+ HashType,+ Message(..),+ AnyContent(..),+ narrowParse,+ PrivateContent(..),+ Post(..),+ Mention(..),+ About(..),+ AboutImage(..),+ Contact(..),+ Vote(..),+ Pub(..),+ parseMessageType+) where++import Ssb.Types.Key+import Ssb.Types.Link++import GHC.Generics+import Data.Aeson+import Data.Aeson.Types+import Data.Int (Int64)+import qualified Data.Text as T+import Data.Maybe+import Data.List+import Data.Char+import Control.Applicative++import Prelude hiding (sequence)++type Signature = String++type HashType = String++data Message a = Message+ { previous :: Maybe MessageLink+ , author :: FeedLink+ , sequence :: Int64+ , timestamp :: Float+ , hash :: HashType+ , content :: a+ , signature :: Signature+ } deriving (Show, Eq, Generic)++instance FromJSON a => FromJSON (Message a)+instance ToJSON a => ToJSON (Message a)++instance Functor Message where+ fmap f m = m { content = f (content m) }++-- | Parsing a Message AnyContent allows parsing the message envelope,+-- regardless of the type of content in the message.+data AnyContent = AnyContent { fromAnyContent :: Value }+ deriving (Show, Eq)++instance FromJSON AnyContent where+ parseJSON = pure . AnyContent++-- | For best efficiency when the type of a message is not known,+-- first parse to a Message AnyContent, and then use this function+-- with `parseMaybe` or `parseEither` to try to further parse that+-- to different message types.+--+-- For example:+--+-- > Just somemsg = decode b :: Maybe Message AnyContent+-- > case parseMaybe narrowParse somemsg :: Maybe (Message Post) of+-- > Just postmsg -> ...+-- > Nothing -> case parseMaybe narrowParse somemsg :: Maybe (Message PrivateContent) of+-- > Just privmsg -> ...+-- > Nothing -> ...+narrowParse :: FromJSON a => Message AnyContent -> Parser (Message a)+narrowParse m = do+ c <- parseJSON (fromAnyContent (content m))+ return $ m { content = c }++-- | A message with encrypted content.+data PrivateContent = PrivateContent T.Text+ deriving (Show, Eq)++-- Any message that has a content that is a string, rather than a JSON+-- object, is an encrypted message.+instance FromJSON PrivateContent where+ parseJSON = withText "PrivateContent" (pure . PrivateContent)++-- | A post is a text-based message, for a public or private audience.+-- It can be a reply to other posts.+data Post = Post+ { text :: T.Text+ , channel :: Maybe T.Text+ , root :: Maybe MessageLink+ , branch :: Maybe MessageLink+ , recps :: Maybe [FeedLink]+ , mentions :: Maybe [Mention]+ } deriving (Show, Eq, Generic)++instance FromJSON Post where+ parseJSON = parseMessageType "post" (genericParseJSON defaultOptions)++-- | A generic reference to other feeds, entities, or blobs+data Mention = Mention+ { mentionLink :: GenericLink+ } deriving (Show, Eq, Generic)++instance FromJSON Mention where+ parseJSON = parseStrippingPrefix "mention"++-- | About-messages set attributes about someone or something.+-- They can be used to set a name or picture for users, files, or messages.+-- However, they're most commonly published about users.+data About = About+ { about :: GenericLink+ , name :: Maybe T.Text+ , image :: Maybe AboutImage+ } deriving (Show, Eq, Generic)++instance FromJSON About where+ parseJSON = parseMessageType "about" (genericParseJSON defaultOptions)++data AboutImage = AboutImage+ { aboutImageLink :: BlobLink+ , aboutImageSize :: Maybe Int+ , aboutImageType :: Maybe T.Text+ , aboutImageWidth :: Maybe Int+ , aboutImageHeight :: Maybe Int+ } deriving (Show, Eq, Generic)++instance FromJSON AboutImage where+ parseJSON = parseStrippingPrefix "aboutimage"++-- | Contact-messages determine who you are following or blocking.+data Contact = Contact+ { contact :: FeedLink+ , following :: Bool+ , blocking :: Bool+ } deriving (Show, Eq, Generic)++instance FromJSON Contact where+ parseJSON = parseMessageType "contact" $+ withObject "Contect" $ \o -> Contact+ <$> o .: "contact"+ <*> o .:? "following" .!= False+ <*> o .:? "blocking" .!= False++-- | Vote-messages signal approval about someone or something.+-- Votes can be on users, messages, or blobs.+data Vote = Vote+ { voteLink :: GenericLink+ , voteValue :: Int+ , voteExpression :: Maybe T.Text+ } deriving (Show, Eq, Generic)++instance FromJSON Vote where+ parseJSON = parseMessageType "vote" $ withObject "Vote" $ \o -> do+ -- For some reason the JSON wraps the vote in another object.+ v <- o .: "vote"+ Vote+ <$> v .: "link"+ <*> (v .: "value" <|> stringvalue v)+ <*> v .:? "expression"+ where+ -- It's not uncommon for the value to be a string containing a+ -- number, although it's supposed to be a plain number.+ stringvalue v = do+ s <- v .: "value" :: Parser T.Text+ case s of+ "1" -> return 1+ "0" -> return 0+ "-1" -> return (-1)+ _ -> fail "unknown vote value"++-- Pub-messages announce the address, port, and public key of pubs.+-- They are automatically published by Scuttlebot after successfully+-- using an invite to a pub.+data Pub = Pub+ { pubHost :: T.Text+ , pubPort :: Int+ , pubKey :: PublicKey+ } deriving (Show, Eq, Generic)++instance FromJSON Pub where+ parseJSON = parseMessageType "pub" $ withObject "Pub" $ \o -> do+ -- For some reason the JSON wraps the pub in another object.+ v <- o .: "address"+ parseStrippingPrefix "pub" v++-- | Parse the content of a message using the provided Parser,+-- which will typically be genericParseJSON defaultOptions.+--+-- The "type" field must contain the specified Text for the parse to succeed.+parseMessageType :: T.Text -> (Value -> Parser a) -> Value -> Parser a+parseMessageType ty parser v@(Object o) = do+ t <- o .: "type"+ if t == ty+ then parser v+ else fail $ "wrong message type " ++ T.unpack t ++ " (expected " ++ T.unpack ty ++ ")"+parseMessageType ty _ invalid = typeMismatch (T.unpack ty) invalid++-- | Parse, stripping a common prefix from the haskell record names.+parseStrippingPrefix :: (Generic a, GFromJSON Zero (Rep a)) => String -> Value -> Parser a+parseStrippingPrefix prefix = + genericParseJSON $ defaultOptions { fieldLabelModifier = f }+ where+ f s = fromMaybe s $ stripPrefix prefix $ map toLower s
+ test/KeySpec.hs view
@@ -0,0 +1,16 @@+{-# LANGUAGE OverloadedStrings #-}++module KeySpec where++import Ssb.Types.Key+import Test.Hspec+import Data.ByteString++base64PublicKey :: ByteString+base64PublicKey = "@opBinLmYiID9SMEoZJPH60LmduSYAR7J00P7/Gj9vHw=.ed25519"++spec :: Spec+spec = describe "public key" $ do+ it "can be deserialized and serialized from base64 bytestrings" $ do+ let (Just publicKey) = parsePublicKey base64PublicKey+ (formatPublicKey publicKey) `shouldBe` base64PublicKey
+ test/MessageSpec.hs view
@@ -0,0 +1,217 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE DeriveGeneric #-}++module MessageSpec where++import Test.Hspec+import Ssb.Types.Message+import Ssb.Types.Key+import Ssb.Types.Link+import Data.Aeson+import Data.ByteString.Lazy+import Data.Maybe++import Prelude hiding (sequence)++spec :: Spec+spec = do+ messageSpec+ postSpec+ aboutSpec+ contactSpec+ voteSpec+ privateContentSpec+ pubSpec++originalMessage :: ByteString+originalMessage = "\+\{\+\ \"previous\": \"%3AWRZYdDHKOmLOWvzHbwJFjx9g8hOQH/NXZkwciA63Y=.sha256\",\+\ \"author\": \"@EMovhfIrFk4NihAKnRNhrfRaqIhBv1Wj8pTxJNgvCCY=.ed25519\",\+\ \"sequence\": 184,\+\ \"timestamp\": 1449954503740,\+\ \"hash\": \"sha256\",\+\ \"content\": {\+\ \"type\": \"post\",\+\ \"text\": \"@johnny use https://github.com/ssbc/ssb-msgs and https://github.com/ssbc/ssb-ref is probably useful too.\",\+\ \"root\": \"%rf1JvoFg1pHE6TkuuMxsjBNevFck7LQvXGhkLMNlaYs=.sha256\",\+\ \"branch\": \"%G37a4PUTbysiETQazyYATwLkfn/ZzigmIheAx905MLU=.sha256\",\+\ \"mentions\": [\+\ {\+\ \"link\": \"@dnr1swLSAgf36g+FzGjNLgmytj2IIyDaYeKZ7F5GdzY=.ed25519\",\+\ \"name\": \"johnny\"\+\ }\+\ ]\+\ },\+\ \"signature\": \"28GiO52GFjJnrwpKo359FamEes7JB9gTiiZaidKLL1C1NRueqGq2IAYQ1V+T2AnBgUJLRZUIyNtLTlBcx4RGAA==.sig.ed25519\"\+\}"++messageSpec :: Spec+messageSpec = describe "message" $ do+ it "extracts message content" $ do+ let (Just message) = (decode originalMessage) :: Maybe (Message AnyContent)+ previous message `shouldBe` Just "%3AWRZYdDHKOmLOWvzHbwJFjx9g8hOQH/NXZkwciA63Y=.sha256"+ author message `shouldBe` fromJust (FeedLink <$> parseEd25519PublicKey "EMovhfIrFk4NihAKnRNhrfRaqIhBv1Wj8pTxJNgvCCY=")+ sequence message `shouldBe` 184+ timestamp message `shouldBe` 1449954503740+ hash message `shouldBe` "sha256"+ signature message `shouldBe` "28GiO52GFjJnrwpKo359FamEes7JB9gTiiZaidKLL1C1NRueqGq2IAYQ1V+T2AnBgUJLRZUIyNtLTlBcx4RGAA==.sig.ed25519"++postSpec :: Spec+postSpec = describe "post" $ do+ it "extracts message post" $ do+ let (Just message) = (decode originalMessage) :: Maybe (Message Post)+ content message `shouldBe` Post+ { text = "@johnny use https://github.com/ssbc/ssb-msgs and https://github.com/ssbc/ssb-ref is probably useful too."+ , channel = Nothing+ , root = Just "%rf1JvoFg1pHE6TkuuMxsjBNevFck7LQvXGhkLMNlaYs=.sha256"+ , branch = Just "%G37a4PUTbysiETQazyYATwLkfn/ZzigmIheAx905MLU=.sha256"+ , recps = Nothing+ , mentions = Just+ [ Mention { mentionLink = "@dnr1swLSAgf36g+FzGjNLgmytj2IIyDaYeKZ7F5GdzY=.ed25519" }+ ]+ }++ it "does not parse content that is not a post" $ do+ (decode originalAbout :: Maybe (Message Post)) `shouldBe` Nothing++originalAbout :: ByteString+originalAbout = "\+\{\+\ \"previous\": \"%6+FS+LB8nDkgHZKanIB6kK5AYBK2SZYH55q5zv/5XIQ=.sha256\",\+\ \"author\": \"@4RUNgFpvI4ZHH6mPPC9GLzsdv33Cs2JGMMcs8rJSMuU=.ed25519\",\+\ \"sequence\": 5,\+\ \"timestamp\": 1464267463411,\+\ \"hash\": \"sha256\",\+\ \"content\": {\+\ \"type\": \"about\",\+\ \"about\": \"@4RUNgFpvI4ZHH6mPPC9GLzsdv33Cs2JGMMcs8rJSMuU=.ed25519\",\+\ \"image\": {\+\ \"link\": \"&Ro1xCJNDIwwKul2SLSU1e4hrwekPcBI6OrBFN0JWnQM=.sha256\",\+\ \"size\": 502712,\+\ \"type\": \"image/png\",\+\ \"width\": 512,\+\ \"height\": 512\+\ }\+\ },\+\ \"signature\": \"mPC156lndHN2oES/UE0GPK/Z8QgFLG8B/bba2TycXzPZZ7IShQhmFiasE0r0ZBTfRmU9KyuVdfwVYogkY2IACA==.sig.ed25519\"\+\}"++aboutSpec :: Spec+aboutSpec = describe "about" $ do+ it "extracts message about" $ do+ let Just message = (decode originalAbout) :: Maybe (Message About)+ about (content message) `shouldBe` "@4RUNgFpvI4ZHH6mPPC9GLzsdv33Cs2JGMMcs8rJSMuU=.ed25519"+ name (content message) `shouldBe` Nothing+ fmap aboutImageLink (image (content message)) `shouldBe`+ Just "&Ro1xCJNDIwwKul2SLSU1e4hrwekPcBI6OrBFN0JWnQM=.sha256"+ fmap aboutImageSize (image (content message)) `shouldBe`+ Just (Just 502712)+ fmap aboutImageType (image (content message)) `shouldBe`+ Just (Just "image/png")+ fmap aboutImageWidth (image (content message)) `shouldBe`+ Just (Just 512)+ fmap aboutImageHeight (image (content message)) `shouldBe`+ Just (Just 512)++originalContact :: ByteString+originalContact = "\+\{\+\ \"previous\": \"%kLJuVKntE9LsPeTSyiVSNnb2vkiGXzBUMeDPoGr6BBw=.sha256\",\+\ \"author\": \"@ye+QM09iPcDJD6YvQYjoQc7sLF/IFhmNbEqgdzQo3lQ=.ed25519\",\+\ \"sequence\": 5442,\+\ \"timestamp\": 1488618790507,\+\ \"hash\": \"sha256\",\+\ \"content\": {\+\ \"type\": \"contact\",\+\ \"contact\": \"@o91tsjBgL0l0zs475aRWYSAXFxvirZS7VrXmirY/msI=.ed25519\",\+\ \"following\": true\+\ },\+\ \"signature\": \"zknuPbUwMeBaDZzZ9iBETOPbZ7SYqetd3RVkq2mYSyr4QI/7uwho3GnerIF3fLcvW3MgRM5SvFvSTnpxxQFoCg==.sig.ed25519\"\+\}"++contactSpec :: Spec+contactSpec = describe "contact" $ do+ it "extracts message contact" $ do+ let Just message = (decode originalContact) :: Maybe (Message Contact)+ content message `shouldBe` Contact+ { contact = fromJust $ FeedLink <$> parseEd25519PublicKey "o91tsjBgL0l0zs475aRWYSAXFxvirZS7VrXmirY/msI="+ , following = True+ , blocking = False+ }++originalVote :: ByteString+originalVote = "\+\{\+\ \"previous\": \"%jiyo59Or2Rrm+05QJIZ/fLfCeBhiaJ+tjJgW/USgtYs=.sha256\",\+\ \"author\": \"@/02iw6SFEPIHl8nMkYSwcCgRWxiG6VP547Wcp1NW8Bo=.ed25519\",\+\ \"sequence\": 3304,\+\ \"timestamp\": 1459235596255,\+\ \"hash\": \"sha256\",\+\ \"content\": {\+\ \"type\": \"vote\",\+\ \"vote\": {\+\ \"link\": \"%lu9JsdUl4astKykyaVFdkLb7//tS0K08BMs6dT+fErA=.sha256\",\+\ \"value\": 1\+\ }\+\ },\+\ \"signature\": \"iw/wREQJ4UEecnfuFNIiZt9kMZXkQc8SP2jmhVbUvPj9Eo50D2yJKfza+Qf/tYwOuFaE9et4pbucPD/4OMlbAg==.sig.ed25519\"\+\}"++voteSpec :: Spec+voteSpec = describe "vote" $ do+ it "extracts message vote" $ do+ let Just message = (decode originalVote) :: Maybe (Message Vote)+ content message `shouldBe` Vote+ { voteLink = "%lu9JsdUl4astKykyaVFdkLb7//tS0K08BMs6dT+fErA=.sha256"+ , voteValue = 1+ , voteExpression = Nothing+ }++-- Not a real private message content, because the test couldn't decrypt it anyway.+originalPrivateContent :: ByteString+originalPrivateContent = "\+\{\+\ \"previous\": \"%RFY6DRn3syOm7GodwpNTePx0I4ZWyZMVsmZ4V1QqiDQ=.sha256\",\+\ \"author\": \"@9nTgtYmvW4HID6ayt6Icwc8WZxdifx5SlSKKIX/X/1g=.ed25519\",\+\ \"sequence\": 245,\+\ \"timestamp\": 1481349557377,\+\ \"hash\": \"sha256\",\+\ \"content\": \"dummy\",\+\ \"signature\": \"2urg9PG4+esti1DIf4DnFfsUXXnOzwXHv+QnflKQQ0145pNw+SIXeYkhNhzqdFm2fz7wzWb7YVlFMPIL11EaBQ==.sig.ed25519\"\+\}"++privateContentSpec :: Spec+privateContentSpec = describe "private" $ do+ it "extracts message private" $ do+ let Just message = (decode originalPrivateContent) :: Maybe (Message PrivateContent)+ author message `shouldBe` fromJust (FeedLink <$> parseEd25519PublicKey "9nTgtYmvW4HID6ayt6Icwc8WZxdifx5SlSKKIX/X/1g=")+ content message `shouldBe` PrivateContent "dummy"++originalPub :: ByteString+originalPub = "\+\{\+\ \"previous\": \"%qYdhzyTbQwrdTOpKVvFS0nFCOvK5SekDhxLjHs6i1jo=.sha256\",\+\ \"author\": \"@26RTWG0+zR1DLO43p4VjB/UzTNKgQ+TNvLey37E/YdI=.ed25519\",\+\ \"sequence\": 3,\+\ \"timestamp\": 1497928539321,\+\ \"hash\": \"sha256\",\+\ \"content\": {\+\ \"type\": \"pub\",\+\ \"address\": {\+\ \"host\": \"ssb.hypersignal.xyz\",\+\ \"port\": 8008,\+\ \"key\": \"@XRg7pXoQqsWDDk4dmgvSWHUqzwS6BmqMo4IdbMKPjWA=.ed25519\"\+\ }\+\ },\+\ \"signature\": \"nWBpjqbnw4sGkaD+0OpxzmTo5VeGOnRcj2hxWjATlO9e7F9eK2yJACOaa5hzGT1E967uFhwmvwUA6FMq3Z0MBQ==.sig.ed25519\"\+\}"++pubSpec :: Spec+pubSpec = describe "pub" $ do+ it "extracts message pub" $ do+ let Just message = (decode originalPub) :: Maybe (Message Pub)+ pubHost (content message) `shouldBe` "ssb.hypersignal.xyz"+ pubPort (content message) `shouldBe` 8008+ Just (pubKey (content message)) `shouldBe`+ parseEd25519PublicKey "XRg7pXoQqsWDDk4dmgvSWHUqzwS6BmqMo4IdbMKPjWA="
+ test/Spec.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}