Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FR: Add spec validators for the WRP messages #84

Merged
merged 15 commits into from
Jun 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ require (
github.com/go-kit/kit v0.8.0
github.com/go-logfmt/logfmt v0.3.0 // indirect
github.com/go-stack/stack v1.8.0 // indirect
github.com/google/uuid v1.3.0
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 // indirect
github.com/stretchr/testify v1.7.0
github.com/ugorji/go/codec v1.2.6
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ github.com/go-logfmt/logfmt v0.3.0 h1:8HUsc87TaSWLKwrnumgC8/YconD2fJQsRJAsWaPg2i
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
Expand Down
160 changes: 160 additions & 0 deletions spec_validator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
/**
* Copyright (c) 2022 Comcast Cable Communications Management, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package wrp

import (
"errors"
"fmt"
"regexp"
"strconv"
"strings"
"unicode"

"github.com/google/uuid"
)

const (
serialPrefix = "serial"
uuidPrefix = "uuid"
eventPrefix = "event"
dnsPrefix = "dns"
)

var (
ErrorInvalidMessageEncoding = errors.New("invalid message encoding")
ErrorInvalidMessageType = errors.New("invalid message type")
ErrorInvalidSource = errors.New("invalid Source name")
ErrorInvalidDestination = errors.New("invalid Destination name")
errInvalidUUID = errors.New("invalid UUID")
errEmptyAuthority = errors.New("invalid empty authority (ID)")
errInvalidMacLength = errors.New("invalid mac length")
errInvalidCharacter = errors.New("invalid character")
errInvalidLocatorPattern = errors.New("value given doesn't match expected locator pattern")
)

// locatorPattern is the precompiled regular expression that all source and dest locators must match.
// Matching is partial, as everything after the authority (ID) is ignored. https://xmidt.io/docs/wrp/basics/#locators
var locatorPattern = regexp.MustCompile(
`^(?P<scheme>(?i)` + macPrefix + `|` + uuidPrefix + `|` + eventPrefix + `|` + dnsPrefix + `|` + serialPrefix + `):(?P<authority>[^/]+)?`,
)

// SpecValidators returns a WRP validator that ensures messages are valid based on
// each spec validator in the list. Only validates the opinionated portions of the spec.
// SpecValidators validates the following fields: UTF8 (all string fields), MessageType, Source, Destination
func SpecValidators() Validators {
return Validators{UTF8Validator(), MessageTypeValidator(), SourceValidator(), DestinationValidator()}
}

// UTF8Validator returns a WRP validator that takes messages and validates that it contains UTF-8 strings.
func UTF8Validator() ValidatorFunc {
return func(m Message) error {
if err := UTF8(m); err != nil {
return fmt.Errorf("%w: %v", ErrorInvalidMessageEncoding, err)
}

return nil
}
}

// MessageTypeValidator returns a WRP validator that takes messages and validates their Type.
func MessageTypeValidator() ValidatorFunc {
return func(m Message) error {
if m.Type < Invalid0MessageType || m.Type > lastMessageType {
return ErrorInvalidMessageType
}

switch m.Type {
case Invalid0MessageType, Invalid1MessageType, lastMessageType:
return ErrorInvalidMessageType
}

return nil
}
}

// SourceValidator returns a WRP validator that takes messages and validates their Source.
// Only mac and uuid sources are validated. Serial, event and dns sources are
// not validated.
func SourceValidator() ValidatorFunc {
return func(m Message) error {
if err := validateLocator(m.Source); err != nil {
return fmt.Errorf("%w '%s': %v", ErrorInvalidSource, m.Source, err)
}

return nil
}
}

// DestinationValidator returns a WRP validator that takes messages and validates their Destination.
// Only mac and uuid destinations are validated. Serial, event and dns destinations are
// not validated.
func DestinationValidator() ValidatorFunc {
return func(m Message) error {
if err := validateLocator(m.Destination); err != nil {
return fmt.Errorf("%w '%s': %v", ErrorInvalidDestination, m.Destination, err)
}

return nil
}
}

// validateLocator validates a given locator's scheme and authority (ID).
// Only mac and uuid schemes' IDs are validated. IDs from serial, event and dns schemes are
// not validated.
func validateLocator(l string) error {
denopink marked this conversation as resolved.
Show resolved Hide resolved
match := locatorPattern.FindStringSubmatch(l)
if match == nil {
return errInvalidLocatorPattern
}

idPart := match[2]
if len(idPart) == 0 {
return errEmptyAuthority
}

switch strings.ToLower(match[1]) {
case macPrefix:
var invalidCharacter rune = -1
idPart = strings.Map(
func(r rune) rune {
switch {
case strings.ContainsRune(hexDigits, r):
return unicode.ToLower(r)
case strings.ContainsRune(macDelimiters, r):
return -1
default:
invalidCharacter = r
return -1
}
},
idPart,
)

if invalidCharacter != -1 {
return fmt.Errorf("%w: %v", errInvalidCharacter, strconv.QuoteRune(invalidCharacter))
} else if len(idPart) != macLength {
return errInvalidMacLength
}
case uuidPrefix:
if _, err := uuid.Parse(idPart); err != nil {
return fmt.Errorf("%w: %v", errInvalidUUID, err)
}
}

return nil
}
Loading