Skip to content

Commit

Permalink
Feature: Parser implementation (#3)
Browse files Browse the repository at this point in the history
* Added parser struct and opts for parser

* Parser improvements and added tests
  • Loading branch information
xBlaz3kx authored Dec 17, 2024
1 parent 4922969 commit 917f609
Show file tree
Hide file tree
Showing 5 changed files with 451 additions and 6 deletions.
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

0 comments on commit 917f609

Please sign in to comment.