Decompiled source of RunicPortals v1.1.3

RunicPortals.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Resources;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using RunicPermissions.Contracts;
using RunicPermissions.Groups;
using RunicPortals.Api;
using RunicPortals.Core;
using RunicPortals.Integration;
using Splatform;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("Runic Portals")]
[assembly: AssemblyDescription("Permission-aware deterministic portal directories and fail-closed universal routing.")]
[assembly: AssemblyCompany("Chazman")]
[assembly: AssemblyProduct("Runic Portals")]
[assembly: AssemblyCopyright("Copyright © 2026 Chazman")]
[assembly: ComVisible(false)]
[assembly: Guid("10c99bf6-a049-48d3-bcb3-4582c10180db")]
[assembly: AssemblyFileVersion("1.1.3.0")]
[assembly: AssemblyInformationalVersion("1.1.3")]
[assembly: InternalsVisibleTo("RunicPortals.Tests")]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyVersion("1.1.3.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace RunicPermissions.Contracts
{
	public sealed class StableIdentity : IEquatable<StableIdentity>, IComparable<StableIdentity>
	{
		public const int MaximumAuthorityLength = 64;

		public const int MaximumSubjectIdLength = 256;

		public string Authority { get; }

		public string SubjectId { get; }

		public string CanonicalKey { get; }

		public StableIdentity(string authority, string subjectId)
		{
			string value = NormalizeAuthority(authority);
			string value2 = NormalizeSubject(subjectId);
			if (!IsValidAuthority(value))
			{
				throw new ArgumentException("Identity authority must contain only ASCII letters, digits, '.', '_', or '-'.", "authority");
			}
			if (!IsValidSubject(value2))
			{
				throw new ArgumentException("Identity subject ID is empty, too long, or contains control characters.", "subjectId");
			}
			Authority = value;
			SubjectId = value2;
			CanonicalKey = Authority + ":" + Uri.EscapeDataString(SubjectId);
		}

		public static bool TryCreate(string authority, string subjectId, out StableIdentity identity)
		{
			try
			{
				identity = new StableIdentity(authority, subjectId);
				return true;
			}
			catch (ArgumentException)
			{
				identity = null;
				return false;
			}
		}

		public bool Equals(StableIdentity other)
		{
			if (other != null && string.Equals(Authority, other.Authority, StringComparison.Ordinal))
			{
				return string.Equals(SubjectId, other.SubjectId, StringComparison.Ordinal);
			}
			return false;
		}

		public override bool Equals(object obj)
		{
			return Equals(obj as StableIdentity);
		}

		public override int GetHashCode()
		{
			return (StringComparer.Ordinal.GetHashCode(Authority) * 397) ^ StringComparer.Ordinal.GetHashCode(SubjectId);
		}

		public int CompareTo(StableIdentity other)
		{
			if (other == null)
			{
				return 1;
			}
			int num = string.Compare(Authority, other.Authority, StringComparison.Ordinal);
			if (num == 0)
			{
				return string.Compare(SubjectId, other.SubjectId, StringComparison.Ordinal);
			}
			return num;
		}

		public override string ToString()
		{
			return CanonicalKey;
		}

		private static string NormalizeAuthority(string value)
		{
			return (value ?? string.Empty).Trim().ToLowerInvariant();
		}

		private static string NormalizeSubject(string value)
		{
			return (value ?? string.Empty).Trim();
		}

		private static bool IsValidAuthority(string value)
		{
			if (value.Length == 0 || value.Length > 64)
			{
				return false;
			}
			foreach (char c in value)
			{
				if ((c < 'a' || c > 'z') && (c < '0' || c > '9') && c != '.' && c != '_' && c != '-')
				{
					return false;
				}
			}
			return true;
		}

		private static bool IsValidSubject(string value)
		{
			if (value.Length == 0 || value.Length > 256)
			{
				return false;
			}
			for (int i = 0; i < value.Length; i++)
			{
				if (char.IsControl(value[i]))
				{
					return false;
				}
			}
			return true;
		}
	}
	public enum IdentityResolutionStatus
	{
		Verified,
		Missing,
		Ambiguous,
		Stale
	}
	public sealed class IdentityClaim
	{
		public StableIdentity Identity { get; }

		public string DisplayNameSnapshot { get; }

		public IdentityResolutionStatus Status { get; }

		public bool IsVerified
		{
			get
			{
				if (Status == IdentityResolutionStatus.Verified)
				{
					return Identity != null;
				}
				return false;
			}
		}

		public IdentityClaim(StableIdentity identity, string displayNameSnapshot, IdentityResolutionStatus status)
		{
			Identity = identity;
			DisplayNameSnapshot = displayNameSnapshot ?? string.Empty;
			Status = status;
		}

		public static IdentityClaim Verified(StableIdentity identity, string displayNameSnapshot = "")
		{
			return new IdentityClaim(identity, displayNameSnapshot, IdentityResolutionStatus.Verified);
		}
	}
}
namespace RunicPermissions.Groups
{
	public enum GroupWorldReadState
	{
		Missing,
		Ready,
		Corrupt,
		EvidenceConflict,
		Unavailable
	}
	public enum GroupWorldCommitState
	{
		Committed,
		RevisionConflict,
		Corrupt,
		EvidenceConflict,
		Unavailable,
		InvalidReplacement
	}
	public sealed class GroupWorldReadResult
	{
		public GroupWorldReadState State { get; }

		public string ReasonCode { get; }

		public GroupCatalog Catalog { get; }

		public string ExactSha256 { get; }

		internal GroupWorldReadResult(GroupWorldReadState state, string reasonCode, GroupCatalog catalog, string exactSha256)
		{
			State = state;
			ReasonCode = reasonCode ?? string.Empty;
			Catalog = catalog;
			ExactSha256 = exactSha256 ?? string.Empty;
		}
	}
	public sealed class GroupWorldCommitResult
	{
		public GroupWorldCommitState State { get; }

		public string ReasonCode { get; }

		public GroupWorldReadResult Current { get; }

		public bool Success => State == GroupWorldCommitState.Committed;

		internal GroupWorldCommitResult(GroupWorldCommitState state, string reasonCode, GroupWorldReadResult current)
		{
			State = state;
			ReasonCode = reasonCode ?? string.Empty;
			Current = current;
		}
	}
	public interface IGroupWorldStore
	{
		GroupWorldReadResult Read(string worldScope);

		GroupWorldCommitResult TryCommit(string worldScope, long expectedCatalogRevision, GroupCatalog replacement);
	}
	internal sealed class CompatibleGroupWorldStore : IGroupWorldStore
	{
		private readonly struct Paths
		{
			internal string Primary { get; }

			internal string Temporary { get; }

			internal string Backup { get; }

			internal string Lock { get; }

			internal Paths(string primary, string temporary, string backup, string @lock)
			{
				Primary = primary;
				Temporary = temporary;
				Backup = backup;
				Lock = @lock;
			}
		}

		private readonly string _root;

		internal CompatibleGroupWorldStore(string root)
		{
			if (string.IsNullOrWhiteSpace(root))
			{
				throw new ArgumentException("A storage root is required.", "root");
			}
			_root = Path.GetFullPath(root).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
			if (string.Equals(_root, Path.GetPathRoot(_root), PathComparison()))
			{
				throw new ArgumentException("A filesystem root cannot be used.", "root");
			}
		}

		public GroupWorldReadResult Read(string worldScope)
		{
			try
			{
				Paths paths = Resolve(worldScope);
				if (!Directory.Exists(_root))
				{
					return Missing();
				}
				using (Acquire(paths.Lock))
				{
					return ReadLocked(paths, worldScope);
				}
			}
			catch (ArgumentException)
			{
				return Unavailable("group-world-scope-invalid");
			}
			catch (Exception exception) when (StorageFailure(exception))
			{
				return Unavailable("group-store-read-unavailable");
			}
		}

		public GroupWorldCommitResult TryCommit(string worldScope, long expectedCatalogRevision, GroupCatalog replacement)
		{
			if (expectedCatalogRevision < 0 || replacement == null || replacement.Revision != expectedCatalogRevision + 1)
			{
				return Commit(GroupWorldCommitState.InvalidReplacement, "group-store-replacement-invalid", null);
			}
			try
			{
				Paths paths = Resolve(worldScope);
				Directory.CreateDirectory(_root);
				using (Acquire(paths.Lock))
				{
					GroupWorldReadResult groupWorldReadResult = ReadLocked(paths, worldScope);
					if (groupWorldReadResult.State == GroupWorldReadState.Corrupt)
					{
						return Commit(GroupWorldCommitState.Corrupt, groupWorldReadResult.ReasonCode, groupWorldReadResult);
					}
					if (groupWorldReadResult.State == GroupWorldReadState.EvidenceConflict)
					{
						return Commit(GroupWorldCommitState.EvidenceConflict, groupWorldReadResult.ReasonCode, groupWorldReadResult);
					}
					if (groupWorldReadResult.State == GroupWorldReadState.Unavailable)
					{
						return Commit(GroupWorldCommitState.Unavailable, groupWorldReadResult.ReasonCode, groupWorldReadResult);
					}
					if (((groupWorldReadResult.State == GroupWorldReadState.Missing) ? 0 : groupWorldReadResult.Catalog.Revision) != expectedCatalogRevision)
					{
						return Commit(GroupWorldCommitState.RevisionConflict, "group-store-revision-conflict", groupWorldReadResult);
					}
					if (!SameLedger((groupWorldReadResult.State == GroupWorldReadState.Missing) ? GroupCommandLedger.Empty : groupWorldReadResult.Catalog.CommandLedger, replacement.CommandLedger))
					{
						return Commit(GroupWorldCommitState.InvalidReplacement, "group-store-command-ledger-mismatch", groupWorldReadResult);
					}
					return Publish(paths, worldScope, replacement, groupWorldReadResult);
				}
			}
			catch (ArgumentException)
			{
				return Commit(GroupWorldCommitState.InvalidReplacement, "group-store-replacement-invalid", null);
			}
			catch (Exception exception) when (StorageFailure(exception))
			{
				return Commit(GroupWorldCommitState.Unavailable, "group-store-commit-unavailable", null);
			}
		}

		private GroupWorldCommitResult Publish(Paths paths, string worldScope, GroupCatalog replacement, GroupWorldReadResult current)
		{
			if (File.Exists(paths.Temporary) || File.Exists(paths.Backup))
			{
				return Commit(GroupWorldCommitState.EvidenceConflict, "group-store-nonprimary-evidence", current);
			}
			byte[] array = GroupCatalogCodec.Encode(worldScope, replacement);
			using (FileStream fileStream = new FileStream(paths.Temporary, FileMode.CreateNew, FileAccess.Write, FileShare.None, 4096, FileOptions.WriteThrough))
			{
				fileStream.Write(array, 0, array.Length);
				fileStream.Flush(flushToDisk: true);
			}
			byte[] array2 = ReadBounded(paths.Temporary);
			if (!Exact(array, array2) || !GroupCatalogCodec.TryDecode(array2, worldScope, out var catalog, out var _) || catalog.Revision != replacement.Revision)
			{
				return Commit(GroupWorldCommitState.Unavailable, "group-store-staged-readback-failed", current);
			}
			if (File.Exists(paths.Primary))
			{
				File.Replace(paths.Temporary, paths.Primary, null, ignoreMetadataErrors: true);
			}
			else
			{
				File.Move(paths.Temporary, paths.Primary);
			}
			GroupWorldReadResult groupWorldReadResult = ReadPrimary(paths.Primary, worldScope);
			if (groupWorldReadResult.State != GroupWorldReadState.Ready || groupWorldReadResult.Catalog.Revision != replacement.Revision)
			{
				return Commit(GroupWorldCommitState.Unavailable, "group-store-commit-readback-failed", groupWorldReadResult);
			}
			return Commit(GroupWorldCommitState.Committed, "group-store-committed", groupWorldReadResult);
		}

		private GroupWorldReadResult ReadLocked(Paths paths, string worldScope)
		{
			if (File.Exists(paths.Temporary) || File.Exists(paths.Backup))
			{
				return new GroupWorldReadResult(GroupWorldReadState.EvidenceConflict, "group-store-nonprimary-evidence", null, string.Empty);
			}
			if (!File.Exists(paths.Primary))
			{
				return Missing();
			}
			return ReadPrimary(paths.Primary, worldScope);
		}

		private static GroupWorldReadResult ReadPrimary(string path, string worldScope)
		{
			byte[] bytes = ReadBounded(path);
			if (!GroupCatalogCodec.TryDecode(bytes, worldScope, out var catalog, out var reason))
			{
				return new GroupWorldReadResult(GroupWorldReadState.Corrupt, reason, null, GroupCatalogCodec.ComputeSha256(bytes));
			}
			return new GroupWorldReadResult(GroupWorldReadState.Ready, "group-store-ready", catalog, GroupCatalogCodec.ComputeSha256(bytes));
		}

		private Paths Resolve(string worldScope)
		{
			string s = GroupIdentity.RequireWorldScope(worldScope);
			byte[] array;
			using (SHA256 sHA = SHA256.Create())
			{
				array = sHA.ComputeHash(Encoding.UTF8.GetBytes(s));
			}
			StringBuilder stringBuilder = new StringBuilder(array.Length * 2);
			for (int i = 0; i < array.Length; i++)
			{
				stringBuilder.Append(array[i].ToString("x2"));
			}
			string text = Path.Combine(_root, stringBuilder?.ToString() + ".groups");
			if (!string.Equals(Path.GetDirectoryName(text), _root, PathComparison()))
			{
				throw new ArgumentException("The group path escaped its root.", "worldScope");
			}
			return new Paths(text, text + ".tmp", text + ".bak", text + ".lock");
		}

		private static FileStream Acquire(string path)
		{
			return new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None, 1, FileOptions.WriteThrough);
		}

		private static byte[] ReadBounded(string path)
		{
			using FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.None, 4096, FileOptions.SequentialScan);
			if (fileStream.Length < 1 || fileStream.Length > 8388608)
			{
				throw new InvalidDataException("The group catalog length is invalid.");
			}
			byte[] array = new byte[(int)fileStream.Length];
			int num;
			for (int i = 0; i < array.Length; i += num)
			{
				num = fileStream.Read(array, i, array.Length - i);
				if (num <= 0)
				{
					throw new EndOfStreamException();
				}
			}
			return array;
		}

		private static bool SameLedger(GroupCommandLedger left, GroupCommandLedger right)
		{
			if (left != null && right != null && left.Epoch == right.Epoch && left.NextSequence == right.NextSequence && left.MinimumAcceptedSequence == right.MinimumAcceptedSequence && left.Issues.Count == right.Issues.Count)
			{
				return left.Receipts.Count == right.Receipts.Count;
			}
			return false;
		}

		private static bool Exact(byte[] left, byte[] right)
		{
			if (left == null || right == null || left.Length != right.Length)
			{
				return false;
			}
			int num = 0;
			for (int i = 0; i < left.Length; i++)
			{
				num |= left[i] ^ right[i];
			}
			return num == 0;
		}

		private static bool StorageFailure(Exception exception)
		{
			if (!(exception is IOException) && !(exception is UnauthorizedAccessException) && !(exception is NotSupportedException))
			{
				return exception is SecurityException;
			}
			return true;
		}

		private static StringComparison PathComparison()
		{
			if (Path.DirectorySeparatorChar != '\\')
			{
				return StringComparison.Ordinal;
			}
			return StringComparison.OrdinalIgnoreCase;
		}

		private static GroupWorldReadResult Missing()
		{
			return new GroupWorldReadResult(GroupWorldReadState.Missing, "group-store-missing", GroupCatalog.Empty, string.Empty);
		}

		private static GroupWorldReadResult Unavailable(string reason)
		{
			return new GroupWorldReadResult(GroupWorldReadState.Unavailable, reason, null, string.Empty);
		}

		private static GroupWorldCommitResult Commit(GroupWorldCommitState state, string reason, GroupWorldReadResult current)
		{
			return new GroupWorldCommitResult(state, reason, current);
		}
	}
	public enum GroupRole : byte
	{
		Member = 1,
		Officer,
		Owner
	}
	public static class GroupLimits
	{
		public const int MaximumGroups = 256;

		public const int MaximumMembersPerGroup = 256;

		public const int MaximumInvitationsPerGroup = 256;

		public const int MaximumGroupsPerIdentity = 64;

		public const int MaximumRetiredGroupIds = 4096;

		public const int MaximumDisplayNameUtf8Bytes = 64;

		public const int MaximumWorldScopeUtf8Bytes = 128;

		public const int MaximumCatalogBytes = 8388608;

		public static readonly TimeSpan MaximumInvitationLifetime = TimeSpan.FromDays(30.0);
	}
	public static class GroupIdentity
	{
		public static string ToCanonicalId(Guid groupId)
		{
			if (groupId == Guid.Empty)
			{
				throw new ArgumentException("A nonempty group UUID is required.", "groupId");
			}
			return groupId.ToString("N");
		}

		public static bool TryParseCanonicalId(string value, out Guid groupId)
		{
			groupId = Guid.Empty;
			if (value != null && value.Length == 32 && Guid.TryParseExact(value, "N", out groupId) && groupId != Guid.Empty)
			{
				return string.Equals(value, groupId.ToString("N"), StringComparison.Ordinal);
			}
			return false;
		}

		public static bool IsCanonicalId(string value)
		{
			Guid groupId;
			return TryParseCanonicalId(value, out groupId);
		}

		internal static string RequireDisplayName(string value)
		{
			if (value == null || value.Length == 0 || !string.Equals(value, value.Trim(), StringComparison.Ordinal) || !value.IsNormalized(NormalizationForm.FormC) || Encoding.UTF8.GetByteCount(value) > 64)
			{
				throw new ArgumentException("A nonempty, trimmed, NFC group display name within 64 UTF-8 bytes is required.", "value");
			}
			for (int i = 0; i < value.Length; i++)
			{
				if (char.IsControl(value[i]))
				{
					throw new ArgumentException("Group display names cannot contain control characters.", "value");
				}
			}
			return value;
		}

		internal static string RequireWorldScope(string value)
		{
			if (value == null || value.Length == 0 || value.Length > 128 || Encoding.UTF8.GetByteCount(value) > 128)
			{
				throw new ArgumentException("A bounded canonical world scope is required.", "value");
			}
			foreach (char c in value)
			{
				if ((c < 'a' || c > 'z') && (c < '0' || c > '9') && c != '.' && c != '_' && c != '-')
				{
					throw new ArgumentException("The world scope is not canonical.", "value");
				}
			}
			return value;
		}
	}
	public sealed class GroupMember
	{
		public StableIdentity Identity { get; }

		public GroupRole Role { get; }

		public long JoinedRevision { get; }

		public GroupMember(StableIdentity identity, GroupRole role, long joinedRevision)
		{
			Identity = identity ?? throw new ArgumentNullException("identity");
			if (!Enum.IsDefined(typeof(GroupRole), role))
			{
				throw new ArgumentOutOfRangeException("role");
			}
			if (joinedRevision < 1)
			{
				throw new ArgumentOutOfRangeException("joinedRevision");
			}
			Role = role;
			JoinedRevision = joinedRevision;
		}
	}
	public sealed class GroupInvitation
	{
		public StableIdentity Invitee { get; }

		public StableIdentity InvitedBy { get; }

		public long IssuedRevision { get; }

		public long ExpiresUtcTicks { get; }

		public GroupInvitation(StableIdentity invitee, StableIdentity invitedBy, long issuedRevision, long expiresUtcTicks)
		{
			Invitee = invitee ?? throw new ArgumentNullException("invitee");
			InvitedBy = invitedBy ?? throw new ArgumentNullException("invitedBy");
			if (issuedRevision < 1)
			{
				throw new ArgumentOutOfRangeException("issuedRevision");
			}
			if (expiresUtcTicks <= 0)
			{
				throw new ArgumentOutOfRangeException("expiresUtcTicks");
			}
			IssuedRevision = issuedRevision;
			ExpiresUtcTicks = expiresUtcTicks;
		}

		public bool IsExpired(long nowUtcTicks)
		{
			if (nowUtcTicks > 0)
			{
				return nowUtcTicks >= ExpiresUtcTicks;
			}
			return true;
		}
	}
	public sealed class GroupRecord
	{
		private readonly GroupMember[] _members;

		private readonly GroupInvitation[] _invitations;

		private readonly ReadOnlyCollection<GroupMember> _memberView;

		private readonly ReadOnlyCollection<GroupInvitation> _invitationView;

		public Guid Id { get; }

		public string IdText { get; }

		public string DisplayName { get; }

		public long Revision { get; }

		public IReadOnlyList<GroupMember> Members => _memberView;

		public IReadOnlyList<GroupInvitation> Invitations => _invitationView;

		public GroupRecord(Guid id, string displayName, long revision, IEnumerable<GroupMember> members, IEnumerable<GroupInvitation> invitations = null)
		{
			Id = id;
			IdText = GroupIdentity.ToCanonicalId(id);
			DisplayName = GroupIdentity.RequireDisplayName(displayName);
			if (revision < 1)
			{
				throw new ArgumentOutOfRangeException("revision");
			}
			Revision = revision;
			_members = CopyMembers(members, revision);
			_invitations = CopyInvitations(invitations, revision, _members);
			_memberView = Array.AsReadOnly(_members);
			_invitationView = Array.AsReadOnly(_invitations);
		}

		public bool TryGetMember(StableIdentity identity, out GroupMember member)
		{
			member = null;
			if (identity == null)
			{
				return false;
			}
			int num = FindMember(_members, identity);
			if (num < 0)
			{
				return false;
			}
			member = _members[num];
			return true;
		}

		public bool TryGetInvitation(StableIdentity identity, out GroupInvitation invitation)
		{
			invitation = null;
			if (identity == null)
			{
				return false;
			}
			int num = FindInvitation(_invitations, identity);
			if (num < 0)
			{
				return false;
			}
			invitation = _invitations[num];
			return true;
		}

		internal GroupRecord Rename(string displayName)
		{
			return new GroupRecord(Id, displayName, checked(Revision + 1), _members, _invitations);
		}

		internal GroupRecord Invite(StableIdentity actor, StableIdentity invitee, long expiresUtcTicks)
		{
			checked
			{
				List<GroupInvitation> invitations = new List<GroupInvitation>(_invitations)
				{
					new GroupInvitation(invitee, actor, Revision + 1, expiresUtcTicks)
				};
				return new GroupRecord(Id, DisplayName, Revision + 1, _members, invitations);
			}
		}

		internal GroupRecord CancelInvitation(StableIdentity invitee)
		{
			GroupInvitation[] invitations = _invitations.Where((GroupInvitation value) => !value.Invitee.Equals(invitee)).ToArray();
			return new GroupRecord(Id, DisplayName, checked(Revision + 1), _members, invitations);
		}

		internal GroupRecord Accept(StableIdentity invitee)
		{
			long num = checked(Revision + 1);
			List<GroupMember> members = new List<GroupMember>(_members)
			{
				new GroupMember(invitee, GroupRole.Member, num)
			};
			GroupInvitation[] invitations = _invitations.Where((GroupInvitation value) => !value.Invitee.Equals(invitee)).ToArray();
			return new GroupRecord(Id, DisplayName, num, members, invitations);
		}

		internal GroupRecord RemoveMember(StableIdentity identity)
		{
			GroupMember[] members = _members.Where((GroupMember value) => !value.Identity.Equals(identity)).ToArray();
			return new GroupRecord(Id, DisplayName, checked(Revision + 1), members, _invitations);
		}

		internal GroupRecord SetRole(StableIdentity identity, GroupRole role)
		{
			GroupMember[] array = new GroupMember[_members.Length];
			for (int i = 0; i < _members.Length; i++)
			{
				GroupMember groupMember = _members[i];
				array[i] = (groupMember.Identity.Equals(identity) ? new GroupMember(groupMember.Identity, role, groupMember.JoinedRevision) : groupMember);
			}
			return new GroupRecord(Id, DisplayName, checked(Revision + 1), array, _invitations);
		}

		internal GroupRecord TransferOwnership(StableIdentity owner, StableIdentity successor)
		{
			GroupMember[] array = new GroupMember[_members.Length];
			for (int i = 0; i < _members.Length; i++)
			{
				GroupMember groupMember = _members[i];
				GroupRole groupRole = (groupMember.Identity.Equals(owner) ? GroupRole.Officer : (groupMember.Identity.Equals(successor) ? GroupRole.Owner : groupMember.Role));
				array[i] = ((groupRole == groupMember.Role) ? groupMember : new GroupMember(groupMember.Identity, groupRole, groupMember.JoinedRevision));
			}
			return new GroupRecord(Id, DisplayName, checked(Revision + 1), array, _invitations);
		}

		internal GroupRecord PruneExpired(long nowUtcTicks)
		{
			GroupInvitation[] array = _invitations.Where((GroupInvitation value) => !value.IsExpired(nowUtcTicks)).ToArray();
			if (array.Length != _invitations.Length)
			{
				return new GroupRecord(Id, DisplayName, checked(Revision + 1), _members, array);
			}
			return this;
		}

		private static GroupMember[] CopyMembers(IEnumerable<GroupMember> source, long revision)
		{
			if (source == null)
			{
				throw new ArgumentNullException("source");
			}
			GroupMember[] array = source.ToArray();
			if (array.Length < 1 || array.Length > 256 || array.Any((GroupMember value) => value == null || value.JoinedRevision > revision))
			{
				throw new ArgumentOutOfRangeException("source");
			}
			Array.Sort(array, (GroupMember left, GroupMember right) => left.Identity.CompareTo(right.Identity));
			int num = 0;
			for (int num2 = 0; num2 < array.Length; num2++)
			{
				if (num2 > 0 && array[num2 - 1].Identity.Equals(array[num2].Identity))
				{
					throw new ArgumentException("Group member identities must be unique.", "source");
				}
				if (array[num2].Role == GroupRole.Owner)
				{
					num++;
				}
			}
			if (num != 1)
			{
				throw new ArgumentException("A group requires exactly one Owner.", "source");
			}
			return array;
		}

		private static GroupInvitation[] CopyInvitations(IEnumerable<GroupInvitation> source, long revision, IReadOnlyList<GroupMember> members)
		{
			GroupInvitation[] array = (source ?? Array.Empty<GroupInvitation>()).ToArray();
			if (array.Length > 256 || array.Any((GroupInvitation value) => value == null || value.IssuedRevision > revision))
			{
				throw new ArgumentOutOfRangeException("source");
			}
			Array.Sort(array, (GroupInvitation left, GroupInvitation right) => left.Invitee.CompareTo(right.Invitee));
			for (int num = 0; num < array.Length; num++)
			{
				if (num > 0 && array[num - 1].Invitee.Equals(array[num].Invitee))
				{
					throw new ArgumentException("Group invitation identities must be unique.", "source");
				}
				if (FindMember(members, array[num].Invitee) >= 0)
				{
					throw new ArgumentException("Group invitation membership evidence is invalid.", "source");
				}
			}
			return array;
		}

		internal static int FindMember(IReadOnlyList<GroupMember> values, StableIdentity identity)
		{
			int num = 0;
			int num2 = values.Count - 1;
			while (num <= num2)
			{
				int num3 = num + (num2 - num) / 2;
				int num4 = values[num3].Identity.CompareTo(identity);
				if (num4 == 0)
				{
					return num3;
				}
				if (num4 < 0)
				{
					num = num3 + 1;
				}
				else
				{
					num2 = num3 - 1;
				}
			}
			return -1;
		}

		private static int FindInvitation(IReadOnlyList<GroupInvitation> values, StableIdentity identity)
		{
			int num = 0;
			int num2 = values.Count - 1;
			while (num <= num2)
			{
				int num3 = num + (num2 - num) / 2;
				int num4 = values[num3].Invitee.CompareTo(identity);
				if (num4 == 0)
				{
					return num3;
				}
				if (num4 < 0)
				{
					num = num3 + 1;
				}
				else
				{
					num2 = num3 - 1;
				}
			}
			return -1;
		}
	}
	public sealed class RetiredGroupId
	{
		public Guid Id { get; }

		public string IdText { get; }

		public long DeletedCatalogRevision { get; }

		public RetiredGroupId(Guid id, long deletedCatalogRevision)
		{
			Id = id;
			IdText = GroupIdentity.ToCanonicalId(id);
			if (deletedCatalogRevision < 1)
			{
				throw new ArgumentOutOfRangeException("deletedCatalogRevision");
			}
			DeletedCatalogRevision = deletedCatalogRevision;
		}
	}
	internal static class GroupCommandDurabilityLimits
	{
		internal const int MaximumOutstandingIssues = 256;

		internal const int MaximumRetainedReceipts = 4096;

		internal const int MaximumReasonCharacters = 96;

		internal static readonly TimeSpan MaximumIssueLifetime = TimeSpan.FromMinutes(10.0);
	}
	internal sealed class GroupCommandIssue
	{
		internal Guid Epoch { get; }

		internal long Sequence { get; }

		internal StableIdentity Actor { get; }

		internal string RequestSha256 { get; }

		internal Guid GroupId { get; }

		internal long ExpectedCatalogRevision { get; }

		internal long ExpectedGroupRevision { get; }

		internal long ExpiresUtcTicks { get; }

		internal string Token => GroupCommandToken.Format(Epoch, Sequence);

		internal GroupCommandIssue(Guid epoch, long sequence, StableIdentity actor, string requestSha256, Guid groupId, long expectedCatalogRevision, long expectedGroupRevision, long expiresUtcTicks)
		{
			if (epoch == Guid.Empty || sequence < 1 || actor == null || groupId == Guid.Empty || expectedCatalogRevision < 0 || expectedGroupRevision < -1 || expiresUtcTicks <= 0)
			{
				throw new ArgumentException("The Group command issue is invalid.");
			}
			Epoch = epoch;
			Sequence = sequence;
			Actor = actor;
			RequestSha256 = RequireSha256(requestSha256);
			GroupId = groupId;
			ExpectedCatalogRevision = expectedCatalogRevision;
			ExpectedGroupRevision = expectedGroupRevision;
			ExpiresUtcTicks = expiresUtcTicks;
		}

		internal bool Matches(StableIdentity actor, string requestSha256)
		{
			if (actor != null && Actor.Equals(actor))
			{
				return string.Equals(RequestSha256, requestSha256, StringComparison.Ordinal);
			}
			return false;
		}

		internal static string RequireSha256(string value)
		{
			if (value == null || value.Length != 64)
			{
				throw new ArgumentException("SHA-256 is invalid.");
			}
			foreach (char c in value)
			{
				if ((c < '0' || c > '9') && (c < 'a' || c > 'f'))
				{
					throw new ArgumentException("SHA-256 is not canonical lowercase hexadecimal.");
				}
			}
			return value;
		}
	}
	internal sealed class GroupCommandReceipt
	{
		internal Guid Epoch { get; }

		internal long Sequence { get; }

		internal StableIdentity Actor { get; }

		internal string RequestSha256 { get; }

		internal GroupMutationCode Code { get; }

		internal string ReasonCode { get; }

		internal long ExpectedCatalogRevision { get; }

		internal long ExpectedGroupRevision { get; }

		internal long CatalogRevision { get; }

		internal long GroupRevision { get; }

		internal string Token => GroupCommandToken.Format(Epoch, Sequence);

		internal bool Success
		{
			get
			{
				if (Code >= GroupMutationCode.Created)
				{
					return Code <= GroupMutationCode.NoChange;
				}
				return false;
			}
		}

		internal GroupCommandReceipt(Guid epoch, long sequence, StableIdentity actor, string requestSha256, GroupMutationCode code, string reasonCode, long expectedCatalogRevision, long expectedGroupRevision, long catalogRevision, long groupRevision)
		{
			if (epoch == Guid.Empty || sequence < 1 || actor == null || !Enum.IsDefined(typeof(GroupMutationCode), code) || expectedCatalogRevision < 0 || expectedGroupRevision < -1 || catalogRevision < 0 || groupRevision < -1)
			{
				throw new ArgumentException("The Group command receipt is invalid.");
			}
			string text = reasonCode ?? string.Empty;
			if (text.Length < 1 || text.Length > 96)
			{
				throw new ArgumentOutOfRangeException("reasonCode");
			}
			foreach (char c in text)
			{
				if ((c < 'a' || c > 'z') && (c < '0' || c > '9') && c != '.' && c != '-' && c != '_')
				{
					throw new ArgumentException("The Group receipt reason is not canonical.");
				}
			}
			Epoch = epoch;
			Sequence = sequence;
			Actor = actor;
			RequestSha256 = GroupCommandIssue.RequireSha256(requestSha256);
			Code = code;
			ReasonCode = text;
			ExpectedCatalogRevision = expectedCatalogRevision;
			ExpectedGroupRevision = expectedGroupRevision;
			CatalogRevision = catalogRevision;
			GroupRevision = groupRevision;
		}

		internal bool Matches(StableIdentity actor, string requestSha256)
		{
			if (actor != null && Actor.Equals(actor))
			{
				return string.Equals(RequestSha256, requestSha256, StringComparison.Ordinal);
			}
			return false;
		}
	}
	internal static class GroupCommandToken
	{
		internal static string Format(Guid epoch, long sequence)
		{
			if (epoch == Guid.Empty || sequence < 1)
			{
				throw new ArgumentException("Token identity is invalid.");
			}
			return epoch.ToString("N") + ":" + sequence.ToString("x16", CultureInfo.InvariantCulture);
		}

		internal static bool TryParse(string value, out Guid epoch, out long sequence)
		{
			epoch = Guid.Empty;
			sequence = 0L;
			if (value == null || value.Length != 49 || value[32] != ':' || !Guid.TryParseExact(value.Substring(0, 32), "N", out epoch) || epoch == Guid.Empty || !long.TryParse(value.Substring(33), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out sequence) || sequence < 1)
			{
				epoch = Guid.Empty;
				sequence = 0L;
				return false;
			}
			return string.Equals(value, Format(epoch, sequence), StringComparison.Ordinal);
		}
	}
	internal sealed class GroupCommandLedger
	{
		private readonly GroupCommandIssue[] _issues;

		private readonly GroupCommandReceipt[] _receipts;

		private readonly ReadOnlyCollection<GroupCommandIssue> _issueView;

		private readonly ReadOnlyCollection<GroupCommandReceipt> _receiptView;

		internal static GroupCommandLedger Empty { get; } = new GroupCommandLedger(Guid.Empty, 0L, 0L);

		internal Guid Epoch { get; }

		internal long NextSequence { get; }

		internal long MinimumAcceptedSequence { get; }

		internal IReadOnlyList<GroupCommandIssue> Issues => _issueView;

		internal IReadOnlyList<GroupCommandReceipt> Receipts => _receiptView;

		internal GroupCommandLedger(Guid epoch, long nextSequence, long minimumAcceptedSequence, IEnumerable<GroupCommandIssue> issues = null, IEnumerable<GroupCommandReceipt> receipts = null)
		{
			if (nextSequence < 0 || minimumAcceptedSequence < 0 || minimumAcceptedSequence > nextSequence || epoch == Guid.Empty != (nextSequence == 0))
			{
				throw new ArgumentException("The Group command ledger frontier is invalid.");
			}
			Epoch = epoch;
			NextSequence = nextSequence;
			MinimumAcceptedSequence = minimumAcceptedSequence;
			_issues = SortedIssues(issues, epoch, minimumAcceptedSequence, nextSequence);
			_receipts = SortedReceipts(receipts, epoch, minimumAcceptedSequence, nextSequence);
			if (_issues.Length > 256 || _receipts.Length > 4096)
			{
				throw new ArgumentOutOfRangeException("issues");
			}
			int num = 0;
			int num2 = 0;
			for (long num3 = minimumAcceptedSequence + 1; num3 <= nextSequence; num3++)
			{
				bool num4 = num < _issues.Length && _issues[num].Sequence == num3;
				bool flag = num2 < _receipts.Length && _receipts[num2].Sequence == num3;
				if (num4 == flag)
				{
					throw new ArgumentException("The Group command ledger has a gap or duplicate.");
				}
				if (num4)
				{
					num++;
				}
				else
				{
					num2++;
				}
			}
			_issueView = Array.AsReadOnly(_issues);
			_receiptView = Array.AsReadOnly(_receipts);
		}

		internal GroupCommandLedger Issue(StableIdentity actor, string requestSha256, Guid groupId, long expectedCatalogRevision, long expectedGroupRevision, long nowUtcTicks, long expiresUtcTicks, out GroupCommandIssue issue)
		{
			if (actor == null || nowUtcTicks <= 0 || expiresUtcTicks <= nowUtcTicks || expiresUtcTicks - nowUtcTicks > GroupCommandDurabilityLimits.MaximumIssueLifetime.Ticks)
			{
				throw new ArgumentException("The Group command issue lifetime is invalid.");
			}
			GroupCommandLedger groupCommandLedger = PruneExpired(nowUtcTicks);
			GroupCommandIssue[] issues = groupCommandLedger._issues;
			foreach (GroupCommandIssue groupCommandIssue in issues)
			{
				if (groupCommandIssue.Matches(actor, requestSha256) && groupCommandIssue.GroupId == groupId && groupCommandIssue.ExpectedCatalogRevision == expectedCatalogRevision && groupCommandIssue.ExpectedGroupRevision == expectedGroupRevision)
				{
					issue = groupCommandIssue;
					return groupCommandLedger;
				}
			}
			if (groupCommandLedger._issues.Length >= 256 || groupCommandLedger.NextSequence == long.MaxValue)
			{
				throw new InvalidOperationException("group-command-issue-capacity");
			}
			Guid epoch = ((groupCommandLedger.Epoch == Guid.Empty) ? Guid.NewGuid() : groupCommandLedger.Epoch);
			long num = checked(groupCommandLedger.NextSequence + 1);
			issue = new GroupCommandIssue(epoch, num, actor, requestSha256, groupId, expectedCatalogRevision, expectedGroupRevision, expiresUtcTicks);
			List<GroupCommandIssue> issues2 = new List<GroupCommandIssue>(groupCommandLedger._issues) { issue };
			return new GroupCommandLedger(epoch, num, groupCommandLedger.MinimumAcceptedSequence, issues2, groupCommandLedger._receipts);
		}

		internal GroupCommandLedger Complete(GroupCommandIssue issue, GroupMutationCode code, string reasonCode, long catalogRevision, long groupRevision, out GroupCommandReceipt receipt)
		{
			if (issue == null || issue.Epoch != Epoch || !TryGetIssue(issue.Token, out var issue2) || (issue2 != issue && !SameIssue(issue2, issue)))
			{
				throw new ArgumentException("The Group command issue is not current.", "issue");
			}
			receipt = new GroupCommandReceipt(Epoch, issue.Sequence, issue.Actor, issue.RequestSha256, code, reasonCode, issue.ExpectedCatalogRevision, issue.ExpectedGroupRevision, catalogRevision, groupRevision);
			List<GroupCommandIssue> issues = _issues.Where((GroupCommandIssue value) => value.Sequence != issue.Sequence).ToList();
			List<GroupCommandReceipt> list = new List<GroupCommandReceipt>(_receipts) { receipt };
			list.Sort((GroupCommandReceipt left, GroupCommandReceipt right) => left.Sequence.CompareTo(right.Sequence));
			return Compact(Epoch, NextSequence, MinimumAcceptedSequence, issues, list);
		}

		internal GroupCommandLedger PruneExpired(long nowUtcTicks)
		{
			if (nowUtcTicks <= 0)
			{
				throw new ArgumentOutOfRangeException("nowUtcTicks");
			}
			List<GroupCommandIssue> list = new List<GroupCommandIssue>(_issues.Length);
			List<GroupCommandReceipt> list2 = new List<GroupCommandReceipt>(_receipts);
			bool flag = false;
			GroupCommandIssue[] issues = _issues;
			foreach (GroupCommandIssue groupCommandIssue in issues)
			{
				if (groupCommandIssue.ExpiresUtcTicks >= nowUtcTicks)
				{
					list.Add(groupCommandIssue);
					continue;
				}
				list2.Add(new GroupCommandReceipt(Epoch, groupCommandIssue.Sequence, groupCommandIssue.Actor, groupCommandIssue.RequestSha256, GroupMutationCode.RevisionConflict, "group-command-token-expired", groupCommandIssue.ExpectedCatalogRevision, groupCommandIssue.ExpectedGroupRevision, groupCommandIssue.ExpectedCatalogRevision, groupCommandIssue.ExpectedGroupRevision));
				flag = true;
			}
			if (!flag)
			{
				return this;
			}
			list2.Sort((GroupCommandReceipt left, GroupCommandReceipt right) => left.Sequence.CompareTo(right.Sequence));
			return Compact(Epoch, NextSequence, MinimumAcceptedSequence, list, list2);
		}

		internal bool TryGetIssue(string token, out GroupCommandIssue issue)
		{
			issue = null;
			if (!GroupCommandToken.TryParse(token, out var epoch, out var sequence) || epoch != Epoch || sequence <= MinimumAcceptedSequence || sequence > NextSequence)
			{
				return false;
			}
			for (int i = 0; i < _issues.Length; i++)
			{
				if (_issues[i].Sequence == sequence)
				{
					issue = _issues[i];
					return true;
				}
			}
			return false;
		}

		internal bool TryGetReceipt(string token, out GroupCommandReceipt receipt)
		{
			receipt = null;
			if (!GroupCommandToken.TryParse(token, out var epoch, out var sequence) || epoch != Epoch || sequence <= MinimumAcceptedSequence || sequence > NextSequence)
			{
				return false;
			}
			for (int i = 0; i < _receipts.Length; i++)
			{
				if (_receipts[i].Sequence == sequence)
				{
					receipt = _receipts[i];
					return true;
				}
			}
			return false;
		}

		private static GroupCommandLedger Compact(Guid epoch, long nextSequence, long minimum, List<GroupCommandIssue> issues, List<GroupCommandReceipt> receipts)
		{
			while (receipts.Count > 4096)
			{
				long next = minimum + 1;
				int num = receipts.FindIndex((GroupCommandReceipt value) => value.Sequence == next);
				if (num < 0)
				{
					throw new InvalidOperationException("group-command-receipt-capacity");
				}
				receipts.RemoveAt(num);
				minimum = next;
			}
			return new GroupCommandLedger(epoch, nextSequence, minimum, issues, receipts);
		}

		private static GroupCommandIssue[] SortedIssues(IEnumerable<GroupCommandIssue> values, Guid epoch, long minimum, long maximum)
		{
			GroupCommandIssue[] array = (values ?? Array.Empty<GroupCommandIssue>()).OrderBy((GroupCommandIssue value) => value?.Sequence ?? 0).ToArray();
			long num = minimum;
			GroupCommandIssue[] array2 = array;
			foreach (GroupCommandIssue groupCommandIssue in array2)
			{
				if (groupCommandIssue == null || groupCommandIssue.Epoch != epoch || groupCommandIssue.Sequence <= num || groupCommandIssue.Sequence > maximum)
				{
					throw new ArgumentException("The Group command issue set is invalid.");
				}
				num = groupCommandIssue.Sequence;
			}
			return array;
		}

		private static GroupCommandReceipt[] SortedReceipts(IEnumerable<GroupCommandReceipt> values, Guid epoch, long minimum, long maximum)
		{
			GroupCommandReceipt[] array = (values ?? Array.Empty<GroupCommandReceipt>()).OrderBy((GroupCommandReceipt value) => value?.Sequence ?? 0).ToArray();
			long num = minimum;
			GroupCommandReceipt[] array2 = array;
			foreach (GroupCommandReceipt groupCommandReceipt in array2)
			{
				if (groupCommandReceipt == null || groupCommandReceipt.Epoch != epoch || groupCommandReceipt.Sequence <= num || groupCommandReceipt.Sequence > maximum)
				{
					throw new ArgumentException("The Group command receipt set is invalid.");
				}
				num = groupCommandReceipt.Sequence;
			}
			return array;
		}

		private static bool SameIssue(GroupCommandIssue left, GroupCommandIssue right)
		{
			if (left != null && right != null && left.Epoch == right.Epoch && left.Sequence == right.Sequence && left.Actor.Equals(right.Actor) && string.Equals(left.RequestSha256, right.RequestSha256, StringComparison.Ordinal) && left.GroupId == right.GroupId && left.ExpectedCatalogRevision == right.ExpectedCatalogRevision && left.ExpectedGroupRevision == right.ExpectedGroupRevision)
			{
				return left.ExpiresUtcTicks == right.ExpiresUtcTicks;
			}
			return false;
		}
	}
	public enum GroupMutationCode
	{
		Created,
		Renamed,
		Invited,
		InvitationCancelled,
		Accepted,
		Left,
		Removed,
		RoleChanged,
		OwnershipTransferred,
		Deleted,
		ExpiredInvitationsPruned,
		NoChange,
		RevisionConflict,
		InvalidRequest,
		GroupMissing,
		NameConflict,
		Unauthorized,
		AlreadyMember,
		InvitationMissing,
		InvitationExpired,
		CapacityReached,
		RetiredIdentity
	}
	public sealed class GroupMutationResult
	{
		public GroupMutationCode Code { get; }

		public string ReasonCode { get; }

		public GroupCatalog Catalog { get; }

		public GroupRecord Group { get; }

		public bool Success
		{
			get
			{
				if (Code >= GroupMutationCode.Created)
				{
					return Code <= GroupMutationCode.NoChange;
				}
				return false;
			}
		}

		internal GroupMutationResult(GroupMutationCode code, string reasonCode, GroupCatalog catalog, GroupRecord group)
		{
			Code = code;
			ReasonCode = reasonCode ?? throw new ArgumentNullException("reasonCode");
			Catalog = catalog ?? throw new ArgumentNullException("catalog");
			Group = group;
		}
	}
	public sealed class GroupMembership
	{
		public Guid GroupId { get; }

		public string GroupIdText { get; }

		public string DisplayName { get; }

		public GroupRole Role { get; }

		public long GroupRevision { get; }

		internal GroupMembership(Guid groupId, string displayName, GroupRole role, long groupRevision)
		{
			GroupId = groupId;
			GroupIdText = GroupIdentity.ToCanonicalId(groupId);
			DisplayName = displayName;
			Role = role;
			GroupRevision = groupRevision;
		}
	}
	public sealed class GroupCatalog
	{
		private readonly GroupRecord[] _groups;

		private readonly RetiredGroupId[] _retired;

		private readonly ReadOnlyCollection<GroupRecord> _groupView;

		private readonly ReadOnlyCollection<RetiredGroupId> _retiredView;

		public long Revision { get; }

		internal GroupCommandLedger CommandLedger { get; }

		public IReadOnlyList<GroupRecord> Groups => _groupView;

		public IReadOnlyList<RetiredGroupId> RetiredGroupIds => _retiredView;

		public static GroupCatalog Empty { get; } = new GroupCatalog(0L);

		public GroupCatalog(long revision, IEnumerable<GroupRecord> groups = null, IEnumerable<RetiredGroupId> retiredGroupIds = null)
			: this(revision, groups, retiredGroupIds, GroupCommandLedger.Empty)
		{
		}

		internal GroupCatalog(long revision, IEnumerable<GroupRecord> groups, IEnumerable<RetiredGroupId> retiredGroupIds, GroupCommandLedger commandLedger)
		{
			if (revision < 0)
			{
				throw new ArgumentOutOfRangeException("revision");
			}
			Revision = revision;
			CommandLedger = commandLedger ?? throw new ArgumentNullException("commandLedger");
			_groups = CopyGroups(groups, revision);
			_retired = CopyRetired(retiredGroupIds, revision, _groups);
			ValidateMembershipBounds(_groups);
			_groupView = Array.AsReadOnly(_groups);
			_retiredView = Array.AsReadOnly(_retired);
		}

		internal GroupCatalog WithCommandLedger(GroupCommandLedger ledger)
		{
			return new GroupCatalog(Revision, _groups, _retired, ledger);
		}

		public bool TryGetGroup(Guid id, out GroupRecord group)
		{
			group = null;
			if (id == Guid.Empty)
			{
				return false;
			}
			int num = FindGroup(_groups, GroupIdentity.ToCanonicalId(id));
			if (num < 0)
			{
				return false;
			}
			group = _groups[num];
			return true;
		}

		public IReadOnlyList<GroupMembership> GetMemberships(StableIdentity identity)
		{
			if (identity == null)
			{
				return Array.Empty<GroupMembership>();
			}
			List<GroupMembership> list = new List<GroupMembership>();
			GroupRecord[] groups = _groups;
			foreach (GroupRecord groupRecord in groups)
			{
				if (groupRecord.TryGetMember(identity, out var member))
				{
					list.Add(new GroupMembership(groupRecord.Id, groupRecord.DisplayName, member.Role, groupRecord.Revision));
				}
			}
			return list.AsReadOnly();
		}

		public GroupMutationResult Create(long expectedCatalogRevision, Guid id, string displayName, StableIdentity owner)
		{
			if (!Expected(expectedCatalogRevision))
			{
				return Conflict();
			}
			if (id == Guid.Empty || owner == null || !TryDisplayName(displayName))
			{
				return Invalid();
			}
			string idText = GroupIdentity.ToCanonicalId(id);
			if (FindGroup(_groups, idText) >= 0)
			{
				return Fail(GroupMutationCode.InvalidRequest, "group-id-exists");
			}
			if (FindRetired(_retired, idText) >= 0)
			{
				return Fail(GroupMutationCode.RetiredIdentity, "group-id-retired");
			}
			if (_groups.Length >= 256 || CountMemberships(owner) >= 64)
			{
				return Fail(GroupMutationCode.CapacityReached, "group-capacity-reached");
			}
			if (NameExists(displayName, null))
			{
				return Fail(GroupMutationCode.NameConflict, "group-name-conflict");
			}
			GroupRecord replacement = new GroupRecord(id, displayName, 1L, new GroupMember[1]
			{
				new GroupMember(owner, GroupRole.Owner, 1L)
			});
			return Replace(null, replacement, GroupMutationCode.Created, "group-created");
		}

		public GroupMutationResult Rename(long expectedCatalogRevision, Guid id, long expectedGroupRevision, StableIdentity actor, string displayName)
		{
			if (!TryMutation(expectedCatalogRevision, id, expectedGroupRevision, out var group, out var failure))
			{
				return failure;
			}
			if (!TryDisplayName(displayName) || actor == null)
			{
				return Invalid(group);
			}
			if (!IsOwner(group, actor))
			{
				return Unauthorized(group);
			}
			if (NameExists(displayName, group.IdText))
			{
				return Fail(GroupMutationCode.NameConflict, "group-name-conflict", group);
			}
			if (string.Equals(group.DisplayName, displayName, StringComparison.Ordinal))
			{
				return Ok(GroupMutationCode.NoChange, "group-name-unchanged", group);
			}
			return Replace(group, group.Rename(displayName), GroupMutationCode.Renamed, "group-renamed");
		}

		public GroupMutationResult Invite(long expectedCatalogRevision, Guid id, long expectedGroupRevision, StableIdentity actor, StableIdentity invitee, long nowUtcTicks, long expiresUtcTicks)
		{
			if (!TryMutation(expectedCatalogRevision, id, expectedGroupRevision, out var group, out var failure))
			{
				return failure;
			}
			if (actor == null || invitee == null || nowUtcTicks <= 0 || expiresUtcTicks <= nowUtcTicks || expiresUtcTicks - nowUtcTicks > GroupLimits.MaximumInvitationLifetime.Ticks)
			{
				return Invalid(group);
			}
			if (!IsOfficerOrOwner(group, actor))
			{
				return Unauthorized(group);
			}
			if (group.TryGetMember(invitee, out var _))
			{
				return Fail(GroupMutationCode.AlreadyMember, "group-already-member", group);
			}
			if (CountMemberships(invitee) >= 64)
			{
				return Fail(GroupMutationCode.CapacityReached, "group-membership-capacity", group);
			}
			if (group.TryGetInvitation(invitee, out var invitation))
			{
				if (invitation.InvitedBy.Equals(actor) && invitation.ExpiresUtcTicks == expiresUtcTicks)
				{
					return Ok(GroupMutationCode.NoChange, "group-invitation-unchanged", group);
				}
				return Fail(GroupMutationCode.InvalidRequest, "group-invitation-exists", group);
			}
			if (group.Invitations.Count >= 256)
			{
				return Fail(GroupMutationCode.CapacityReached, "group-invitation-capacity", group);
			}
			return Replace(group, group.Invite(actor, invitee, expiresUtcTicks), GroupMutationCode.Invited, "group-invited");
		}

		public GroupMutationResult CancelInvitation(long expectedCatalogRevision, Guid id, long expectedGroupRevision, StableIdentity actor, StableIdentity invitee)
		{
			if (!TryMutation(expectedCatalogRevision, id, expectedGroupRevision, out var group, out var failure))
			{
				return failure;
			}
			if (actor == null || invitee == null)
			{
				return Invalid(group);
			}
			if (!IsOfficerOrOwner(group, actor))
			{
				return Unauthorized(group);
			}
			if (!group.TryGetInvitation(invitee, out var _))
			{
				return Fail(GroupMutationCode.InvitationMissing, "group-invitation-missing", group);
			}
			return Replace(group, group.CancelInvitation(invitee), GroupMutationCode.InvitationCancelled, "group-invitation-cancelled");
		}

		public GroupMutationResult Accept(long expectedCatalogRevision, Guid id, long expectedGroupRevision, StableIdentity invitee, long nowUtcTicks)
		{
			if (!TryMutation(expectedCatalogRevision, id, expectedGroupRevision, out var group, out var failure))
			{
				return failure;
			}
			if (invitee == null || nowUtcTicks <= 0)
			{
				return Invalid(group);
			}
			if (group.TryGetMember(invitee, out var _))
			{
				return Fail(GroupMutationCode.AlreadyMember, "group-already-member", group);
			}
			if (!group.TryGetInvitation(invitee, out var invitation))
			{
				return Fail(GroupMutationCode.InvitationMissing, "group-invitation-missing", group);
			}
			if (!invitation.Invitee.Equals(invitee))
			{
				return Fail(GroupMutationCode.Unauthorized, "group-invitation-identity-mismatch", group);
			}
			if (invitation.IsExpired(nowUtcTicks))
			{
				return Fail(GroupMutationCode.InvitationExpired, "group-invitation-expired", group);
			}
			if (group.Members.Count >= 256 || CountMemberships(invitee) >= 64)
			{
				return Fail(GroupMutationCode.CapacityReached, "group-membership-capacity", group);
			}
			return Replace(group, group.Accept(invitee), GroupMutationCode.Accepted, "group-invitation-accepted");
		}

		public GroupMutationResult Leave(long expectedCatalogRevision, Guid id, long expectedGroupRevision, StableIdentity actor)
		{
			if (!TryMutation(expectedCatalogRevision, id, expectedGroupRevision, out var group, out var failure))
			{
				return failure;
			}
			if (actor == null || !group.TryGetMember(actor, out var member))
			{
				return Unauthorized(group);
			}
			if (member.Role == GroupRole.Owner)
			{
				return Fail(GroupMutationCode.Unauthorized, "group-owner-transfer-required", group);
			}
			return Replace(group, group.RemoveMember(actor), GroupMutationCode.Left, "group-left");
		}

		public GroupMutationResult Remove(long expectedCatalogRevision, Guid id, long expectedGroupRevision, StableIdentity actor, StableIdentity target)
		{
			if (!TryMutation(expectedCatalogRevision, id, expectedGroupRevision, out var group, out var failure))
			{
				return failure;
			}
			if (actor == null || target == null || actor.Equals(target) || !group.TryGetMember(actor, out var member) || !group.TryGetMember(target, out var member2))
			{
				return Unauthorized(group);
			}
			if (member2.Role == GroupRole.Owner || member.Role == GroupRole.Member || (member.Role == GroupRole.Officer && member2.Role != GroupRole.Member))
			{
				return Unauthorized(group);
			}
			return Replace(group, group.RemoveMember(target), GroupMutationCode.Removed, "group-member-removed");
		}

		public GroupMutationResult SetRole(long expectedCatalogRevision, Guid id, long expectedGroupRevision, StableIdentity actor, StableIdentity target, GroupRole role)
		{
			if (!TryMutation(expectedCatalogRevision, id, expectedGroupRevision, out var group, out var failure))
			{
				return failure;
			}
			if (actor == null || target == null || role == GroupRole.Owner || !Enum.IsDefined(typeof(GroupRole), role) || !IsOwner(group, actor) || !group.TryGetMember(target, out var member) || member.Role == GroupRole.Owner)
			{
				return Unauthorized(group);
			}
			if (member.Role == role)
			{
				return Ok(GroupMutationCode.NoChange, "group-role-unchanged", group);
			}
			return Replace(group, group.SetRole(target, role), GroupMutationCode.RoleChanged, "group-role-changed");
		}

		public GroupMutationResult TransferOwnership(long expectedCatalogRevision, Guid id, long expectedGroupRevision, StableIdentity actor, StableIdentity successor)
		{
			if (!TryMutation(expectedCatalogRevision, id, expectedGroupRevision, out var group, out var failure))
			{
				return failure;
			}
			if (actor == null || successor == null || actor.Equals(successor) || !IsOwner(group, actor) || !group.TryGetMember(successor, out var member) || member.Role == GroupRole.Owner)
			{
				return Unauthorized(group);
			}
			return Replace(group, group.TransferOwnership(actor, successor), GroupMutationCode.OwnershipTransferred, "group-ownership-transferred");
		}

		public GroupMutationResult Delete(long expectedCatalogRevision, Guid id, long expectedGroupRevision, StableIdentity actor)
		{
			if (!TryMutation(expectedCatalogRevision, id, expectedGroupRevision, out var group, out var failure))
			{
				return failure;
			}
			if (actor == null || !IsOwner(group, actor))
			{
				return Unauthorized(group);
			}
			if (_retired.Length >= 4096)
			{
				return Fail(GroupMutationCode.CapacityReached, "group-retired-id-capacity", group);
			}
			long num = checked(Revision + 1);
			GroupRecord[] groups = _groups.Where((GroupRecord value) => value.Id != id).ToArray();
			List<RetiredGroupId> retiredGroupIds = new List<RetiredGroupId>(_retired)
			{
				new RetiredGroupId(id, num)
			};
			GroupCatalog catalog = new GroupCatalog(num, groups, retiredGroupIds, CommandLedger);
			return new GroupMutationResult(GroupMutationCode.Deleted, "group-deleted", catalog, null);
		}

		public GroupMutationResult PruneExpiredInvitations(long expectedCatalogRevision, Guid id, long expectedGroupRevision, long nowUtcTicks)
		{
			if (!TryMutation(expectedCatalogRevision, id, expectedGroupRevision, out var group, out var failure))
			{
				return failure;
			}
			if (nowUtcTicks <= 0)
			{
				return Invalid(group);
			}
			GroupRecord groupRecord = group.PruneExpired(nowUtcTicks);
			if (group != groupRecord)
			{
				return Replace(group, groupRecord, GroupMutationCode.ExpiredInvitationsPruned, "group-expired-invitations-pruned");
			}
			return Ok(GroupMutationCode.NoChange, "group-no-expired-invitations", group);
		}

		private bool TryMutation(long expectedCatalogRevision, Guid id, long expectedGroupRevision, out GroupRecord group, out GroupMutationResult failure)
		{
			group = null;
			failure = null;
			if (!Expected(expectedCatalogRevision))
			{
				failure = Conflict();
				return false;
			}
			if (id == Guid.Empty || expectedGroupRevision < 1)
			{
				failure = Invalid();
				return false;
			}
			if (!TryGetGroup(id, out group))
			{
				failure = Fail(GroupMutationCode.GroupMissing, "group-missing");
				return false;
			}
			if (group.Revision != expectedGroupRevision)
			{
				failure = Fail(GroupMutationCode.RevisionConflict, "group-revision-conflict", group);
				return false;
			}
			return true;
		}

		private GroupMutationResult Replace(GroupRecord current, GroupRecord replacement, GroupMutationCode code, string reason)
		{
			List<GroupRecord> list = new List<GroupRecord>(_groups.Length + ((current == null) ? 1 : 0));
			GroupRecord[] groups = _groups;
			foreach (GroupRecord groupRecord in groups)
			{
				if (current == null || groupRecord.Id != current.Id)
				{
					list.Add(groupRecord);
				}
			}
			list.Add(replacement);
			GroupCatalog catalog = new GroupCatalog(checked(Revision + 1), list, _retired, CommandLedger);
			return new GroupMutationResult(code, reason, catalog, replacement);
		}

		private bool NameExists(string name, string exceptId)
		{
			GroupRecord[] groups = _groups;
			foreach (GroupRecord groupRecord in groups)
			{
				if (!string.Equals(groupRecord.IdText, exceptId, StringComparison.Ordinal) && string.Equals(groupRecord.DisplayName, name, StringComparison.OrdinalIgnoreCase))
				{
					return true;
				}
			}
			return false;
		}

		private int CountMemberships(StableIdentity identity)
		{
			int num = 0;
			GroupRecord[] groups = _groups;
			for (int i = 0; i < groups.Length; i++)
			{
				if (groups[i].TryGetMember(identity, out var _))
				{
					num++;
				}
			}
			return num;
		}

		private static bool IsOwner(GroupRecord group, StableIdentity identity)
		{
			if (group.TryGetMember(identity, out var member))
			{
				return member.Role == GroupRole.Owner;
			}
			return false;
		}

		private static bool IsOfficerOrOwner(GroupRecord group, StableIdentity identity)
		{
			if (group.TryGetMember(identity, out var member))
			{
				return (int)member.Role >= 2;
			}
			return false;
		}

		private bool Expected(long revision)
		{
			if (revision >= 0)
			{
				return revision == Revision;
			}
			return false;
		}

		private GroupMutationResult Conflict()
		{
			return Fail(GroupMutationCode.RevisionConflict, "group-catalog-revision-conflict");
		}

		private GroupMutationResult Invalid(GroupRecord group = null)
		{
			return Fail(GroupMutationCode.InvalidRequest, "group-request-invalid", group);
		}

		private GroupMutationResult Unauthorized(GroupRecord group)
		{
			return Fail(GroupMutationCode.Unauthorized, "group-actor-unauthorized", group);
		}

		private GroupMutationResult Ok(GroupMutationCode code, string reason, GroupRecord group)
		{
			return new GroupMutationResult(code, reason, this, group);
		}

		private GroupMutationResult Fail(GroupMutationCode code, string reason, GroupRecord group = null)
		{
			return new GroupMutationResult(code, reason, this, group);
		}

		private static bool TryDisplayName(string value)
		{
			try
			{
				GroupIdentity.RequireDisplayName(value);
				return true;
			}
			catch (ArgumentException)
			{
				return false;
			}
		}

		private static GroupRecord[] CopyGroups(IEnumerable<GroupRecord> source, long revision)
		{
			GroupRecord[] array = (source ?? Array.Empty<GroupRecord>()).ToArray();
			if (array.Length > 256 || array.Any((GroupRecord value) => value == null || value.Revision > revision))
			{
				throw new ArgumentOutOfRangeException("source");
			}
			Array.Sort(array, (GroupRecord left, GroupRecord right) => string.Compare(left.IdText, right.IdText, StringComparison.Ordinal));
			HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			for (int num = 0; num < array.Length; num++)
			{
				if (num > 0 && string.Equals(array[num - 1].IdText, array[num].IdText, StringComparison.Ordinal))
				{
					throw new ArgumentException("Group UUIDs must be unique.", "source");
				}
				if (!hashSet.Add(array[num].DisplayName))
				{
					throw new ArgumentException("Group display names must be unique.", "source");
				}
			}
			return array;
		}

		private static RetiredGroupId[] CopyRetired(IEnumerable<RetiredGroupId> source, long revision, IReadOnlyList<GroupRecord> groups)
		{
			RetiredGroupId[] array = (source ?? Array.Empty<RetiredGroupId>()).ToArray();
			if (array.Length > 4096 || array.Any((RetiredGroupId value) => value == null || value.DeletedCatalogRevision > revision))
			{
				throw new ArgumentOutOfRangeException("source");
			}
			Array.Sort(array, (RetiredGroupId left, RetiredGroupId right) => string.Compare(left.IdText, right.IdText, StringComparison.Ordinal));
			for (int num = 0; num < array.Length; num++)
			{
				if (num > 0 && string.Equals(array[num - 1].IdText, array[num].IdText, StringComparison.Ordinal))
				{
					throw new ArgumentException("Retired group UUIDs must be unique.", "source");
				}
				if (FindGroup(groups, array[num].IdText) >= 0)
				{
					throw new ArgumentException("A current group UUID cannot also be retired.", "source");
				}
			}
			return array;
		}

		private static void ValidateMembershipBounds(IEnumerable<GroupRecord> groups)
		{
			Dictionary<StableIdentity, int> dictionary = new Dictionary<StableIdentity, int>();
			foreach (GroupRecord group in groups)
			{
				foreach (GroupMember member in group.Members)
				{
					dictionary.TryGetValue(member.Identity, out var value);
					value++;
					if (value > 64)
					{
						throw new ArgumentOutOfRangeException("groups");
					}
					dictionary[member.Identity] = value;
				}
			}
		}

		private static int FindGroup(IReadOnlyList<GroupRecord> groups, string idText)
		{
			int num = 0;
			int num2 = groups.Count - 1;
			while (num <= num2)
			{
				int num3 = num + (num2 - num) / 2;
				int num4 = string.Compare(groups[num3].IdText, idText, StringComparison.Ordinal);
				if (num4 == 0)
				{
					return num3;
				}
				if (num4 < 0)
				{
					num = num3 + 1;
				}
				else
				{
					num2 = num3 - 1;
				}
			}
			return -1;
		}

		private static int FindRetired(IReadOnlyList<RetiredGroupId> retired, string idText)
		{
			int num = 0;
			int num2 = retired.Count - 1;
			while (num <= num2)
			{
				int num3 = num + (num2 - num) / 2;
				int num4 = string.Compare(retired[num3].IdText, idText, StringComparison.Ordinal);
				if (num4 == 0)
				{
					return num3;
				}
				if (num4 < 0)
				{
					num = num3 + 1;
				}
				else
				{
					num2 = num3 - 1;
				}
			}
			return -1;
		}
	}
	internal static class GroupCatalogCodec
	{
		private const uint Magic = 1196446279u;

		private const ushort SchemaVersion = 2;

		private const int DigestBytes = 32;

		private static readonly UTF8Encoding StrictUtf8 = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);

		internal static byte[] Encode(string worldScope, GroupCatalog catalog)
		{
			worldScope = GroupIdentity.RequireWorldScope(worldScope);
			if (catalog == null)
			{
				throw new ArgumentNullException("catalog");
			}
			byte[] array;
			using (MemoryStream memoryStream = new MemoryStream())
			{
				using BinaryWriter binaryWriter = new BinaryWriter(memoryStream, StrictUtf8, leaveOpen: true);
				binaryWriter.Write(1196446279u);
				binaryWriter.Write((ushort)2);
				WriteText(binaryWriter, worldScope, 128);
				binaryWriter.Write(catalog.Revision);
				binaryWriter.Write(catalog.Groups.Count);
				foreach (GroupRecord group in catalog.Groups)
				{
					WriteText(binaryWriter, group.IdText, 32);
					WriteText(binaryWriter, group.DisplayName, 64);
					binaryWriter.Write(group.Revision);
					binaryWriter.Write(group.Members.Count);
					foreach (GroupMember member in group.Members)
					{
						WriteIdentity(binaryWriter, member.Identity);
						binaryWriter.Write((byte)member.Role);
						binaryWriter.Write(member.JoinedRevision);
					}
					binaryWriter.Write(group.Invitations.Count);
					foreach (GroupInvitation invitation in group.Invitations)
					{
						WriteIdentity(binaryWriter, invitation.Invitee);
						WriteIdentity(binaryWriter, invitation.InvitedBy);
						binaryWriter.Write(invitation.IssuedRevision);
						binaryWriter.Write(invitation.ExpiresUtcTicks);
					}
				}
				binaryWriter.Write(catalog.RetiredGroupIds.Count);
				foreach (RetiredGroupId retiredGroupId in catalog.RetiredGroupIds)
				{
					WriteText(binaryWriter, retiredGroupId.IdText, 32);
					binaryWriter.Write(retiredGroupId.DeletedCatalogRevision);
				}
				WriteCommandLedger(binaryWriter, catalog.CommandLedger);
				binaryWriter.Flush();
				array = memoryStream.ToArray();
			}
			byte[] array2 = Sha256(array);
			if (array.Length > 8388608 - array2.Length)
			{
				throw new InvalidOperationException("The group catalog exceeds its hard byte limit.");
			}
			byte[] array3 = new byte[array.Length + array2.Length];
			Buffer.BlockCopy(array, 0, array3, 0, array.Length);
			Buffer.BlockCopy(array2, 0, array3, array.Length, array2.Length);
			return array3;
		}

		internal static bool TryDecode(byte[] bytes, string expectedWorldScope, out GroupCatalog catalog, out string reason)
		{
			catalog = null;
			reason = "group-catalog-corrupt";
			try
			{
				expectedWorldScope = GroupIdentity.RequireWorldScope(expectedWorldScope);
				if (bytes == null || bytes.Length < 55 || bytes.Length > 8388608)
				{
					return false;
				}
				int num = bytes.Length - 32;
				if (!FixedEquals(Sha256(bytes, 0, num), bytes, num))
				{
					reason = "group-catalog-digest-mismatch";
					return false;
				}
				using (MemoryStream memoryStream = new MemoryStream(bytes, 0, num, writable: false, publiclyVisible: true))
				{
					using BinaryReader binaryReader = new BinaryReader(memoryStream, StrictUtf8, leaveOpen: true);
					if (binaryReader.ReadUInt32() != 1196446279)
					{
						reason = "group-catalog-schema-unsupported";
						return false;
					}
					ushort num2 = binaryReader.ReadUInt16();
					if (num2 != 1 && num2 != 2)
					{
						reason = "group-catalog-schema-unsupported";
						return false;
					}
					if (!string.Equals(ReadText(binaryReader, 128), expectedWorldScope, StringComparison.Ordinal))
					{
						reason = "group-catalog-world-mismatch";
						return false;
					}
					long revision = binaryReader.ReadInt64();
					int num3 = ReadCount(binaryReader, 256);
					List<GroupRecord> list = new List<GroupRecord>(num3);
					for (int i = 0; i < num3; i++)
					{
						if (!GroupIdentity.TryParseCanonicalId(ReadText(binaryReader, 32), out var groupId))
						{
							return false;
						}
						string displayName = ReadText(binaryReader, 64);
						long revision2 = binaryReader.ReadInt64();
						int num4 = ReadCount(binaryReader, 256);
						if (num4 < 1)
						{
							return false;
						}
						List<GroupMember> list2 = new List<GroupMember>(num4);
						for (int j = 0; j < num4; j++)
						{
							list2.Add(new GroupMember(ReadIdentity(binaryReader), (GroupRole)binaryReader.ReadByte(), binaryReader.ReadInt64()));
						}
						int num5 = ReadCount(binaryReader, 256);
						List<GroupInvitation> list3 = new List<GroupInvitation>(num5);
						for (int k = 0; k < num5; k++)
						{
							list3.Add(new GroupInvitation(ReadIdentity(binaryReader), ReadIdentity(binaryReader), binaryReader.ReadInt64(), binaryReader.ReadInt64()));
						}
						list.Add(new GroupRecord(groupId, displayName, revision2, list2, list3));
					}
					int num6 = ReadCount(binaryReader, 4096);
					List<RetiredGroupId> list4 = new List<RetiredGroupId>(num6);
					for (int l = 0; l < num6; l++)
					{
						if (!GroupIdentity.TryParseCanonicalId(ReadText(binaryReader, 32), out var groupId2))
						{
							return false;
						}
						list4.Add(new RetiredGroupId(groupId2, binaryReader.ReadInt64()));
					}
					GroupCommandLedger commandLedger = ((num2 == 1) ? GroupCommandLedger.Empty : ReadCommandLedger(binaryReader));
					if (memoryStream.Position != num)
					{
						reason = "group-catalog-trailing-data";
						return false;
					}
					catalog = new GroupCatalog(revision, list, list4, commandLedger);
					byte[] array = ((num2 == 2) ? Encode(expectedWorldScope, catalog) : null);
					if (array != null && !ExactEquals(bytes, array))
					{
						catalog = null;
						reason = "group-catalog-noncanonical";
						return false;
					}
				}
				reason = "group-catalog-ready";
				return true;
			}
			catch (Exception ex) when (ex is ArgumentException || ex is IOException || ex is EndOfStreamException || ex is DecoderFallbackException || ex is OverflowException)
			{
				catalog = null;
				return false;
			}
		}

		internal static string ComputeSha256(byte[] bytes)
		{
			byte[] array = Sha256(bytes ?? Array.Empty<byte>());
			StringBuilder stringBuilder = new StringBuilder(array.Length * 2);
			byte[] array2 = array;
			foreach (byte b in array2)
			{
				stringBuilder.Append(b.ToString("x2"));
			}
			return stringBuilder.ToString();
		}

		private static void WriteIdentity(BinaryWriter writer, StableIdentity identity)
		{
			if (identity == null)
			{
				throw new ArgumentNullException("identity");
			}
			WriteText(writer, identity.Authority, 64);
			WriteText(writer, identity.SubjectId, 1024);
		}

		private static StableIdentity ReadIdentity(BinaryReader reader)
		{
			return new StableIdentity(ReadText(reader, 64), ReadText(reader, 1024));
		}

		private static void WriteCommandLedger(BinaryWriter writer, GroupCommandLedger ledger)
		{
			ledger = ledger ?? GroupCommandLedger.Empty;
			writer.Write(ledger.Epoch.ToByteArray());
			writer.Write(ledger.NextSequence);
			writer.Write(ledger.MinimumAcceptedSequence);
			writer.Write(ledger.Issues.Count);
			foreach (GroupCommandIssue issue in ledger.Issues)
			{
				writer.Write(issue.Sequence);
				WriteIdentity(writer, issue.Actor);
				WriteText(writer, issue.RequestSha256, 64);
				WriteText(writer, GroupIdentity.ToCanonicalId(issue.GroupId), 32);
				writer.Write(issue.ExpectedCatalogRevision);
				writer.Write(issue.ExpectedGroupRevision);
				writer.Write(issue.ExpiresUtcTicks);
			}
			writer.Write(ledger.Receipts.Count);
			foreach (GroupCommandReceipt receipt in ledger.Receipts)
			{
				writer.Write(receipt.Sequence);
				WriteIdentity(writer, receipt.Actor);
				WriteText(writer, receipt.RequestSha256, 64);
				writer.Write((int)receipt.Code);
				WriteText(writer, receipt.ReasonCode, 96);
				writer.Write(receipt.ExpectedCatalogRevision);
				writer.Write(receipt.ExpectedGroupRevision);
				writer.Write(receipt.CatalogRevision);
				writer.Write(receipt.GroupRevision);
			}
		}

		private static GroupCommandLedger ReadCommandLedger(BinaryReader reader)
		{
			byte[] array = reader.ReadBytes(16);
			if (array.Length != 16)
			{
				throw new EndOfStreamException();
			}
			Guid epoch = new Guid(array);
			long nextSequence = reader.ReadInt64();
			long minimumAcceptedSequence = reader.ReadInt64();
			int num = ReadCount(reader, 256);
			List<GroupCommandIssue> list = new List<GroupCommandIssue>(num);
			for (int i = 0; i < num; i++)
			{
				long sequence = reader.ReadInt64();
				StableIdentity actor = ReadIdentity(reader);
				string requestSha = ReadText(reader, 64);
				if (!GroupIdentity.TryParseCanonicalId(ReadText(reader, 32), out var groupId))
				{
					throw new InvalidDataException();
				}
				list.Add(new GroupCommandIssue(epoch, sequence, actor, requestSha, groupId, reader.ReadInt64(), reader.ReadInt64(), reader.ReadInt64()));
			}
			int num2 = ReadCount(reader, 4096);
			List<GroupCommandReceipt> list2 = new List<GroupCommandReceipt>(num2);
			for (int j = 0; j < num2; j++)
			{
				list2.Add(new GroupCommandReceipt(epoch, reader.ReadInt64(), ReadIdentity(reader), ReadText(reader, 64), (GroupMutationCode)reader.ReadInt32(), ReadText(reader, 96), reader.ReadInt64(), reader.ReadInt64(), reader.ReadInt64(), reader.ReadInt64()));
			}
			return new GroupCommandLedger(epoch, nextSequence, minimumAcceptedSequence, list, list2);
		}

		private static void WriteText(BinaryWriter writer, string value, int maximumBytes)
		{
			byte[] bytes = StrictUtf8.GetBytes(value ?? string.Empty);
			if (bytes.Length < 1 || bytes.Length > maximumBytes)
			{
				throw new ArgumentOutOfRangeException("value");
			}
			writer.Write(bytes.Length);
			writer.Write(bytes);
		}

		private static string ReadText(BinaryReader reader, int maximumBytes)
		{
			int num = reader.ReadInt32();
			if (num < 1 || num > maximumBytes)
			{
				throw new InvalidDataException();
			}
			byte[] array = reader.ReadBytes(num);
			if (array.Length != num)
			{
				throw new EndOfStreamException();
			}
			return StrictUtf8.GetString(array);
		}

		private static int ReadCount(BinaryReader reader, int maximum)
		{
			int num = reader.ReadInt32();
			if (num < 0 || num > maximum)
			{
				throw new InvalidDataException();
			}
			return num;
		}

		private static byte[] Sha256(byte[] bytes)
		{
			return Sha256(bytes, 0, bytes.Length);
		}

		private static byte[] Sha256(byte[] bytes, int offset, int count)
		{
			using SHA256 sHA = SHA256.Create();
			return sHA.ComputeHash(bytes, offset, count);
		}

		private static bool FixedEquals(byte[] expected, byte[] source, int offset)
		{
			if (expected.Length != source.Length - offset)
			{
				return false;
			}
			int num = 0;
			for (int i = 0; i < expected.Length; i++)
			{
				num |= expected[i] ^ source[offset + i];
			}
			return num == 0;
		}

		private static bool ExactEquals(byte[] left, byte[] right)
		{
			if (left == null || right == null || left.Length != right.Length)
			{
				return false;
			}
			int num = 0;
			for (int i = 0; i < left.Length; i++)
			{
				num |= left[i] ^ right[i];
			}
			return num == 0;
		}
	}
	public enum GroupCommandKind : byte
	{
		Create = 1,
		Rename,
		Invite,
		CancelInvitation,
		Accept,
		Leave,
		Remove,
		SetRole,
		TransferOwnership,
		Delete
	}
	public sealed class GroupCommand
	{
		public GroupCommandKind Kind { get; }

		public Guid GroupId { get; }

		public string DisplayName { get; }

		public StableIdentity Target { get; }

		public GroupRole Role { get; }

		public long InvitationExpiresUtcTicks { get; }

		public GroupCommand(GroupCommandKind kind, Guid groupId, string displayName = "", StableIdentity target = null, GroupRole role = GroupRole.Member, long invitationExpiresUtcTicks = 0L)
		{
			Kind = kind;
			GroupId = groupId;
			DisplayName = displayName ?? string.Empty;
			Target = target;
			Role = role;
			InvitationExpiresUtcTicks = invitationExpiresUtcTicks;
			Validate();
		}

		private void Validate()
		{
			if (!Enum.IsDefined(typeof(GroupCommandKind), Kind) || GroupId == Guid.Empty)
			{
				throw new ArgumentException("The group command kind or ID is invalid.");
			}
			if (!Enum.IsDefined(typeof(GroupRole), Role))
			{
				throw new ArgumentOutOfRangeException("Role");
			}
			bool num = Kind == GroupCommandKind.Create || Kind == GroupCommandKind.Rename;
			bool flag = Kind == GroupCommandKind.Invite || Kind == GroupCommandKind.CancelInvitation || Kind == GroupCommandKind.Remove || Kind == GroupCommandKind.SetRole || Kind == GroupCommandKind.TransferOwnership;
			if (num)
			{
				GroupIdentity.RequireDisplayName(DisplayName);
			}
			else if (DisplayName.Length != 0)
			{
				throw new ArgumentException("This group command does not accept a display name.");
			}
			if (flag != (Target != null))
			{
				throw new ArgumentException("The group command target shape is invalid.");
			}
			if (Kind == GroupCommandKind.SetRole)
			{
				if (Role != GroupRole.Member && Role != GroupRole.Officer)
				{
					throw new ArgumentException("SetRole accepts Member or Officer only.");
				}
			}
			else if (Role != GroupRole.Member)
			{
				throw new ArgumentException("This group command does not accept a role.");
			}
			if (Kind == GroupCommandKind.Invite)
			{
				if (InvitationExpiresUtcTicks <= 0)
				{
					throw new ArgumentOutOfRangeException("InvitationExpiresUtcTicks");
				}
			}
			else if (InvitationExpiresUtcTicks != 0L)
			{
				throw new ArgumentException("This group command does not accept an expiry.");
			}
		}
	}
	public static class GroupCommandCodec
	{
		private const uint Magic = 1129337415u;

		private const byte Schema = 1;

		public const int MaximumPayloadBytes = 1024;

		private static readonly UTF8Encoding StrictUtf8 = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);

		public static byte[] Encode(GroupCommand command)
		{
			if (command == null)
			{
				throw new ArgumentNullException("command");
			}
			using MemoryStream memoryStream = new MemoryStream();
			using BinaryWriter binaryWriter = new BinaryWriter(memoryStream, StrictUtf8, leaveOpen: true);
			binaryWriter.Write(1129337415u);
			binaryWriter.Write((byte)1);
			binaryWriter.Write((byte)command.Kind);
			binaryWriter.Write(command.GroupId.ToByteArray());
			WriteText(binaryWriter, command.DisplayName, 64);
			WriteText(binaryWriter, command.Target?.Authority ?? string.Empty, 64);
			WriteText(binaryWriter, command.Target?.SubjectId ?? string.Empty, 1024);
			binaryWriter.Write((byte)command.Role);
			binaryWriter.Write(command.InvitationExpiresUtcTicks);
			binaryWriter.Flush();
			if (memoryStream.Length > 1024)
			{
				throw new InvalidOperationException("The group command exceeds its wire bound.");
			}
			return memoryStream.ToArray();
		}

		public static bool TryDecode(byte[] payload, out GroupCommand command, out string failureCode)
		{
			command = null;
			failureCode = "group-command-invalid";
			if (payload == null || payload.Length < 33 || payload.Length > 1024)
			{
				return false;
			}
			try
			{
				using (MemoryStream memoryStream = new MemoryStream(payload, writable: false))
				{
					using BinaryReader binaryReader = new BinaryReader(memoryStream, StrictUtf8, leaveOpen: true);
					if (binaryReader.ReadUInt32() != 1129337415 || binaryReader.ReadByte() != 1)
					{
						failureCode = "group-command-schema-invalid";
						return false;
					}
					GroupCommandKind kind = (GroupCommandKind)binaryReader.ReadByte();
					byte[] array = binaryReader.ReadBytes(16);
					if (array.Length != 16)
					{
						failureCode = "group-command-id-truncated";
						return false;
					}
					Guid groupId = new Guid(array);
					string displayName = ReadText(binaryReader, 64);
					string text = ReadText(binaryReader, 64);
					string text2 = ReadText(binaryReader, 1024);
					GroupRole role = (GroupRole)binaryReader.ReadByte();
					long invitationExpiresUtcTicks = binaryReader.ReadInt64();
					if (memoryStream.Position != memoryStream.Length)
					{
						failureCode = "group-command-trailing-data";
						return false;
					}
					StableIdentity target = ((text.Length == 0 && text2.Length == 0) ? null : new StableIdentity(text, text2));
					if (text.Length == 0 != (text2.Length == 0))
					{
						failureCode = "group-command-target-invalid";
						return false;
					}
					command = new GroupCommand(kind, groupId, displayName, target, role, invitationExpiresUtcTicks);
				}
				if (!payload.SequenceEqual(Encode(command)))
				{
					command = null;
					failureCode = "group-command-noncanonical";
					return false;
				}
				failureCode = "ok";
				return true;
			}
			catch (Exception ex) when (ex is ArgumentException || ex is EndOfStreamException || ex is IOException || ex is DecoderFallbackException || ex is OverflowException)
			{
				command = null;
				failureCode = "group-command-invalid";
				return false;
			}
		}

		private static void WriteText(BinaryWriter writer, string value, int maximumBytes)
		{
			byte[] bytes = StrictUtf8.GetBytes(value ?? string.Empty);
			if (bytes.Length > maximumBytes || bytes.Length > 65535)
			{
				throw new ArgumentOutOfRangeException("value");
			}
			writer.Write((ushort)bytes.Length);
			writer.Write(bytes);
		}

		private static string ReadText(BinaryReader reader, int maximumBytes)
		{
			int num = reader.ReadUInt16();
			if (num > maximumBytes || num > reader.BaseStream.Length - reader.BaseStream.Position)
			{
				throw new InvalidDataException("The group command text length is invalid.");
			}
			byte[] array = reader.ReadBytes(num);
			if (array.Length != num)
			{
				throw new EndOfStreamException();
			}
			string text = StrictUtf8.GetString(array);
			if (!StrictUtf8.GetBytes(text).SequenceEqual(array))
			{
				throw new InvalidDataException("The group command text is not canonical UTF-8.");
			}
			return text;
		}
	}
	public sealed class GroupCommandExecutionResult
	{
		public GroupMutationCode Code { get; }

		public string ReasonCode { get; }

		public GroupCatalog Catalog { get; }

		public GroupRecord Group { get; }

		public bool Success
		{
			get
			{
				if (Code >= GroupMutationCode.Created)
				{
					return Code <= GroupMutationCode.NoChange;
				}
				return false;
			}
		}

		internal GroupCommandExecutionResult(GroupMutationCode code, string reasonCode, GroupCatalog catalog, GroupRecord group)
		{
			Code = code;
			ReasonCode = reasonCode ?? string.Empty;
			Catalog = catalog;
			Group = group;
		}
	}
	public sealed class GroupCommandProcessor
	{
		private readonly IGroupWorldStore _store;

		private readonly Func<string> _worldScopeProvider;

		private readonly Func<long> _utcTicksProvider;

		public GroupCommandProcessor(IGroupWorldStore store, Func<string> worldScopeProvider, Func<long> utcTicksProvider = null)
		{
			_store = store ?? throw new ArgumentNullException("store");
			_worldScopeProvider = worldScopeProvider ?? throw new ArgumentNullException("worldScopeProvider");
			_utcTicksProvider = utcTicksProvider ?? ((Func<long>)(() => DateTime.UtcNow.Ticks));
		}

		public GroupCommandExecutionResult Execute(StableIdentity actor, GroupCommand command)
		{
			if (actor == null || command == null)
			{
				return Fail(GroupMutationCode.InvalidRequest, "group-command-invalid", null);
			}
			string text;
			try
			{
				text = _worldScopeProvider() ?? string.Empty;
			}
			catch
			{
				return Fail(GroupMutationCode.RevisionConflict, "group-world-unavailable", null);
			}
			if (text.Length == 0)
			{
				return Fail(GroupMutationCode.RevisionConflict, "group-world-unavailable", null);
			}
			GroupWorldReadResult groupWorldReadResult;
			try
			{
				groupWorldReadResult = _store.Read(text);
			}
			catch
			{
				return Fail(GroupMutationCode.RevisionConflict, "group-store-unavailable", null);
			}
			if (groupWorldReadResult == null || groupWorldReadResult.State == GroupWorldReadState.Corrupt || groupWorldReadResult.State == GroupWorldReadState.EvidenceConflict)
			{
				return Fail(GroupMutationCode.RevisionConflict, "group-store-evidence-conflict", groupWorldReadResult?.Catalog);
			}
			if (groupWorldReadResult.State == GroupWorldReadState.Unavailable)
			{
				return Fail(GroupMutationCode.RevisionConflict, "group-store-unavailable", groupWorldReadResult.Catalog);
			}
			GroupCatalog groupCatalog = ((groupWorldReadResult.State == GroupWorldReadState.Missing) ? GroupCatalog.Empty : groupWorldReadResult.Catalog);
			if (groupCatalog == null)
			{
				return Fail(GroupMutationCode.RevisionConflict, "group-store-invalid", null);
			}
			GroupCommandExecutionResult groupCommandExecutionResult = TryExactDesiredReplay(groupCatalog, actor, command);
			if (groupCommandExecutionResult != null)
			{
				return groupCommandExecutionResult;
			}
			GroupMutationResult groupMutationResult;
			if (command.Kind == GroupCommandKind.Create)
			{
				groupMutationResult = groupCatalog.Create(groupCatalog.Revision, command.GroupId, command.DisplayName, actor);
			}
			else
			{
				if (!groupCatalog.TryGetGroup(command.GroupId, out var group))
				{
					return Fail(GroupMutationCode.GroupMissing, "group-missing", groupCatalog);
				}
				long now = _utcTicksProvider();
				groupMutationResult = Apply(groupCatalog, group, actor, command, now);
			}
			if (!groupMutationResult.Success)
			{
				return From(groupMutationResult);
			}
			if (groupMutationResult.Code == GroupMutationCode.NoChange || groupMutationResult.Catalog == groupCatalog)
			{
				return From(groupMutationResult);
			}
			GroupWorldCommitResult groupWorldCommitResult;
			try
			{
				groupWorldCommitResult = _store.TryCommit(text, groupCatalog.Revision, groupMutationResult.Catalog);
			}
			catch
			{
				return Fail(GroupMutationCode.RevisionConflict, "group-store-commit-failed", groupCatalog);
			}
			if (groupWorldCommitResult == null || !groupWorldCommitResult.Success)
			{
				return Fail(GroupMutationCode.RevisionConflict, groupWorldCommitResult?.ReasonCode ?? "group-store-commit-failed", groupWorldCommitResult?.Current?.Catalog ?? groupCatalog);
			}
			return From(groupMutationResult);
		}

		internal static GroupCommandExecutionResult EvaluateIssued(GroupCatalog current, StableIdentity actor, GroupCommand command, long expectedCatalogRevision, long expectedGroupRevision, long nowUtcTicks)
		{
			if (current == null || actor == null || command == null || nowUtcTicks <= 0)
			{
				return Fail(GroupMutationCode.InvalidRequest, "group-command-invalid", current);
			}
			if (current.Revision != expectedCatalogRevision)
			{
				return Fail(GroupMutationCode.RevisionConflict, "group-catalog-revision-conflict", current);
			}
			GroupMutationResult result;
			if (command.Kind == GroupCommandKind.Create)
			{
				if (expectedGroupRevision != -1 || current.TryGetGroup(command.GroupId, out var _))
				{
					return Fail(GroupMutationCode.RevisionConflict, "group-create-revision-conflict", current);
				}
				result = current.Create(expectedCatalogRevision, command.GroupId, command.DisplayName, actor);
			}
			else
			{
				if (expectedGroupRevision < 1 || !current.TryGetGroup(command.GroupId, out var group2) || group2.Revision != expectedGroupRevision)
				{
					return Fail(GroupMutationCode.RevisionConflict, "group-revision-conflict", current);
				}
				result = Apply(current, group2, actor, command, nowUtcTicks);
			}
			return From(result);
		}

		private static GroupMutationResult Apply(GroupCatalog catalog, GroupRecord group, StableIdentity actor, GroupCommand command, long now)
		{
			return command.Kind switch
			{
				GroupCommandKind.Rename => catalog.Rename(catalog.Revision, group.Id, group.Revision, actor, command.DisplayName), 
				GroupCommandKind.Invite => catalog.Invite(catalog.Revision, group.Id, group.Revision, actor, command.Target, now, command.InvitationExpiresUtcTicks), 
				GroupCommandKind.CancelInvitation => catalog.CancelInvitation(catalog.Revision, group.Id, group.Revision, actor, command.Target), 
				GroupCommandKind.Accept => catalog.Accept(catalog.Revision, group.Id, group.Revision, actor, now), 
				GroupCommandKind.Leave => catalog.Leave(catalog.Revision, group.Id, group.Revision, actor), 
				GroupCommandKind.Remove => catalog.Remove(catalog.Revision, group.Id, group.Revision, actor, command.Target), 
				GroupCommandKind.SetRole => catalog.SetRole(catalog.Revision, group.Id, group.Revision, actor, command.Target, command.Role), 
				GroupCommandKind.TransferOwnership => catalog.TransferOwnership(catalog.Revision, group.Id, group.Revision, actor, command.Target), 
				GroupCommandKind.Delete => catalog.Delete(catalog.Revision, group.Id, group.Revision, actor), 
				_ => new GroupMutationResult(GroupMutationCode.InvalidRequest, "group-command-kind-invalid", catalog, group), 
			};
		}

		private static GroupCommandExecutionResult TryExactDesiredReplay(GroupCatalog catalog, StableIdentity actor, GroupCommand command)
		{
			if (command.Kind == GroupCommandKind.Create && catalog.TryGetGroup(command.GroupId, out var group) && string.Equals(group.DisplayName, command.DisplayName, StringComparison.Ordinal) && group.TryGetMember(actor, out var member) && member.Role == GroupRole.Owner)
			{
				return NoChange("group-create-already-applied", catalog, group);
			}
			if (command.Kind == GroupCommandKind.Delete && catalog.RetiredGroupIds.Any((RetiredGroupId value) => value.Id == command.GroupId))
			{
				return NoChange("group-delete-already-applied", catalog, null);
			}
			if (!catalog.TryGetGroup(command.GroupId, out var group2))
			{
				return null;
			}
			if (command.Kind == GroupCommandKind.Accept && group2.TryGetMember(actor, out var member2))
			{
				return NoChange("group-accept-already-applied", catalog, group2);
			}
			if (command.Kind == GroupCommandKind.Leave && !group2.TryGetMember(actor, out member2))
			{
				return NoChange("group-leave-already-applied", catalog, group2);
			}
			if (command.Kind == GroupCommandKind.TransferOwnership && command.Target != null && group2.TryGetMember(command.Target, out var member3) && member3.Role == GroupRole.Owner && group2.TryGetMember(actor, out member2))
			{
				return NoChange("group-transfer-already-applied", catalog, group2);
			}
			return null;
		}

		private static GroupCommandExecutionResult From(GroupMutationResult result)
		{
			return new GroupCommandExecutionResult(result.Code, result.ReasonCode, result.Catalog, result.Group);
		}

		private static GroupCommandExecutionResult NoChange(string reason, GroupCatalog catalog, GroupRecord group)
		{
			return new GroupCommandExecutionResult(GroupMutationCode.NoChange, reason, catalog, group);
		}

		private static GroupCommandExecutionResult Fail(GroupMutationCode code, string reason, GroupCatalog catalog)
		{
			return new GroupCommandExecutionResult(code, reason, catalog, null);
		}
	}
	public enum ActiveGroupSelectionStatus : byte
	{
		Available = 1,
		NoneSelected,
		Stale,
		Ambiguous,
		GroupMissing,
		NotMember
	}
	public sealed class ActiveGroupSelection
	{
		public ActiveGroupSelectionStatus Status { get; }

		public string GroupId { get; }

		public string DisplayName { get; }

		public bool IsAvailable => Status == ActiveGroupSelectionStatus.Available;

		internal static ActiveGroupSelection None { get; } = new ActiveGroupSelection(ActiveGroupSelectionStatus.NoneSelected);

		internal static ActiveGroupSelection Stale { get; } = new ActiveGroupSelection(ActiveGroupSelectionStatus.Stale);

		internal static ActiveGroupSelection Ambiguous { get; } = new ActiveGroupSelection(ActiveGroupSelectionStatus.Ambiguous);

		internal ActiveGroupSelection(ActiveGroupSelectionStatus status, string groupId = "", string displayName = "")
		{
			if (!Enum.IsDefined(typeof(ActiveGroupSelectionStatus), status))
			{
				throw new ArgumentOutOfRangeException("status");
			}
			if (status == ActiveGroupSelectionStatus.Available && !GroupIdentity.IsCanonicalId(groupId))
			{
				throw new ArgumentException("An available active Group requires an exact Group UUID.", "groupId");
			}
			if (status != ActiveGroupSelectionStatus.Available && !string.IsNullOrEmpty(groupId))
			{
				throw new ArgumentException("An unavailable active Group cannot expose an ID.", "groupId");
			}
			Status = status;
			GroupId = groupId ?? string.Empty;
			DisplayName = displayName ?? string.Empty;
		}
	}
	public interface IActiveGroupSelectionService
	{
		ActiveGroupSelection Resolve(StableIdentity identity);
	}
	public enum GroupActiveSelectionReadState
	{
		Missing,
		Ready,
		Corrupt,
		Ambiguous,
		Unavailable
	}
	public sealed class GroupActiveSelectionReadResult
	{
		public GroupActiveSelectionReadState State { get; }

		public string ReasonCode { get; }

		public Guid GroupId { get; }

		public bool HasSelection
		{
			get
			{
				if (State == GroupActiveSelectionReadState.Ready)
				{
					return GroupId != Guid.Empty;
				}
				return false;
			}
		}

		internal GroupActiveSelectionReadResult(GroupActiveSelectionReadState state, string reasonCode, Guid groupId)
		{
			State = state;
			ReasonCode = reasonCode ?? string.Empty;
			GroupId = groupId;
		}
	}
	public interface IGroupActiveSelectionStore
	{
		GroupActiveSelectionReadResult Read(string worldScope, StableIdentity identity);

		bool TrySet(string worldScope, StableIdentity identity, Guid groupId, out string reasonCode);
	}
	public sealed class FileGroupActiveSelectionStore : IGroupActiveSelectionStore
	{
		private readonly struct Paths
		{
			internal string Primary { get; }

			internal string Temporary { get; }

			internal string Lock { get; }

			internal Paths(string primary, string temporary, string @lock)
			{
				Primary = primary;
				Temporary = temporary;
				Lock = @lock;
			}
		}

		private const uint Magic = 826361682u;

		private const ushort Schema = 1;

		private const int MaximumFileBytes = 2048;

		private static readonly UTF8Encoding StrictUtf8 = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);

		private readonly string _root;

		public FileGroupActiveSelectionStore(string root)
		{
			if (string.IsNullOrWhiteSpace(root))
			{
				throw new ArgumentException("A storage root is required.", "root");
			}
			_root = TrimTrailingSeparators(Path.GetFullPath(root));
			if (string.Equals(_root, Path.GetPathRoot(_root), PathComparison()))
			{
				throw new ArgumentException("A filesystem root cannot be the active Group storage root.", "root");
			}
		}

		public GroupActiveSelectionReadResult Read(string worldScope, StableIdentity identity)
		{
			if (identity == null)
			{
				return Unavailable("group-active-identity-missing");
			}
			try
			{
				Paths paths = Resolve(worldScope, identity);
				if (!Directory.Exists(_root))
				{
					return Missing();
				}
				using (Acquire(paths.Lock))
				{
					if (File.Exists(paths.Temporary))
					{
						return Ambiguous("group-active-temporary-evidence");
					}
					if (!File.Exists(paths.Primary))
					{
						return Missing();
					}
					Guid groupId;
					return TryDecode(ReadBounded(paths.Primary), worldScope, identity, out groupId) ? new GroupActiveSelectionReadResult(GroupActiveSelectionReadState.Ready, "group-active-ready", groupId) : Corrupt("group-active-corrupt");
				}
			}
			catch (ArgumentException)
			{
				return Unavailable("group-active-request-invalid");
			}
			catch (Exception exception) when (IsStorageFailure(exception))
			{
				return Unavailable("group-active-read-unavailable");
			}
		}

		public bool TrySet(string worldScope, StableIdentity identity, Guid groupId, out string reasonCode)
		{
			reasonCode = "group-active-write-unavailable";
			if (identity == null)
			{
				reasonCode = "group-active-identity-missing";
				return false;
			}
			try
			{
				Paths paths = Resolve(worldScope, identity);
				Directory.CreateDirectory(_root);
				using (Acquire(paths.Lock))
				{
					byte[] array = Encode(worldScope, identity, groupId);
					using (FileStream fileStream = new FileStream(paths.Temporary, FileMode.Create, FileAccess.Write, FileShare.None, 4096, FileOptions.WriteThrough))
					{
						fileStream.Write(array, 0, array.Length);
						fileStream.Flush(flushToDisk: true);
					}
					byte[] array2 = ReadBounded(paths.Temporary);
					if (!ExactEquals(array, array2) || !TryDecode(array2, worldScope, identity, out var groupId2) || groupId2 != groupId)
					{
						reasonCode = "group-active-staged-readback-failed";
						return false;
					}
					if (File.Exists(paths.Primary))
					{
						File.Replace(paths.Temporary, paths.Primary, null, ignoreMetadataErrors: true);
					}
					else
					{
						File.Move(paths.Temporary, paths.Primary);
					}
					if (!TryDecode(ReadBounded(paths.Primary), worldScope, identity, out var groupId3) || groupId3 != groupId || File.Exists(paths.Temporary))
					{
						reasonCode = "group-active-commit-readback-failed";
						return false;
					}
					reasonCode = ((groupId == Guid.Empty) ? "group-active-cleared" : "group-active-selected");
					return true;
				}
			}
			catch (ArgumentException)
			{
				reasonCode = "group-active-request-invalid";
				return false;
			}
			catch (Exception exception) when (IsStorageFailure(exception))
			{
				reasonCode = "group-active-write-unavailable";
				return false;
			}
		}

		internal string GetPrimaryPath(string worldScope, StableIdentity identity)
		{
			return Resolve(worldScope, identity).Primary;
		}

		private Paths Resolve(string worldScope, StableIdentity identity)
		{
			worldScope = GroupIdentity.RequireWorldScope(worldScope);
			if (identity == null)
			{
				throw new ArgumentNullException("identity");
			}
			byte[] bytes = StrictUtf8.GetBytes(worldScope + "\n" + identity.CanonicalKey);
			string text;
			using (SHA256 sHA = SHA256.Create())
			{
				byte[] array = sHA.ComputeHash(bytes);
				StringBuilder stringBuilder = new StringBuilder(array.Length * 2);
				for (int i = 0; i < array.Length; i++)
				{
					stringBuilder.Append(array[i].ToString("x2"));
				}
				text = stringBuilder.ToString();
			}
			string text2 = Path.Combine(_root, text + ".active");
			if (!IsExactChild(text2, _root))
			{
				throw new ArgumentException("The active Group path escaped its trusted root.");
			}
			return new Paths(text2, text2 + ".tmp", text2 + ".lock");
		}

		private static byte[] Encode(string worldScope, StableIdentity identity, Guid groupId)
		{
			worldScope = GroupIdentity.RequireWorldScope(worldScope);
			if (identity == null)
			{
				throw new ArgumentNullException("identity");
			}
			byte[] array;
			using (MemoryStream memoryStream = new MemoryStream())
			{
				using BinaryWriter binaryWriter = new BinaryWriter(memoryStream, StrictUtf8, leaveOpen: true);
				binaryWriter.Write(826361682u);
				binaryWriter.Write((ushort)1);
				WriteText(binaryWriter, worldScope, 128);
				WriteText(binaryWriter, identity.Authority, 64);
				WriteText(binaryWriter, identity.SubjectId, 1024);
				binaryWriter.Write(groupId.ToByteArray());
				binaryWriter.Flush();
				array = memoryStream.ToArray();
			}
			byte[] array2;
			using (SHA256 sHA = SHA256.Create())
			{
				array2 = sHA.ComputeHash(array);
			}
			byte[] array3 = new byte[array.Length + array2.Length];
			Buffer.BlockCopy(array, 0, array3, 0, array.Length);
			Buffer.BlockCopy(array2, 0, array3, array.Length, array2.Length);
			if (array3.Length > 2048)
			{
				throw new InvalidDataException();
			}
			return array3;
		}

		private static bool TryDecode(byte[] exact, string expectedWorld, StableIdentity expectedIdentity, out Guid groupId)
		{
			groupId = Guid.Empty;
			if (exact == null || exact.Length < 64 || exact.Length > 2048 || expectedIdentity == null)
			{
				return false;
			}
			int num = exact.Length - 32;
			byte[] array = new byte[num];
			byte[] array2 = new byte[32];
			Buffer.BlockCopy(exact, 0, array, 0, num);
			Buffer.BlockCopy(exact, num, array2, 0, array2.Length);
			byte[] right;
			using (SHA256 sHA = SHA256.Create())
			{
				right = sHA.ComputeHash(array);
			}
			if (!ExactEquals(array2, right))
			{
				return false;
			}
			try
			{
				using MemoryStream memoryStream = new MemoryStream(array, writable: false);
				using BinaryReader binaryReader = new BinaryReader(memoryStream, StrictUtf8, leaveOpen: true);
				if (binaryReader.ReadUInt32() != 826361682 || binaryReader.ReadUInt16() != 1)
				{
					return false;
				}
				string a = ReadText(binaryReader, 128);
				string authority = ReadText(binaryReader, 64);
				string subjectId = ReadText(binaryReader, 1024);
				byte[] array3 = binaryReader.ReadBytes(16);
				if (array3.Length != 16 || memoryStream.Position != memoryStream.Length)
				{
					return false;
				}
				StableIdentity stableIdentity = new StableIdentity(authority, subjectId);
				if (!string.Equals(a, GroupIdentity.RequireWorldScope(expectedWorld), StringComparison.Ordinal) || !stableIdentity.Equals(expectedIdentity))
				{
					return false;
				}
				groupId = new Guid(array3);
				return true;
			}
			catch (Exception ex) when (ex is ArgumentException || ex is IOException || ex is DecoderFallbackException)
			{
				return false;
			}
		}

		private static void WriteText(BinaryWriter writer, string value, int maximumBytes)
		{
			byte[] bytes = StrictUtf8.GetBytes(value ?? string.Empty);
			if (bytes.Length < 1 || bytes.Length > maximumBytes || bytes.Length > 65535)
			{
				throw new ArgumentOutOfRangeException("value");
			}
			writer.Write((ushort)bytes.Length);
			writer.Write(bytes);
		}

		private static string ReadText(BinaryReader reader, int maximumBytes)
		{
			int num = reader.ReadUInt16();
			if (num < 1 || num > maximumBytes || num > reader.BaseStream.Length - reader.BaseStream.Position)
			{
				throw new InvalidDataException();
			}
			byte[] array = reader.ReadBytes(num);
			string text = StrictUtf8.GetString(array);
			if (!ExactEquals(array, StrictUtf8.GetBytes(text)))
			{
				throw new InvalidDataException();
			}
			return text;
		}

		private static FileStream Acquire(string path)
		{
			return new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None, 1, FileOptions.WriteThrough);
		}

		private static byte[] ReadBounded(string path)
		{
			using FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.None, 4096, FileOptions.SequentialScan);
			if (fileStream.Length < 1 || fileStream.Length > 2048)
			{
				throw new InvalidDataException();
			}
			byte[] array = new byte[(int)fileStream.Length];
			int num;
			for (int i = 0; i < array.Length; i += num)
			{
				num = fileStream.Read(array, i, array.Length - i);
				if (num <= 0)
				{
					throw new EndOfStreamException();
				}
			}
			return array;
		}

		private static bool ExactEquals(byte[] left, byte[] right)
		{
			if (left == null || right ==