/// <summary> /// Inserts a field at the specified index in the existing list of Structured Fields /// </summary> /// <param name="newField"></param> The field that will be inserted /// <param name="index"></param> The index at which to insert the new field. Will push existing items down from index position and on. public void AddField(StructuredField newField, int index) { // Insert at specified index _fields.Insert(index, newField); // Sync containers SetupContainers(_fields); }
/// <summary> /// Removes a field from the list of structured fields /// </summary> /// <param name="field"></param> The field that will be removed from the list. public void DeleteField(StructuredField field) { _fields.Remove(field); // Remove this field from all containers that know about it foreach (Container c in _fields .Select(f => f.LowestLevelContainer) .Distinct() .Where(cn => cn != null && cn.Structures.Contains(field))) { c.Structures.Remove(field); } // Sync containers SetupContainers(_fields); }
public AFPNode(byte[] data, int start, int end) { Prefix = data[start]; Length = data[start + 2] + (data[start + 1] * 256); Flags = data[start + 6]; Reserved = new byte[] { data[start + 7], data[start + 8] }; RawData = new List <byte>(); for (int i = start + 9; i < end; i++) { RawData.Add(data[i]); } //StructuredField sf = new StructuredField((data[start + 3].ToString("x2") + data[start + 4].ToString("x2") + data[start + 5].ToString("x2")).ToUpper()); StructuredField sf = new StructuredField(data[start + 3], data[start + 4], data[start + 5]); Code = sf.Code; CategoryDescription = sf.CategoryDescription; Name = sf.Type + ": " + sf.Category; CategoryName = sf.Category; TypeDescription = sf.TypeDescription; TypeName = sf.Type; //Data }
protected List <StructuredField> DecodeStream(byte[] stream, bool parseData) { List <StructuredField> fields = new List <StructuredField>(); // Next, loop through each 5A block and store a StructuredField object int curIdx = 0; while (curIdx < stream.Length - 1) { byte[] lengthBytes = new byte[2], identifierBytes = new byte[3], introducer = new byte[8], sequenceBytes = new byte[2]; byte flag; string curOffsetHex = $"0x{curIdx.ToString("X8")}"; // Check for 0x5A prefix on each field if (stream[curIdx] != 0x5A) { throw new Exception($"Unexpected byte at offset {curOffsetHex}. Is it a true AFP file?"); } // Check for expected length of data if (curIdx + 9 > stream.Length) { throw new Exception($"No room for SFI at offset {curOffsetHex}. Is it a true AFP file?"); } // Read the introducer Array.ConstrainedCopy(stream, curIdx + 1, introducer, 0, 8); Array.ConstrainedCopy(introducer, 0, lengthBytes, 0, 2); Array.ConstrainedCopy(introducer, 2, identifierBytes, 0, 3); flag = introducer[5]; Array.ConstrainedCopy(introducer, 6, sequenceBytes, 0, 2); // Get a couple pieces of data from introducer ushort length = DataStructure.GetNumericValue <ushort>(lengthBytes); ushort sequence = DataStructure.GetNumericValue <ushort>(sequenceBytes); // Check the length isn't over what we can read if (curIdx + 1 + length > stream.Length) { throw new Exception($"Invalid field length of {length} at offset {curOffsetHex}. Is it a true AFP file?"); } // Get the data byte[] data = new byte[length - 8]; Array.ConstrainedCopy(stream, curIdx + 9, data, 0, length - 8); // Lookup what type of field we need by the structured fields hex dictionary Type fieldType = typeof(StructuredFields.UNKNOWN); string idStr = BitConverter.ToString(identifierBytes).Replace("-", ""); if (Lookups.StructuredFields.ContainsKey(idStr)) { fieldType = Lookups.StructuredFields[idStr]; } StructuredField field = (StructuredField)Activator.CreateInstance(fieldType, identifierBytes, flag, sequence, data); // Append to AFP file fields.Add(field); // Go to next 5A curIdx += length + 1; } SetupContainers(fields); // Parse data if needed if (parseData) { foreach (StructuredField field in fields) { field.ParseData(); } } return(fields); }
/// <summary> /// Will ensure all Structured Fields in the file are in a place (container) that they should not be, architecturally. /// All error messages are added to the list of validation issues /// Object and container heirarchy can be found in the MO:DCA documentation /// </summary> /// <returns>True if all validation passes</returns> private bool Validates() { _messages = new List <string>(); // Ensure all containers have open/close tags foreach (Container c in Fields.Where(f => f.LowestLevelContainer != null).Select(f => f.LowestLevelContainer).Distinct()) { if (c.Structures.Any() && (c.Structures[0].HexID[1] != 0xA8 || c.Structures.Last().HexID[1] != 0xA9)) { _messages.Add("One or more containers are missing a proper begin and/or end tag."); break; } } // Make sure each begin tag has a container with a matching end tag, and vice versa foreach (StructuredField beginOrEnd in Fields.Where(f => f.HexID[1] == 0xA8 || f.HexID[1] == 0xA9)) { if (beginOrEnd.LowestLevelContainer == null || !beginOrEnd.LowestLevelContainer.Structures.Any() || // Container and its structures exist (beginOrEnd.HexID[1] == 0xA8 && beginOrEnd.LowestLevelContainer.Structures[0] != beginOrEnd) || // If begin, first structure is itself (beginOrEnd.HexID[1] == 0xA9 && beginOrEnd.LowestLevelContainer.Structures.Last() != beginOrEnd) || // If end, last structure is itself beginOrEnd.LowestLevelContainer.Structures[0].HexID[2] != beginOrEnd.LowestLevelContainer.Structures.Last().HexID[2]) // Begin/end are same type { _messages.Add($"One or more begin/end tags are not enveloped in a proper container."); break; } } // Validate each field's positioning in the defined architecture (skip end tags and NOPs foreach (StructuredField field in Fields.Where(f => f.GetType() != typeof(StructuredFields.NOP) && f.HexID[1] != 0xA9)) { Type fieldType = field.GetType(); StructuredField parentField = (StructuredField)(field.HexID[1] == 0xA8 ? field.ParentContainer?.Structures[0] : field.LowestLevelContainer?.Structures[0]); Type parentType = parentField?.GetType(); if (parentType == null) { // If this is a print file, or a type that is a child of print file, ignore. BPF/EPF tags are not necessary List <Type> subPFTypes = Lookups.FieldsParentOptions.Where(f => f.Value.Contains(typeof(BPF))).Select(f => f.Key).ToList(); if (fieldType != typeof(BPF) && !subPFTypes.Contains(fieldType)) { _messages.Add($"A {field.Abbreviation} field has no parent container."); } } else { // Verify the existing parent container type (if begin tag), or lowest container type is in the list of accepted parent objects if (Lookups.FieldsParentOptions.ContainsKey(fieldType)) { if (!Lookups.FieldsParentOptions[fieldType].Contains(parentType)) { _messages.Add($"A {field.Abbreviation} field has an incorrect parent container of type {parentField.Abbreviation}. " + $"Accepted types are: {string.Join(", ", Lookups.FieldsParentOptions[fieldType].Select(t => t.Name))}."); } } // Disable the "missing lookup" validation - we might not be able to cover every scenario //else // _validationMessages.Add($"Field type {field.Abbreviation} has no heirarchy information in the lookup table."); } } return(!_messages.Any()); }