mmzk-typeid-0.3.0.1: src/Data/TypeID/Internal.hs
-- |
-- Module : Data.TypeID.Internal
-- License : MIT
-- Maintainer : mmzk1526@outlook.com
-- Portability : GHC
--
module Data.TypeID.Internal where
import Control.Exception
import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.ST
import Data.Aeson.Types hiding (Array, String)
import Data.Array
import Data.Array.ST
import Data.Array.Unsafe (unsafeFreeze)
import Data.Binary.Get
import Data.Binary.Put
import Data.Bifunctor
import Data.Bits
import Data.ByteString.Lazy (ByteString)
import qualified Data.ByteString.Lazy as BSL
import Data.Char
import Data.Hashable
import Data.Proxy
import Data.String
import Data.Text (Text)
import qualified Data.Text as T
import Data.Text.Encoding
import Data.TypeID.Class
import Data.TypeID.Error
import Data.UUID.Types.Internal (UUID(..))
import qualified Data.UUID.Types.Internal as UUID
import qualified Data.UUID.V7 as V7
import Data.Word
-- | The constructor is not exposed to the public API to prevent generating
-- invalid @TypeID@s.
data TypeID = TypeID { _getPrefix :: Text
, _getUUID :: UUID }
deriving (Eq, Ord)
instance Show TypeID where
show :: TypeID -> String
show = toString
{-# INLINE show #-}
instance Read TypeID where
readsPrec :: Int -> String -> [(TypeID, String)]
readsPrec _ str = case parseStringS str of
Left _ -> []
Right (x,y) -> [(x, y)]
{-# INLINE readsPrec #-}
instance ToJSON TypeID where
toJSON :: TypeID -> Value
toJSON = toJSON . toText
{-# INLINE toJSON #-}
instance FromJSON TypeID where
parseJSON :: Value -> Parser TypeID
parseJSON str = do
s <- parseJSON str
case parseText s of
Left err -> fail $ show err
Right tid -> pure tid
{-# INLINE parseJSON #-}
instance ToJSONKey TypeID where
toJSONKey :: ToJSONKeyFunction TypeID
toJSONKey = toJSONKeyText toText
{-# INLINE toJSONKey #-}
instance FromJSONKey TypeID where
fromJSONKey :: FromJSONKeyFunction TypeID
fromJSONKey = FromJSONKeyTextParser \t -> case parseText t of
Left err -> fail $ show err
Right tid -> pure tid
{-# INLINE fromJSONKey #-}
instance Hashable TypeID where
hashWithSalt :: Int -> TypeID -> Int
hashWithSalt salt (TypeID prefix uuid)
= salt `hashWithSalt` prefix `hashWithSalt` uuid
{-# INLINE hashWithSalt #-}
-- | Get the prefix, 'UUID', and timestamp of a 'TypeID'.
instance IDType TypeID where
getPrefix :: TypeID -> Text
getPrefix = _getPrefix
{-# INLINE getPrefix #-}
getUUID :: TypeID -> UUID
getUUID = _getUUID
{-# INLINE getUUID #-}
getTime :: TypeID -> Word64
getTime = V7.getTime . getUUID
{-# INLINE getTime #-}
-- | Conversion between 'TypeID' and 'String'/'Text'/'ByteString'.
instance IDConv TypeID where
string2ID :: String -> Either TypeIDError TypeID
string2ID = parseString
{-# INLINE string2ID #-}
text2ID :: Text -> Either TypeIDError TypeID
text2ID = parseText
{-# INLINE text2ID #-}
byteString2ID :: ByteString -> Either TypeIDError TypeID
byteString2ID = parseByteString
{-# INLINE byteString2ID #-}
id2String :: TypeID -> String
id2String = toString
{-# INLINE id2String #-}
id2Text :: TypeID -> Text
id2Text = toText
{-# INLINE id2Text #-}
id2ByteString :: TypeID -> ByteString
id2ByteString = toByteString
{-# INLINE id2ByteString #-}
-- | Generate 'TypeID's.
instance IDGen TypeID where
type IDGenPrefix TypeID = 'Just Text
genID_ :: MonadIO m => Proxy TypeID -> Text -> m TypeID
genID_ _ = genTypeID
{-# INLINE genID_ #-}
genID'_ :: MonadIO m => Proxy TypeID -> Text -> m TypeID
genID'_ _ = genTypeID'
{-# INLINE genID'_ #-}
genIDs_ :: MonadIO m => Proxy TypeID -> Text -> Word16 -> m [TypeID]
genIDs_ _ = genTypeIDs
{-# INLINE genIDs_ #-}
decorate_ :: Proxy TypeID -> Text -> UUID -> Either TypeIDError TypeID
decorate_ _ = decorateTypeID
{-# INLINE decorate_ #-}
checkID_ :: Proxy TypeID -> TypeID -> Maybe TypeIDError
checkID_ _ = checkTypeID
{-# INLINE checkID_ #-}
checkIDWithEnv_ :: MonadIO m
=> Proxy TypeID -> TypeID -> m (Maybe TypeIDError)
checkIDWithEnv_ _ = checkTypeIDWithEnv
{-# INLINE checkIDWithEnv_ #-}
-- | Generate a new 'TypeID' from a prefix.
--
-- It throws a 'TypeIDError' if the prefix does not match the specification,
-- namely if it's longer than 63 characters or if it contains characters other
-- than lowercase latin letters.
genTypeID :: MonadIO m => Text -> m TypeID
genTypeID = fmap head . (`genTypeIDs` 1)
{-# INLINE genTypeID #-}
-- | Generate a new 'TypeID' from a prefix based on statelesss 'UUID'v7.
--
-- See the documentation of 'V7.genUUID'' for more information.
genTypeID' :: MonadIO m => Text -> m TypeID
genTypeID' prefix = case checkPrefix prefix of
Nothing -> unsafeGenTypeID' prefix
Just err -> liftIO $ throwIO err
{-# INLINE genTypeID' #-}
-- | Generate a list of 'TypeID's from a prefix.
--
-- It tries its best to generate 'TypeID's at the same timestamp, but it may not
-- be possible if we are asking too many 'UUID's at the same time.
--
-- It is guaranteed that the first 32768 'TypeID's are generated at the same
-- timestamp.
genTypeIDs :: MonadIO m => Text -> Word16 -> m [TypeID]
genTypeIDs prefix n = case checkPrefix prefix of
Nothing -> unsafeGenTypeIDs prefix n
Just err -> liftIO $ throwIO err
{-# INLINE genTypeIDs #-}
-- | Generate a new 'TypeID' from a prefix, but without checking if the prefix
-- is valid.
unsafeGenTypeID :: MonadIO m => Text -> m TypeID
unsafeGenTypeID = fmap head . (`unsafeGenTypeIDs` 1)
{-# INLINE unsafeGenTypeID #-}
-- | Generate a new 'TypeID' from a prefix based on statelesss 'UUID'v7, but
-- without checking if the prefix is valid.
unsafeGenTypeID' :: MonadIO m => Text -> m TypeID
unsafeGenTypeID' prefix = TypeID prefix <$> V7.genUUID'
{-# INLINE unsafeGenTypeID' #-}
-- | Generate n 'TypeID's from a prefix, but without checking if the prefix is
-- valid.
--
-- It tries its best to generate 'TypeID's at the same timestamp, but it may not
-- be possible if we are asking too many 'UUID's at the same time.
--
-- It is guaranteed that the first 32768 'TypeID's are generated at the same
-- timestamp.
unsafeGenTypeIDs :: MonadIO m => Text -> Word16 -> m [TypeID]
unsafeGenTypeIDs prefix n = map (TypeID prefix) <$> V7.genUUIDs n
{-# INLINE unsafeGenTypeIDs #-}
-- | The nil 'TypeID'.
nilTypeID :: TypeID
nilTypeID = TypeID "" UUID.nil
{-# INLINE nilTypeID #-}
{-# DEPRECATED nilTypeID "Will be removed in the next major release." #-}
-- | Obtain a 'TypeID' from a prefix and a 'UUID'.
decorateTypeID :: Text -> UUID -> Either TypeIDError TypeID
decorateTypeID prefix uuid = case checkPrefix prefix of
Nothing -> Right $ TypeID prefix uuid
Just err -> Left err
{-# INLINE decorateTypeID #-}
-- | Pretty-print a 'TypeID'. It is 'id2String' with concrete type.
toString :: TypeID -> String
toString (TypeID prefix (UUID w1 w2)) = if T.null prefix
then suffixEncode bs
else T.unpack prefix ++ "_" ++ suffixEncode bs
where
bs = runPut $ mapM_ putWord64be [w1, w2]
{-# INLINE toString #-}
-- | Pretty-print a 'TypeID' to strict 'Text'. It is 'id2Text' with concrete
-- type.
toText :: TypeID -> Text
toText (TypeID prefix (UUID w1 w2)) = if T.null prefix
then T.pack (suffixEncode bs)
else prefix <> "_" <> T.pack (suffixEncode bs)
where
bs = runPut $ mapM_ putWord64be [w1, w2]
{-# INLINE toText #-}
-- | Pretty-print a 'TypeID' to lazy 'ByteString'. It is 'id2ByteString' with
-- concrete type.
toByteString :: TypeID -> ByteString
toByteString = fromString . toString
{-# INLINE toByteString #-}
-- | Parse a 'TypeID' from its 'String' representation. It is 'string2ID' with
-- concrete type.
parseString :: String -> Either TypeIDError TypeID
parseString str = case parseStringS str of
Left err -> Left err
Right (tid, "") -> Right tid
_ -> Left TypeIDErrorUUIDError
{-# INLINE parseString #-}
parseStringS :: String -> Either TypeIDError (TypeID, String)
parseStringS str = case span (/= '_') str of
("", _) -> Left TypeIDExtraSeparator
(_, "") -> do
let (uuid, rem) = splitAt 26 str
bs = fromString uuid
(, rem) . TypeID "" <$> decodeUUID bs
(prefix, _ : suffix) -> do
let prefix' = T.pack prefix
(uuid, rem) = splitAt 26 suffix
bs = fromString uuid
case checkPrefix prefix' of
Nothing -> (, rem) . TypeID prefix' <$> decodeUUID bs
Just err -> Left err
-- | Parse a 'TypeID' from its string representation as a strict 'Text'. It is
-- 'text2ID' with concrete type.
parseText :: Text -> Either TypeIDError TypeID
parseText text = case second T.uncons $ T.span (/= '_') text of
("", _) -> Left TypeIDExtraSeparator
(_, Nothing) -> TypeID "" <$> decodeUUID bs
(prefix, Just (_, suffix)) -> do
case checkPrefix prefix of
Nothing -> TypeID prefix
<$> decodeUUID (BSL.fromStrict $ encodeUtf8 suffix)
Just err -> Left err
where
bs = BSL.fromStrict $ encodeUtf8 text
-- | Parse a 'TypeID' from its string representation as a lazy 'ByteString'. It
-- is 'byteString2ID' with concrete type.
parseByteString :: ByteString -> Either TypeIDError TypeID
parseByteString bs = case second BSL.uncons $ BSL.span (/= 95) bs of
("", _) -> Left TypeIDExtraSeparator
(_, Nothing) -> TypeID "" <$> decodeUUID bs
(prefix, Just (_, suffix)) -> do
let prefix' = decodeUtf8 $ BSL.toStrict prefix
case checkPrefix prefix' of
Nothing -> TypeID prefix' <$> decodeUUID suffix
Just err -> Left err
-- | Check if the given prefix is a valid TypeID prefix.
checkPrefix :: Text -> Maybe TypeIDError
checkPrefix prefix
| T.length prefix > 63 = Just $ TypeIDErrorPrefixTooLong (T.length prefix)
| otherwise
= case T.uncons (T.dropWhile (liftM2 (&&) isLower isAscii) prefix) of
Nothing -> Nothing
Just (c, _) -> Just $ TypeIDErrorPrefixInvalidChar c
{-# INLINE checkPrefix #-}
-- | Check if the prefix is valid and the suffix 'UUID' has the correct v7
-- version and variant.
checkTypeID :: TypeID -> Maybe TypeIDError
checkTypeID (TypeID prefix uuid)
= msum [ checkPrefix prefix
, TypeIDErrorUUIDError <$ guard (not $ V7.validate uuid) ]
{-# INLINE checkTypeID #-}
-- | Similar to 'checkTypeID', but also checks if the suffix 'UUID' is
-- generated in the past.
checkTypeIDWithEnv :: MonadIO m => TypeID -> m (Maybe TypeIDError)
checkTypeIDWithEnv tid@(TypeID _ uuid)
= fmap (checkTypeID tid `mplus`)
((TypeIDErrorUUIDError <$) . guard . not <$> V7.validateWithTime uuid)
{-# INLINE checkTypeIDWithEnv #-}
-- | Parse a 'TypeID' from its 'String' representation, but crashes when
-- parsing fails.
unsafeParseString :: String -> TypeID
unsafeParseString str = case span (/= '_') str of
(_, "") -> TypeID "" $ unsafeDecodeUUID bs
(prefix, _ : suffix) -> TypeID (T.pack prefix)
. unsafeDecodeUUID $ fromString suffix
where
bs = fromString str
{-# INLINE unsafeParseString #-}
-- | Parse a 'TypeID' from its string representation as a strict 'Text', but
-- crashes when parsing fails.
unsafeParseText :: Text -> TypeID
unsafeParseText text = case second T.uncons $ T.span (/= '_') text of
(_, Nothing) -> TypeID "" $ unsafeDecodeUUID bs
(prefix, Just (_, suffix)) -> TypeID prefix . unsafeDecodeUUID
. BSL.fromStrict . encodeUtf8 $ suffix
where
bs = BSL.fromStrict $ encodeUtf8 text
{-# INLINE unsafeParseText #-}
-- | Parse a 'TypeID' from its string representation as a lazy 'ByteString',
-- but crashes when parsing fails.
unsafeParseByteString :: ByteString -> TypeID
unsafeParseByteString bs = case second BSL.uncons $ BSL.span (/= 95) bs of
(_, Nothing) -> TypeID "" $ unsafeDecodeUUID bs
(prefix, Just (_, suffix)) -> TypeID (decodeUtf8 $ BSL.toStrict prefix)
. unsafeDecodeUUID $ suffix
{-# INLINE unsafeParseByteString #-}
-- The helpers below are verbatim translations from the official highly magical
-- Go implementation.
suffixEncode :: ByteString -> String
suffixEncode bs = (alphabet !) <$> runST do
dest <- newArray_ (0, 25) :: ST s (STUArray s Int Word8)
writeArray dest 0 $ (bs `BSL.index` 0 .&. 224) `shiftR` 5
writeArray dest 1 $ bs `BSL.index` 0 .&. 31
writeArray dest 2 $ (bs `BSL.index` 1 .&. 248) `shiftR` 3
writeArray dest 3 $ ((bs `BSL.index` 1 .&. 7) `shiftL` 2) .|. ((bs `BSL.index` 2 .&. 192) `shiftR` 6)
writeArray dest 4 $ (bs `BSL.index` 2 .&. 62) `shiftR` 1
writeArray dest 5 $ ((bs `BSL.index` 2 .&. 1) `shiftL` 4) .|. ((bs `BSL.index` 3 .&. 240) `shiftR` 4)
writeArray dest 6 $ ((bs `BSL.index` 3 .&. 15) `shiftL` 1) .|. ((bs `BSL.index` 4 .&. 128) `shiftR` 7)
writeArray dest 7 $ (bs `BSL.index` 4 .&. 124) `shiftR` 2
writeArray dest 8 $ ((bs `BSL.index` 4 .&. 3) `shiftL` 3) .|. ((bs `BSL.index` 5 .&. 224) `shiftR` 5)
writeArray dest 9 $ bs `BSL.index` 5 .&. 31
writeArray dest 10 $ (bs `BSL.index` 6 .&. 248) `shiftR` 3
writeArray dest 11 $ ((bs `BSL.index` 6 .&. 7) `shiftL` 2) .|. ((bs `BSL.index` 7 .&. 192) `shiftR` 6)
writeArray dest 12 $ (bs `BSL.index` 7 .&. 62) `shiftR` 1
writeArray dest 13 $ ((bs `BSL.index` 7 .&. 1) `shiftL` 4) .|. ((bs `BSL.index` 8 .&. 240) `shiftR` 4)
writeArray dest 14 $ ((bs `BSL.index` 8 .&. 15) `shiftL` 1) .|. ((bs `BSL.index` 9 .&. 128) `shiftR` 7)
writeArray dest 15 $ (bs `BSL.index` 9 .&. 124) `shiftR` 2
writeArray dest 16 $ ((bs `BSL.index` 9 .&. 3) `shiftL` 3) .|. ((bs `BSL.index` 10 .&. 224) `shiftR` 5)
writeArray dest 17 $ bs `BSL.index` 10 .&. 31
writeArray dest 18 $ (bs `BSL.index` 11 .&. 248) `shiftR` 3
writeArray dest 19 $ ((bs `BSL.index` 11 .&. 7) `shiftL` 2) .|. ((bs `BSL.index` 12 .&. 192) `shiftR` 6)
writeArray dest 20 $ (bs `BSL.index` 12 .&. 62) `shiftR` 1
writeArray dest 21 $ ((bs `BSL.index` 12 .&. 1) `shiftL` 4) .|. ((bs `BSL.index` 13 .&. 240) `shiftR` 4)
writeArray dest 22 $ ((bs `BSL.index` 13 .&. 15) `shiftL` 1) .|. ((bs `BSL.index` 14 .&. 128) `shiftR` 7)
writeArray dest 23 $ (bs `BSL.index` 14 .&. 124) `shiftR` 2
writeArray dest 24 $ ((bs `BSL.index` 14 .&. 3) `shiftL` 3) .|. ((bs `BSL.index` 15 .&. 224) `shiftR` 5)
writeArray dest 25 $ bs `BSL.index` 15 .&. 31
elems <$> unsafeFreeze dest
where
alphabet = listArray (0, 31) "0123456789abcdefghjkmnpqrstvwxyz"
suffixDecode :: ByteString -> ByteString
suffixDecode bs = BSL.pack $ runST do
dest <- newArray_ (0, 15) :: ST s (STUArray s Int Word8)
writeArray dest 0 $ ((table ! (bs `BSL.index` 0)) `shiftL` 5) .|. (table ! (bs `BSL.index` 1))
writeArray dest 1 $ ((table ! (bs `BSL.index` 2)) `shiftL` 3) .|. ((table ! (bs `BSL.index` 3)) `shiftR` 2)
writeArray dest 2 $ ((table ! (bs `BSL.index` 3)) `shiftL` 6) .|. ((table ! (bs `BSL.index` 4)) `shiftL` 1) .|. ((table ! (bs `BSL.index` 5)) `shiftR` 4)
writeArray dest 3 $ ((table ! (bs `BSL.index` 5)) `shiftL` 4) .|. ((table ! (bs `BSL.index` 6)) `shiftR` 1)
writeArray dest 4 $ ((table ! (bs `BSL.index` 6)) `shiftL` 7) .|. ((table ! (bs `BSL.index` 7)) `shiftL` 2) .|. ((table ! (bs `BSL.index` 8)) `shiftR` 3)
writeArray dest 5 $ ((table ! (bs `BSL.index` 8)) `shiftL` 5) .|. (table ! (bs `BSL.index` 9))
writeArray dest 6 $ ((table ! (bs `BSL.index` 10)) `shiftL` 3) .|. ((table ! (bs `BSL.index` 11)) `shiftR` 2)
writeArray dest 7 $ ((table ! (bs `BSL.index` 11)) `shiftL` 6) .|. ((table ! (bs `BSL.index` 12)) `shiftL` 1) .|. ((table ! (bs `BSL.index` 13)) `shiftR` 4)
writeArray dest 8 $ ((table ! (bs `BSL.index` 13)) `shiftL` 4) .|. ((table ! (bs `BSL.index` 14)) `shiftR` 1)
writeArray dest 9 $ ((table ! (bs `BSL.index` 14)) `shiftL` 7) .|. ((table ! (bs `BSL.index` 15)) `shiftL` 2) .|. ((table ! (bs `BSL.index` 16)) `shiftR` 3)
writeArray dest 10 $ ((table ! (bs `BSL.index` 16)) `shiftL` 5) .|. (table ! (bs `BSL.index` 17))
writeArray dest 11 $ ((table ! (bs `BSL.index` 18)) `shiftL` 3) .|. (table ! (bs `BSL.index` 19)) `shiftR` 2
writeArray dest 12 $ ((table ! (bs `BSL.index` 19)) `shiftL` 6) .|. ((table ! (bs `BSL.index` 20)) `shiftL` 1) .|. ((table ! (bs `BSL.index` 21)) `shiftR` 4)
writeArray dest 13 $ ((table ! (bs `BSL.index` 21)) `shiftL` 4) .|. ((table ! (bs `BSL.index` 22)) `shiftR` 1)
writeArray dest 14 $ ((table ! (bs `BSL.index` 22)) `shiftL` 7) .|. ((table ! (bs `BSL.index` 23)) `shiftL` 2) .|. ((table ! (bs `BSL.index` 24)) `shiftR` 3)
writeArray dest 15 $ ((table ! (bs `BSL.index` 24)) `shiftL` 5) .|. (table ! (bs `BSL.index` 25))
elems <$> unsafeFreeze dest
decodeUUID :: ByteString -> Either TypeIDError UUID
decodeUUID bs = do
unless (BSL.length bs == 26) $ Left TypeIDErrorUUIDError
unless (bs `BSL.index` 0 <= 55) $ Left TypeIDErrorUUIDError
when (any ((== 0xFF) . (table !)) $ BSL.unpack bs) $ Left TypeIDErrorUUIDError
pure $ unsafeDecodeUUID bs
{-# INLINE decodeUUID #-}
unsafeDecodeUUID :: ByteString -> UUID
unsafeDecodeUUID bs
= uncurry UUID . runGet (join (liftM2 (,)) getWord64be) $ suffixDecode bs
{-# INLINE unsafeDecodeUUID #-}
table :: Array Word8 Word8
table = listArray (0, 255)
[ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x01
, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xFF, 0xFF
, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0A, 0x0B, 0x0C
, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0xFF, 0x12, 0x13, 0xFF, 0x14
, 0x15, 0xFF, 0x16, 0x17, 0x18, 0x19, 0x1A, 0xFF, 0x1B, 0x1C
, 0x1D, 0x1E, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF ]