├── Setup.hs ├── bench.png ├── blacktip.jpg ├── gen-docs.sh ├── stack.yaml ├── .gitignore ├── tests └── tests.hs ├── Makefile ├── benchmarks └── benches.hs ├── package.yaml ├── src └── Database │ ├── Blacktip │ └── Types.hs │ └── Blacktip.hs ├── README.md ├── blacktip.cabal └── LICENSE /Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /bench.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitemyapp/blacktip/HEAD/bench.png -------------------------------------------------------------------------------- /blacktip.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitemyapp/blacktip/HEAD/blacktip.jpg -------------------------------------------------------------------------------- /gen-docs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cabal haddock --hoogle --hyperlink-source --html-location='http://hackage.haskell.org/package/$pkg/docs' --contents-location='http://hackage.haskell.org/package/$pkg' 4 | -------------------------------------------------------------------------------- /stack.yaml: -------------------------------------------------------------------------------- 1 | resolver: lts-11.7 2 | 3 | packages: 4 | - '.' 5 | 6 | extra-deps: 7 | - bitwise-1.0.0.1 8 | - locators-0.2.4.3 9 | 10 | flags: {} 11 | 12 | extra-package-dbs: [] 13 | 14 | system-ghc: false 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | dist-* 3 | cabal-dev 4 | *.o 5 | *.hi 6 | *.chi 7 | *.chs.h 8 | *.dyn_o 9 | *.dyn_hi 10 | .hpc 11 | .hsenv 12 | .cabal-sandbox/ 13 | cabal.sandbox.config 14 | *.prof 15 | *.aux 16 | *.hp 17 | *.eventlog 18 | .stack-work/ 19 | cabal.project.local 20 | cabal.project.local~ 21 | .HTF/ 22 | .ghc.environment.* 23 | 24 | -------------------------------------------------------------------------------- /tests/tests.hs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | import Control.Monad (replicateM) 4 | import Database.Blacktip 5 | import Data.List (sort) 6 | import Test.Hspec 7 | 8 | wirelessConfig :: Config 9 | wirelessConfig = defaultConfig { interface = IName "wlan0" } 10 | 11 | main :: IO () 12 | main = hspec $ do 13 | describe "identities generated are proper" $ do 14 | it "identities are well ordered" $ do 15 | results <- replicateM 100 (generateUniqueId wirelessConfig) 16 | sort results `shouldBe` results 17 | it "identities form an isomorphism" $ do 18 | result <- generateUniqueId' wirelessConfig 19 | fmap snd result `shouldBe` fmap (integerToRecord . fst) result 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | package = blacktip 2 | 3 | stack_yaml = STACK_YAML="stack.yaml" 4 | stack = $(stack_yaml) stack 5 | 6 | build: 7 | $(stack) build $(package) 8 | 9 | build-dirty: 10 | $(stack) build --ghc-options=-fforce-recomp $(package) 11 | 12 | build-profile: 13 | $(stack) --work-dir .stack-work-profiling --profile build 14 | 15 | run: 16 | $(stack) build --fast && $(stack) exec -- $(package) 17 | 18 | install: 19 | $(stack) install 20 | 21 | ghci: 22 | $(stack) ghci $(package):lib 23 | 24 | test: 25 | $(stack) test $(package) 26 | 27 | test-ghci: 28 | $(stack) ghci $(package):test:$(package)-tests 29 | 30 | bench: 31 | $(stack) bench $(package) 32 | 33 | ghcid: 34 | $(stack) exec -- ghcid -c "stack ghci $(package):lib --test --ghci-options='-fobject-code -fno-warn-unused-do-bind' --main-is $(package):$(package)" 35 | 36 | dev-deps: 37 | stack install ghcid 38 | 39 | .PHONY : build build-dirty run install ghci test test-ghci ghcid dev-deps 40 | 41 | -------------------------------------------------------------------------------- /benchmarks/benches.hs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | import Control.Monad (replicateM) 4 | import Control.Concurrent.Async (mapConcurrently) 5 | import Criterion.Main 6 | import qualified Database.Blacktip as BT 7 | import qualified Network.Info as NI 8 | import qualified Data.Time.Clock.POSIX as PSX 9 | 10 | import Data.Maybe (fromJust) 11 | import System.IO.Unsafe (unsafePerformIO) 12 | 13 | {-# NOINLINE interf #-} 14 | interf :: NI.NetworkInterface 15 | interf = fromJust $ unsafePerformIO $ BT.getInterfaceByName "wlan0" 16 | 17 | wc :: BT.Config 18 | wc = BT.defaultConfig { BT.interface = BT.NIInterface interf } 19 | 20 | gen :: BT.Config -> IO (Either BT.NoInterfaceError BT.UniqueId) 21 | gen = BT.generateUniqueId 22 | 23 | genMany :: BT.Config -> Int -> IO [Either BT.NoInterfaceError BT.UniqueId] 24 | genMany config n = mapConcurrently gen (replicate n config) 25 | 26 | timeJustMultiply :: IO PSX.POSIXTime 27 | timeJustMultiply = fmap (*1000) PSX.getPOSIXTime 28 | 29 | main :: IO () 30 | main = defaultMain 31 | [ bench "one unique id" $ whnfIO (gen wc) 32 | , bench "sequential 1000 unique ids" $ whnfIO (replicateM 1000 (gen wc)) 33 | , bench "sequential 100,000 unique ids" $ whnfIO (replicateM 100000 (gen wc)) 34 | -- , bench "concurrent 1000 unique ids" $ nfIO (genMany wc 1000) 35 | , bench "get Unix Millis" $ whnfIO BT.getUnixMillis 36 | -- , bench "get POSIX time" $ nfIO PSX.getPOSIXTime 37 | -- , bench "POSIX time w/ mult" $ nfIO timeJustMultiply 38 | , bench "get interface by name" $ whnfIO (BT.getInterfaceByName "wlan0") 39 | ] 40 | -------------------------------------------------------------------------------- /package.yaml: -------------------------------------------------------------------------------- 1 | name: blacktip 2 | version: 0.1.0.1 3 | synopsis: Decentralized, k-ordered unique ID generator. 4 | description: Clone of Boundary\'s Flake unique id service for Haskell, itself based on Snowflake. 5 | homepage: https://github.com/bitemyapp/blacktip 6 | license: Apache-2.0 7 | author: Chris Allen 8 | maintainer: cma@bitemyapp.com 9 | copyright: 2017, Chris Allen 10 | category: Database 11 | 12 | default-extensions: 13 | - OverloadedStrings 14 | # - QuasiQuotes 15 | # - TemplateHaskell 16 | 17 | dependencies: 18 | - base >= 4.7 && < 5 19 | - bitwise >= 0.1.0.0 20 | - bytestring 21 | - clock >= 0.7 22 | - deepseq 23 | - deepseq-generics >= 0.1.1.0 24 | - locators >= 0.2.4.2 25 | - network-info >= 0.2 26 | - safe >= 0.3 27 | - split >= 0.2.0 28 | - system-fileio >= 0.3 29 | - system-filepath 30 | - time >= 1.4.0 31 | 32 | library: 33 | ghc-options: -Wall -threaded 34 | source-dirs: src 35 | exposed-modules: 36 | - Database.Blacktip 37 | - Database.Blacktip.Types 38 | 39 | tests: 40 | tests: 41 | main: tests.hs 42 | source-dirs: test 43 | ghc-options: 44 | - -threaded 45 | - -Wall 46 | - -rtsopts 47 | - -with-rtsopts=-N 48 | dependencies: 49 | - base 50 | - blacktip 51 | - hspec 52 | 53 | benchmarks: 54 | benches: 55 | main: benches.hs 56 | source-dirs: benchmarks 57 | ghc-options: 58 | - -Wall 59 | - -O 60 | - -threaded 61 | dependencies: 62 | - base 63 | - async 64 | - blacktip 65 | - criterion 66 | - network-info 67 | - time 68 | -------------------------------------------------------------------------------- /src/Database/Blacktip/Types.hs: -------------------------------------------------------------------------------- 1 | module Database.Blacktip.Types 2 | ( Config(..) 3 | , Interface(..) 4 | , ServerState(..) 5 | , ArrowOfTimeError(..) 6 | , NoInterfaceError(..) 7 | , ParseError(..) 8 | , IdentityRecord(..) 9 | , InterfaceName 10 | , Milliseconds 11 | , Sequence 12 | , UniqueId 13 | , defaultConfig 14 | ) 15 | where 16 | 17 | import qualified Data.Int as DI 18 | import qualified Filesystem.Path.CurrentOS as FPC 19 | import qualified Network.Info as NI 20 | 21 | type InterfaceName = String 22 | 23 | data Interface = NIInterface NI.NetworkInterface 24 | | IName InterfaceName 25 | deriving Show 26 | 27 | type Milliseconds = Int 28 | type Sequence = DI.Int16 29 | 30 | data Config = 31 | Config { interface :: Interface 32 | , timestampPath :: FPC.FilePath 33 | , allowableDowntime :: Milliseconds } 34 | deriving Show 35 | 36 | defaultConfig :: Config 37 | defaultConfig = 38 | Config { interface = IName "eth0" 39 | , timestampPath = FPC.decodeString "/tmp/blacktip-timestamp" 40 | -- 720 hours. 41 | , allowableDowntime = 2592000000 } 42 | 43 | 44 | data ServerState = 45 | ServerState { ssTime :: Milliseconds 46 | , ssSequence :: Sequence } 47 | deriving Show 48 | 49 | -- {64 bit timestamp, 48 bit id (MAC address), 16 bit sequence} 50 | type UniqueId = Integer 51 | 52 | data IdentityRecord = 53 | IdentityRecord { identityTime :: Milliseconds 54 | , identityMac :: NI.MAC 55 | , identitySequence :: Sequence } 56 | deriving (Eq, Ord, Show) 57 | 58 | -- if the data in the file doesn't parse 59 | data ParseError = ParseError deriving Show 60 | 61 | -- If our current timestamp is younger than the one in our state 62 | data ArrowOfTimeError = ArrowOfTimeError deriving Show 63 | 64 | -- We couldn't find an interface by that name, did you change it 65 | -- from the default IName "eth0" ? 66 | data NoInterfaceError = NoInterfaceError deriving (Eq, Ord, Show) 67 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Blacktip 2 | 3 | ![](blacktip.jpg "Blacktip Shark") 4 | 5 | Blacktip is a k-ordered unique id service and a clone of Boundary's Flake. 6 | 7 | ## Examples 8 | 9 | ```haskell 10 | import Database.Blacktip 11 | 12 | wirelessConfig :: Config 13 | wirelessConfig = defaultConfig { interface = IName "wlan0" } 14 | 15 | main = do 16 | result <- generateUniqueId wirelessConfig 17 | case result of 18 | Left NoInterfaceError -> error "fack" 19 | Right uid -> putStrLn (show uid) 20 | ``` 21 | 22 | ## Benchmarks 23 | 24 | ### Blacktip 25 | 26 | ![](bench.png "Benchmark results, Blacktip is faster than Flake") 27 | 28 | ### Flake 29 | 30 | ``` 31 | (flake@127.0.0.1)11> flake_harness:timed_generate(100000). 32 | src/flake_harness.erl:33:<0.75.0>: generating ids: 0.272 s 33 | ``` 34 | 35 | ## Exceptional cases / Warnings 36 | 37 | ### If you care about performance at all... 38 | 39 | Don't pass the interface via the `IName` string. Fetch it yourself and put it in the config as an `NIInterface`. 40 | 41 | I wrote `getInterfaceByName` for this purpose. 42 | 43 | ### This is a singleton service, ONE INSTANCE PER SERVER/MAC ADDRESS 44 | 45 | Do not embed this unless: 46 | 47 | 1. You don't care about globally unique identities (then why do you need this?) 48 | 49 | 2. You can guarantee there will be one instance of this running per server / MAC address. 50 | 51 | ### System time 52 | 53 | If system time flows backwards (this *can* happen), `Database.Blacktip.Types.ArrowOfTimeError` will be thrown. 54 | 55 | This possibility could be eliminated by making an FFI wrapper for the monotonic system clock. 56 | 57 | ### Sequence bounds checking 58 | 59 | Currently there's no bounds checking on sequence. We start at 0 on a 16-bit signed integer. We're getting ~1500 uids per millisecond, we would need to exceed 32,767 (or 65,535 if we let negative numbers fly). We could flip it over to a Word16, which would at least eliminate the weirdness of negative sequence numbers but doesn't change anything at the bit level. 60 | 61 | Possible solution would be to add bounds checking and yield. MVar has fairness and queue-like properties so this is a reasonable solution. For now, not a realistic possibility. 62 | 63 | ### Timestamp file doesn't exist 64 | 65 | Following the convention of existing implementations, we default the timestamp to 0 if there is no timestamp file at the location specified in the `Database.Blacktip.Types.Config` passed to `generateUniqueId`. 66 | 67 | ### Timestamp file contains empty bits 68 | 69 | Seems to be an artifact of something criterion (the benchmarks) does. Can't reproduce outside that, so defaulting timestamp to 0 when that happens. 70 | -------------------------------------------------------------------------------- /blacktip.cabal: -------------------------------------------------------------------------------- 1 | -- This file has been generated from package.yaml by hpack version 0.28.2. 2 | -- 3 | -- see: https://github.com/sol/hpack 4 | -- 5 | -- hash: 871017841cd9b224014c9544c88bc8b862490f4c094eadc1d526074edc942149 6 | 7 | name: blacktip 8 | version: 0.1.0.1 9 | synopsis: Decentralized, k-ordered unique ID generator. 10 | description: Clone of Boundary\'s Flake unique id service for Haskell, itself based on Snowflake. 11 | homepage: https://github.com/bitemyapp/blacktip 12 | license: Apache-2.0 13 | license-file: LICENSE 14 | author: Chris Allen 15 | maintainer: cma@bitemyapp.com 16 | copyright: 2017, Chris Allen 17 | category: Database 18 | build-type: Simple 19 | cabal-version: >= 1.10 20 | 21 | library 22 | ghc-options: -Wall -threaded 23 | default-extensions: OverloadedStrings 24 | build-depends: 25 | base >=4.7 && <5 26 | , bitwise >=0.1.0.0 27 | , bytestring 28 | , clock >=0.7 29 | , deepseq 30 | , deepseq-generics >=0.1.1.0 31 | , locators >=0.2.4.2 32 | , network-info >=0.2 33 | , safe >=0.3 34 | , split >=0.2.0 35 | , system-fileio >=0.3 36 | , system-filepath 37 | , time >=1.4.0 38 | hs-source-dirs: 39 | src 40 | exposed-modules: 41 | Database.Blacktip 42 | Database.Blacktip.Types 43 | other-modules: 44 | Paths_blacktip 45 | default-language: Haskell2010 46 | 47 | test-suite tests 48 | ghc-options: -threaded -Wall -rtsopts -with-rtsopts=-N 49 | default-extensions: OverloadedStrings 50 | type: exitcode-stdio-1.0 51 | main-is: tests.hs 52 | other-modules: 53 | Paths_blacktip 54 | hs-source-dirs: 55 | test 56 | build-depends: 57 | base 58 | , bitwise >=0.1.0.0 59 | , blacktip 60 | , bytestring 61 | , clock >=0.7 62 | , deepseq 63 | , deepseq-generics >=0.1.1.0 64 | , hspec 65 | , locators >=0.2.4.2 66 | , network-info >=0.2 67 | , safe >=0.3 68 | , split >=0.2.0 69 | , system-fileio >=0.3 70 | , system-filepath 71 | , time >=1.4.0 72 | default-language: Haskell2010 73 | 74 | benchmark benches 75 | type: exitcode-stdio-1.0 76 | main-is: benches.hs 77 | other-modules: 78 | Paths_blacktip 79 | ghc-options: -Wall -O -threaded 80 | hs-source-dirs: 81 | benchmarks 82 | default-extensions: OverloadedStrings 83 | build-depends: 84 | async 85 | , base 86 | , bitwise >=0.1.0.0 87 | , blacktip 88 | , bytestring 89 | , clock >=0.7 90 | , criterion 91 | , deepseq 92 | , deepseq-generics >=0.1.1.0 93 | , locators >=0.2.4.2 94 | , network-info 95 | , safe >=0.3 96 | , split >=0.2.0 97 | , system-fileio >=0.3 98 | , system-filepath 99 | , time 100 | default-language: Haskell2010 101 | -------------------------------------------------------------------------------- /src/Database/Blacktip.hs: -------------------------------------------------------------------------------- 1 | module Database.Blacktip 2 | ( module Database.Blacktip.Types 3 | , generateUniqueId 4 | , generateUniqueId' 5 | , getInterfaceByName 6 | , getUnixMillis 7 | , integerToRecord 8 | , toBase62 9 | ) where 10 | 11 | import qualified Control.Concurrent as CC 12 | import qualified Control.Concurrent.MVar as MV 13 | import qualified Data.ByteString.Char8 as B 14 | import qualified Data.Locator as DL 15 | import qualified Data.Time.Clock.POSIX as PSX 16 | import qualified Filesystem as FS 17 | import qualified Filesystem.Path.CurrentOS as FPC 18 | import qualified Network.Info as NI 19 | import qualified Safe 20 | import Control.Exception (mask, try) 21 | import Control.Monad (forever, when) 22 | import Data.Bits 23 | import Data.Bits.Bitwise (fromListBE) 24 | import Data.List.Split (chunksOf) 25 | import Database.Blacktip.Types 26 | -- import System.Clock 27 | import System.IO.Unsafe (unsafePerformIO) 28 | 29 | -- There are only supposed to be one of these 30 | -- babies running per node (MAC address)! 31 | getInterfaceByName :: InterfaceName -> IO (Maybe NI.NetworkInterface) 32 | getInterfaceByName n = fmap (Safe.headMay 33 | . filter ((n ==) 34 | . NI.name)) 35 | NI.getNetworkInterfaces 36 | 37 | toBase62 :: Integer -> String 38 | toBase62 = DL.toBase62 39 | 40 | getMac :: Interface -> IO (Maybe NI.MAC) 41 | getMac iface = case iface of 42 | NIInterface interf -> return $ Just (NI.mac interf) 43 | IName name -> (fmap . fmap) NI.mac (getInterfaceByName name) 44 | 45 | getUnixMillis :: IO Milliseconds 46 | getUnixMillis = fmap (round . (*1000)) PSX.getPOSIXTime 47 | 48 | -- getUnixMillisMonotonic :: IO Milliseconds 49 | -- getUnixMillisMonotonic = fmap (round . (*1000)) PSX.getPOSIXTime 50 | 51 | -- We don't want multiple of these running around via inlining 52 | {-# NOINLINE serverState #-} 53 | serverState :: MV.MVar ServerState 54 | -- unsafePerformIO so it doesn't create an 55 | -- emptyMVar when I bind against serverState 56 | -- Don't complain to me about the unsafePerformIO, I know what I'm doing. 57 | -- http://www.reddit.com/r/haskell/comments/2jbl78/from_60_frames_per_second_to_500_in_haskell/claf6hg?context=3 58 | serverState = unsafePerformIO MV.newEmptyMVar 59 | 60 | readTimestamp :: FPC.FilePath -> IO Int 61 | readTimestamp path = do 62 | result <- try $ FS.readFile path :: IO (Either IOError B.ByteString) 63 | case result of 64 | (Right "") -> return 0 65 | _ -> return $ either (const 0) (read . B.unpack) result 66 | 67 | writeTimestamp :: MV.MVar ServerState -> FPC.FilePath -> IO CC.ThreadId 68 | writeTimestamp s path = CC.forkIO go 69 | where go = forever $ do 70 | ss <- MV.readMVar s 71 | mask $ \_ -> FS.writeFile path (B.pack (show (ssTime ss))) 72 | -- sleep for 1 second 73 | CC.threadDelay 1000000 74 | 75 | arrowOfTimeError :: (Show a, Show b) => a -> b -> c 76 | arrowOfTimeError ts stateTime = 77 | error ("ERROR ARROW OF TIME BACKWARDS - Had timestamp: " 78 | ++ show ts 79 | ++ " and state time: " 80 | ++ show stateTime) 81 | 82 | bumpItYo :: Milliseconds -> ServerState -> ServerState 83 | bumpItYo ts s 84 | | ts == stateTime = s { ssSequence = (+1) stateSeq } 85 | | ts > stateTime = s { ssTime = ts, ssSequence = 0 } 86 | | otherwise = arrowOfTimeError ts stateTime 87 | where stateTime = ssTime s 88 | stateSeq = ssSequence s 89 | 90 | generateUniqueId' :: Config -> IO (Either NoInterfaceError 91 | (UniqueId, IdentityRecord)) 92 | generateUniqueId' config = do 93 | millis <- getUnixMillis 94 | empty <- MV.isEmptyMVar serverState 95 | _ <- when empty (initWriter (timestampPath config)) 96 | -- newState <- MV.modifyMVar ss (bumpItYo ms) 97 | mState <- MV.takeMVar serverState 98 | let newState = bumpItYo millis mState 99 | _ <- MV.putMVar serverState newState 100 | let sSeq = ssSequence newState 101 | mMac <- (getMac . interface) config 102 | case mMac of 103 | Nothing -> return $ Left NoInterfaceError 104 | Just mac -> return $ Right (binnify millis mac sSeq, IdentityRecord millis mac sSeq) 105 | 106 | generateUniqueId :: Config -> IO (Either NoInterfaceError UniqueId) 107 | generateUniqueId config = do 108 | uid <- generateUniqueId' config 109 | return $ fmap fst uid 110 | 111 | initWriter :: FPC.FilePath -> IO () 112 | initWriter path = do 113 | ms <- getUnixMillis 114 | stateTime <- readTimestamp path 115 | _ <- when (ms <= stateTime) (arrowOfTimeError ms stateTime) 116 | _ <- MV.putMVar serverState (ServerState ms 0) 117 | _ <- writeTimestamp serverState path 118 | return () 119 | 120 | binnify :: Milliseconds -> NI.MAC -> Sequence -> Integer 121 | binnify ms (NI.MAC a b c d e f) sq = withSq 122 | where withTimestamp = shift (toInteger ms) 64 123 | withMacA = shift (toInteger a) 56 .|. withTimestamp 124 | withMacB = shift (toInteger b) 48 .|. withMacA 125 | withMacC = shift (toInteger c) 40 .|. withMacB 126 | withMacD = shift (toInteger d) 32 .|. withMacC 127 | withMacE = shift (toInteger e) 24 .|. withMacD 128 | withMacF = shift (toInteger f) 16 .|. withMacE 129 | withSq = withMacF .|. toInteger sq 130 | 131 | -- works with Integer since I am providing range, big-endian 132 | bitRange :: Bits a => a -> Int -> Int -> [Bool] 133 | bitRange n lo hi = reverse $ map (testBit n) [lo..hi] 134 | 135 | integerToRecord :: Integer -> IdentityRecord 136 | integerToRecord n = IdentityRecord milliseconds mac recSequence 137 | -- extract 128 bits. 138 | where extractedBits = bitRange n 0 127 139 | milliBits = take 64 extractedBits 140 | macBits = chunksOf 8 $ take 48 $ drop 64 extractedBits 141 | sequenceBits = take 16 $ drop 112 extractedBits 142 | milliseconds = fromListBE milliBits :: Milliseconds 143 | a = fromListBE (head macBits) 144 | b = fromListBE (macBits !! 1) 145 | c = fromListBE (macBits !! 2) 146 | d = fromListBE (macBits !! 3) 147 | e = fromListBE (macBits !! 4) 148 | f = fromListBE (macBits !! 5) 149 | mac = NI.MAC a b c d e f 150 | recSequence = fromListBE sequenceBits :: Sequence 151 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | --------------------------------------------------------------------------------