Files
Disco/Disco.Services/Documents/DocumentUniqueIdentifier.cs
T
2022-12-04 13:47:23 +11:00

460 lines
18 KiB
C#

using Disco.Data.Repository;
using Disco.Models.Repository;
using Disco.Services.Interop.ActiveDirectory;
using Newtonsoft.Json;
using System;
using System.IO;
using System.Text;
namespace Disco.Services.Documents
{
public class DocumentUniqueIdentifier
{
private const int CurrentVersion = 2;
private const byte MagicNumber = 0xC4;
private DiscoDataContext database;
public int Version { get; private set; }
public short DeploymentChecksum { get; private set; }
public string DocumentTemplateId { get; private set; }
public string TargetId { get; private set; }
public string CreatorId { get; private set; }
public DateTime TimeStamp { get; private set; }
public int PageIndex { get; private set; }
private DocumentTemplate documentTemplate;
private AttachmentTypes? attachmentType;
private IAttachmentTarget target;
private User creator;
public DocumentTemplate DocumentTemplate
{
get
{
if (documentTemplate == null && !string.IsNullOrWhiteSpace(DocumentTemplateId) && !DocumentTemplateId.StartsWith("--", StringComparison.Ordinal))
{
documentTemplate = database.DocumentTemplates.Find(DocumentTemplateId);
if (documentTemplate != null)
{
attachmentType = documentTemplate.AttachmentType;
DocumentTemplateId = documentTemplate.Id;
}
}
return documentTemplate;
}
}
public string DocumentGroupingId
{
get
{
// Unique identifier to distinguish this document from others (but keep pages together)
return $"{TimeStamp.Ticks}|{TargetId}|{DocumentTemplateId}|{CreatorId}|{Version}|{DeploymentChecksum}";
}
}
public AttachmentTypes? AttachmentType
{
get
{
if (!attachmentType.HasValue)
{
if (DocumentTemplateId.StartsWith("--", StringComparison.Ordinal))
{
if (DocumentTemplateId.Equals("--DEVICE", StringComparison.Ordinal))
{
attachmentType = AttachmentTypes.Device;
}
else if (DocumentTemplateId.Equals("--JOB", StringComparison.Ordinal))
{
attachmentType = AttachmentTypes.Job;
}
else if (DocumentTemplateId.Equals("--USER", StringComparison.Ordinal))
{
attachmentType = AttachmentTypes.User;
}
}
else
{
var dt = DocumentTemplate;
if (dt != null)
{
attachmentType = dt.AttachmentType;
}
}
}
return attachmentType;
}
}
public IAttachmentTarget Target
{
get
{
if (target == null)
{
switch (AttachmentType)
{
case AttachmentTypes.Device:
target = database.Devices.Find(TargetId);
break;
case AttachmentTypes.Job:
target = database.Jobs.Find(int.Parse(TargetId));
break;
case AttachmentTypes.User:
target = database.Users.Find(ActiveDirectory.ParseDomainAccountId(TargetId));
break;
default:
throw new ArgumentException("Unexpected Attachment Type", nameof(AttachmentType));
}
if (target != null)
{
TargetId = target.AttachmentReferenceId;
}
}
return target;
}
}
public User Creator
{
get
{
if (creator == null)
{
creator = database.Users.Find(ActiveDirectory.ParseDomainAccountId(CreatorId));
if (creator != null)
{
CreatorId = creator.UserId;
}
}
return creator;
}
}
private DocumentUniqueIdentifier(DiscoDataContext Database, int Version, short DeploymentChecksum, string DocumentTemplateId, string TargetId, string CreatorId, DateTime TimeStamp, int PageIndex, AttachmentTypes? AttachmentType)
{
database = Database;
this.Version = Version;
this.DeploymentChecksum = DeploymentChecksum;
this.DocumentTemplateId = DocumentTemplateId;
attachmentType = AttachmentType;
this.TargetId = TargetId;
this.CreatorId = ActiveDirectory.ParseDomainAccountId(CreatorId);
this.TimeStamp = TimeStamp;
this.PageIndex = PageIndex;
}
public string ToQRCodeString()
{
return $"Disco|1|{DocumentTemplate.Id}|{TargetId}|{CreatorId}|{TimeStamp:s}|{PageIndex}";
}
public string ToJson()
{
var builder = new StringBuilder();
using (var stringWriter = new StringWriter(builder))
{
using (var writer = new JsonTextWriter(stringWriter))
{
writer.Formatting = Formatting.Indented;
writer.WriteStartObject();
writer.WritePropertyName("version");
writer.WriteValue(CurrentVersion);
writer.WritePropertyName("attachmentType");
writer.WriteValue(AttachmentType.Value.ToString());
writer.WritePropertyName("documentTemplateId");
writer.WriteValue(DocumentTemplateId);
writer.WritePropertyName("targetId");
writer.WriteValue(TargetId);
writer.WritePropertyName("creatorId");
writer.WriteValue(CreatorId);
writer.WritePropertyName("timestamp");
writer.WriteValue(TimeStamp);
writer.WritePropertyName("pageIndex");
writer.WriteValue(PageIndex);
writer.WriteEndObject();
}
}
return builder.ToString();
}
public byte[] ToQRCodeBytes()
{
// Byte | Meaning
// 0 | magic number = 0xC4
// 1 | bits 0-3 = version;
// | 4 = flag: has document template id
// | 5-6 = (01 = device attachment, 10 = job attachment, 11 = user attachment)
// | 7 = not used
// 2-3 | deployment checksum (int16)
// 4-7 | timestamp (uint32 unix epoch)
// 8-9 | page index (uint16)
// 10 | creator id encoded
// ? | target id encoded
// ? | document template id encoded (optional based on flag)
const int version = 2;
// encode variable-length parameters first
// encode creator id (strip default domain)
var creatorIdBytes = this.BinaryEncode(ActiveDirectory.FriendlyAccountId(CreatorId));
// encode target id
byte[] targetIdBytes;
if (AttachmentType.HasValue && AttachmentType.Value == AttachmentTypes.User)
{
// strip default domain from user targetted attachments
targetIdBytes = this.BinaryEncode(ActiveDirectory.FriendlyAccountId(TargetId));
}
else
{
targetIdBytes = this.BinaryEncode(TargetId);
}
byte[] documentTemplateIdBytes = null;
if (DocumentTemplateId != null)
{
documentTemplateIdBytes = this.BinaryEncode(DocumentTemplateId);
}
var result = new byte[10 + creatorIdBytes.Length + targetIdBytes.Length + (documentTemplateIdBytes?.Length ?? 0)];
// write magic number
result[0] = MagicNumber;
// write version
result[1] = (version << 4);
// write 'has document template id' flag
if (documentTemplateIdBytes != null)
{
result[1] |= 0x8; // 0000 1000
}
// write attachment type
switch (AttachmentType)
{
case AttachmentTypes.Device:
result[1] |= 0x2; // 0000 0010 - 01
break;
case AttachmentTypes.Job:
result[1] |= 0x4; // 0000 0100 - 10
break;
case AttachmentTypes.User:
result[1] |= 0x6; // 0000 0110 - 11
break;
}
// write deployment checksum
result[2] = (byte)(DeploymentChecksum >> 8);
result[3] = (byte)DeploymentChecksum;
// write timestamp
var timestamp = (uint)(TimeStamp.ToUniversalTime().Subtract(DateTime.FromFileTimeUtc(116444736000000000L)).Ticks / TimeSpan.TicksPerSecond);
result[4] = (byte)(timestamp >> 24);
result[5] = (byte)(timestamp >> 16);
result[6] = (byte)(timestamp >> 8);
result[7] = (byte)timestamp;
// write page index
result[8] = (byte)(PageIndex >> 8);
result[9] = (byte)PageIndex;
// write creator id
creatorIdBytes.CopyTo(result, 10);
// write target id
targetIdBytes.CopyTo(result, 10 + creatorIdBytes.Length);
// write document template id
if (documentTemplateIdBytes != null)
{
documentTemplateIdBytes.CopyTo(result, 10 + creatorIdBytes.Length + targetIdBytes.Length);
}
return result;
}
public static DocumentUniqueIdentifier Create(DiscoDataContext Database, DocumentTemplate DocumentTemplate, IAttachmentTarget Target, User Creator, DateTime TimeStamp, int PageIndex)
{
var deploymentChecksum = Database.DiscoConfiguration.DeploymentChecksum;
var documentTemplateId = DocumentTemplate.Id;
var targetId = Target.AttachmentReferenceId;
var creatorId = Creator.UserId;
var attachmentType = DocumentTemplate.AttachmentType;
var identifier = new DocumentUniqueIdentifier(Database, CurrentVersion, deploymentChecksum, documentTemplateId, targetId, creatorId, TimeStamp, PageIndex, attachmentType);
identifier.documentTemplate = DocumentTemplate;
identifier.target = Target;
identifier.creator = Creator;
return identifier;
}
public static DocumentUniqueIdentifier Create(DiscoDataContext Database, string DocumentTemplateId, string TargetId, string CreatorId, DateTime TimeStamp, int PageIndex)
{
var deploymentChecksum = Database.DiscoConfiguration.DeploymentChecksum;
return new DocumentUniqueIdentifier(Database, CurrentVersion, deploymentChecksum, DocumentTemplateId, TargetId, CreatorId, TimeStamp, PageIndex, null);
}
public static DocumentUniqueIdentifier Create(DiscoDataContext Database, string DocumentTemplateId, AttachmentTypes attachmentType, string TargetId, string CreatorId, DateTime TimeStamp, int PageIndex)
{
var deploymentChecksum = Database.DiscoConfiguration.DeploymentChecksum;
return new DocumentUniqueIdentifier(Database, CurrentVersion, deploymentChecksum, DocumentTemplateId, TargetId, CreatorId, TimeStamp, PageIndex, attachmentType);
}
public static DocumentUniqueIdentifier Parse(DiscoDataContext Database, byte[] UniqueIdentifier)
{
DocumentUniqueIdentifier identifier;
if (TryParse(Database, UniqueIdentifier, out identifier))
{
return identifier;
}
else
{
throw new FormatException("Invalid Document Unique Identifier");
}
}
public static DocumentUniqueIdentifier Parse(DiscoDataContext Database, string UniqueIdentifier)
{
DocumentUniqueIdentifier identifier;
if (TryParse(Database, UniqueIdentifier, out identifier))
{
return identifier;
}
else
{
throw new FormatException("Invalid Document Unique Identifier");
}
}
public static bool TryParse(DiscoDataContext Database, string UniqueIdentifier, out DocumentUniqueIdentifier Identifier)
{
if (IsDocumentUniqueIdentifier(UniqueIdentifier))
{
var components = UniqueIdentifier.Split('|');
// Version 0, Version 1 Handling
if ((components.Length == 7 || components.Length == 6) &&
(components[1].Equals("1", StringComparison.Ordinal) || components[1].Equals("AT", StringComparison.OrdinalIgnoreCase)))
{
var documentTemplateId = components[2];
var targetId = components[3];
var creatorId = components[4];
var timeStamp = DateTime.Parse(components[5]);
var page = 0;
if (components.Length == 7)
{
page = int.Parse(components[6]);
}
Identifier = new DocumentUniqueIdentifier(Database, 1, -1, documentTemplateId, targetId, creatorId, timeStamp, page, null);
return true;
}
}
Identifier = null;
return false;
}
public static bool TryParse(DiscoDataContext Database, byte[] Data, out DocumentUniqueIdentifier Identifier)
{
if (IsDocumentUniqueIdentifier(Data))
{
// first 4 bit indicate version
var version = Data[1] >> 4;
// Version 2
if (version == 2)
{
// Byte | Meaning
// 0 | magic number = 0xC4
// 1 | bits 0-3 = version;
// | 4 = flag: has document template id
// | 5-6 = (01 = device attachment, 10 = job attachment, 11 = user attachment)
// | 7 = not used
// 2-3 | deployment checksum (int16)
// 4-7 | timestamp (uint32 unix epoch)
// 8-9 | page index (uint16)
// 10 | creator id encoded
// ? | target id encoded
// ? | document template id encoded (optional based on flag)
// read flags
var flags = Data[1] & 0x0F;
// read deployment checksum
short deploymentChecksum = (short)((Data[2] << 8) | Data[3]);
// read timestamp
var timeStampEpoch = ((long)Data[4] << 24) |
((long)Data[5] << 16) |
((long)Data[6] << 8) |
Data[7];
// write page index
var pageIndex = (Data[8] << 8) | Data[9];
var position = 10;
// write creator id
var creatorId = DocumentUniqueIdentifierExtensions.BinaryDecode(Data, position, out position);
// write target id
var targetId = DocumentUniqueIdentifierExtensions.BinaryDecode(Data, position, out position);
// write document template id
string documentTemplateId = null;
// Has document template id flag
if ((flags & 0x8) == 0x8)
{
documentTemplateId = DocumentUniqueIdentifierExtensions.BinaryDecode(Data, position, out position);
}
AttachmentTypes? attachmentType = null;
switch (flags & 0x6)
{
case 0x2:
attachmentType = AttachmentTypes.Device;
break;
case 0x4:
attachmentType = AttachmentTypes.Job;
break;
case 0x6:
attachmentType = AttachmentTypes.User;
break;
}
var timeStamp = DateTime.FromFileTimeUtc(116444736000000000L).AddTicks(TimeSpan.TicksPerSecond * timeStampEpoch).ToLocalTime();
Identifier = new DocumentUniqueIdentifier(Database, version, deploymentChecksum, documentTemplateId, targetId, creatorId, timeStamp, pageIndex, attachmentType);
return true;
}
}
Identifier = null;
return false;
}
public static bool IsDocumentUniqueIdentifier(string Identifier)
{
return Identifier != null && Identifier.StartsWith("Disco|", StringComparison.OrdinalIgnoreCase);
}
public static bool IsDocumentUniqueIdentifier(byte[] Identifier)
{
// Identifier[0] = 0xC4; Magic number to identify Disco ICT QR Codes
return Identifier != null && Identifier.Length > 2 && Identifier[0] == MagicNumber;
}
}
}