From 0ad35dd87126131cf6db912e980d8e14078621e7 Mon Sep 17 00:00:00 2001 From: Markus Quaritsch <markus.quaritsch@tugraz.at> Date: Mon, 25 Sep 2017 16:11:38 +0200 Subject: [PATCH] refactoring: connect job directly to report viewmodels (without verifyresults as mediator), more validation tests, --- .../ViewModel/HashComponentDataViewModel.cs | 4 +- .../UserControl/ManufacturerReportXMLFile.cs | 151 ++++++++---------- .../ViewModel/UserControl/ReportXMLFile.cs | 113 +++++++++++-- .../ViewModel/VerifyResultDataViewModel.cs | 33 +--- HashingTool/Views/HashComponentData.xaml | 4 +- HashingTool/Views/VerifyResults.xaml | 12 +- 6 files changed, 177 insertions(+), 140 deletions(-) diff --git a/HashingTool/ViewModel/HashComponentDataViewModel.cs b/HashingTool/ViewModel/HashComponentDataViewModel.cs index 44d25b172b..da18a04281 100644 --- a/HashingTool/ViewModel/HashComponentDataViewModel.cs +++ b/HashingTool/ViewModel/HashComponentDataViewModel.cs @@ -17,7 +17,7 @@ using TUGraz.VectoHashing.Impl; namespace HashingTool.ViewModel { - public class HashComponentDataViewModel : HashedXMLFile, IMainView + public class HashComponentDataViewModel : VectoXMLFile, IMainView { private string _digestValue; @@ -30,7 +30,7 @@ namespace HashingTool.ViewModel private bool _busy; public HashComponentDataViewModel() - : base("Hash Component Data", HashingHelper.IsComponentFile) + : base("Hash Component Data", false, HashingHelper.IsComponentFile) { _xmlFile.PropertyChanged += SourceChanged; _saveCommand = new RelayCommand(SaveDocument, diff --git a/HashingTool/ViewModel/UserControl/ManufacturerReportXMLFile.cs b/HashingTool/ViewModel/UserControl/ManufacturerReportXMLFile.cs index d17b5ac2c1..39810da2b9 100644 --- a/HashingTool/ViewModel/UserControl/ManufacturerReportXMLFile.cs +++ b/HashingTool/ViewModel/UserControl/ManufacturerReportXMLFile.cs @@ -15,7 +15,7 @@ namespace HashingTool.ViewModel.UserControl { private ViewModel.ComponentEntry[] _jobComponents; - private readonly ObservableCollection<string> _validationErrors = new ObservableCollection<string>(); + private bool _manufacturerReportValid; public ManufacturerReportXMLFile(string name, Func<XmlDocument, IErrorLogger, bool?> contentCheck, Action<XmlDocument, VectoXMLFile> hashValidation = null) : base(name, contentCheck, hashValidation) @@ -23,23 +23,11 @@ namespace HashingTool.ViewModel.UserControl _xmlFile.PropertyChanged += UpdateComponents; } - public ViewModel.ComponentEntry[] JobComponents + protected override void VerifyJobDataMatchesReport() { - set { - if (_jobComponents == value) { - return; - } - _jobComponents = value; - DoUpdateComponents(); - RaisePropertyChanged("JobComponents"); - RaisePropertyChanged("ManufacturerReportValid"); - } - private get { return _jobComponents; } - } + base.VerifyJobDataMatchesReport(); - public ObservableCollection<string> ValidationErrors - { - get { return _validationErrors; } + DoUpdateComponentData(); } private void UpdateComponents(object sender, PropertyChangedEventArgs e) @@ -47,22 +35,27 @@ namespace HashingTool.ViewModel.UserControl if (e.PropertyName != "UPDATED") { return; } - DoUpdateComponents(); - RaisePropertyChanged("ManufacturerReportValid"); + DoUpdateComponentData(); RaisePropertyChanged("UPDATED"); } - private void DoUpdateComponents() + private void DoUpdateComponentData() { - if (_xmlFile.Document == null || _xmlFile.Document.DocumentElement == null) { + if (_xmlFile.Document == null || _xmlFile.Document.DocumentElement == null || + _xmlFile.IsValid != XmlFileStatus.ValidXML) { Components = new ComponentEntry[] { }; - VehicleIdentificationNumber = ""; RaisePropertyChanged("Components"); - RaisePropertyChanged("VehicleIdentificationNumber"); return; } var components = GetContainigComponents().GroupBy(s => s) .Select(g => new { Entry = g.Key, Count = g.Count() }); + var jobComponents = _jobData == null ? new ViewModel.ComponentEntry[]{} : _jobData.Components.ToArray(); + _validationErrors.Clear(); + + var hasComponentsFromJob = _jobData != null && _jobData.JobDataValid != null && _jobData.JobDataValid.Value && jobComponents.Any(); + + // iterate over components in manufacturer report, read out c14n, digest method, digest; + // collect c14n, digest method, digest value read, certification nr., digest value from job (re-computed) var componentData = new List<ComponentEntry>(); foreach (var component in components) { if (component.Entry == XMLNames.Component_Vehicle) { @@ -77,6 +70,7 @@ namespace HashingTool.ViewModel.UserControl DigestValue = ReadElementValue(node, XMLNames.DI_Signature_Reference_DigestValue), CertificationMethod = ReadElementValue(node, XMLNames.Report_Component_CertificationMethod), }; + // rename 'Axle' from report to 'Tyre' as in job if (entry.Component.StartsWith("Axle ")) { entry.Component = entry.Component.Replace("Axle", "Tyre"); entry.CertificationNumber = ReadElementValue(node, XMLNames.Report_Tyre_TyreCertificationNumber); @@ -85,81 +79,62 @@ namespace HashingTool.ViewModel.UserControl entry.CertificationNumber = ReadElementValue(node, XMLNames.Report_Component_CertificationNumber) ?? ReadElementValue(node, XMLNames.Report_Component_CertificationMethod); } - if (JobComponents != null) { - var jobComponent = JobComponents.Where( - x => x.Component == entry.Component).ToArray(); - if (jobComponent.Any()) { - entry.DigestValueMatchesJobComponent = entry.Component.StartsWith("Tyre ") - ? (bool?)null - : (jobComponent.First().DigestValueRead == entry.DigestValue); - entry.DigestValueExpected = jobComponent.First().DigestValueRead; - if (entry.CertificationMethod != CertificationMethod.StandardValues.ToXMLFormat()) { - entry.CertificationNumberMatchesJobComponent = jobComponent.First().CertificationNumber == - entry.CertificationNumber; - entry.CertificationNumberExpected = jobComponent.First().CertificationNumber; - } - } - } componentData.Add(entry); + if (!hasComponentsFromJob) { + continue; + } + var jobComponent = jobComponents.Where(x => x.Component == entry.Component).ToArray(); + if (!jobComponent.Any()) { + continue; + } + entry.DigestValueMatchesJobComponent = entry.Component.StartsWith("Tyre ") + ? (bool?)null + : (jobComponent.First().DigestValueComputed == entry.DigestValue); + entry.DigestValueExpected = jobComponent.First().DigestValueComputed; + + if (entry.CertificationMethod == CertificationMethod.StandardValues.ToXMLFormat()) { + continue; + } + entry.CertificationNumberMatchesJobComponent = jobComponent.First().CertificationNumber == + entry.CertificationNumber; + entry.CertificationNumberExpected = jobComponent.First().CertificationNumber; } } Components = componentData.ToArray(); - VehicleIdentificationNumber = GetVehicleIdentificationNumber(); - RaisePropertyChanged("Components"); - RaisePropertyChanged("VehicleIdentificationNumber"); - } - - private string GetVehicleIdentificationNumber() - { - if (_xmlFile.Document == null || _xmlFile.IsValid != XmlFileStatus.ValidXML || _xmlFile.ContentValid == null || - !_xmlFile.ContentValid.Value) { - return ""; - } - var node = _xmlFile.Document.SelectSingleNode(string.Format("//*[local-name()='{0}']", XMLNames.Vehicle_VIN)); - if (node == null) { - return ""; + var certificationNumberMismatch = + componentData.Where( + x => x.CertificationNumberMatchesJobComponent != null && !x.CertificationNumberMatchesJobComponent.Value).ToArray(); + var digestMismatch = + componentData.Where(x => !x.Component.StartsWith("Tyre ")) + .Where(x => x.DigestValueMatchesJobComponent == null || !x.DigestValueMatchesJobComponent.Value).ToArray(); + if (jobComponents.Any()) { + foreach (var entry in certificationNumberMismatch) { + _validationErrors.Add( + string.Format( + "Verifying Manufacturer Report: Certification number for component '{0}' does not match! Job-file: '{1}', Report: '{2}'", + entry.Component, entry.CertificationNumberExpected, entry.CertificationNumber)); + } + foreach (var entry in digestMismatch) { + _validationErrors.Add( + string.Format( + "Verifying Manufacturer Report: Digest Value for component '{0}' does not match! Job-file: '{1}', Report: '{2}'", + entry.Component, entry.DigestValueExpected, entry.DigestValue)); + } } - return node.InnerText; + + ManufacturerReportValid = hasComponentsFromJob && !certificationNumberMismatch.Any() && !digestMismatch.Any(); } public bool ManufacturerReportValid { - get { - _validationErrors.Clear(); - var componentsValid = JobComponents != null && JobComponents.Length > 0; - if (Components == null || JobComponents == null || JobComponents.Length == 0) { - return false; - } - - foreach (var entry in Components) { - // certification number is optional (iff standard values are used) - var entryCertificationNbr = entry.CertificationNumberMatchesJobComponent == null || - entry.CertificationNumberMatchesJobComponent.Value; - if (!entryCertificationNbr) { - var msg = - string.Format( - "Verifying Manufacturer Report: Certification number for component '{0}' does not match! Job-File: '{1}', Report: '{2}'", - entry.Component, entry.CertificationNumberExpected, entry.CertificationNumber); - _validationErrors.Add(msg); - } - componentsValid &= entryCertificationNbr; - // digest value is mandatory (except for tires) - if (entry.Component.StartsWith("Tyre ")) { - continue; - } - - var entryDigest = entry.DigestValueMatchesJobComponent != null && entry.DigestValueMatchesJobComponent.Value; - if (!entryDigest) { - var msg = - string.Format( - "Verifying Manufacturer Report: Digest value for component '{0}' does not match! Job-File: '{1}', Report: '{2}'", - entry.Component, entry.DigestValueExpected, entry.DigestValue); - _validationErrors.Add(msg); - } - componentsValid &= entryDigest; + get { return _manufacturerReportValid; } + set { + if (_manufacturerReportValid == value) { + return; } - return JobDigestValid && componentsValid; + _manufacturerReportValid = value; + RaisePropertyChanged("ManufacturerReportValid"); } } @@ -217,7 +192,6 @@ namespace HashingTool.ViewModel.UserControl return retVal; } - public string VehicleIdentificationNumber { get; private set; } public ComponentEntry[] Components { get; private set; } @@ -232,8 +206,11 @@ namespace HashingTool.ViewModel.UserControl public string CertificationMethod { get; set; } public bool? DigestValueMatchesJobComponent { get; set; } + public bool? CertificationNumberMatchesJobComponent { get; set; } + public string DigestValueExpected { get; set; } + public string CertificationNumberExpected { get; set; } } } diff --git a/HashingTool/ViewModel/UserControl/ReportXMLFile.cs b/HashingTool/ViewModel/UserControl/ReportXMLFile.cs index 72588b143b..70419a59f1 100644 --- a/HashingTool/ViewModel/UserControl/ReportXMLFile.cs +++ b/HashingTool/ViewModel/UserControl/ReportXMLFile.cs @@ -3,32 +3,104 @@ using System.Collections.ObjectModel; using System.ComponentModel; using System.Linq; using System.Xml; +using TUGraz.VectoHashing; namespace HashingTool.ViewModel.UserControl { public class ReportXMLFile : HashedXMLFile { + protected readonly ObservableCollection<string> _validationErrors = new ObservableCollection<string>(); + private string _jobDigestValueReadRead; private string _jobDigestMethodRead; private string[] _jobCanonicalizationMethodRead; private string _jobDigestComputed; - private bool _jobDigestValid; + private bool _jobDigestMatchesReport; + protected VectoJobFile _jobData; + private string _reportVin; public ReportXMLFile(string name, Func<XmlDocument, IErrorLogger, bool?> contentCheck, Action<XmlDocument, VectoXMLFile> hashValidation = null) : base(name, contentCheck, hashValidation) { - _xmlFile.PropertyChanged += ReadJobDigest; + _xmlFile.PropertyChanged += ReportChanged; + } + + public ObservableCollection<string> ValidationErrors + { + get { return _validationErrors; } + } + + public VectoJobFile JobData + { + private get { return _jobData; } + set { + if (_jobData == value) { + return; + } + _jobData = value; + _jobData.PropertyChanged += JobDataChanged; + } + } + + private void ReportChanged(object sender, PropertyChangedEventArgs e) + { + if (sender == _xmlFile && e.PropertyName == "UPDATED") { + ReadReportData(); + VerifyJobDataMatchesReport(); + } } - private void ReadJobDigest(object sender, PropertyChangedEventArgs e) + + protected virtual void JobDataChanged(object sender, PropertyChangedEventArgs e) { - if (e.PropertyName != "UPDATED") { + if (sender == _jobData && e.PropertyName == "UPDATED") { + VerifyJobDataMatchesReport(); + } + } + + + // check digest value and vin of report against given job + protected virtual void VerifyJobDataMatchesReport() + { + if (_xmlFile.IsValid != XmlFileStatus.ValidXML || _jobData == null || + _jobData.XMLFile.IsValid != XmlFileStatus.ValidXML) { + JobDigestValueComputed = ""; + JobDigestMatchesReport = false; return; } + try { + var h = VectoHash.Load(_jobData.XMLFile.Document); + var jobDigest = h.ComputeHash(JobCanonicalizationMethodRead, + JobDigestMethodRead); + JobDigestValueComputed = jobDigest; + var digestMatch = _jobDigestComputed == JobDigestValueRead; + var vinMatch = _jobData.VehicleIdentificationNumber == ReportVIN; + + if (!digestMatch) { + _validationErrors.Add(string.Format("Job Digest Value mismatch! Computed job digest: '{0}', digest read: '{1}'", + _jobDigestComputed, JobDigestValueRead)); + } + + if (!vinMatch) { + _validationErrors.Add(string.Format("VIN mismatch! VIN from job data: '{0}', VIN from report: '{1}'", + _jobData.VehicleIdentificationNumber, ReportVIN)); + } + + JobDigestMatchesReport = vinMatch + && digestMatch; + } catch (Exception) { + JobDigestValueComputed = ""; + JobDigestMatchesReport = false; + } + } + // readout all required fields from the report xml: c14n, digest method, digest value of job, VIN, ... + protected virtual void ReadReportData() + { var jobDigest = ""; var jobDigestMethod = ""; + var vin = ""; var jobc14NMethod = new string[] { }; if (_xmlFile.Document != null && _xmlFile.Document.DocumentElement != null) { @@ -49,13 +121,30 @@ namespace HashingTool.ViewModel.UserControl if (c14NtMethodNodes != null) { jobc14NMethod = (from XmlNode node in c14NtMethodNodes select node.InnerText).ToArray(); } + var vinNode = _xmlFile.Document.SelectSingleNode("//*[local-name()='VIN']"); + if (vinNode != null) { + vin = vinNode.InnerText; + } } JobCanonicalizationMethodRead = jobc14NMethod; JobDigestMethodRead = jobDigestMethod; JobDigestValueRead = jobDigest; + ReportVIN = vin; RaisePropertyChanged("UPDATED"); } + public string ReportVIN + { + get { return _reportVin; } + set { + if (_reportVin == value) { + return; + } + _reportVin = value; + RaisePropertyChanged("ReportVIN"); + } + } + public string JobDigestMethodRead { get { return _jobDigestMethodRead; } @@ -95,26 +184,24 @@ namespace HashingTool.ViewModel.UserControl public string JobDigestValueComputed { get { return _jobDigestComputed; } - set { + protected set { if (_jobDigestComputed == value) { - JobDigestValid = _jobDigestComputed == JobDigestValueRead; return; } _jobDigestComputed = value; RaisePropertyChanged("JobDigestValueComputed"); - JobDigestValid = _jobDigestComputed == JobDigestValueRead; } } - public bool JobDigestValid + public bool JobDigestMatchesReport { - get { return _jobDigestValid; } - set { - if (_jobDigestValid == value) { + get { return _jobDigestMatchesReport; } + protected set { + if (_jobDigestMatchesReport == value) { return; } - _jobDigestValid = value; - RaisePropertyChanged("JobDigestValid"); + _jobDigestMatchesReport = value; + RaisePropertyChanged("JobDigestMatchesReport"); } } } diff --git a/HashingTool/ViewModel/VerifyResultDataViewModel.cs b/HashingTool/ViewModel/VerifyResultDataViewModel.cs index 09d7e3da74..f5e712ddb6 100644 --- a/HashingTool/ViewModel/VerifyResultDataViewModel.cs +++ b/HashingTool/ViewModel/VerifyResultDataViewModel.cs @@ -24,8 +24,10 @@ namespace HashingTool.ViewModel _jobFile = new VectoJobFile("Job File", HashingHelper.IsJobFile, HashingHelper.HashJobFile); _manufacturerReport = new ManufacturerReportXMLFile("Manufacturer Report", HashingHelper.IsManufacturerReport, HashingHelper.ValidateDocumentHash); + _manufacturerReport.JobData = _jobFile; _customerReport = new ReportXMLFile("Customer Report", HashingHelper.IsCustomerReport, HashingHelper.ValidateDocumentHash); + _customerReport.JobData = _jobFile; Files = new ObservableCollection<VectoXMLFile> { _jobFile, _manufacturerReport, _customerReport }; ErrorsAndWarnings = new CompositeCollection(); @@ -34,11 +36,9 @@ namespace HashingTool.ViewModel AddErrorCollection(_manufacturerReport.XMLFile.XMLValidationErrors); AddErrorCollection(_customerReport.XMLFile.XMLValidationErrors); AddErrorCollection(_manufacturerReport.ValidationErrors); + AddErrorCollection(_customerReport.ValidationErrors); RaisePropertyChanged("CanonicalizationMethods"); - _customerReport.PropertyChanged += Update; - _manufacturerReport.PropertyChanged += Update; - _jobFile.PropertyChanged += Update; } private void AddErrorCollection(ObservableCollection<string> errorCollection) @@ -57,17 +57,6 @@ namespace HashingTool.ViewModel get { return ErrorsAndWarnings.Cast<CollectionContainer>().Sum(entry => (entry.Collection as ICollection).Count); } } - private void Update(object sender, PropertyChangedEventArgs e) - { - if (e.PropertyName != "UPDATED") { - return; - } - UpdateReportJobDigest(_manufacturerReport); - UpdateReportJobDigest(_customerReport); - - _manufacturerReport.JobComponents = _jobFile.Components.ToArray(); - } - public string Name { @@ -98,21 +87,5 @@ namespace HashingTool.ViewModel public ObservableCollection<VectoXMLFile> Files { get; private set; } public CompositeCollection ErrorsAndWarnings { get; private set; } - - private void UpdateReportJobDigest(ReportXMLFile reportXML) - { - if (reportXML.FileIntegrityValid == null || !reportXML.FileIntegrityValid.Value || _jobFile.XMLFile.Document == null) { - reportXML.JobDigestValueComputed = ""; - return; - } - try { - var h = VectoHash.Load(_jobFile.XMLFile.Document); - var jobDigest = h.ComputeHash(reportXML.JobCanonicalizationMethodRead, - reportXML.JobDigestMethodRead); - reportXML.JobDigestValueComputed = jobDigest; - } catch (Exception) { - reportXML.JobDigestValueComputed = ""; - } - } } } diff --git a/HashingTool/Views/HashComponentData.xaml b/HashingTool/Views/HashComponentData.xaml index c4a51a49d0..d553019f19 100644 --- a/HashingTool/Views/HashComponentData.xaml +++ b/HashingTool/Views/HashComponentData.xaml @@ -48,7 +48,7 @@ <ColumnDefinition Width="*" /> <ColumnDefinition Width="Auto" /> </Grid.ColumnDefinitions> - <StackPanel Orientation="Horizontal" Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Margin="0,0,0,5"> + <!--<StackPanel Orientation="Horizontal" Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Margin="0,0,0,5"> <Label> <Label.Content> <TextBlock Text="{Binding XMLFile.XMLValidationErrors.Count, StringFormat='{}{0} Warnings/Errors'}" /> @@ -76,7 +76,7 @@ </Style> </Button.Style> </Button> - </StackPanel> + </StackPanel>--> <Label Grid.Row="1" Grid.Column="0" Name="lblC14N" Content="Canonicalization:" HorizontalAlignment="Left" Margin="0" /> diff --git a/HashingTool/Views/VerifyResults.xaml b/HashingTool/Views/VerifyResults.xaml index 14fd4db17d..f25f1f1d29 100644 --- a/HashingTool/Views/VerifyResults.xaml +++ b/HashingTool/Views/VerifyResults.xaml @@ -311,7 +311,7 @@ <Style TargetType="TextBox" BasedOn="{StaticResource DigestValueTextboxStyle}"> <Setter Property="Foreground" Value="{StaticResource Color.ErrorRed}" /> <Style.Triggers> - <DataTrigger Binding="{Binding JobDigestValid}" Value="True"> + <DataTrigger Binding="{Binding JobDigestMatchesReport}" Value="True"> <Setter Property="Foreground" Value="{StaticResource Color.SuccessGreen}" /> </DataTrigger> </Style.Triggers> @@ -327,7 +327,7 @@ <Style TargetType="TextBox" BasedOn="{StaticResource DigestValueTextboxStyle}"> <Setter Property="Foreground" Value="{StaticResource Color.ErrorRed}" /> <Style.Triggers> - <DataTrigger Binding="{Binding JobDigestValid}" Value="True"> + <DataTrigger Binding="{Binding JobDigestMatchesReport}" Value="True"> <Setter Property="Foreground" Value="{StaticResource Color.SuccessGreen}" /> </DataTrigger> </Style.Triggers> @@ -439,7 +439,7 @@ <Style TargetType="TextBox" BasedOn="{StaticResource DigestValueTextboxStyle}"> <Setter Property="Foreground" Value="{StaticResource Color.ErrorRed}" /> <Style.Triggers> - <DataTrigger Binding="{Binding JobDigestValid}" Value="True"> + <DataTrigger Binding="{Binding JobDigestMatchesReport}" Value="True"> <Setter Property="Foreground" Value="{StaticResource Color.SuccessGreen}" /> </DataTrigger> </Style.Triggers> @@ -455,7 +455,7 @@ <Style TargetType="TextBox" BasedOn="{StaticResource DigestValueTextboxStyle}"> <Setter Property="Foreground" Value="{StaticResource Color.ErrorRed}" /> <Style.Triggers> - <DataTrigger Binding="{Binding JobDigestValid}" Value="True"> + <DataTrigger Binding="{Binding JobDigestMatchesReport}" Value="True"> <Setter Property="Foreground" Value="{StaticResource Color.SuccessGreen}" /> </DataTrigger> </Style.Triggers> @@ -481,7 +481,7 @@ <Label Grid.Row="0" Grid.Column="0" Content="Vehicle" FontWeight="Bold" Margin="0,0,10,0" /> <Label Grid.Row="0" Grid.Column="1" Content="VIN:" /> - <TextBox Grid.Row="0" Grid.Column="2" Text="{Binding VehicleIdentificationNumber, Mode=OneWay}" + <TextBox Grid.Row="0" Grid.Column="2" Text="{Binding ReportVIN, Mode=OneWay}" Margin="10,2" IsReadOnly="True" /> <ItemsControl Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="5" DockPanel.Dock="Bottom" @@ -763,7 +763,7 @@ <Style TargetType="ContentControl"> <Setter Property="ContentTemplate" Value="{StaticResource Icon_NOK}" /> <Style.Triggers> - <DataTrigger Binding="{Binding CustomerReport.JobDigestValid}" Value="True"> + <DataTrigger Binding="{Binding CustomerReport.JobDigestMatchesReport}" Value="True"> <Setter Property="ContentTemplate" Value="{StaticResource Icon_OK}" /> </DataTrigger> </Style.Triggers> -- GitLab