Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions config/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ const (
MaxLatency string = "MaxLatency"
PersistMessages string = "PersistMessages"
RejectInvalidMessage string = "RejectInvalidMessage"
AllowUnknownMessageFields string = "AllowUnknownMsgFields"
CheckUserDefinedFields string = "ValidateUserDefinedFields"
DynamicSessions string = "DynamicSessions"
DynamicQualifier string = "DynamicQualifier"
)
12 changes: 12 additions & 0 deletions session_factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
80 changes: 60 additions & 20 deletions validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,30 @@ import (
"github.com/quickfixgo/quickfix/datadictionary"
)

const (
UserDefinedTagMin int = 5000
)

// Validator validates a FIX message.
type Validator interface {
Validate(*Message) MessageRejectError
}

// 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 {
Expand Down Expand Up @@ -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
}
}
Expand All @@ -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
}
}
Expand All @@ -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)

Expand All @@ -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
}
Expand Down Expand Up @@ -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
}
}
Expand All @@ -327,23 +344,46 @@ 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 {
return ValueIsIncorrect(field.tag)
}
}

fieldType := d.FieldTypeByTag[int(field.tag)]
var prototype FieldValue
switch fieldType.Type {
case "MULTIPLESTRINGVALUE", "MULTIPLEVALUESTRING":
Expand Down
170 changes: 170 additions & 0 deletions validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,14 @@ func TestValidate(t *testing.T) {
tcInvalidTagCheckDisabledFixT(),
tcInvalidTagCheckEnabled(),
tcInvalidTagCheckEnabledFixT(),
tcAllowUnknownMessageFieldsEnabled(),
tcAllowUnknownMessageFieldsEnabledFixT(),
tcAllowUnknownMessageFieldsDisabled(),
tcAllowUnknownMessageFieldsDisabledFixT(),
tcCheckUserDefinedFieldsEnabled(),
tcCheckUserDefinedFieldsEnabledFixT(),
tcCheckUserDefinedFieldsDisabled(),
tcCheckUserDefinedFieldsDisabledFixT(),
tcMultipleRepeatingGroupFields(),
}

Expand Down Expand Up @@ -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
Expand Down