diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index 527e5b2..256759e 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -14,10 +14,10 @@ jobs: benchmark: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Setup .NET - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@v5 with: dotnet-version: | 8.0.x @@ -58,7 +58,7 @@ jobs: - name: Upload raw artifacts if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: benchmark-results-${{ github.run_number }} path: PasswordGenerator.Benchmarks/BenchmarkDotNet.Artifacts/results/ diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0e291c2..d04e877 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,12 +13,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 0 # full history so SourceLink can resolve the commit - name: Setup .NET - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@v5 with: dotnet-version: | 8.0.x @@ -39,7 +39,7 @@ jobs: - name: Upload test results if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: test-results path: ./test-results/*.trx @@ -48,7 +48,7 @@ jobs: run: dotnet pack PasswordGenerator/PasswordGenerator.csproj -c Release --no-build -o artifacts - name: Upload package - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: nuget path: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e392bd6..07e75c6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,12 +12,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 0 # full history so SourceLink can resolve the commit - name: Setup .NET - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@v5 with: dotnet-version: | 8.0.x diff --git a/PasswordGenerator.Benchmarks/Benchmarks/AsyncBenchmarks.cs b/PasswordGenerator.Benchmarks/Benchmarks/AsyncBenchmarks.cs index a0ab1ef..05b597d 100644 --- a/PasswordGenerator.Benchmarks/Benchmarks/AsyncBenchmarks.cs +++ b/PasswordGenerator.Benchmarks/Benchmarks/AsyncBenchmarks.cs @@ -26,13 +26,13 @@ public void Cleanup() } [Benchmark] - public Task NextAsync() + public ValueTask NextAsync() { return _password.NextAsync(); } [Benchmark] - public Task> GenerateAsync() + public ValueTask> GenerateAsync() { return _password.GenerateAsync(Count); } diff --git a/PasswordGenerator.Tests/Phase3Tests.cs b/PasswordGenerator.Tests/Phase3Tests.cs index 095e3a0..2758096 100644 --- a/PasswordGenerator.Tests/Phase3Tests.cs +++ b/PasswordGenerator.Tests/Phase3Tests.cs @@ -29,6 +29,30 @@ public void NextAsync_WithAlreadyCancelledToken_Throws() Throws.InstanceOf()); } + [Test] + public void NextAsync_WithCancelledToken_SurfacesCancellationThroughTask() + { + var pwd = new Password(20); + var cts = new CancellationTokenSource(); + cts.Cancel(); + + // Cancellation must come back through the returned task, not as a synchronous throw, + // so the result composes correctly when not awaited immediately. + var task = pwd.NextAsync(cts.Token); + Assert.That(task.IsCanceled, Is.True); + } + + [Test] + public void GenerateAsync_WithCancelledToken_SurfacesCancellationThroughTask() + { + var pwd = new Password(16); + var cts = new CancellationTokenSource(); + cts.Cancel(); + + var task = pwd.GenerateAsync(10, cts.Token); + Assert.That(task.IsCanceled, Is.True); + } + [Test] public void Generate_ReturnsRequestedCount() { diff --git a/PasswordGenerator/CharacterClass.cs b/PasswordGenerator/CharacterClass.cs index ea525c7..e5947f3 100644 --- a/PasswordGenerator/CharacterClass.cs +++ b/PasswordGenerator/CharacterClass.cs @@ -6,9 +6,16 @@ namespace PasswordGenerator /// public enum CharacterClass { + /// Lowercase letters (a–z). Lowercase, + + /// Uppercase letters (A–Z). Uppercase, + + /// Digits (0–9). Numeric, + + /// Special / symbol characters. Special } } diff --git a/PasswordGenerator/CryptoRandomSource.cs b/PasswordGenerator/CryptoRandomSource.cs index 93a220e..d3f7ace 100644 --- a/PasswordGenerator/CryptoRandomSource.cs +++ b/PasswordGenerator/CryptoRandomSource.cs @@ -10,6 +10,15 @@ namespace PasswordGenerator /// public sealed class CryptoRandomSource : IRandomSource { + /// + /// Returns a uniformly distributed, non-negative random integer that is less than + /// . + /// + /// The exclusive upper bound; must be positive. + /// A random integer in the range [0, maxExclusive). + /// + /// is zero or negative. + /// public int NextInt(int maxExclusive) { if (maxExclusive <= 0) diff --git a/PasswordGenerator/IPassword.cs b/PasswordGenerator/IPassword.cs index 70fe6ee..a778230 100644 --- a/PasswordGenerator/IPassword.cs +++ b/PasswordGenerator/IPassword.cs @@ -2,12 +2,31 @@ namespace PasswordGenerator { + /// + /// Fluent builder for configuring and generating passwords. Each configuration method returns the + /// same instance so calls can be chained. + /// public interface IPassword { + /// Includes lowercase letters in the pool. + /// The same builder, for chaining. IPassword IncludeLowercase(); + + /// Includes uppercase letters in the pool. + /// The same builder, for chaining. IPassword IncludeUppercase(); + + /// Includes digits in the pool. + /// The same builder, for chaining. IPassword IncludeNumeric(); + + /// Includes the default special characters in the pool. + /// The same builder, for chaining. IPassword IncludeSpecial(); + + /// Includes the given special characters in the pool. + /// The special characters to add to the pool. + /// The same builder, for chaining. IPassword IncludeSpecial(string specialCharactersToInclude); /// Replaces the pool with an explicit set of characters (no forced composition). @@ -22,9 +41,25 @@ public interface IPassword /// Requires at least characters from the given class. IPassword RequireAtLeast(CharacterClass characterClass, int count); + /// Sets the required password length. + /// The number of characters the generated password should contain. + /// The same builder, for chaining. IPassword LengthRequired(int passwordLength); + + /// Generates a single password using the current settings. + /// The generated password. string Next(); + + /// Attempts to generate a single password without throwing on invalid settings. + /// + /// When this method returns , the generated password; otherwise . + /// + /// if a password was generated; otherwise . bool TryNext(out string? password); + + /// Generates a sequence of passwords using the current settings. + /// How many passwords to generate. + /// The generated passwords. IEnumerable NextGroup(int numberOfPasswordsToGenerate); } } \ No newline at end of file diff --git a/PasswordGenerator/IPasswordGenerator.cs b/PasswordGenerator/IPasswordGenerator.cs index b842dc4..e3651c9 100644 --- a/PasswordGenerator/IPasswordGenerator.cs +++ b/PasswordGenerator/IPasswordGenerator.cs @@ -17,10 +17,12 @@ public interface IPasswordGenerator bool TryNext(out string? password); /// - /// Generates a single password. Generation is CPU-bound; this overload exists for ergonomics - /// and to honour cancellation, not to offload work to another thread. + /// Generates a single password. Generation is CPU-bound and completes synchronously; this overload + /// exists for ergonomics and to honour cancellation, not to offload work to another thread. A + /// is used because the result is always available synchronously. + /// If is already cancelled, the returned task is cancelled. /// - Task NextAsync(CancellationToken cancellationToken = default); + ValueTask NextAsync(CancellationToken cancellationToken = default); /// Generates the default number of passwords (configurable; one unless overridden). IReadOnlyList Generate(); @@ -29,9 +31,9 @@ public interface IPasswordGenerator IReadOnlyList Generate(int count); /// Generates the default number of passwords, observing . - Task> GenerateAsync(CancellationToken cancellationToken = default); + ValueTask> GenerateAsync(CancellationToken cancellationToken = default); /// Generates passwords, observing . - Task> GenerateAsync(int count, CancellationToken cancellationToken = default); + ValueTask> GenerateAsync(int count, CancellationToken cancellationToken = default); } } diff --git a/PasswordGenerator/IPasswordSettings.cs b/PasswordGenerator/IPasswordSettings.cs index 8dbebd3..ddd2b97 100644 --- a/PasswordGenerator/IPasswordSettings.cs +++ b/PasswordGenerator/IPasswordSettings.cs @@ -7,11 +7,22 @@ namespace PasswordGenerator /// public interface IPasswordSettings { + /// Whether lowercase letters are included in the pool. bool IncludeLowercase { get; } + + /// Whether uppercase letters are included in the pool. bool IncludeUppercase { get; } + + /// Whether digits are included in the pool. bool IncludeNumeric { get; } + + /// Whether special characters are included in the pool. bool IncludeSpecial { get; } + + /// The number of characters the generated password should contain. int PasswordLength { get; set; } + + /// The full set of characters the password is drawn from, after applying all settings. string CharacterSet { get; } /// True when a custom pool (e.g. ) replaces the per-class sets. @@ -28,13 +39,35 @@ public interface IPasswordSettings /// guarantee at least one character from each required class is present in the output. /// IReadOnlyList CharacterGroups { get; } + + /// The maximum number of attempts allowed when generating a valid password. int MaximumAttempts { get; } + + /// The smallest allowed password length. int MinimumLength { get; } + + /// The largest allowed password length. int MaximumLength { get; } + + /// Enables lowercase letters in the pool. + /// The same settings, for chaining. IPasswordSettings AddLowercase(); + + /// Enables uppercase letters in the pool. + /// The same settings, for chaining. IPasswordSettings AddUppercase(); + + /// Enables digits in the pool. + /// The same settings, for chaining. IPasswordSettings AddNumeric(); + + /// Enables the default special characters in the pool. + /// The same settings, for chaining. IPasswordSettings AddSpecial(); + + /// Enables the given special characters in the pool. + /// The special characters to add to the pool. + /// The same settings, for chaining. IPasswordSettings AddSpecial(string specialCharactersToAdd); /// Replaces the entire pool with an explicit set of characters (no forced composition). @@ -49,6 +82,7 @@ public interface IPasswordSettings /// Requires at least characters from the given class, enabling it if needed. IPasswordSettings RequireAtLeast(CharacterClass characterClass, int count); + /// The special characters used when special characters are included. string SpecialCharacters { get; set; } } } \ No newline at end of file diff --git a/PasswordGenerator/PassphraseGenerator.cs b/PasswordGenerator/PassphraseGenerator.cs index e4ea7b7..00549e1 100644 --- a/PasswordGenerator/PassphraseGenerator.cs +++ b/PasswordGenerator/PassphraseGenerator.cs @@ -16,6 +16,16 @@ public class PassphraseGenerator : IPasswordGenerator, IDisposable private readonly IRandomSource _random; private readonly bool _ownsRandom; + /// Creates a passphrase generator. + /// The number of words in each passphrase; must be at least one. + /// The character placed between words (and before the trailing number). + /// Whether to capitalize the first letter of each word. + /// Whether to append a random two-digit number. + /// + /// An optional random source. When supplied, the caller owns it; otherwise a + /// is created and owned by this instance. + /// + /// is less than one. public PassphraseGenerator(int wordCount = 4, char separator = '-', bool capitalize = false, bool includeNumber = true, IRandomSource? randomSource = null) { @@ -30,14 +40,22 @@ public PassphraseGenerator(int wordCount = 4, char separator = '-', bool capital _ownsRandom = randomSource == null; } + /// The number of words in each passphrase. public int WordCount { get; } + + /// The character placed between words. public char Separator { get; } + + /// Whether the first letter of each word is capitalized. public bool Capitalize { get; } + + /// Whether a random two-digit number is appended. public bool IncludeNumber { get; } /// The number of passphrases produced by the parameterless overload. public int DefaultBatchCount { get; set; } = 1; + /// public string Next() { var sb = new StringBuilder(); @@ -67,23 +85,28 @@ public string Next() return sb.ToString(); } + /// public bool TryNext(out string? password) { password = Next(); return true; } - public Task NextAsync(CancellationToken cancellationToken = default) + /// + public ValueTask NextAsync(CancellationToken cancellationToken = default) { - cancellationToken.ThrowIfCancellationRequested(); - return Task.FromResult(Next()); + return cancellationToken.IsCancellationRequested + ? ValueTask.FromCanceled(cancellationToken) + : new ValueTask(Next()); } + /// public IReadOnlyList Generate() { return Generate(DefaultBatchCount); } + /// public IReadOnlyList Generate(int count) { if (count < 0) @@ -96,24 +119,30 @@ public IReadOnlyList Generate(int count) return passphrases; } - public Task> GenerateAsync(CancellationToken cancellationToken = default) + /// + public ValueTask> GenerateAsync(CancellationToken cancellationToken = default) { return GenerateAsync(DefaultBatchCount, cancellationToken); } - public Task> GenerateAsync(int count, CancellationToken cancellationToken = default) + /// + public ValueTask> GenerateAsync(int count, CancellationToken cancellationToken = default) { if (count < 0) throw new ArgumentOutOfRangeException(nameof(count), "count cannot be negative."); + if (cancellationToken.IsCancellationRequested) + return ValueTask.FromCanceled>(cancellationToken); + var passphrases = new List(count); for (var i = 0; i < count; i++) { - cancellationToken.ThrowIfCancellationRequested(); + if (cancellationToken.IsCancellationRequested) + return ValueTask.FromCanceled>(cancellationToken); passphrases.Add(Next()); } - return Task.FromResult>(passphrases); + return new ValueTask>(passphrases); } /// Estimates passphrase entropy in bits from the word-list size, word count, and trailing number. @@ -124,6 +153,7 @@ public double EstimateEntropyBits() return bits; } + /// Disposes the random source when this instance owns it (i.e. it was not supplied by the caller). public void Dispose() { if (_ownsRandom && _random is IDisposable disposable) diff --git a/PasswordGenerator/Password.cs b/PasswordGenerator/Password.cs index 4e44763..8cc48f5 100644 --- a/PasswordGenerator/Password.cs +++ b/PasswordGenerator/Password.cs @@ -20,6 +20,7 @@ public class Password : IPassword, IPasswordGenerator, IDisposable private readonly IRandomSource _random; private readonly bool _ownsRandom; + /// Creates a generator with the default settings (all character classes, length 16). public Password() { Settings = new PasswordSettings(DefaultIncludeLowercase, DefaultIncludeUppercase, @@ -29,6 +30,8 @@ public Password() _ownsRandom = true; } + /// Creates a generator from the supplied settings. + /// The settings to use. public Password(IPasswordSettings settings) { Settings = settings; @@ -36,6 +39,8 @@ public Password(IPasswordSettings settings) _ownsRandom = true; } + /// Creates a generator with the default character classes and the given length. + /// The required password length. public Password(int passwordLength) { Settings = new PasswordSettings(DefaultIncludeLowercase, DefaultIncludeUppercase, @@ -44,6 +49,11 @@ public Password(int passwordLength) _ownsRandom = true; } + /// Creates a generator with the given character classes enabled and the default length. + /// Whether to include lowercase letters. + /// Whether to include uppercase letters. + /// Whether to include digits. + /// Whether to include special characters. public Password(bool includeLowercase, bool includeUppercase, bool includeNumeric, bool includeSpecial) { Settings = new PasswordSettings(includeLowercase, includeUppercase, includeNumeric, @@ -52,6 +62,12 @@ public Password(bool includeLowercase, bool includeUppercase, bool includeNumeri _ownsRandom = true; } + /// Creates a generator with the given character classes enabled and a specific length. + /// Whether to include lowercase letters. + /// Whether to include uppercase letters. + /// Whether to include digits. + /// Whether to include special characters. + /// The required password length. public Password(bool includeLowercase, bool includeUppercase, bool includeNumeric, bool includeSpecial, int passwordLength) { @@ -61,6 +77,13 @@ public Password(bool includeLowercase, bool includeUppercase, bool includeNumeri _ownsRandom = true; } + /// Creates a generator with the given character classes, length, and attempt limit. + /// Whether to include lowercase letters. + /// Whether to include uppercase letters. + /// Whether to include digits. + /// Whether to include special characters. + /// The required password length. + /// The maximum number of generation attempts. public Password(bool includeLowercase, bool includeUppercase, bool includeNumeric, bool includeSpecial, int passwordLength, int maximumAttempts) { @@ -81,62 +104,73 @@ public Password(IPasswordSettings settings, IRandomSource randomSource) _ownsRandom = false; } + /// The settings that drive generation. Replaced in place by the fluent configuration methods. public IPasswordSettings Settings { get; set; } + /// public IPassword IncludeLowercase() { Settings = Settings.AddLowercase(); return this; } + /// public IPassword IncludeUppercase() { Settings = Settings.AddUppercase(); return this; } + /// public IPassword IncludeNumeric() { Settings = Settings.AddNumeric(); return this; } + /// public IPassword IncludeSpecial() { Settings = Settings.AddSpecial(); return this; } + /// public IPassword IncludeSpecial(string specialCharactersToInclude) { Settings = Settings.AddSpecial(specialCharactersToInclude); return this; } + /// public IPassword WithCharacters(string characters) { Settings = Settings.UseCharacters(characters); return this; } + /// public IPassword WithAllAscii() { Settings = Settings.UseAllAscii(); return this; } + /// public IPassword ExcludeAmbiguous() { Settings = Settings.ExcludeAmbiguousCharacters(); return this; } + /// public IPassword RequireAtLeast(CharacterClass characterClass, int count) { Settings = Settings.RequireAtLeast(characterClass, count); return this; } + /// public IPassword LengthRequired(int passwordLength) { Settings.PasswordLength = passwordLength; @@ -182,22 +216,27 @@ public bool TryNext(out string? password) return true; } + /// public IEnumerable NextGroup(int numberOfPasswordsToGenerate) { return Generate(numberOfPasswordsToGenerate); } - public Task NextAsync(CancellationToken cancellationToken = default) + /// + public ValueTask NextAsync(CancellationToken cancellationToken = default) { - cancellationToken.ThrowIfCancellationRequested(); - return Task.FromResult(Next()); + return cancellationToken.IsCancellationRequested + ? ValueTask.FromCanceled(cancellationToken) + : new ValueTask(Next()); } + /// public IReadOnlyList Generate() { return Generate(DefaultBatchCount); } + /// public IReadOnlyList Generate(int count) { if (count < 0) @@ -210,24 +249,30 @@ public IReadOnlyList Generate(int count) return passwords; } - public Task> GenerateAsync(CancellationToken cancellationToken = default) + /// + public ValueTask> GenerateAsync(CancellationToken cancellationToken = default) { return GenerateAsync(DefaultBatchCount, cancellationToken); } - public Task> GenerateAsync(int count, CancellationToken cancellationToken = default) + /// + public ValueTask> GenerateAsync(int count, CancellationToken cancellationToken = default) { if (count < 0) throw new ArgumentOutOfRangeException(nameof(count), "count cannot be negative."); + if (cancellationToken.IsCancellationRequested) + return ValueTask.FromCanceled>(cancellationToken); + var passwords = new List(count); for (var i = 0; i < count; i++) { - cancellationToken.ThrowIfCancellationRequested(); + if (cancellationToken.IsCancellationRequested) + return ValueTask.FromCanceled>(cancellationToken); passwords.Add(Next()); } - return Task.FromResult>(passwords); + return new ValueTask>(passwords); } private static readonly CharacterClass[] OrderedClasses = @@ -369,6 +414,7 @@ private static bool LengthIsValid(int passwordLength, int minLength, int maxLeng return passwordLength >= minLength && passwordLength <= maxLength; } + /// Disposes the random source when this instance owns it (i.e. it was not supplied by the caller). public void Dispose() { if (_ownsRandom && _random is IDisposable disposable) diff --git a/PasswordGenerator/PasswordGenerator.csproj b/PasswordGenerator/PasswordGenerator.csproj index ae8ce7a..a4c08df 100644 --- a/PasswordGenerator/PasswordGenerator.csproj +++ b/PasswordGenerator/PasswordGenerator.csproj @@ -5,6 +5,9 @@ enable latest + + true + 3.0.0 3.0.0.0 diff --git a/PasswordGenerator/PasswordOptions.cs b/PasswordGenerator/PasswordOptions.cs index b9eb066..a025bbe 100644 --- a/PasswordGenerator/PasswordOptions.cs +++ b/PasswordGenerator/PasswordOptions.cs @@ -6,14 +6,22 @@ namespace PasswordGenerator /// public class PasswordOptions { + /// Whether lowercase letters are included in the pool. Defaults to . public bool IncludeLowercase { get; set; } = true; + + /// Whether uppercase letters are included in the pool. Defaults to . public bool IncludeUppercase { get; set; } = true; + + /// Whether digits are included in the pool. Defaults to . public bool IncludeNumeric { get; set; } = true; + + /// Whether special characters are included in the pool. Defaults to . public bool IncludeSpecial { get; set; } = true; /// Custom special characters. When null/empty the library default special set is used. public string? SpecialCharacters { get; set; } + /// The password length. Defaults to 16. public int Length { get; set; } = 16; /// Removes look-alike characters from the pool when true. diff --git a/PasswordGenerator/PasswordSettings.cs b/PasswordGenerator/PasswordSettings.cs index 26b6431..5f9c947 100644 --- a/PasswordGenerator/PasswordSettings.cs +++ b/PasswordGenerator/PasswordSettings.cs @@ -9,14 +9,33 @@ namespace PasswordGenerator /// public class PasswordSettings : IPasswordSettings { + /// The lowercase letters (a–z) used when lowercase is enabled. public const string LowercaseCharacters = "abcdefghijklmnopqrstuvwxyz"; + + /// The uppercase letters (A–Z) used when uppercase is enabled. public const string UppercaseCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + /// The digits (0–9) used when numeric is enabled. public const string NumericCharacters = "0123456789"; + private const string DefaultSpecialCharacters = @"!#$%&*@\"; private const int DefaultMinPasswordLength = 4; private const int DefaultMaxPasswordLength = 256; + + /// public string SpecialCharacters { get; set; } + /// Creates a settings instance. + /// Whether to include lowercase letters. + /// Whether to include uppercase letters. + /// Whether to include digits. + /// Whether to include special characters. + /// The required password length. + /// The maximum number of generation attempts. + /// + /// Whether these are the library defaults; when , the first fluent + /// configuration call clears the default pool before applying changes. + /// public PasswordSettings(bool includeLowercase, bool includeUppercase, bool includeNumeric, bool includeSpecial, int passwordLength, int maximumAttempts, bool usingDefaults) { @@ -36,19 +55,43 @@ public PasswordSettings(bool includeLowercase, bool includeUppercase, bool inclu private bool UsingDefaults { get; set; } private readonly Dictionary _minimumCounts = new Dictionary(); + /// public bool IncludeLowercase { get; private set; } + + /// public bool IncludeUppercase { get; private set; } + + /// public bool IncludeNumeric { get; private set; } + + /// public bool IncludeSpecial { get; private set; } + + /// public int PasswordLength { get; set; } + + /// public string CharacterSet { get; private set; } + + /// public bool IsCustomPool { get; private set; } + + /// public bool ExcludeAmbiguous { get; private set; } + + /// public IReadOnlyDictionary MinimumCounts => _minimumCounts; + + /// public int MaximumAttempts { get; } + + /// public int MinimumLength { get; } + + /// public int MaximumLength { get; } + /// public IReadOnlyList CharacterGroups { get @@ -62,6 +105,7 @@ public IReadOnlyList CharacterGroups } } + /// public IPasswordSettings AddLowercase() { StopUsingDefaults(); @@ -70,6 +114,7 @@ public IPasswordSettings AddLowercase() return this; } + /// public IPasswordSettings AddUppercase() { StopUsingDefaults(); @@ -78,6 +123,7 @@ public IPasswordSettings AddUppercase() return this; } + /// public IPasswordSettings AddNumeric() { StopUsingDefaults(); @@ -86,6 +132,7 @@ public IPasswordSettings AddNumeric() return this; } + /// public IPasswordSettings AddSpecial() { StopUsingDefaults(); @@ -95,6 +142,7 @@ public IPasswordSettings AddSpecial() return this; } + /// public IPasswordSettings AddSpecial(string specialCharactersToAdd) { StopUsingDefaults(); @@ -104,6 +152,7 @@ public IPasswordSettings AddSpecial(string specialCharactersToAdd) return this; } + /// public IPasswordSettings UseCharacters(string characters) { if (characters == null) throw new ArgumentNullException(nameof(characters)); @@ -119,17 +168,20 @@ public IPasswordSettings UseCharacters(string characters) return this; } + /// public IPasswordSettings UseAllAscii() { return UseCharacters(CharacterFilter.AllPrintableAscii); } + /// public IPasswordSettings ExcludeAmbiguousCharacters() { ExcludeAmbiguous = true; return this; } + /// public IPasswordSettings RequireAtLeast(CharacterClass characterClass, int count) { if (count < 0) throw new ArgumentOutOfRangeException(nameof(count), "count cannot be negative."); diff --git a/PasswordGenerator/PoolEntropyEstimator.cs b/PasswordGenerator/PoolEntropyEstimator.cs index 0bd8753..b8a2308 100644 --- a/PasswordGenerator/PoolEntropyEstimator.cs +++ b/PasswordGenerator/PoolEntropyEstimator.cs @@ -7,6 +7,15 @@ namespace PasswordGenerator /// public class PoolEntropyEstimator : IEntropyEstimator { + /// + /// Estimates the entropy, in bits, of passwords produced with the given + /// as length × log2(effective pool size). + /// + /// The settings describing the character pool and length. + /// + /// The estimated entropy in bits, or 0 when the pool is empty or the length is not positive. + /// + /// is . public double EstimateBits(IPasswordSettings settings) { if (settings == null) throw new ArgumentNullException(nameof(settings)); diff --git a/docs/api-surface.md b/docs/api-surface.md index 61723f4..7e3fffc 100644 --- a/docs/api-surface.md +++ b/docs/api-surface.md @@ -28,7 +28,7 @@ flowchart TD subgraph Gen["Generation (IPasswordGenerator)"] g1["Next() : string (throws on bad config)"] g2["TryNext(out string) : bool"] - g3["NextAsync(ct) : Task~string~"] + g3["NextAsync(ct) : ValueTask~string~"] g4["Generate() / Generate(count)"] g5["GenerateAsync() / GenerateAsync(count, ct)"] end