diff --git a/net-mqtt.cabal b/net-mqtt.cabal
--- a/net-mqtt.cabal
+++ b/net-mqtt.cabal
@@ -4,10 +4,10 @@
 --
 -- see: https://github.com/sol/hpack
 --
--- hash: 6ce49962b4292c81389d6a35017dccb8270e7ffafc1d99dac05ae0dc4565610c
+-- hash: b87d4bc935903b8bef226af95e7e418363259b0aaf2509ea8faa9d4157dab02a
 
 name:           net-mqtt
-version:        0.2.2.0
+version:        0.2.3.0
 synopsis:       An MQTT Protocol Implementation.
 description:    Please see the README on GitHub at <https://github.com/dustin/mqtt#readme>
 category:       Network
@@ -30,6 +30,7 @@
 library
   exposed-modules:
       Network.MQTT.Client
+      Network.MQTT.Topic
       Network.MQTT.Types
   other-modules:
       Paths_net_mqtt
diff --git a/src/Network/MQTT/Client.hs b/src/Network/MQTT/Client.hs
--- a/src/Network/MQTT/Client.hs
+++ b/src/Network/MQTT/Client.hs
@@ -54,10 +54,8 @@
 
 
 
+import           Network.MQTT.Topic         (Topic)
 import           Network.MQTT.Types         as T
-
--- | Topic is a type alias for topic values.
-type Topic = Text
 
 data ConnState = Starting | Connected | Disconnected deriving (Eq, Show)
 
diff --git a/src/Network/MQTT/Topic.hs b/src/Network/MQTT/Topic.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/MQTT/Topic.hs
@@ -0,0 +1,37 @@
+{-|
+Module      : Network.MQTT.Topic.
+Description : An MQTT client.
+Copyright   : (c) Dustin Sallings, 2019
+License     : BSD3
+Maintainer  : dustin@spy.net
+Stability   : experimental
+
+Topic and topic related utiilities.
+-}
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Network.MQTT.Topic (
+  Topic, match
+) where
+
+import           Data.Text (Text, splitOn, isPrefixOf)
+
+-- | An MQTT topic.
+type Topic = Text
+
+-- | match returns true iff the given pattern can be matched by the
+-- specified Topic as defined in the
+-- <http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718107 MQTT 3.1.1 specification>.
+match :: Text -> Topic -> Bool
+match pat top = cmp (splitOn "/" pat) (splitOn "/" top)
+
+  where
+    cmp [] []   = True
+    cmp [] _    = False
+    cmp _ []    = False
+    cmp ["#"] (t:_) = not $ "$" `isPrefixOf` t
+    cmp (p:ps) (t:ts)
+      | p == t = cmp ps ts
+      | p == "+" && not ("$" `isPrefixOf` t) = cmp ps ts
+      | otherwise = False
diff --git a/test/Spec.hs b/test/Spec.hs
--- a/test/Spec.hs
+++ b/test/Spec.hs
@@ -14,6 +14,7 @@
 import           Test.Tasty.HUnit
 import           Test.Tasty.QuickCheck           as QC
 
+import           Network.MQTT.Topic
 import           Network.MQTT.Types
 
 prop_rtLengthParser :: NonNegative (Large Int) -> Property
@@ -131,12 +132,27 @@
 
   where lab x = let (s,_) = break (== ' ') . show $ x in s
 
+testTopicMatching :: [TestTree]
+testTopicMatching = let allTopics = ["a", "a/b", "a/b/c/d", "b/a/c/d",
+                                     "$SYS/a/b", "a/$SYS/b"]
+                        tsts = [("a", ["a"]), ("a/#", ["a/b", "a/b/c/d"]),
+                                ("+/b", ["a/b"]),
+                                ("+/+/c/+", ["a/b/c/d", "b/a/c/d"]),
+                                ("+/+/b", []),
+                                ("+/$SYS/b", ["a/$SYS/b"]),
+                                ("$SYS/#", ["$SYS/a/b"]),
+                                ("+/$SYS/+", ["a/$SYS/b"]),
+                                ("#/b", [])] in
+    map (\(p,want) -> testCase (show p) $ assertEqual "" want (filter (match p) allTopics)) tsts
+
 tests :: [TestTree]
 tests = [
   localOption (QC.QuickCheckTests 10000) $ testProperty "header length rt (parser)" prop_rtLengthParser,
 
   testCase "rt some packets" testPacketRT,
-  localOption (QC.QuickCheckTests 1000) $ testProperty "rt packets" prop_PacketRT
+  localOption (QC.QuickCheckTests 1000) $ testProperty "rt packets" prop_PacketRT,
+
+  testGroup "topic matching" testTopicMatching
   ]
 
 main :: IO ()
