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

Feature: Parser implementation #3

Merged
merged 9 commits into from
Dec 17, 2024
6 changes: 0 additions & 6 deletions builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,6 @@ type builderTestSuite struct {
suite.Suite
}

func (s *builderTestSuite) SetupTest() {
}

func (s *builderTestSuite) TearDownSuite() {
}

func (s *builderTestSuite) TestNewBuilder() {
tests := []struct {
name string
Expand Down
120 changes: 120 additions & 0 deletions parser.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package ocmf_go

import (
"encoding/json"
"strings"

"github.com/pkg/errors"
)

var (
ErrInvalidFormat = errors.New("invalid OCMF message format")
ErrVerificationFailure = errors.New("verification failed")
ErrPayloadEmpty = errors.New("payload is empty")
)

type Parser struct {
payload *PayloadSection
signature *Signature
opts ParserOpts
err error
}

func NewParser(opts ...Opt) *Parser {
defaults := defaultOpts()
// Apply opts
for _, opt := range opts {
opt(&defaults)
}

return &Parser{
opts: defaults,
}
}

// ParseOcmfMessageFromString Returns a new Parser instance with the payload and signature fields set
func (p *Parser) ParseOcmfMessageFromString(data string) *Parser {
payloadSection, signature, err := parseOcmfMessageFromString(data)
if err != nil {
return &Parser{err: err, opts: p.opts}
}

return &Parser{
payload: payloadSection,
signature: signature,
opts: p.opts,
}
}

func (p *Parser) GetPayload() (*PayloadSection, error) {
if p.err != nil {
return nil, p.err
}

// Validate the payload if automatic validation is enabled
if p.opts.withAutomaticValidation {
if err := p.payload.Validate(); err != nil {
return nil, errors.Wrap(err, "payload validation failed")
}
}

return p.payload, nil
}

func (p *Parser) GetSignature() (*Signature, error) {
if p.err != nil {
return nil, p.err
}

// Validate the signature if automatic validation is enabled
if p.opts.withAutomaticValidation {
if err := p.signature.Validate(); err != nil {
return nil, errors.Wrap(err, "signature validation failed")
}
}

if p.opts.withAutomaticSignatureVerification {
if p.payload == nil {
return nil, ErrPayloadEmpty
}

valid, err := p.signature.Verify(*p.payload, p.opts.publicKey)
if err != nil {
return nil, errors.Wrap(err, "unable to verify signature")
}

// Even if the signature is valid, we still return an error if the verification failed
if !valid {
return p.signature, ErrVerificationFailure
}
}

return p.signature, nil
}

func parseOcmfMessageFromString(data string) (*PayloadSection, *Signature, error) {
if !strings.HasPrefix(data, "OCMF|") {
return nil, nil, ErrInvalidFormat
}

data, _ = strings.CutPrefix(data, "OCMF|")
splitData := strings.Split(data, "|")

if len(splitData) != 2 {
return nil, nil, ErrInvalidFormat
}

payloadSection := PayloadSection{}
err := json.Unmarshal([]byte(splitData[0]), &payloadSection)
if err != nil {
return nil, nil, errors.Wrap(err, "failed to unmarshal payload")
}

signature := Signature{}
err = json.Unmarshal([]byte(splitData[1]), &signature)
if err != nil {
return nil, nil, errors.Wrap(err, "failed to unmarshal signature")
}

return &payloadSection, &signature, nil
}
30 changes: 30 additions & 0 deletions parser_opts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package ocmf_go

import "crypto/ecdsa"

type ParserOpts struct {
withAutomaticValidation bool
withAutomaticSignatureVerification bool
publicKey *ecdsa.PublicKey
}

type Opt func(*ParserOpts)

func WithAutomaticValidation() Opt {
return func(p *ParserOpts) {
p.withAutomaticValidation = true
}
}

func WithAutomaticSignatureVerification(publicKey *ecdsa.PublicKey) Opt {
return func(p *ParserOpts) {
p.withAutomaticSignatureVerification = true
p.publicKey = publicKey
}
}

func defaultOpts() ParserOpts {
return ParserOpts{
withAutomaticValidation: false,
}
}
102 changes: 102 additions & 0 deletions parser_opts_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package ocmf_go

import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"testing"

"github.com/stretchr/testify/suite"
)

type parserOptsTestSuite struct {
suite.Suite
}

func (s *parserOptsTestSuite) TestParserOptions() {
curve := elliptic.P256()
privateKey, err := ecdsa.GenerateKey(curve, rand.Reader)
s.Require().NoError(err)

tests := []struct {
name string
opts []Opt
expectedOptions ParserOpts
}{
{
name: "Default options",
opts: []Opt{},
expectedOptions: ParserOpts{
withAutomaticValidation: false,
withAutomaticSignatureVerification: false,
publicKey: nil,
},
},
{
name: "With automatic validation",
opts: []Opt{
WithAutomaticValidation(),
},
expectedOptions: ParserOpts{
withAutomaticValidation: true,
withAutomaticSignatureVerification: false,
publicKey: nil,
},
},
{
name: "With automatic signature verification but public key is empty",
opts: []Opt{
WithAutomaticSignatureVerification(nil),
},
expectedOptions: ParserOpts{
withAutomaticValidation: false,
withAutomaticSignatureVerification: true,
publicKey: nil,
},
},
{
name: "With automatic signature verification",
opts: []Opt{
WithAutomaticSignatureVerification(&privateKey.PublicKey),
},
expectedOptions: ParserOpts{
withAutomaticValidation: false,
withAutomaticSignatureVerification: true,
publicKey: &privateKey.PublicKey,
},
},
{
name: "With automatic validation and signature verification",
opts: []Opt{
WithAutomaticValidation(),
WithAutomaticSignatureVerification(&privateKey.PublicKey),
},
expectedOptions: ParserOpts{
withAutomaticValidation: true,
withAutomaticSignatureVerification: true,
publicKey: &privateKey.PublicKey,
},
},
}

for _, tt := range tests {
s.T().Run(tt.name, func(t *testing.T) {
parser := NewParser(tt.opts...)
s.Equal(tt.expectedOptions, parser.opts)
})
}
}

func (s *parserOptsTestSuite) TestParserDefaultOptions() {
opts := defaultOpts()
expectedDefaults := ParserOpts{
withAutomaticValidation: false,
withAutomaticSignatureVerification: false,
publicKey: nil,
}
s.Equal(expectedDefaults, opts)
}

func TestParserOpts(t *testing.T) {
suite.Run(t, new(parserOptsTestSuite))
}
Loading
Loading