Skip to content

Commit 917f609

Browse files
authored
Feature: Parser implementation (#3)
* Added parser struct and opts for parser * Parser improvements and added tests
1 parent 4922969 commit 917f609

File tree

5 files changed

+451
-6
lines changed

5 files changed

+451
-6
lines changed

builder_test.go

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,6 @@ type builderTestSuite struct {
1313
suite.Suite
1414
}
1515

16-
func (s *builderTestSuite) SetupTest() {
17-
}
18-
19-
func (s *builderTestSuite) TearDownSuite() {
20-
}
21-
2216
func (s *builderTestSuite) TestNewBuilder() {
2317
tests := []struct {
2418
name string

parser.go

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package ocmf_go
2+
3+
import (
4+
"encoding/json"
5+
"strings"
6+
7+
"github.com/pkg/errors"
8+
)
9+
10+
var (
11+
ErrInvalidFormat = errors.New("invalid OCMF message format")
12+
ErrVerificationFailure = errors.New("verification failed")
13+
ErrPayloadEmpty = errors.New("payload is empty")
14+
)
15+
16+
type Parser struct {
17+
payload *PayloadSection
18+
signature *Signature
19+
opts ParserOpts
20+
err error
21+
}
22+
23+
func NewParser(opts ...Opt) *Parser {
24+
defaults := defaultOpts()
25+
// Apply opts
26+
for _, opt := range opts {
27+
opt(&defaults)
28+
}
29+
30+
return &Parser{
31+
opts: defaults,
32+
}
33+
}
34+
35+
// ParseOcmfMessageFromString Returns a new Parser instance with the payload and signature fields set
36+
func (p *Parser) ParseOcmfMessageFromString(data string) *Parser {
37+
payloadSection, signature, err := parseOcmfMessageFromString(data)
38+
if err != nil {
39+
return &Parser{err: err, opts: p.opts}
40+
}
41+
42+
return &Parser{
43+
payload: payloadSection,
44+
signature: signature,
45+
opts: p.opts,
46+
}
47+
}
48+
49+
func (p *Parser) GetPayload() (*PayloadSection, error) {
50+
if p.err != nil {
51+
return nil, p.err
52+
}
53+
54+
// Validate the payload if automatic validation is enabled
55+
if p.opts.withAutomaticValidation {
56+
if err := p.payload.Validate(); err != nil {
57+
return nil, errors.Wrap(err, "payload validation failed")
58+
}
59+
}
60+
61+
return p.payload, nil
62+
}
63+
64+
func (p *Parser) GetSignature() (*Signature, error) {
65+
if p.err != nil {
66+
return nil, p.err
67+
}
68+
69+
// Validate the signature if automatic validation is enabled
70+
if p.opts.withAutomaticValidation {
71+
if err := p.signature.Validate(); err != nil {
72+
return nil, errors.Wrap(err, "signature validation failed")
73+
}
74+
}
75+
76+
if p.opts.withAutomaticSignatureVerification {
77+
if p.payload == nil {
78+
return nil, ErrPayloadEmpty
79+
}
80+
81+
valid, err := p.signature.Verify(*p.payload, p.opts.publicKey)
82+
if err != nil {
83+
return nil, errors.Wrap(err, "unable to verify signature")
84+
}
85+
86+
// Even if the signature is valid, we still return an error if the verification failed
87+
if !valid {
88+
return p.signature, ErrVerificationFailure
89+
}
90+
}
91+
92+
return p.signature, nil
93+
}
94+
95+
func parseOcmfMessageFromString(data string) (*PayloadSection, *Signature, error) {
96+
if !strings.HasPrefix(data, "OCMF|") {
97+
return nil, nil, ErrInvalidFormat
98+
}
99+
100+
data, _ = strings.CutPrefix(data, "OCMF|")
101+
splitData := strings.Split(data, "|")
102+
103+
if len(splitData) != 2 {
104+
return nil, nil, ErrInvalidFormat
105+
}
106+
107+
payloadSection := PayloadSection{}
108+
err := json.Unmarshal([]byte(splitData[0]), &payloadSection)
109+
if err != nil {
110+
return nil, nil, errors.Wrap(err, "failed to unmarshal payload")
111+
}
112+
113+
signature := Signature{}
114+
err = json.Unmarshal([]byte(splitData[1]), &signature)
115+
if err != nil {
116+
return nil, nil, errors.Wrap(err, "failed to unmarshal signature")
117+
}
118+
119+
return &payloadSection, &signature, nil
120+
}

parser_opts.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package ocmf_go
2+
3+
import "crypto/ecdsa"
4+
5+
type ParserOpts struct {
6+
withAutomaticValidation bool
7+
withAutomaticSignatureVerification bool
8+
publicKey *ecdsa.PublicKey
9+
}
10+
11+
type Opt func(*ParserOpts)
12+
13+
func WithAutomaticValidation() Opt {
14+
return func(p *ParserOpts) {
15+
p.withAutomaticValidation = true
16+
}
17+
}
18+
19+
func WithAutomaticSignatureVerification(publicKey *ecdsa.PublicKey) Opt {
20+
return func(p *ParserOpts) {
21+
p.withAutomaticSignatureVerification = true
22+
p.publicKey = publicKey
23+
}
24+
}
25+
26+
func defaultOpts() ParserOpts {
27+
return ParserOpts{
28+
withAutomaticValidation: false,
29+
}
30+
}

parser_opts_test.go

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package ocmf_go
2+
3+
import (
4+
"crypto/ecdsa"
5+
"crypto/elliptic"
6+
"crypto/rand"
7+
"testing"
8+
9+
"github.com/stretchr/testify/suite"
10+
)
11+
12+
type parserOptsTestSuite struct {
13+
suite.Suite
14+
}
15+
16+
func (s *parserOptsTestSuite) TestParserOptions() {
17+
curve := elliptic.P256()
18+
privateKey, err := ecdsa.GenerateKey(curve, rand.Reader)
19+
s.Require().NoError(err)
20+
21+
tests := []struct {
22+
name string
23+
opts []Opt
24+
expectedOptions ParserOpts
25+
}{
26+
{
27+
name: "Default options",
28+
opts: []Opt{},
29+
expectedOptions: ParserOpts{
30+
withAutomaticValidation: false,
31+
withAutomaticSignatureVerification: false,
32+
publicKey: nil,
33+
},
34+
},
35+
{
36+
name: "With automatic validation",
37+
opts: []Opt{
38+
WithAutomaticValidation(),
39+
},
40+
expectedOptions: ParserOpts{
41+
withAutomaticValidation: true,
42+
withAutomaticSignatureVerification: false,
43+
publicKey: nil,
44+
},
45+
},
46+
{
47+
name: "With automatic signature verification but public key is empty",
48+
opts: []Opt{
49+
WithAutomaticSignatureVerification(nil),
50+
},
51+
expectedOptions: ParserOpts{
52+
withAutomaticValidation: false,
53+
withAutomaticSignatureVerification: true,
54+
publicKey: nil,
55+
},
56+
},
57+
{
58+
name: "With automatic signature verification",
59+
opts: []Opt{
60+
WithAutomaticSignatureVerification(&privateKey.PublicKey),
61+
},
62+
expectedOptions: ParserOpts{
63+
withAutomaticValidation: false,
64+
withAutomaticSignatureVerification: true,
65+
publicKey: &privateKey.PublicKey,
66+
},
67+
},
68+
{
69+
name: "With automatic validation and signature verification",
70+
opts: []Opt{
71+
WithAutomaticValidation(),
72+
WithAutomaticSignatureVerification(&privateKey.PublicKey),
73+
},
74+
expectedOptions: ParserOpts{
75+
withAutomaticValidation: true,
76+
withAutomaticSignatureVerification: true,
77+
publicKey: &privateKey.PublicKey,
78+
},
79+
},
80+
}
81+
82+
for _, tt := range tests {
83+
s.T().Run(tt.name, func(t *testing.T) {
84+
parser := NewParser(tt.opts...)
85+
s.Equal(tt.expectedOptions, parser.opts)
86+
})
87+
}
88+
}
89+
90+
func (s *parserOptsTestSuite) TestParserDefaultOptions() {
91+
opts := defaultOpts()
92+
expectedDefaults := ParserOpts{
93+
withAutomaticValidation: false,
94+
withAutomaticSignatureVerification: false,
95+
publicKey: nil,
96+
}
97+
s.Equal(expectedDefaults, opts)
98+
}
99+
100+
func TestParserOpts(t *testing.T) {
101+
suite.Run(t, new(parserOptsTestSuite))
102+
}

0 commit comments

Comments
 (0)