123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836 |
- package fp
- import (
- "bytes"
- "fmt"
- "sort"
- "strconv"
- "strings"
- )
- // Client request signature and fingerprint strings have the format
- // <version>:<cipher>:<extension>:<curve>:<ecpointfmt>:<header>:<quirk>
- //
- // For fingerprints the parts have the formats
- // <version>:
- // <vers>
- // <cipher>, <extension>, <curve>, <ecpointfmt>:
- // <int-list>
- // <header>, <quirk>:
- // <str-list>
- // where <vers> is a TLS version ('', '2.0', '3.0', '3.1', '3.2', '3.3', '3.4')
- // <int-list> is a comma-separated list of hex-encoded ints, and <str-list> is
- // a comma-separated list of strings.
- //
- // and for signatures the parts have the formats
- // <version>:
- // [<exp>|<min>,<exp>,<max>]
- // <cipher>, <extension>, <curve>, <ecpointfmt>:
- // [*~][<[!?+]int-list>]
- // <header>, <quirk>:
- // [*~][<[!?+]str-list>]
- // where items in enclosed in square brackets are optional,
- // <exp> is the expected TLS version, <min> is the minimum TLS version, <max> is the maximum TLS version,
- // '*' and '~' are optional list prefixes, and '!' and '?' are optional list element prefixes.
- //
- // A list prefix can be one of the following options:
- // '*' means to allow extra items and any ordering of items
- // '~' means to allow any ordering of items
- // '' means to enforce ordering of items (default)
- //
- // An item prefix can be one of the following options:
- // '!' means the item is possible, but not expected (unlikely)
- // '?' means the item is expected, but not required (optional)
- // '^' means the item is excluded, and not possible (excluded)
- // '' means the item is required (default)
- const (
- requestFieldCount int = 7
- requestFieldSep string = ":"
- fieldElemSep string = ","
- )
- const (
- flagAnyItems byte = '*'
- flagAnyOrder byte = '~'
- flagUnlikely byte = '!'
- flagOptional byte = '?'
- flagExcluded byte = '^'
- )
- // A RequestFingerprint represents the features of a client request, including client
- // hello features, http headers, and any additional quirks.
- type RequestFingerprint struct {
- Version Version
- Cipher IntList
- Extension IntList
- Curve IntList
- EcPointFmt IntList
- Header StringList
- Quirk StringList
- }
- // NewRequestFingerprint is a wrapper around RequestFingerprint.Parse
- func NewRequestFingerprint(s string) (RequestFingerprint, error) {
- var a RequestFingerprint
- err := a.Parse(s)
- return a, err
- }
- // Parse a fingerprint from a string and return an error on failure.
- func (a *RequestFingerprint) Parse(s string) error {
- fields := strings.Split(s, requestFieldSep)
- if len(fields) != requestFieldCount {
- return fmt.Errorf("bad request field count '%s': exp %d, got %d", s, requestFieldCount, len(fields))
- }
- fieldIdx := 0
- if err := a.Version.Parse(fields[fieldIdx]); err != nil {
- return err
- }
- fieldIdx++
- if err := a.Cipher.Parse(fields[fieldIdx]); err != nil {
- return err
- }
- fieldIdx++
- if err := a.Extension.Parse(fields[fieldIdx]); err != nil {
- return err
- }
- fieldIdx++
- if err := a.Curve.Parse(fields[fieldIdx]); err != nil {
- return err
- }
- fieldIdx++
- if err := a.EcPointFmt.Parse(fields[fieldIdx]); err != nil {
- return err
- }
- fieldIdx++
- if err := a.Header.Parse(fields[fieldIdx]); err != nil {
- return err
- }
- fieldIdx++
- if err := a.Quirk.Parse(fields[fieldIdx]); err != nil {
- return err
- }
- return nil
- }
- // String returns a string representation of the fingerprint.
- func (a RequestFingerprint) String() string {
- return strings.Join([]string{
- a.Version.String(),
- a.Cipher.String(),
- a.Extension.String(),
- a.Curve.String(),
- a.EcPointFmt.String(),
- a.Header.String(),
- a.Quirk.String(),
- }, requestFieldSep)
- }
- // A RequestSignature represents a set of client request fingerprints. Many TLS/HTTPS
- // implementations can be uniquely identified by their signatures.
- type RequestSignature struct {
- Version VersionSignature
- Cipher IntSignature
- Extension IntSignature
- Curve IntSignature
- EcPointFmt IntSignature
- Header StringSignature
- Quirk StringSignature
- // non-exported fields
- pfs bool
- pfsCached bool
- grade Grade
- gradeCached bool
- }
- // A VersionSignature is a signature for a TLS version.
- type VersionSignature struct {
- Min Version
- Exp Version
- Max Version
- }
- // An IntSignature is a signature on a list of integers.
- type IntSignature struct {
- OrderedList IntList
- RequiredSet *IntSet
- OptionalSet *IntSet
- UnlikelySet *IntSet
- ExcludedSet *IntSet
- }
- // A StringSignature is a signature on a list of strings.
- type StringSignature struct {
- OrderedList StringList
- RequiredSet StringSet
- OptionalSet StringSet
- UnlikelySet StringSet
- ExcludedSet StringSet
- }
- // NewRequestSignature is a wrapper around RequestSignature.Parse
- func NewRequestSignature(s string) (RequestSignature, error) {
- var a RequestSignature
- err := a.Parse(s)
- return a, err
- }
- // Parse a signature from a string and return an error on failure.
- func (a *RequestSignature) Parse(s string) error {
- fields := strings.Split(s, requestFieldSep)
- if len(fields) != requestFieldCount {
- return fmt.Errorf("bad request field count '%s': exp %d, got %d", s, requestFieldCount, len(fields))
- }
- fieldIdx := 0
- if err := a.Version.Parse(fields[fieldIdx]); err != nil {
- return err
- }
- fieldIdx++
- if err := a.Cipher.Parse(fields[fieldIdx]); err != nil {
- return err
- }
- fieldIdx++
- if err := a.Extension.Parse(fields[fieldIdx]); err != nil {
- return err
- }
- fieldIdx++
- if err := a.Curve.Parse(fields[fieldIdx]); err != nil {
- return err
- }
- fieldIdx++
- if err := a.EcPointFmt.Parse(fields[fieldIdx]); err != nil {
- return err
- }
- fieldIdx++
- if err := a.Header.Parse(fields[fieldIdx]); err != nil {
- return err
- }
- fieldIdx++
- if err := a.Quirk.Parse(fields[fieldIdx]); err != nil {
- return err
- }
- return nil
- }
- // Grade returns the security grade for the request signature.
- func (a *RequestSignature) Grade() Grade {
- if !a.gradeCached {
- a.grade = GlobalCipherCheck.Grade(a.Cipher.OrderedList)
- a.gradeCached = true
- }
- return a.grade
- }
- // IsPfs returns true if the request signature has perfect forward secrecy.
- func (a *RequestSignature) IsPfs() bool {
- if !a.pfsCached {
- a.pfs = GlobalCipherCheck.IsFirstPfs(a.Cipher.OrderedList)
- a.pfsCached = true
- }
- return a.pfs
- }
- // Parse a version signature from a string and return an error on failure.
- func (a *VersionSignature) Parse(s string) error {
- a.Min, a.Exp, a.Max = VersionEmpty, VersionEmpty, VersionEmpty
- if len(s) == 0 {
- return nil
- }
- fields := strings.Split(s, fieldElemSep)
- var err error
- switch len(fields) {
- case 1:
- if err = a.Min.Parse(fields[0]); err != nil {
- return err
- }
- a.Exp = a.Min
- a.Max = a.Min
- case 3:
- if err = a.Min.Parse(fields[0]); err != nil {
- return err
- }
- if err = a.Exp.Parse(fields[1]); err != nil {
- return err
- }
- if err = a.Max.Parse(fields[2]); err != nil {
- return err
- }
- default:
- return fmt.Errorf("invalid version format: '%s'", s)
- }
- // sanity check
- if a.Min != VersionEmpty {
- if a.Exp != VersionEmpty && a.Min > a.Exp {
- return fmt.Errorf("version: Min > Exp")
- }
- if a.Max != VersionEmpty && a.Min > a.Max {
- return fmt.Errorf("version: Min > Max")
- }
- }
- if a.Exp != VersionEmpty {
- if a.Max != VersionEmpty && a.Exp > a.Max {
- return fmt.Errorf("version: Exp > Max")
- }
- }
- return nil
- }
- // NewVersionSignature returns a new int signature parsed from a string.
- func NewVersionSignature(s string) (VersionSignature, error) {
- var a VersionSignature
- err := a.Parse(s)
- return a, err
- }
- // NewIntSignature returns a new int signature parsed from a string.
- func NewIntSignature(s string) (IntSignature, error) {
- var a IntSignature
- err := a.Parse(s)
- return a, err
- }
- // NewStringSignature returns a new string signature parsed from a string.
- func NewStringSignature(s string) (StringSignature, error) {
- var a StringSignature
- err := a.Parse(s)
- return a, err
- }
- // Parse an int signature from a string and return an error on failure.
- func (a *IntSignature) Parse(s string) error {
- a.OrderedList = IntList{}
- a.ExcludedSet = new(IntSet)
- a.UnlikelySet = new(IntSet)
- a.OptionalSet = new(IntSet)
- a.RequiredSet = new(IntSet)
- if len(s) == 0 {
- return nil
- }
- anyItems, anyOrder := false, false
- switch s[0] {
- case flagAnyItems:
- anyItems = true
- s = s[1:]
- case flagAnyOrder:
- anyOrder = true
- s = s[1:]
- }
- var split []string
- if len(s) > 0 {
- split = strings.Split(s, fieldElemSep)
- }
- for _, v := range split {
- if len(v) == 0 {
- return fmt.Errorf("invalid int signature format: '%s'", s)
- }
- flag := v[0]
- switch flag {
- case flagOptional, flagUnlikely, flagExcluded:
- v = v[1:]
- }
- elem64bit, err := strconv.ParseUint(v, 16, 16)
- elem := int(elem64bit)
- if err != nil {
- return err
- }
- switch flag {
- case flagOptional:
- a.OptionalSet.Insert(elem)
- case flagUnlikely:
- a.UnlikelySet.Insert(elem)
- case flagExcluded:
- a.ExcludedSet.Insert(elem)
- continue // do not add to ordered list
- default:
- a.RequiredSet.Insert(elem)
- }
- a.OrderedList = append(a.OrderedList, elem)
- }
- if anyItems {
- // allow any order and any optional items
- // still check for required, unlikely, and excluded items
- a.OrderedList = nil
- a.OptionalSet.Clear()
- }
- if anyOrder {
- // allow any order
- a.OrderedList = nil
- }
- return nil
- }
- // Parse a string signature from a string and return an error on failure.
- func (a *StringSignature) Parse(s string) error {
- a.OrderedList = StringList{}
- a.UnlikelySet = make(StringSet)
- a.OptionalSet = make(StringSet)
- a.ExcludedSet = make(StringSet)
- a.RequiredSet = make(StringSet)
- if len(s) == 0 {
- return nil
- }
- anyItems, anyOrder := false, false
- switch s[0] {
- case flagAnyItems:
- anyItems = true
- s = s[1:]
- case flagAnyOrder:
- anyOrder = true
- s = s[1:]
- }
- var split []string
- if len(s) > 0 {
- split = strings.Split(s, fieldElemSep)
- }
- for _, v := range split {
- if len(v) == 0 {
- return fmt.Errorf("invalid int signature format: '%s'", s)
- }
- flag := v[0]
- switch flag {
- case flagOptional, flagUnlikely, flagExcluded:
- v = v[1:]
- }
- switch flag {
- case flagOptional:
- a.OptionalSet[v] = true
- case flagUnlikely:
- a.UnlikelySet[v] = true
- case flagExcluded:
- a.ExcludedSet[v] = true
- continue // do not add to ordered list
- default:
- a.RequiredSet[v] = true
- }
- a.OrderedList = append(a.OrderedList, v)
- }
- if anyItems {
- // allow any order and any optional items
- // still check for required, unlikely, and excluded items
- a.OrderedList = nil
- a.OptionalSet = nil
- }
- if anyOrder {
- // allow any order
- a.OrderedList = nil
- }
- return nil
- }
- // Returns a string representation of the signature.
- func (a RequestSignature) String() string {
- return strings.Join([]string{
- a.Version.String(),
- a.Cipher.String(),
- a.Extension.String(),
- a.Curve.String(),
- a.EcPointFmt.String(),
- a.Header.String(),
- a.Quirk.String(),
- }, requestFieldSep)
- }
- // Return a string representation of the version signature.
- func (a VersionSignature) String() string {
- if a.Min == a.Exp && a.Max == a.Exp {
- return a.Exp.String()
- }
- return strings.Join([]string{
- a.Exp.String(),
- a.Min.String(),
- a.Max.String(),
- }, fieldElemSep)
- }
- // String returns a string representation of the int signature.
- func (a IntSignature) String() string {
- var buf bytes.Buffer
- var list IntList
- if a.OrderedList != nil {
- // element ordering is strict
- list = a.OrderedList
- } else {
- if a.RequiredSet.IsEmpty() {
- buf.WriteByte(flagAnyItems)
- } else {
- buf.WriteByte(flagAnyOrder)
- }
- list = append(list, a.RequiredSet.List()...)
- list = append(list, a.OptionalSet.List()...)
- list = append(list, a.UnlikelySet.List()...)
- }
- list = append(list, a.ExcludedSet.List()...)
- if a.OrderedList == nil {
- sort.Slice(list, func(a, b int) bool { return list[a] < list[b] })
- }
- for idx, elem := range list {
- if idx != 0 {
- buf.WriteString(fieldElemSep)
- }
- switch {
- case a.OptionalSet.Has(elem):
- buf.WriteByte(flagOptional)
- case a.UnlikelySet.Has(elem):
- buf.WriteByte(flagUnlikely)
- case a.ExcludedSet.Has(elem):
- buf.WriteByte(flagExcluded)
- }
- buf.WriteString(fmt.Sprintf("%x", elem))
- }
- return buf.String()
- }
- // String returns a string representation of the string signature.
- func (a StringSignature) String() string {
- var buf bytes.Buffer
- var list StringList
- if a.OrderedList != nil {
- // element ordering is strict
- list = a.OrderedList
- } else {
- if a.OptionalSet == nil {
- buf.WriteByte(flagAnyItems)
- } else {
- buf.WriteByte(flagAnyOrder)
- }
- list = append(list, a.RequiredSet.List()...)
- list = append(list, a.OptionalSet.List()...)
- list = append(list, a.UnlikelySet.List()...)
- }
- list = append(list, a.ExcludedSet.List()...)
- if a.OrderedList == nil {
- sort.Slice(list, func(a, b int) bool { return list[a] < list[b] })
- }
- for idx, elem := range list {
- if idx != 0 {
- buf.WriteString(fieldElemSep)
- }
- switch {
- case a.OptionalSet[elem]:
- buf.WriteByte(flagOptional)
- case a.UnlikelySet[elem]:
- buf.WriteByte(flagUnlikely)
- case a.ExcludedSet[elem]:
- buf.WriteByte(flagExcluded)
- }
- buf.WriteString(elem)
- }
- return buf.String()
- }
- // Merge signatures a and b to match fingerprints from both.
- func (a RequestSignature) Merge(b RequestSignature) (merged RequestSignature) {
- merged.Version = a.Version.Merge(b.Version)
- merged.Cipher = a.Cipher.Merge(b.Cipher)
- merged.Extension = a.Extension.Merge(b.Extension)
- merged.Curve = a.Curve.Merge(b.Curve)
- merged.EcPointFmt = a.EcPointFmt.Merge(b.EcPointFmt)
- merged.Header = a.Header.Merge(b.Header)
- merged.Quirk = a.Quirk.Merge(b.Quirk)
- merged.pfsCached = false
- merged.gradeCached = false
- return
- }
- // Merge version signatures a and b to match fingerprints from both.
- func (a VersionSignature) Merge(b VersionSignature) (merged VersionSignature) {
- merged = a
- if a.Exp != VersionEmpty {
- if b.Exp == VersionEmpty || b.Exp < a.Exp {
- merged.Exp = b.Exp
- }
- }
- if a.Min != VersionEmpty {
- if b.Min == VersionEmpty || b.Min < a.Min {
- merged.Min = b.Min
- }
- }
- if a.Max != VersionEmpty {
- if b.Max == VersionEmpty || b.Max > a.Max {
- merged.Max = b.Max
- }
- }
- return
- }
- // Merge int signatures a and b to match fingerprints from both.
- func (a IntSignature) Merge(b IntSignature) (merged IntSignature) {
- // Merge lists according to the following rules:
- // 1) The merged list should not have any duplicate elements.
- // 2) The order of elements in a and b must remain the same.
- // 3) If there exists elements e1, e2 that appear in different orders
- // in a and b, the merged list should be nil (accept any ordering).
- merged = IntSignature{
- IntList{},
- new(IntSet),
- new(IntSet),
- new(IntSet),
- new(IntSet),
- }
- anyOrder := false
- if a.OrderedList == nil || b.OrderedList == nil {
- anyOrder = true
- } else {
- var mergedSet IntSet
- var bSet IntSet
- bSet.Copy(b.RequiredSet.Union(b.OptionalSet).Union(b.UnlikelySet))
- bIdx := 0
- bLen := len(b.OrderedList)
- for _, elem := range a.OrderedList {
- // check if elem is already merged
- if mergedSet.Has(elem) {
- // elem is already merged, so abort and accept any ordering
- anyOrder = true
- break
- }
- // check if b contains elem
- if bSet.Has(elem) {
- // add all elems of b up to elem
- for ; bIdx < bLen && b.OrderedList[bIdx] != elem; bIdx++ {
- merged.OrderedList = append(merged.OrderedList, b.OrderedList[bIdx])
- mergedSet.Insert(b.OrderedList[bIdx])
- }
- // skip past elem since it is added below
- bIdx++
- }
- // add elem to merged list and set
- merged.OrderedList = append(merged.OrderedList, elem)
- mergedSet.Insert(elem)
- }
- // add remaining elems of b to merged list
- merged.OrderedList = append(merged.OrderedList, b.OrderedList[bIdx:bLen]...)
- }
- // Clear ordered list if any ordering is accepted
- if anyOrder {
- merged.OrderedList = nil
- }
- // Take intersection of required elems
- if !a.RequiredSet.IsEmpty()|| !b.RequiredSet.IsEmpty() {
- merged.RequiredSet.Copy(a.RequiredSet.Inter(b.RequiredSet))
- }
- // Take intersection of excluded elems
- if !a.ExcludedSet.IsEmpty() || !b.ExcludedSet.IsEmpty() {
- merged.ExcludedSet.Copy(a.ExcludedSet.Inter(b.ExcludedSet))
- }
- // If intersection of a and b's RequiredSets is not empty (=> wildcard), then take union of optional elems
- if !a.RequiredSet.IsEmpty() && !b.RequiredSet.IsEmpty() {
- merged.OptionalSet.Copy(a.OptionalSet.Union(b.OptionalSet).Union(a.RequiredSet).Union(b.RequiredSet).Diff(merged.RequiredSet))
- }
- // If intersection of optional sets is not empty, then let the unmerged optional variables be considered unlikely
- // variables.
- if !a.OptionalSet.IsEmpty() && !b.OptionalSet.IsEmpty() {
- merged.UnlikelySet.Copy(a.UnlikelySet.Union(b.UnlikelySet).Union(a.OptionalSet).Union(b.OptionalSet).Diff(merged.OptionalSet))
- }
- return
- }
- // Merge string signatures a and b to match fingerprints from both.
- func (a StringSignature) Merge(b StringSignature) (merged StringSignature) {
- // Merge lists according to the following rules:
- // 1) The merged list should not have any duplicate elements.
- // 2) The order of elements in a and b must remain the same.
- // 3) If there exists elements e1, e2 that appear in different orders
- // in a and b, the merged list should be nil (accept any ordering).
- anyOrder := false
- if a.OrderedList == nil || b.OrderedList == nil {
- anyOrder = true
- } else {
- mergedSet := make(StringSet)
- merged.OrderedList = StringList{}
- bSet := b.RequiredSet.Union(b.OptionalSet).Union(b.UnlikelySet)
- bIdx := 0
- bLen := len(b.OrderedList)
- for _, elem := range a.OrderedList {
- // check if elem is already merged
- if mergedSet[elem] {
- // elem is already merged, so abort and accept any ordering
- anyOrder = true
- break
- }
- // check if b contains elem
- if bSet[elem] {
- // add all elems of b up to elem
- for ; bIdx < bLen && b.OrderedList[bIdx] != elem; bIdx++ {
- merged.OrderedList = append(merged.OrderedList, b.OrderedList[bIdx])
- mergedSet[b.OrderedList[bIdx]] = true
- }
- // skip past elem since it is added below
- bIdx++
- }
- // add elem to merged list/set
- merged.OrderedList = append(merged.OrderedList, elem)
- mergedSet[elem] = true
- }
- // add remaining elems of b to merged list
- merged.OrderedList = append(merged.OrderedList, b.OrderedList[bIdx:bLen]...)
- }
- // Clear ordered list if any ordering is accepted
- if anyOrder {
- merged.OrderedList = nil
- }
- // Take intersection of required elems
- if a.RequiredSet != nil || b.RequiredSet != nil {
- merged.RequiredSet = a.RequiredSet.Inter(b.RequiredSet)
- }
- // Take intersection of excluded elems
- if a.ExcludedSet != nil || b.ExcludedSet != nil {
- merged.ExcludedSet = a.ExcludedSet.Inter(b.ExcludedSet)
- }
- // Take union of optional elems
- if a.OptionalSet == nil || b.OptionalSet == nil {
- merged.OptionalSet = nil
- } else {
- merged.OptionalSet = a.OptionalSet.Union(b.OptionalSet).Union(a.RequiredSet).Union(b.RequiredSet).Diff(merged.RequiredSet)
- }
- // Take union of unlikely elems
- if a.UnlikelySet == nil || b.UnlikelySet == nil {
- merged.UnlikelySet = nil
- } else {
- merged.UnlikelySet = a.UnlikelySet.Union(b.UnlikelySet).Union(a.OptionalSet).Union(b.OptionalSet).Diff(merged.OptionalSet)
- }
- return
- }
- // Match a fingerprint against the signature.
- // Returns MatchImpossible if no match is possible, MatchUnlikely if the match
- // is possible with an unlikely configuration, and MatchPossible otherwise.
- func (a RequestSignature) Match(fingerprint RequestFingerprint) (Match, int) {
- matchMap, similarity := a.MatchMap(fingerprint)
- for _, v := range matchMap {
- if v == MatchImpossible {
- return MatchImpossible, similarity
- }
- }
- for _, v := range matchMap {
- if v == MatchUnlikely {
- return MatchUnlikely, similarity
- }
- }
- return MatchPossible, similarity
- }
- // MatchMap returns (1) a map of the match results of the fingerprint against the signature,
- // and (2) the count of overlapping cipher, extension, curve, and ecpointfmt values.
- // The second value helps a caller deduce the closest matching record in the case there is no "MatchPossible" match.
- func (a RequestSignature) MatchMap(fingerprint RequestFingerprint) (map[string]Match, int) {
- matchMap := make(map[string]Match)
- var similarity int
- var matchCount int
- matchMap["version"] = a.Version.Match(fingerprint.Version)
- matchMap["cipher"], matchCount = a.Cipher.Match(fingerprint.Cipher)
- similarity += matchCount
- matchMap["extension"], matchCount = a.Extension.Match(fingerprint.Extension)
- similarity += matchCount
- matchMap["curve"], matchCount = a.Curve.Match(fingerprint.Curve)
- similarity += matchCount
- matchMap["ecpointfmt"], matchCount = a.EcPointFmt.Match(fingerprint.EcPointFmt)
- similarity += matchCount
- matchMap["header"] = a.Header.Match(fingerprint.Header)
- matchMap["quirk"] = a.Quirk.Match(fingerprint.Quirk)
- return matchMap, similarity
- }
- // Match a version against the version signature.
- // Returns MatchImpossible if no match is possible, MatchUnlikely if the match
- // is possible with an unlikely configuration, and MatchPossible otherwise.
- func (a VersionSignature) Match(version Version) Match {
- if a.Min != VersionEmpty && version < a.Min {
- return MatchImpossible
- }
- if a.Max != VersionEmpty && version > a.Max {
- return MatchImpossible
- }
- if a.Exp != VersionEmpty && version < a.Exp {
- return MatchUnlikely
- }
- return MatchPossible
- }
- // Match an int list against the int signature.
- // Returns MatchImpossible if no match is possible, MatchUnlikely if the match
- // is possible with an unlikely configuration, and MatchPossible otherwise.
- func (a IntSignature) Match(list IntList) (Match, int) {
- set := list.Set()
- // Compute number of overlapping values between a and set to use as a "best match" metric if no exact match
- similarity := set.Inter(a.RequiredSet).Len() + set.Inter(a.OptionalSet).Len()
- // check if the ordered list matches
- if a.OrderedList != nil && !a.OrderedList.Contains(list) {
- return MatchImpossible, similarity
- }
- // check that the set does not contain any excluded items
- if !set.Inter(a.ExcludedSet).IsEmpty() {
- return MatchImpossible, similarity
- }
- // check that the set has all required items
- if !a.RequiredSet.Diff(set).IsEmpty() {
- return MatchImpossible, similarity
- }
- // see if there's anything left after removing required and optional items
- set.Copy(set.Diff(a.RequiredSet).Diff(a.OptionalSet))
- if !a.OptionalSet.IsEmpty() && !set.IsEmpty() {
- // check if the remaining items are unlikely or impossible
- if !a.UnlikelySet.IsEmpty() && !set.Diff(a.UnlikelySet).IsEmpty() {
- return MatchImpossible, similarity
- }
- return MatchUnlikely, similarity
- }
- // check if the set has any unlikely items
- if !set.Inter(a.UnlikelySet).IsEmpty() {
- return MatchUnlikely, similarity
- }
- return MatchPossible, similarity
- }
- // Match a string list against the string signature.
- // Returns MatchImpossible if no match is possible, MatchUnlikely if the match
- // is possible with an unlikely configuration, and MatchPossible otherwise.
- func (a StringSignature) Match(list StringList) Match {
- set := list.Set()
- // check if the ordered list matches
- if a.OrderedList != nil && !a.OrderedList.Contains(list) {
- return MatchImpossible
- }
- // check that the set does not contain any excluded items
- if len(set.Inter(a.ExcludedSet)) > 0 {
- return MatchImpossible
- }
- // check that the set has all required items
- if len(a.RequiredSet.Diff(set)) > 0 {
- return MatchImpossible
- }
- // see if there's anything left after removing required and optional items
- set = set.Diff(a.RequiredSet).Diff(a.OptionalSet)
- if len(a.OptionalSet) != 0 && len(set) > 0 {
- // check if the remaining items are unlikely or impossible
- if a.UnlikelySet != nil && len(set.Diff(a.UnlikelySet)) > 0 {
- return MatchImpossible
- }
- return MatchUnlikely
- }
- // check if the set has any unlikely items
- if len(set.Inter(a.UnlikelySet)) > 0 {
- return MatchUnlikely
- }
- return MatchPossible
- }
|