// Copyright (c) quickfixengine.org All rights reserved. // // This file may be distributed under the terms of the quickfixengine.org // license as defined by quickfixengine.org and appearing in the file // LICENSE included in the packaging of this file. // // This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING // THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A // PARTICULAR PURPOSE. // // See http://www.quickfixengine.org/LICENSE for licensing information. // // Contact ask@quickfixengine.org if any conditions of this licensing // are not clear to you. package quickfix 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 AllowUnknownMessageFields bool CheckUserDefinedFields bool CheckFieldsHaveValues bool } // Default configuration for message validation. // See http://www.quickfixengine.org/quickfix/doc/html/configuration.html. var defaultValidatorSettings = ValidatorSettings{ CheckFieldsOutOfOrder: true, CheckFieldsHaveValues: true, RejectInvalidMessage: true, AllowUnknownMessageFields: false, CheckUserDefinedFields: true, } type fixValidator struct { dataDictionary *datadictionary.DataDictionary settings ValidatorSettings } type fixtValidator struct { transportDataDictionary *datadictionary.DataDictionary appDataDictionary *datadictionary.DataDictionary settings ValidatorSettings } // NewValidator creates a FIX message validator from the given data dictionaries. func NewValidator(settings ValidatorSettings, appDataDictionary, transportDataDictionary *datadictionary.DataDictionary) Validator { if transportDataDictionary != nil { return &fixtValidator{ transportDataDictionary: transportDataDictionary, appDataDictionary: appDataDictionary, settings: settings, } } return &fixValidator{ dataDictionary: appDataDictionary, settings: settings, } } // Validate tests the message against the provided data dictionary. func (v *fixValidator) Validate(msg *Message) MessageRejectError { if !msg.Header.Has(tagMsgType) { return RequiredTagMissing(tagMsgType) } msgType, err := msg.Header.GetString(tagMsgType) if err != nil { return err } return validateFIX(v.dataDictionary, v.settings, msgType, msg) } // Validate tests the message against the provided transport and app data dictionaries. // If the message is an admin message, it will be validated against the transport data dictionary. func (v *fixtValidator) Validate(msg *Message) MessageRejectError { if !msg.Header.Has(tagMsgType) { return RequiredTagMissing(tagMsgType) } msgType, err := msg.Header.GetString(tagMsgType) if err != nil { return err } if isAdminMessageType([]byte(msgType)) { return validateFIX(v.transportDataDictionary, v.settings, msgType, msg) } return validateFIXT(v.transportDataDictionary, v.appDataDictionary, v.settings, msgType, msg) } func validateFIX(d *datadictionary.DataDictionary, settings ValidatorSettings, msgType string, msg *Message) MessageRejectError { if d != nil { if err := validateMsgType(d, msgType, msg); err != nil { return err } if err := validateRequired(d, d, msgType, msg); err != nil { return err } } if err := validateFieldContent(msg, settings.CheckFieldsHaveValues, settings.CheckFieldsOutOfOrder); err != nil { return err } if settings.RejectInvalidMessage && d != nil { if err := validateFields(d, d, settings, msgType, msg); err != nil { return err } if err := validateWalk(d, d, settings, msgType, msg); err != nil { return err } } return nil } func validateFIXT(transportDD, appDD *datadictionary.DataDictionary, settings ValidatorSettings, msgType string, msg *Message) MessageRejectError { if appDD != nil && transportDD != nil { if err := validateMsgType(appDD, msgType, msg); err != nil { return err } if err := validateRequired(transportDD, appDD, msgType, msg); err != nil { return err } } if err := validateFieldContent(msg, settings.CheckFieldsHaveValues, settings.CheckFieldsOutOfOrder); err != nil { return err } if settings.RejectInvalidMessage && appDD != nil && transportDD != nil { if err := validateFields(transportDD, appDD, settings, msgType, msg); err != nil { return err } if err := validateWalk(transportDD, appDD, settings, msgType, msg); err != nil { return err } } return nil } func validateMsgType(d *datadictionary.DataDictionary, msgType string, _ *Message) MessageRejectError { if _, validMsgType := d.Messages[msgType]; !validMsgType { return InvalidMessageType() } return nil } func validateWalk(transportDD *datadictionary.DataDictionary, appDD *datadictionary.DataDictionary, settings ValidatorSettings, msgType string, msg *Message) MessageRejectError { remainingFields := msg.fields iteratedTags := make(datadictionary.TagSet) var messageDef *datadictionary.MessageDef var fieldDef *datadictionary.FieldDef var err MessageRejectError var ok bool for len(remainingFields) > 0 { field := remainingFields[0] tag := field.tag switch { case tag.IsHeader(): messageDef = transportDD.Header case tag.IsTrailer(): messageDef = transportDD.Trailer default: // is body messageDef = appDD.Messages[msgType] } 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 } } if len(remainingFields) != 0 { return TagNotDefinedForThisMessageType(remainingFields[0].tag) } return nil } func validateVisitField(fieldDef *datadictionary.FieldDef, fields []TagValue) ([]TagValue, MessageRejectError) { if fieldDef.IsGroup() { var err MessageRejectError if fields, err = validateVisitGroupField(fieldDef, fields); err != nil { return nil, err } return fields, nil } return fields[1:], nil } func validateVisitGroupField(fieldDef *datadictionary.FieldDef, fieldStack []TagValue) ([]TagValue, MessageRejectError) { numInGroupTag := fieldStack[0].tag var numInGroup FIXInt if err := numInGroup.Read(fieldStack[0].value); err != nil { return nil, IncorrectDataFormatForValue(numInGroupTag) } fieldStack = fieldStack[1:] var childDefs []*datadictionary.FieldDef groupCount := 0 for len(fieldStack) > 0 { // Start of repeating group. if int(fieldStack[0].tag) == fieldDef.Fields[0].Tag() { childDefs = fieldDef.Fields groupCount++ } // Group complete. if len(childDefs) == 0 { break } if int(fieldStack[0].tag) == childDefs[0].Tag() { var err MessageRejectError if fieldStack, err = validateVisitField(childDefs[0], fieldStack); err != nil { return fieldStack, err } } else { if childDefs[0].Required() { return fieldStack, RequiredTagMissing(Tag(childDefs[0].Tag())) } } childDefs = childDefs[1:] } if groupCount != int(numInGroup) { return fieldStack, incorrectNumInGroupCountForRepeatingGroup(numInGroupTag) } return fieldStack, nil } func validateFieldContent(msg *Message, checkFieldsHaveValues, checkFieldsOutOfOrder bool) MessageRejectError { if !checkFieldsHaveValues && !checkFieldsOutOfOrder { return nil } inHeader := true inTrailer := false for _, field := range msg.fields { t := field.tag if checkFieldsHaveValues && len(field.value) == 0 { return TagSpecifiedWithoutAValue(t) } switch { case inHeader && t.IsHeader(): case inHeader && !t.IsHeader(): inHeader = false case !inHeader && t.IsHeader() && checkFieldsOutOfOrder: return tagSpecifiedOutOfRequiredOrder(t) case t.IsTrailer(): inTrailer = true case inTrailer && !t.IsTrailer() && checkFieldsOutOfOrder: return tagSpecifiedOutOfRequiredOrder(t) } } return nil } func validateRequired(transportDD *datadictionary.DataDictionary, appDD *datadictionary.DataDictionary, msgType string, message *Message) MessageRejectError { if err := validateRequiredFieldMap(message, transportDD.Header.RequiredTags, message.Header.FieldMap); err != nil { return err } if err := validateRequiredFieldMap(message, appDD.Messages[msgType].RequiredTags, message.Body.FieldMap); err != nil { return err } if err := validateRequiredFieldMap(message, transportDD.Trailer.RequiredTags, message.Trailer.FieldMap); err != nil { return err } return nil } func validateRequiredFieldMap(_ *Message, requiredTags map[int]struct{}, fieldMap FieldMap) MessageRejectError { for required := range requiredTags { requiredTag := Tag(required) if !fieldMap.Has(requiredTag) { return RequiredTagMissing(requiredTag) } } return nil } 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, settings, transportDD.Header.Tags, field); err != nil { return err } case field.tag.IsTrailer(): if err := validateField(transportDD, settings, transportDD.Trailer.Tags, field); err != nil { return err } default: if err := validateField(appDD, settings, appDD.Messages[msgType].Tags, field); err != nil { return err } } } return nil } 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) } 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) } } var prototype FieldValue switch fieldType.Type { case "MULTIPLESTRINGVALUE", "MULTIPLEVALUESTRING": fallthrough case "MULTIPLECHARVALUE": fallthrough case "CHAR": fallthrough case "CURRENCY": fallthrough case "DATA": fallthrough case "MONTHYEAR": fallthrough case "LOCALMKTDATE", "DATE": fallthrough case "EXCHANGE": fallthrough case "LANGUAGE": fallthrough case "XMLDATA": fallthrough case "COUNTRY": fallthrough case "UTCTIMEONLY": fallthrough case "UTCDATEONLY", "UTCDATE": fallthrough case "TZTIMEONLY": fallthrough case "TZTIMESTAMP": fallthrough case "STRING": prototype = new(FIXString) case "BOOLEAN": prototype = new(FIXBoolean) case "LENGTH": fallthrough case "DAYOFMONTH": fallthrough case "NUMINGROUP": fallthrough case "SEQNUM": fallthrough case "INT": prototype = new(FIXInt) case "UTCTIMESTAMP", "TIME": prototype = new(FIXUTCTimestamp) case "QTY", "QUANTITY": fallthrough case "AMT": fallthrough case "PRICE": fallthrough case "PRICEOFFSET": fallthrough case "PERCENTAGE": fallthrough case "FLOAT": prototype = new(FIXFloat) } if err := prototype.Read(field.value); err != nil { return IncorrectDataFormatForValue(field.tag) } return nil }