From e2a2f27797497e8d713ada1609e7c829db8253c3 Mon Sep 17 00:00:00 2001 From: yanghao Date: Thu, 25 Apr 2024 05:03:37 -0400 Subject: [PATCH] feat: AllowUnknownMessageFields & CheckUserDefinedFields --- config/configuration.go | 2 + session_factory.go | 12 +++ validation.go | 80 ++++++++++++++----- validation_test.go | 170 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 244 insertions(+), 20 deletions(-) diff --git a/config/configuration.go b/config/configuration.go index 2c73ac369..b5fa7c28a 100644 --- a/config/configuration.go +++ b/config/configuration.go @@ -66,6 +66,8 @@ const ( MaxLatency string = "MaxLatency" PersistMessages string = "PersistMessages" RejectInvalidMessage string = "RejectInvalidMessage" + AllowUnknownMessageFields string = "AllowUnknownMessageFields" + CheckUserDefinedFields string = "CheckUserDefinedFields" DynamicSessions string = "DynamicSessions" DynamicQualifier string = "DynamicQualifier" ) diff --git a/session_factory.go b/session_factory.go index f28e552bd..37818deb9 100644 --- a/session_factory.go +++ b/session_factory.go @@ -99,6 +99,18 @@ func (f sessionFactory) newSession( } } + if settings.HasSetting(config.AllowUnknownMessageFields) { + if validatorSettings.AllowUnknownMessageFields, err = settings.BoolSetting(config.AllowUnknownMessageFields); err != nil { + return + } + } + + if settings.HasSetting(config.CheckUserDefinedFields) { + if validatorSettings.CheckUserDefinedFields, err = settings.BoolSetting(config.CheckUserDefinedFields); err != nil { + return + } + } + if sessionID.IsFIXT() { if s.DefaultApplVerID, err = settings.Setting(config.DefaultApplVerID); err != nil { return diff --git a/validation.go b/validation.go index 23564ca3d..e9b718138 100644 --- a/validation.go +++ b/validation.go @@ -19,6 +19,10 @@ import ( "github.com/quickfixgo/quickfix/datadictionary" ) +const ( + UserDefinedTagMin int = 5000 +) + // Validator validates a FIX message. type Validator interface { Validate(*Message) MessageRejectError @@ -26,15 +30,19 @@ type Validator interface { // ValidatorSettings describe validation behavior. type ValidatorSettings struct { - CheckFieldsOutOfOrder bool - RejectInvalidMessage bool + CheckFieldsOutOfOrder bool + RejectInvalidMessage bool + AllowUnknownMessageFields bool + CheckUserDefinedFields bool } // Default configuration for message validation. // See http://www.quickfixengine.org/quickfix/doc/html/configuration.html. var defaultValidatorSettings = ValidatorSettings{ - CheckFieldsOutOfOrder: true, - RejectInvalidMessage: true, + CheckFieldsOutOfOrder: true, + RejectInvalidMessage: true, + AllowUnknownMessageFields: false, + CheckUserDefinedFields: true, } type fixValidator struct { @@ -109,11 +117,11 @@ func validateFIX(d *datadictionary.DataDictionary, settings ValidatorSettings, m } if settings.RejectInvalidMessage { - if err := validateFields(d, d, msgType, msg); err != nil { + if err := validateFields(d, d, settings, msgType, msg); err != nil { return err } - if err := validateWalk(d, d, msgType, msg); err != nil { + if err := validateWalk(d, d, settings, msgType, msg); err != nil { return err } } @@ -137,11 +145,11 @@ func validateFIXT(transportDD, appDD *datadictionary.DataDictionary, settings Va } if settings.RejectInvalidMessage { - if err := validateFields(transportDD, appDD, msgType, msg); err != nil { + if err := validateFields(transportDD, appDD, settings, msgType, msg); err != nil { return err } - if err := validateWalk(transportDD, appDD, msgType, msg); err != nil { + if err := validateWalk(transportDD, appDD, settings, msgType, msg); err != nil { return err } } @@ -156,7 +164,7 @@ func validateMsgType(d *datadictionary.DataDictionary, msgType string, _ *Messag return nil } -func validateWalk(transportDD *datadictionary.DataDictionary, appDD *datadictionary.DataDictionary, msgType string, msg *Message) MessageRejectError { +func validateWalk(transportDD *datadictionary.DataDictionary, appDD *datadictionary.DataDictionary, settings ValidatorSettings, msgType string, msg *Message) MessageRejectError { remainingFields := msg.fields iteratedTags := make(datadictionary.TagSet) @@ -178,15 +186,19 @@ func validateWalk(transportDD *datadictionary.DataDictionary, appDD *datadiction messageDef = appDD.Messages[msgType] } - if fieldDef, ok = messageDef.Fields[int(tag)]; !ok { - return TagNotDefinedForThisMessageType(tag) - } - if _, duplicate := iteratedTags[int(tag)]; duplicate { return tagAppearsMoreThanOnce(tag) } iteratedTags.Add(int(tag)) + if fieldDef, ok = messageDef.Fields[int(tag)]; !ok { + if !checkFieldNotDefined(settings, tag) { + return TagNotDefinedForThisMessageType(tag) + } + remainingFields = remainingFields[1:] + continue + } + if remainingFields, err = validateVisitField(fieldDef, remainingFields); err != nil { return err } @@ -306,19 +318,24 @@ func validateRequiredFieldMap(_ *Message, requiredTags map[int]struct{}, fieldMa return nil } -func validateFields(transportDD *datadictionary.DataDictionary, appDD *datadictionary.DataDictionary, msgType string, message *Message) MessageRejectError { +func validateFields(transportDD *datadictionary.DataDictionary, + appDD *datadictionary.DataDictionary, + settings ValidatorSettings, + msgType string, + message *Message, +) MessageRejectError { for _, field := range message.fields { switch { case field.tag.IsHeader(): - if err := validateField(transportDD, transportDD.Header.Tags, field); err != nil { + if err := validateField(transportDD, settings, transportDD.Header.Tags, field); err != nil { return err } case field.tag.IsTrailer(): - if err := validateField(transportDD, transportDD.Trailer.Tags, field); err != nil { + if err := validateField(transportDD, settings, transportDD.Trailer.Tags, field); err != nil { return err } default: - if err := validateField(appDD, appDD.Messages[msgType].Tags, field); err != nil { + if err := validateField(appDD, settings, appDD.Messages[msgType].Tags, field); err != nil { return err } } @@ -327,15 +344,39 @@ func validateFields(transportDD *datadictionary.DataDictionary, appDD *datadicti return nil } -func validateField(d *datadictionary.DataDictionary, _ datadictionary.TagSet, field TagValue) MessageRejectError { +func getFieldType(d *datadictionary.DataDictionary, field int) (*datadictionary.FieldType, bool) { + fieldType, isMessageField := d.FieldTypeByTag[field] + return fieldType, isMessageField +} + +func checkFieldNotDefined(settings ValidatorSettings, field Tag) bool { + fail := false + if int(field) < UserDefinedTagMin { + fail = !settings.AllowUnknownMessageFields + } else { + fail = settings.CheckUserDefinedFields + } + return !fail +} + +func validateField(d *datadictionary.DataDictionary, + settings ValidatorSettings, + _ datadictionary.TagSet, + field TagValue, +) MessageRejectError { if len(field.value) == 0 { return TagSpecifiedWithoutAValue(field.tag) } - if _, valid := d.FieldTypeByTag[int(field.tag)]; !valid { + fieldType, isMessageField := getFieldType(d, int(field.tag)) + if !isMessageField && !checkFieldNotDefined(settings, field.tag) { return InvalidTagNumber(field.tag) } + if !isMessageField { + return nil + } + allowedValues := d.FieldTypeByTag[int(field.tag)].Enums if len(allowedValues) != 0 { if _, validValue := allowedValues[string(field.value)]; !validValue { @@ -343,7 +384,6 @@ func validateField(d *datadictionary.DataDictionary, _ datadictionary.TagSet, fi } } - fieldType := d.FieldTypeByTag[int(field.tag)] var prototype FieldValue switch fieldType.Type { case "MULTIPLESTRINGVALUE", "MULTIPLEVALUESTRING": diff --git a/validation_test.go b/validation_test.go index 861dd863e..1647dc94e 100644 --- a/validation_test.go +++ b/validation_test.go @@ -74,6 +74,14 @@ func TestValidate(t *testing.T) { tcInvalidTagCheckDisabledFixT(), tcInvalidTagCheckEnabled(), tcInvalidTagCheckEnabledFixT(), + tcAllowUnknownMessageFieldsEnabled(), + tcAllowUnknownMessageFieldsEnabledFixT(), + tcAllowUnknownMessageFieldsDisabled(), + tcAllowUnknownMessageFieldsDisabledFixT(), + tcCheckUserDefinedFieldsEnabled(), + tcCheckUserDefinedFieldsEnabledFixT(), + tcCheckUserDefinedFieldsDisabled(), + tcCheckUserDefinedFieldsDisabledFixT(), tcMultipleRepeatingGroupFields(), } @@ -786,6 +794,168 @@ func tcInvalidTagCheckEnabledFixT() validateTest { } } +func tcAllowUnknownMessageFieldsEnabled() validateTest { + dict, _ := datadictionary.Parse("spec/FIX40.xml") + customValidatorSettings := defaultValidatorSettings + customValidatorSettings.AllowUnknownMessageFields = true + validator := NewValidator(customValidatorSettings, dict, nil) + + builder := createFIX40NewOrderSingle() + tag := Tag(41) + builder.Body.SetField(tag, FIXString("hello")) + msgBytes := builder.build() + + return validateTest{ + TestName: "Allow Unknown Message Fields - Enabled", + Validator: validator, + MessageBytes: msgBytes, + DoNotExpectReject: true, + } +} + +func tcAllowUnknownMessageFieldsEnabledFixT() validateTest { + tDict, _ := datadictionary.Parse("spec/FIXT11.xml") + appDict, _ := datadictionary.Parse("spec/FIX50SP2.xml") + customValidatorSettings := defaultValidatorSettings + customValidatorSettings.AllowUnknownMessageFields = true + validator := NewValidator(customValidatorSettings, appDict, tDict) + + builder := createFIX50SP2NewOrderSingle() + tag := Tag(41) + builder.Body.SetField(tag, FIXString("hello")) + msgBytes := builder.build() + + return validateTest{ + TestName: "Allow Unknown Message Fields - Enabled FIXT", + Validator: validator, + MessageBytes: msgBytes, + DoNotExpectReject: true, + } +} + +func tcAllowUnknownMessageFieldsDisabled() validateTest { + dict, _ := datadictionary.Parse("spec/FIX40.xml") + customValidatorSettings := defaultValidatorSettings + customValidatorSettings.AllowUnknownMessageFields = false + validator := NewValidator(customValidatorSettings, dict, nil) + + builder := createFIX40NewOrderSingle() + tag := Tag(41) + builder.Body.SetField(tag, FIXString("hello")) + msgBytes := builder.build() + + return validateTest{ + TestName: "Allow Unknown Message Fields - Disabled", + Validator: validator, + MessageBytes: msgBytes, + DoNotExpectReject: false, + ExpectedRejectReason: rejectReasonTagNotDefinedForThisMessageType, + ExpectedRefTagID: &tag, + } +} + +func tcAllowUnknownMessageFieldsDisabledFixT() validateTest { + tDict, _ := datadictionary.Parse("spec/FIXT11.xml") + appDict, _ := datadictionary.Parse("spec/FIX50SP2.xml") + customValidatorSettings := defaultValidatorSettings + customValidatorSettings.RejectInvalidMessage = true + validator := NewValidator(customValidatorSettings, appDict, tDict) + + builder := createFIX50SP2NewOrderSingle() + tag := Tag(41) + builder.Body.SetField(tag, FIXString("hello")) + msgBytes := builder.build() + + return validateTest{ + TestName: "Allow Unknown Message Fields - Disabled FIXT", + Validator: validator, + MessageBytes: msgBytes, + DoNotExpectReject: false, + ExpectedRejectReason: rejectReasonTagNotDefinedForThisMessageType, + ExpectedRefTagID: &tag, + } +} + +func tcCheckUserDefinedFieldsEnabled() validateTest { + dict, _ := datadictionary.Parse("spec/FIX40.xml") + customValidatorSettings := defaultValidatorSettings + customValidatorSettings.CheckUserDefinedFields = true + validator := NewValidator(customValidatorSettings, dict, nil) + + builder := createFIX40NewOrderSingle() + tag := Tag(9999) + builder.Body.SetField(tag, FIXString("hello")) + msgBytes := builder.build() + + return validateTest{ + TestName: "Check User Defined Fields - Enabled", + Validator: validator, + MessageBytes: msgBytes, + DoNotExpectReject: false, + ExpectedRefTagID: &tag, + } +} + +func tcCheckUserDefinedFieldsEnabledFixT() validateTest { + tDict, _ := datadictionary.Parse("spec/FIXT11.xml") + appDict, _ := datadictionary.Parse("spec/FIX50SP2.xml") + customValidatorSettings := defaultValidatorSettings + customValidatorSettings.RejectInvalidMessage = true + validator := NewValidator(customValidatorSettings, appDict, tDict) + + builder := createFIX50SP2NewOrderSingle() + tag := Tag(9999) + builder.Body.SetField(tag, FIXString("hello")) + msgBytes := builder.build() + + return validateTest{ + TestName: "Check User Defined Fields - Enabled FIXT", + Validator: validator, + MessageBytes: msgBytes, + DoNotExpectReject: false, + ExpectedRefTagID: &tag, + } +} + +func tcCheckUserDefinedFieldsDisabled() validateTest { + dict, _ := datadictionary.Parse("spec/FIX40.xml") + customValidatorSettings := defaultValidatorSettings + customValidatorSettings.CheckUserDefinedFields = false + validator := NewValidator(customValidatorSettings, dict, nil) + + builder := createFIX40NewOrderSingle() + tag := Tag(9999) + builder.Body.SetField(tag, FIXString("hello")) + msgBytes := builder.build() + + return validateTest{ + TestName: "Check User Defined Fields - Disabled", + Validator: validator, + MessageBytes: msgBytes, + DoNotExpectReject: true, + } +} + +func tcCheckUserDefinedFieldsDisabledFixT() validateTest { + tDict, _ := datadictionary.Parse("spec/FIXT11.xml") + appDict, _ := datadictionary.Parse("spec/FIX50SP2.xml") + customValidatorSettings := defaultValidatorSettings + customValidatorSettings.CheckUserDefinedFields = false + validator := NewValidator(customValidatorSettings, appDict, tDict) + + builder := createFIX50SP2NewOrderSingle() + tag := Tag(9999) + builder.Body.SetField(tag, FIXString("hello")) + msgBytes := builder.build() + + return validateTest{ + TestName: "Check User Defined Fields - Disabled FIXT", + Validator: validator, + MessageBytes: msgBytes, + DoNotExpectReject: true, + } +} + func tcTagSpecifiedOutOfRequiredOrderDisabledHeader() validateTest { dict, _ := datadictionary.Parse("spec/FIX40.xml") customValidatorSettings := defaultValidatorSettings