diff --git a/parser.go b/parser.go index ba7dc48..7884ddf 100644 --- a/parser.go +++ b/parser.go @@ -8,7 +8,9 @@ import ( ) var ( - ErrInvalidFormat = errors.New("invalid OCMF message format") + ErrInvalidFormat = errors.New("invalid OCMF message format") + ErrVerificationFailure = errors.New("verification failed") + ErrPayloadEmpty = errors.New("payload is empty") ) type Parser struct { @@ -73,7 +75,7 @@ func (p *Parser) GetSignature() (*Signature, error) { if p.opts.withAutomaticSignatureVerification { if p.payload == nil { - return nil, errors.New("payload is empty") + return nil, ErrPayloadEmpty } valid, err := p.signature.Verify(*p.payload, p.opts.publicKey) @@ -83,7 +85,7 @@ func (p *Parser) GetSignature() (*Signature, error) { // Even if the signature is valid, we still return an error if the verification failed if !valid { - return p.signature, errors.New("verification failed") + return p.signature, ErrVerificationFailure } } diff --git a/parser_opts.go b/parser_opts.go index 5ac0699..e500f3a 100644 --- a/parser_opts.go +++ b/parser_opts.go @@ -18,11 +18,6 @@ func WithAutomaticValidation() Opt { func WithAutomaticSignatureVerification(publicKey *ecdsa.PublicKey) Opt { return func(p *ParserOpts) { - if publicKey == nil { - return - } - - // If a public key is provided, enable automatic signature verification p.withAutomaticSignatureVerification = true p.publicKey = publicKey } diff --git a/parser_opts_test.go b/parser_opts_test.go index 6b47f4d..bbcc1f7 100644 --- a/parser_opts_test.go +++ b/parser_opts_test.go @@ -50,7 +50,7 @@ func (s *parserOptsTestSuite) TestParserOptions() { }, expectedOptions: ParserOpts{ withAutomaticValidation: false, - withAutomaticSignatureVerification: false, + withAutomaticSignatureVerification: true, publicKey: nil, }, }, diff --git a/parser_test.go b/parser_test.go index 2a87247..5499542 100644 --- a/parser_test.go +++ b/parser_test.go @@ -1,6 +1,9 @@ package ocmf_go import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" "strings" "testing" @@ -39,63 +42,154 @@ func (s *parserTestSuite) TestParseOcmfMessageFromString_invalid_format() { s.Nil(signature) } -func (s *parserTestSuite) TestGetPayload() { - tests := []struct { - name string - }{} +func (s *parserTestSuite) TestGetPayload_valid() { + parser := NewParser().ParseOcmfMessageFromString(examplePayload) - for _, tt := range tests { - s.T().Run(tt.name, func(t *testing.T) { + payload, err := parser.GetPayload() + s.NoError(err) + s.NotNil(payload) - }) - } + s.Equal("EEM-350-D-MCB", payload.MeterModel) + s.Equal("BQ27400330016", payload.MeterSerial) +} + +func (s *parserTestSuite) TestGetPayload_unparsable() { + malformedPayload := "OCMF|{}|{" + parser := NewParser().ParseOcmfMessageFromString(malformedPayload) + + payload, err := parser.GetPayload() + s.ErrorContains(err, "failed to unmarshal signature") + s.Nil(payload) } func (s *parserTestSuite) TestGetSignature_valid() { + // Generate private and public ECDSA keys + curve := elliptic.P256() + privateKey, err := ecdsa.GenerateKey(curve, rand.Reader) + s.Require().NoError(err) + + builder := NewBuilder(privateKey). + WithPagination("1"). + WithMeterSerial("exampleSerial123"). + WithIdentificationStatus(true). + WithIdentificationType(string(RfidNone)). + AddReading(Reading{ + Time: "2018-07-24T13:22:04,000+0200 S", + ReadingValue: 1.0, + ReadingUnit: string(UnitskWh), + Status: string(MeterOk), + }) + + message, err := builder.Build() + s.Require().NoError(err) + tests := []struct { name string parserOpts []Opt data string expectedSignature *Signature }{ + { + name: "No validation", + parserOpts: []Opt{}, + data: *message, + expectedSignature: &builder.signature, + }, { name: "With automatic signature verification", + parserOpts: []Opt{ + WithAutomaticSignatureVerification(&privateKey.PublicKey), + }, + data: *message, + expectedSignature: &builder.signature, }, { - name: "With automatic signature validation", + name: "With automatic payload validation", + parserOpts: []Opt{ + WithAutomaticValidation(), + }, + data: *message, + expectedSignature: &builder.signature, }, } for _, tt := range tests { s.T().Run(tt.name, func(t *testing.T) { + parser := NewParser(tt.parserOpts...).ParseOcmfMessageFromString(tt.data) + signature, err := parser.GetSignature() + s.NoError(err) + s.NotNil(signature) + s.Equal(*tt.expectedSignature, *signature) }) } } func (s *parserTestSuite) TestGetSignature_invalid() { + // Generate private and public ECDSA keys + curve := elliptic.P256() + privateKey, err := ecdsa.GenerateKey(curve, rand.Reader) + s.Require().NoError(err) + + builder := NewBuilder(privateKey). + WithPagination("1"). + WithMeterSerial("exampleSerial123"). + WithIdentificationStatus(true). + WithIdentificationType(string(RfidNone)). + AddReading(Reading{ + Time: "2018-07-24T13:22:04,000+0200 S", + ReadingValue: 1.0, + ReadingUnit: string(UnitskWh), + Status: string(MeterOk), + }) + + message, err := builder.Build() + s.Require().NoError(err) + + privateKey2, err := ecdsa.GenerateKey(curve, rand.Reader) + s.Require().NoError(err) + tests := []struct { - name string - parserOpts []Opt - data string - expectedSignature *Signature - error string + name string + parserOpts []Opt + data string + error string }{ - {}, + { + name: "Signature validation failed", + parserOpts: []Opt{ + WithAutomaticSignatureVerification(&privateKey2.PublicKey), + }, + data: *message, + error: "verification failed", + }, { + name: "Nil public key", + parserOpts: []Opt{ + WithAutomaticSignatureVerification(nil), + }, + data: *message, + error: "unable to verify signature", + }, + { + name: "Payload empty", + parserOpts: []Opt{ + WithAutomaticSignatureVerification(&privateKey.PublicKey), + }, + data: *message, + error: "payload is empty", + }, } for _, tt := range tests { s.T().Run(tt.name, func(t *testing.T) { - parser := NewParser(tt.parserOpts...) + parser := NewParser(tt.parserOpts...).ParseOcmfMessageFromString(tt.data) - signature, err := parser.GetSignature() - if tt.error != "" { - s.ErrorContains(err, tt.error) - } else { - s.NoError(err) - s.NotNil(signature) - s.Equal(*tt.expectedSignature, *signature) + if tt.name == "Payload empty" { + parser.payload = nil } + + _, err := parser.GetSignature() + s.ErrorContains(err, tt.error) }) } }