diff --git a/Changelog.md b/Changelog.md
--- a/Changelog.md
+++ b/Changelog.md
@@ -1,5 +1,11 @@
 # Changelog for `proto-lens`
 
+## v0.7.1.0
+- Support GHC 9.0.
+- Fixed parsing of UTF8 chars in text format proto string literals.
+- Support all valid text format boolean field values
+- Remove redundant (.&. 127) from putVarInt (#396)
+
 ## v0.7.0.0
 - Support GHC 8.10.
 - Add a method to `Data.ProtoLens.Message` for getting the `DescriptorProto`
diff --git a/proto-lens-imports/google/protobuf/compiler/plugin.proto b/proto-lens-imports/google/protobuf/compiler/plugin.proto
--- a/proto-lens-imports/google/protobuf/compiler/plugin.proto
+++ b/proto-lens-imports/google/protobuf/compiler/plugin.proto
@@ -45,11 +45,12 @@
 // flag "--${NAME}_out" is passed to protoc.
 
 syntax = "proto2";
+
 package google.protobuf.compiler;
 option java_package = "com.google.protobuf.compiler";
 option java_outer_classname = "PluginProtos";
 
-option go_package = "github.com/golang/protobuf/protoc-gen-go/plugin;plugin_go";
+option go_package = "google.golang.org/protobuf/types/pluginpb";
 
 import "google/protobuf/descriptor.proto";
 
@@ -106,6 +107,16 @@
   // exiting with a non-zero status code.
   optional string error = 1;
 
+  // A bitmask of supported features that the code generator supports.
+  // This is a bitwise "or" of values from the Feature enum.
+  optional uint64 supported_features = 2;
+
+  // Sync with code_generator.h.
+  enum Feature {
+    FEATURE_NONE = 0;
+    FEATURE_PROTO3_OPTIONAL = 1;
+  }
+
   // Represents a single generated file.
   message File {
     // The file name, relative to the output directory.  The name must not
@@ -162,6 +173,11 @@
 
     // The file contents.
     optional string content = 15;
+
+    // Information describing the file content being inserted. If an insertion
+    // point is used, this information will be appropriately offset and inserted
+    // into the code generation metadata for the generated files.
+    optional GeneratedCodeInfo generated_code_info = 16;
   }
   repeated File file = 15;
 }
diff --git a/proto-lens-imports/google/protobuf/descriptor.proto b/proto-lens-imports/google/protobuf/descriptor.proto
--- a/proto-lens-imports/google/protobuf/descriptor.proto
+++ b/proto-lens-imports/google/protobuf/descriptor.proto
@@ -40,7 +40,8 @@
 syntax = "proto2";
 
 package google.protobuf;
-option go_package = "github.com/golang/protobuf/protoc-gen-go/descriptor;descriptor";
+
+option go_package = "google.golang.org/protobuf/types/descriptorpb";
 option java_package = "com.google.protobuf";
 option java_outer_classname = "DescriptorProtos";
 option csharp_namespace = "Google.Protobuf.Reflection";
@@ -59,8 +60,8 @@
 
 // Describes a complete .proto file.
 message FileDescriptorProto {
-  optional string name = 1;       // file name, relative to root of source tree
-  optional string package = 2;    // e.g. "foo", "foo.bar", etc.
+  optional string name = 1;     // file name, relative to root of source tree
+  optional string package = 2;  // e.g. "foo", "foo.bar", etc.
 
   // Names of files imported by this file.
   repeated string dependency = 3;
@@ -100,8 +101,8 @@
   repeated EnumDescriptorProto enum_type = 4;
 
   message ExtensionRange {
-    optional int32 start = 1;
-    optional int32 end = 2;
+    optional int32 start = 1;  // Inclusive.
+    optional int32 end = 2;    // Exclusive.
 
     optional ExtensionRangeOptions options = 3;
   }
@@ -115,8 +116,8 @@
   // fields or extension ranges in the same message. Reserved ranges may
   // not overlap.
   message ReservedRange {
-    optional int32 start = 1; // Inclusive.
-    optional int32 end = 2;   // Exclusive.
+    optional int32 start = 1;  // Inclusive.
+    optional int32 end = 2;    // Exclusive.
   }
   repeated ReservedRange reserved_range = 9;
   // Reserved field names, which may not be used by fields in the same message.
@@ -128,6 +129,7 @@
   // The parser stores options it doesn't recognize here. See above.
   repeated UninterpretedOption uninterpreted_option = 999;
 
+
   // Clients can define custom options in extensions of this message. See above.
   extensions 1000 to max;
 }
@@ -137,42 +139,42 @@
   enum Type {
     // 0 is reserved for errors.
     // Order is weird for historical reasons.
-    TYPE_DOUBLE         = 1;
-    TYPE_FLOAT          = 2;
+    TYPE_DOUBLE = 1;
+    TYPE_FLOAT = 2;
     // Not ZigZag encoded.  Negative numbers take 10 bytes.  Use TYPE_SINT64 if
     // negative values are likely.
-    TYPE_INT64          = 3;
-    TYPE_UINT64         = 4;
+    TYPE_INT64 = 3;
+    TYPE_UINT64 = 4;
     // Not ZigZag encoded.  Negative numbers take 10 bytes.  Use TYPE_SINT32 if
     // negative values are likely.
-    TYPE_INT32          = 5;
-    TYPE_FIXED64        = 6;
-    TYPE_FIXED32        = 7;
-    TYPE_BOOL           = 8;
-    TYPE_STRING         = 9;
+    TYPE_INT32 = 5;
+    TYPE_FIXED64 = 6;
+    TYPE_FIXED32 = 7;
+    TYPE_BOOL = 8;
+    TYPE_STRING = 9;
     // Tag-delimited aggregate.
     // Group type is deprecated and not supported in proto3. However, Proto3
     // implementations should still be able to parse the group wire format and
     // treat group fields as unknown fields.
-    TYPE_GROUP          = 10;
-    TYPE_MESSAGE        = 11;  // Length-delimited aggregate.
+    TYPE_GROUP = 10;
+    TYPE_MESSAGE = 11;  // Length-delimited aggregate.
 
     // New in version 2.
-    TYPE_BYTES          = 12;
-    TYPE_UINT32         = 13;
-    TYPE_ENUM           = 14;
-    TYPE_SFIXED32       = 15;
-    TYPE_SFIXED64       = 16;
-    TYPE_SINT32         = 17;  // Uses ZigZag encoding.
-    TYPE_SINT64         = 18;  // Uses ZigZag encoding.
-  };
+    TYPE_BYTES = 12;
+    TYPE_UINT32 = 13;
+    TYPE_ENUM = 14;
+    TYPE_SFIXED32 = 15;
+    TYPE_SFIXED64 = 16;
+    TYPE_SINT32 = 17;  // Uses ZigZag encoding.
+    TYPE_SINT64 = 18;  // Uses ZigZag encoding.
+  }
 
   enum Label {
     // 0 is reserved for errors
-    LABEL_OPTIONAL      = 1;
-    LABEL_REQUIRED      = 2;
-    LABEL_REPEATED      = 3;
-  };
+    LABEL_OPTIONAL = 1;
+    LABEL_REQUIRED = 2;
+    LABEL_REPEATED = 3;
+  }
 
   optional string name = 1;
   optional int32 number = 3;
@@ -211,6 +213,29 @@
   optional string json_name = 10;
 
   optional FieldOptions options = 8;
+
+  // If true, this is a proto3 "optional". When a proto3 field is optional, it
+  // tracks presence regardless of field type.
+  //
+  // When proto3_optional is true, this field must be belong to a oneof to
+  // signal to old proto3 clients that presence is tracked for this field. This
+  // oneof is known as a "synthetic" oneof, and this field must be its sole
+  // member (each proto3 optional field gets its own synthetic oneof). Synthetic
+  // oneofs exist in the descriptor only, and do not generate any API. Synthetic
+  // oneofs must be ordered after all "real" oneofs.
+  //
+  // For message fields, proto3_optional doesn't create any semantic change,
+  // since non-repeated message fields always track presence. However it still
+  // indicates the semantic detail of whether the user wrote "optional" or not.
+  // This can be useful for round-tripping the .proto file. For consistency we
+  // give message fields a synthetic oneof also, even though it is not required
+  // to track presence. This is especially important because the parser can't
+  // tell if a field is a message or an enum, so it must always create a
+  // synthetic oneof.
+  //
+  // Proto2 optional fields do not set this flag, because they already indicate
+  // optional with `LABEL_OPTIONAL`.
+  optional bool proto3_optional = 17;
 }
 
 // Describes a oneof.
@@ -234,8 +259,8 @@
   // is inclusive such that it can appropriately represent the entire int32
   // domain.
   message EnumReservedRange {
-    optional int32 start = 1; // Inclusive.
-    optional int32 end = 2;   // Inclusive.
+    optional int32 start = 1;  // Inclusive.
+    optional int32 end = 2;    // Inclusive.
   }
 
   // Range of reserved numeric values. Reserved numeric values may not be used
@@ -276,9 +301,9 @@
   optional MethodOptions options = 4;
 
   // Identifies if client streams multiple client messages
-  optional bool client_streaming = 5 [default=false];
+  optional bool client_streaming = 5 [default = false];
   // Identifies if server streams multiple server messages
-  optional bool server_streaming = 6 [default=false];
+  optional bool server_streaming = 6 [default = false];
 }
 
 
@@ -314,7 +339,6 @@
 //   If this turns out to be popular, a web service will be set up
 //   to automatically assign option numbers.
 
-
 message FileOptions {
 
   // Sets the Java package where classes generated from this .proto will be
@@ -337,7 +361,7 @@
   // named by java_outer_classname.  However, the outer class will still be
   // generated to contain the file's getDescriptor() method as well as any
   // top-level extensions defined in the file.
-  optional bool java_multiple_files = 10 [default=false];
+  optional bool java_multiple_files = 10 [default = false];
 
   // This option does nothing.
   optional bool java_generate_equals_and_hash = 20 [deprecated=true];
@@ -348,17 +372,17 @@
   // Message reflection will do the same.
   // However, an extension field still accepts non-UTF-8 byte sequences.
   // This option has no effect on when used with the lite runtime.
-  optional bool java_string_check_utf8 = 27 [default=false];
+  optional bool java_string_check_utf8 = 27 [default = false];
 
 
   // Generated classes can be optimized for speed or code size.
   enum OptimizeMode {
-    SPEED = 1;        // Generate complete code for parsing, serialization,
-                      // etc.
-    CODE_SIZE = 2;    // Use ReflectionOps to implement these methods.
-    LITE_RUNTIME = 3; // Generate code using MessageLite and the lite runtime.
+    SPEED = 1;         // Generate complete code for parsing, serialization,
+                       // etc.
+    CODE_SIZE = 2;     // Use ReflectionOps to implement these methods.
+    LITE_RUNTIME = 3;  // Generate code using MessageLite and the lite runtime.
   }
-  optional OptimizeMode optimize_for = 9 [default=SPEED];
+  optional OptimizeMode optimize_for = 9 [default = SPEED];
 
   // Sets the Go package where structs generated from this .proto will be
   // placed. If omitted, the Go package will be derived from the following:
@@ -369,6 +393,7 @@
 
 
 
+
   // Should generic services be generated in each language?  "Generic" services
   // are not specific to any particular RPC system.  They are generated by the
   // main code generators in each language (without additional plugins).
@@ -379,20 +404,20 @@
   // that generate code specific to your particular RPC system.  Therefore,
   // these default to false.  Old code which depends on generic services should
   // explicitly set them to true.
-  optional bool cc_generic_services = 16 [default=false];
-  optional bool java_generic_services = 17 [default=false];
-  optional bool py_generic_services = 18 [default=false];
-  optional bool php_generic_services = 42 [default=false];
+  optional bool cc_generic_services = 16 [default = false];
+  optional bool java_generic_services = 17 [default = false];
+  optional bool py_generic_services = 18 [default = false];
+  optional bool php_generic_services = 42 [default = false];
 
   // Is this file deprecated?
   // Depending on the target platform, this can emit Deprecated annotations
   // for everything in the file, or it will be completely ignored; in the very
   // least, this is a formalization for deprecating files.
-  optional bool deprecated = 23 [default=false];
+  optional bool deprecated = 23 [default = false];
 
   // Enables the use of arenas for the proto messages in this file. This applies
   // only to generated classes for C++.
-  optional bool cc_enable_arenas = 31 [default=false];
+  optional bool cc_enable_arenas = 31 [default = true];
 
 
   // Sets the objective c class prefix which is prepended to all objective c
@@ -417,10 +442,9 @@
   // determining the namespace.
   optional string php_namespace = 41;
 
-
   // Use this option to change the namespace of php generated metadata classes.
-  // Default is empty. When this option is empty, the proto file name will be used
-  // for determining the namespace.
+  // Default is empty. When this option is empty, the proto file name will be
+  // used for determining the namespace.
   optional string php_metadata_namespace = 44;
 
   // Use this option to change the package of ruby generated classes. Default
@@ -428,6 +452,7 @@
   // determining the ruby package.
   optional string ruby_package = 45;
 
+
   // The parser stores options it doesn't recognize here.
   // See the documentation for the "Options" section above.
   repeated UninterpretedOption uninterpreted_option = 999;
@@ -458,18 +483,18 @@
   //
   // Because this is an option, the above two restrictions are not enforced by
   // the protocol compiler.
-  optional bool message_set_wire_format = 1 [default=false];
+  optional bool message_set_wire_format = 1 [default = false];
 
   // Disables the generation of the standard "descriptor()" accessor, which can
   // conflict with a field of the same name.  This is meant to make migration
   // from proto1 easier; new code should avoid fields named "descriptor".
-  optional bool no_standard_descriptor_accessor = 2 [default=false];
+  optional bool no_standard_descriptor_accessor = 2 [default = false];
 
   // Is this message deprecated?
   // Depending on the target platform, this can emit Deprecated annotations
   // for the message, or it will be completely ignored; in the very least,
   // this is a formalization for deprecating messages.
-  optional bool deprecated = 3 [default=false];
+  optional bool deprecated = 3 [default = false];
 
   // Whether the message is an automatically generated map entry type for the
   // maps field.
@@ -486,7 +511,7 @@
   //
   // Implementations may choose not to generate the map_entry=true message, but
   // use a native map in the target language to hold the keys and values.
-  // The reflection APIs in such implementions still need to work as
+  // The reflection APIs in such implementations still need to work as
   // if the field is a repeated message field.
   //
   // NOTE: Do not set the option in .proto files. Always use the maps syntax
@@ -497,6 +522,7 @@
   reserved 8;  // javalite_serializable
   reserved 9;  // javanano_as_lite
 
+
   // The parser stores options it doesn't recognize here. See above.
   repeated UninterpretedOption uninterpreted_option = 999;
 
@@ -576,16 +602,16 @@
   // implementation must either *always* check its required fields, or *never*
   // check its required fields, regardless of whether or not the message has
   // been parsed.
-  optional bool lazy = 5 [default=false];
+  optional bool lazy = 5 [default = false];
 
   // Is this field deprecated?
   // Depending on the target platform, this can emit Deprecated annotations
   // for accessors, or it will be completely ignored; in the very least, this
   // is a formalization for deprecating fields.
-  optional bool deprecated = 3 [default=false];
+  optional bool deprecated = 3 [default = false];
 
   // For Google-internal migration only. Do not use.
-  optional bool weak = 10 [default=false];
+  optional bool weak = 10 [default = false];
 
 
   // The parser stores options it doesn't recognize here. See above.
@@ -615,7 +641,7 @@
   // Depending on the target platform, this can emit Deprecated annotations
   // for the enum, or it will be completely ignored; in the very least, this
   // is a formalization for deprecating enums.
-  optional bool deprecated = 3 [default=false];
+  optional bool deprecated = 3 [default = false];
 
   reserved 5;  // javanano_as_lite
 
@@ -631,7 +657,7 @@
   // Depending on the target platform, this can emit Deprecated annotations
   // for the enum value, or it will be completely ignored; in the very least,
   // this is a formalization for deprecating enum values.
-  optional bool deprecated = 1 [default=false];
+  optional bool deprecated = 1 [default = false];
 
   // The parser stores options it doesn't recognize here. See above.
   repeated UninterpretedOption uninterpreted_option = 999;
@@ -651,7 +677,7 @@
   // Depending on the target platform, this can emit Deprecated annotations
   // for the service, or it will be completely ignored; in the very least,
   // this is a formalization for deprecating services.
-  optional bool deprecated = 33 [default=false];
+  optional bool deprecated = 33 [default = false];
 
   // The parser stores options it doesn't recognize here. See above.
   repeated UninterpretedOption uninterpreted_option = 999;
@@ -671,18 +697,18 @@
   // Depending on the target platform, this can emit Deprecated annotations
   // for the method, or it will be completely ignored; in the very least,
   // this is a formalization for deprecating methods.
-  optional bool deprecated = 33 [default=false];
+  optional bool deprecated = 33 [default = false];
 
   // Is this method side-effect-free (or safe in HTTP parlance), or idempotent,
   // or neither? HTTP based RPC implementation may choose GET verb for safe
   // methods, and PUT verb for idempotent methods instead of the default POST.
   enum IdempotencyLevel {
     IDEMPOTENCY_UNKNOWN = 0;
-    NO_SIDE_EFFECTS     = 1; // implies idempotent
-    IDEMPOTENT          = 2; // idempotent, but may have side effects
+    NO_SIDE_EFFECTS = 1;  // implies idempotent
+    IDEMPOTENT = 2;       // idempotent, but may have side effects
   }
-  optional IdempotencyLevel idempotency_level =
-      34 [default=IDEMPOTENCY_UNKNOWN];
+  optional IdempotencyLevel idempotency_level = 34
+      [default = IDEMPOTENCY_UNKNOWN];
 
   // The parser stores options it doesn't recognize here. See above.
   repeated UninterpretedOption uninterpreted_option = 999;
@@ -763,7 +789,7 @@
   //   beginning of the "extend" block and is shared by all extensions within
   //   the block.
   // - Just because a location's span is a subset of some other location's span
-  //   does not mean that it is a descendent.  For example, a "group" defines
+  //   does not mean that it is a descendant.  For example, a "group" defines
   //   both a type and a field in a single declaration.  Thus, the locations
   //   corresponding to the type and field and their components will overlap.
   // - Code which tries to interpret locations should probably be designed to
@@ -794,14 +820,14 @@
     //   [ 4, 3, 2, 7 ]
     // this path refers to the whole field declaration (from the beginning
     // of the label to the terminating semicolon).
-    repeated int32 path = 1 [packed=true];
+    repeated int32 path = 1 [packed = true];
 
     // Always has exactly three or four elements: start line, start column,
     // end line (optional, otherwise assumed same as start line), end column.
     // These are packed into a single field for efficiency.  Note that line
     // and column numbers are zero-based -- typically you will want to add
     // 1 to each before displaying to a user.
-    repeated int32 span = 2 [packed=true];
+    repeated int32 span = 2 [packed = true];
 
     // If this SourceCodeInfo represents a complete declaration, these are any
     // comments appearing before and after the declaration which appear to be
@@ -866,7 +892,7 @@
   message Annotation {
     // Identifies the element in the original source .proto file. This field
     // is formatted the same as SourceCodeInfo.Location.path.
-    repeated int32 path = 1 [packed=true];
+    repeated int32 path = 1 [packed = true];
 
     // Identifies the filesystem path to the original source .proto.
     optional string source_file = 2;
diff --git a/proto-lens.cabal b/proto-lens.cabal
--- a/proto-lens.cabal
+++ b/proto-lens.cabal
@@ -1,13 +1,11 @@
 cabal-version: 1.12
 
--- This file has been generated from package.yaml by hpack version 0.33.0.
+-- This file has been generated from package.yaml by hpack version 0.34.4.
 --
 -- see: https://github.com/sol/hpack
---
--- hash: d71629464ee51493ce31ad480ec5872af89ba3c917588b69a61484409a22896c
 
 name:           proto-lens
-version:        0.7.0.0
+version:        0.7.1.0
 synopsis:       A lens-based implementation of protocol buffers in Haskell.
 description:    The proto-lens library provides an API for protocol buffers using modern Haskell language and library patterns.  Specifically, it provides:
                 .
@@ -60,16 +58,16 @@
   hs-source-dirs:
       src
   build-depends:
-      base >=4.10 && <4.15
+      base >=4.10 && <4.16
     , bytestring ==0.10.*
     , containers >=0.5 && <0.7
     , deepseq ==1.4.*
-    , ghc-prim >=0.4 && <0.7
-    , lens-family >=1.2 && <2.1
+    , ghc-prim >=0.4 && <0.8
+    , lens-family >=1.2 && <2.2
     , parsec ==3.1.*
     , pretty ==1.1.*
     , primitive >=0.6 && <0.8
-    , profunctors >=5.2 && <5.6
+    , profunctors >=5.2 && <6.0
     , tagged ==0.8.*
     , text ==1.2.*
     , transformers >=0.4 && <0.6
diff --git a/src/Data/ProtoLens/Encoding/Bytes.hs b/src/Data/ProtoLens/Encoding/Bytes.hs
--- a/src/Data/ProtoLens/Encoding/Bytes.hs
+++ b/src/Data/ProtoLens/Encoding/Bytes.hs
@@ -51,7 +51,7 @@
 import Control.Monad.Trans.Except (throwE, ExceptT)
 import Data.Bits
 import Data.ByteString (ByteString)
-import Data.ByteString.Lazy.Builder as Builder
+import Data.ByteString.Builder as Builder
 import qualified Data.ByteString.Builder.Internal as Internal
 import qualified Data.ByteString.Lazy as L
 import Data.Int (Int32, Int64)
@@ -126,7 +126,7 @@
 putVarInt :: Word64 -> Builder
 putVarInt n
     | n < 128 = Builder.word8 (fromIntegral n)
-    | otherwise = Builder.word8 (fromIntegral $ n .&. 127 .|. 128)
+    | otherwise = Builder.word8 (fromIntegral $ n .|. 128)
                       <> putVarInt (n `shiftR` 7)
 
 getFixed32 :: Parser Word32
diff --git a/src/Data/ProtoLens/Service/Types.hs b/src/Data/ProtoLens/Service/Types.hs
--- a/src/Data/ProtoLens/Service/Types.hs
+++ b/src/Data/ProtoLens/Service/Types.hs
@@ -23,8 +23,10 @@
   , StreamingType (..)
   ) where
 
-import Data.Kind (Constraint)
+import qualified Data.ByteString as B
+import Data.Kind (Constraint, Type)
 import Data.ProtoLens.Message (Message)
+import Data.Proxy (Proxy(..))
 import GHC.TypeLits
 
 
@@ -53,6 +55,7 @@
   type ServicePackage s :: Symbol
   type ServiceMethods s :: [Symbol]
 
+  packedServiceDescriptor :: Proxy s -> B.ByteString
 
 ------------------------------------------------------------------------------
 -- | Data type to be used as a promoted type for 'MethodStreamingType'.
@@ -82,8 +85,8 @@
       , Message (MethodOutput s m)
       ) => HasMethodImpl s (m :: Symbol) where
   type MethodName          s m :: Symbol
-  type MethodInput         s m :: *
-  type MethodOutput        s m :: *
+  type MethodInput         s m :: Type
+  type MethodOutput        s m :: Type
   type MethodStreamingType s m :: StreamingType
 
 
diff --git a/src/Data/ProtoLens/TextFormat.hs b/src/Data/ProtoLens/TextFormat.hs
--- a/src/Data/ProtoLens/TextFormat.hs
+++ b/src/Data/ProtoLens/TextFormat.hs
@@ -317,8 +317,8 @@
 makeScalarValue DoubleField (Parser.DoubleValue x) = Right x
 makeScalarValue FloatField (Parser.DoubleValue x) = Right (realToFrac x)
 makeScalarValue BoolField (Parser.EnumValue x)
-    | x == "true" = Right True
-    | x == "false" = Right False
+    | x `elem` ["true", "True", "t"] = Right True
+    | x `elem` ["false", "False", "f"] = Right False
     | otherwise = Left $ "Unrecognized bool value " ++ show x
 makeScalarValue StringField (Parser.ByteStringValue x) = Right (Text.decodeUtf8 x)
 makeScalarValue BytesField (Parser.ByteStringValue x) = Right x
diff --git a/src/Data/ProtoLens/TextFormat/Parser.hs b/src/Data/ProtoLens/TextFormat/Parser.hs
--- a/src/Data/ProtoLens/TextFormat/Parser.hs
+++ b/src/Data/ProtoLens/TextFormat/Parser.hs
@@ -16,23 +16,23 @@
     , parser
     ) where
 
-import Data.ByteString (ByteString, pack)
-import Data.Char (ord, isSpace)
+import Control.Applicative ((<|>), many)
+import Control.Monad (liftM, liftM2, mzero, unless)
+import Data.ByteString (ByteString)
+import Data.ByteString.Builder (Builder, char8, charUtf8, toLazyByteString, word8)
+import Data.ByteString.Lazy (toStrict)
+import Data.Char (digitToInt, isSpace)
+import Data.Functor (($>))
 import Data.Functor.Identity (Identity)
 import Data.List (intercalate)
-import Data.Maybe (catMaybes)
-import Data.Text.Lazy (Text)
 import qualified Data.Text as StrictText
-import Data.Word (Word8)
-import Numeric (readOct, readHex)
+import Data.Text.Lazy (Text)
 import Text.Parsec ((<?>))
 import Text.Parsec.Char
   (alphaNum, char, hexDigit, letter, octDigit, oneOf, satisfy)
 import Text.Parsec.Text.Lazy (Parser)
-import Text.Parsec.Combinator (choice, eof, many1, optionMaybe, sepBy1)
+import Text.Parsec.Combinator (choice, eof, many1, sepBy1)
 import Text.Parsec.Token hiding (octal)
-import Control.Applicative ((<|>), many)
-import Control.Monad (liftM, liftM2, mzero)
 
 -- | A 'TokenParser' for the protobuf text format.
 ptp :: GenTokenParser Text () Identity
@@ -55,7 +55,6 @@
   , caseSensitive = True
   }
 
-
 type Message = [Field]
 
 data Field = Field Key Value
@@ -131,39 +130,43 @@
 protoStringLiteral :: Parser ByteString
 protoStringLiteral = do
     initialQuoteChar <- char '\'' <|> char '\"'
-    word8s <- many $ stringChar initialQuoteChar
+    let quoted = do
+          _ <- char '\\'
+          choice
+            [ char 'a'   $> char8 '\a'
+            , char 'b'   $> char8 '\b'
+            , char 'f'   $> char8 '\f'
+            , char 'n'   $> char8 '\n'
+            , char 'r'   $> char8 '\r'
+            , char 't'   $> char8 '\t'
+            , char 'v'   $> char8 '\v'
+            , char '\\'  $> char8 '\\'
+            , char '\''  $> char8 '\''
+            , char '\"'  $> char8 '\"'
+            , oneOf "xX" *> parse8BitToBuilder hexDigit 16 (1, 2)
+            , oneOf "uU" *> fail "Unicode in string literals not yet supported"
+            ,               parse8BitToBuilder octDigit 8 (1, 3)
+            ]
+        unquoted = charUtf8 <$> satisfy (/= initialQuoteChar)
+    builders <- many $ quoted <|> unquoted
     _ <- char initialQuoteChar
-    return $ pack word8s
+    return $ toStrict $ toLazyByteString $ mconcat builders
   where
-    stringChar :: Char -> Parser Word8
-    stringChar quote = (nonEscape quote) <|> stringEscape
-    nonEscape quote = fmap (fromIntegral . ord)
-        $ satisfy (\c -> c `notElem` "\\" ++ [quote] && ord c < 256)
-    stringEscape = char '\\' >> (octal <|> hex <|> unicode <|> simple)
-    octal = do d0 <- octDigit
-               d1 <- optionMaybe octDigit
-               d2 <- optionMaybe octDigit
-               readMaybeDigits readOct [Just d0, d1, d2]
-    readMaybeDigits :: ReadS Word8 -> [Maybe Char] -> Parser Word8
-    readMaybeDigits reader
-        = return . (\str -> let [(v, "")] = reader str in v) . catMaybes
-    hex = do _ <- oneOf "xX"
-             d0 <- hexDigit
-             d1 <- optionMaybe hexDigit
-             readMaybeDigits readHex [Just d0, d1]
-    unicode = oneOf "uU" >> fail "Unicode in string literals not yet supported"
-    simple = choice $ map charRet [ ('a', '\a')
-                                  , ('b', '\b')
-                                  , ('f', '\f')
-                                  , ('n', '\n')
-                                  , ('r', '\r')
-                                  , ('t', '\t')
-                                  , ('v', '\v')
-                                  , ('\\', '\\')
-                                  , ('\'', '\'')
-                                  , ('\"', '\"')
-                                  ]
-      where
-        charRet :: (Char, Char) -> Parser Word8
-        charRet (escapeCh, ch) = do _ <- char escapeCh
-                                    return $ fromIntegral $ ord ch
+    -- | Apply a parser between 'min' and 'max' times, failing otherwise.
+    manyN :: Parser a -> (Int, Int) -> Parser [a]
+    manyN _ (_, 0) = return []
+    manyN p (0, maX) = ((:) <$> p <*> manyN p (0, maX - 1)) <|> pure []
+    manyN p (miN, maX) = (:) <$> p <*> manyN p (miN - 1, maX - 1)
+
+    -- | Parse a number in 'base' with between 'min' and 'max' digits.
+    parseNum :: Parser Char -> Int -> (Int, Int) -> Parser Int
+    parseNum p base range = do
+      digits <- map digitToInt <$> manyN p range
+      return $ foldl (\a d -> a * base + d) 0 digits
+
+    -- | Parse a number and return a builder for the 8-bit char it represents.
+    parse8BitToBuilder :: Parser Char -> Int -> (Int, Int) -> Parser Builder
+    parse8BitToBuilder p base range = do
+      value <- parseNum p base range
+      unless (value < 256) $ fail "Escaped number is not 8-bit"
+      return $ word8 $ fromIntegral value
