Giter Site home page Giter Site logo

junian / standard.licensing Goto Github PK

View Code? Open in Web Editor NEW
526.0 31.0 120.0 12.03 MB

Easy-to-use licensing library for .NET Framework, Mono, .NET Core, and MAUI / Xamarin products

Home Page: https://junian.dev/Standard.Licensing/

License: MIT License

C# 100.00%
licensing netcore netstandard mono netframework xamarin dotnet

standard.licensing's Introduction

Standard.Licensing Logo

Standard.Licensing

Easy-to-use licensing library for .NET and .NET Framework products.

Standard.Licensing latest version on NuGet Standard.Licensing total downloads on NuGet


Installation

Get Standard.Licensing from NuGet.

dotnet add package Standard.Licensing

Usage

Create a private and public key for your product

Standard.Licensing uses the Elliptic Curve Digital Signature Algorithm (ECDSA) to ensure the license cannot be altered after creation.

First you need to create a new public/private key pair for your product:

var keyGenerator = Standard.Licensing.Security.Cryptography.KeyGenerator.Create(); 
var keyPair = keyGenerator.GenerateKeyPair(); 
var privateKey = keyPair.ToEncryptedPrivateKeyString(passPhrase);  
var publicKey = keyPair.ToPublicKeyString();

Store the private key securely and distribute the public key with your product. Normally you create one key pair for each product, otherwise it is possible to use a license with all products using the same key pair. If you want your customer to buy a new license on each major release you can create a key pair for each release and product.

Create the license generator

Now we need something to generate licenses. This could be easily done with the LicenseFactory:

var license = License.New()  
    .WithUniqueIdentifier(Guid.NewGuid())  
    .As(LicenseType.Trial)  
    .ExpiresAt(DateTime.Now.AddDays(30))  
    .WithMaximumUtilization(5)  
    .WithProductFeatures(new Dictionary<string, string>  
        {  
            {"Sales Module", "yes"},  
            {"Purchase Module", "yes"},  
            {"Maximum Transactions", "10000"}  
        })  
    .LicensedTo("John Doe", "[email protected]")  
    .CreateAndSignWithPrivateKey(privateKey, passPhrase);

Now you can take the license and save it to a file:

File.WriteAllText("License.lic", license.ToString(), Encoding.UTF8);

or

license.Save(xmlWriter);

Validate the license in your application

The easiest way to assert the license is in the entry point of your application.

First load the license from a file or resource:

var license = License.Load(...);

Then you can assert the license:

using Standard.Licensing.Validation;

var validationFailures = license.Validate()  
                                .ExpirationDate(systemDateTime: DateTime.Now)  
                                .When(lic => lic.Type == LicenseType.Trial)  
                                .And()  
                                .Signature(publicKey)  
                                .AssertValidLicense();

Standard.Licensing will not throw any Exception and just return an enumeration of validation failures.

Now you can iterate over possible validation failures:

foreach (var failure in validationFailures)
     Console.WriteLine(failure.GetType().Name + ": " + failure.Message + " - " + failure.HowToResolve);

Or simply check if there is any failure:

if (validationFailures.Any())
   // ...

Make sure to call validationFailures.ToList() or validationFailures.ToArray() before using the result multiple times.

Third Party Projects

Credits

This is project is derived from Portable.Licensing library. The purpose of this fork is to add support for more .NET platforms, especially .NET Standard and .NET Core.

License

This project is licensed under MIT License.


Made with โ˜• by Junian.dev.

standard.licensing's People

Contributors

antiguideakquinet avatar braincrumbz avatar dnauck avatar forki avatar junian avatar kfrancis avatar sven-s avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

standard.licensing's Issues

validationFailures.ToList() not working

It seems that after iterate over the failures collection, calling ToList() or ToArray() doesn't populate it again.
You can test with this code.
I've generated an xml license file (copy/paste from main page), changed the expiration date inside the xml file and called the verification.
Any returns true, but the foreach is skipped.

if (validationFailures.Any()) { validationFailures.ToList(); foreach (var failure in validationFailures.ToList()) list.Items.Add(failure.GetType().Name + ": " + failure.Message + " - " + failure.HowToResolve); } else list.Items.Add("All ok");

What are valid options for ProductFeatures

I was wondering where I can see what the .WithProductFeatures(Dictionary<string, string> productFeatures) function actually does and where I could see which options I could set there?

How to use in license server that package?

Hi!
I have a windows service that I want to license on the remote server. For example, I will give my client license key for 2 servers. Cannot install on 3rd server. Can I perform this editing with this package? Why?

know installation done on new Hardware ? [Question]

Hi ,

if we validate license with PC , and same distribution copied to new PC , i need to installation copied so i will ask user to enter some password or whatever feature , is it possible ?

in first run of application , my idea to ask user enter a key and i will validated once after that in each run of application i need to run validation but i don't want to ask user to entered key ? also it is not right to save Key in database even if it is hashed ?

thanks in advance

Multiple enumeration of AssertValidLicense results in incorrect validation

When given an invalid license, AssertValidLicense returns an IEnumerable with a ValidationFailure on the first enumeration but an empty IEnumerable on the second enumeration.

As a workaround, I suggest users of the library use ToArray or alike to enumerate the IEnumerable only once.

The reason for this behavior is the current implementation of the validators collection in ValidationChainBuilder as a queue. In AssertValidLicense, all validators will be dequeued every time, resulting in different results.

I suggest either the usage of a list or the change of AssertValidLicense behavior to throw instead of returning failures. The name indicates a throw on invalid licenses and could potentially be dangerous to people relying on the implied behavior.

using Standard.Licensing;
using Standard.Licensing.Validation;

var keyGenerator = Standard.Licensing.Security.Cryptography.KeyGenerator.Create();
var keyPair = keyGenerator.GenerateKeyPair();
var publicKey = keyPair.ToPublicKeyString();

var invalidLicense = @"<License>
  <Signature>WFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFg=</Signature>
</License>";

var licenseToVerify = License.Load(invalidLicense);

var validationFailures = licenseToVerify
    .Validate()
    .Signature(publicKey)
    .AssertValidLicense();

Console.WriteLine("validationFailures.Length: " + validationFailures.ToArray().Length);
Console.WriteLine("validationFailures.Length: " + validationFailures.ToArray().Length);

// validationFailures.Length: 1
// validationFailures.Length: 0

WithMaximumUtilization

Due to lack of documentation, I was wondering what does the .WithMaximumUtilization(1) would do when a license is generated? Does this mean the license allowed 1 installation? If this is the case how is it validated?

Without ExpireAt?

Hi, is it possible to generate a Licence without an ExpiresAt condition?
Thx

invalid signature string throws unhandled exception

When modifying the signature base64 string, the validation process won't produce a proper validation failure, but throws an unhandled exception. Maybe this can be fixed by checking if the signature string is even valid.

Pad Block Corrupted Exception

Hello. After reading a private key created with

KeyGenerator keyGen = KeyGenerator.Create();
KeyPair keyPair = keyGen.GenerateKeyPair();
string privateKey = keyPair.ToEncryptedPrivateKeyString(args[Array.IndexOf(args, "--private-key") + 1]);
string publicKey = keyPair.ToPublicKeyString();
File.WriteAllText("privKey.txt", privateKey, Encoding.ASCII);
File.WriteAllText("pubKey.txt", publicKey, Encoding.ASCII);

I am unable to create a license by reading the file:

var utilization = Int32.Parse(args[Array.IndexOf(args, "--license-std") + 3]);
var name = args[Array.IndexOf(args, "--license-std") + 1];
var email = args[Array.IndexOf(args, "--license-std") + 2];
var privKeyLocation = args[Array.IndexOf(args, "--license-std") + 4];
var passPhrase = args[Array.IndexOf(args, "--license-std") + 5];
var privKey = File.ReadAllBytes(privKeyLocation);
var privKeyStr = Encoding.ASCII.GetString(privKey);
ILicenseBuilder license = License.New()
	.WithUniqueIdentifier(Guid.NewGuid())
	.As(LicenseType.Standard)
	.WithMaximumUtilization(utilization)
	.LicensedTo(name, email);
License l = license.CreateAndSignWithPrivateKey(privKeyStr, passPhrase);

due to an exception thrown by BouncyCastle:

Org.BouncyCastle.Crypto.InvalidCipherTextException: 'pad block corrupted'

LicenseType is not validated

A license is returned as valid despite the different license types, e.g. if License.LicenseType = LicneseType.Trail and one tries to use the following example from your documentation and using the following code:

var passPhrase = "TestPassword101";
var keyGenerator = Standard.Licensing.Security.Cryptography.KeyGenerator.Create();
var keyPair = keyGenerator.GenerateKeyPair();
var privateKey = keyPair.ToEncryptedPrivateKeyString (passPhrase);
var publicKey = keyPair.ToPublicKeyString();

var license = License.New()
    .WithUniqueIdentifier (Guid.NewGuid())
    .As (LicenseType.Trial)
    .ExpiresAt (DateTime.Now.AddDays (30))
    .WithMaximumUtilization (5)
    .WithProductFeatures (new Dictionary<string, string>
        {
            {"Sales Module", "yes"},
            {"Purchase Module", "yes"},
            {"Maximum Transactions", "10000"}
        })
    .LicensedTo ("John Doe", "[email protected]")
    .CreateAndSignWithPrivateKey (privateKey, passPhrase);

var savedLicense = license.ToString();

var licenseToVerify = License.Load (savedLicense);

var validationFailures = license.Validate()
                                .ExpirationDate()
                                .When (lic => lic.Type == LicenseType.Standard)
                                .And()
                                .Signature (publicKey)
                                .AssertValidLicense();

if (validationFailures.Any())
{
    foreach (var failure in validationFailures)
        Console.WriteLine (failure.GetType().Name + ": " + failure.Message + " - " + failure.HowToResolve);
}
else
    Console.WriteLine ("License valid!");


var errors = license.Validate()
       .AssertThat (lic => lic.Type == LicenseType.Standard,
                    new GeneralValidationFailure ("License type wrong!"))
       .AssertValidLicense().ToList();

Console.WriteLine ($"\nErrors found: {errors.Any ()}");

if (errors.Any())
{
    foreach (var failure in errors)
        Console.WriteLine (failure.GetType().Name + ": " + failure.Message + " - " + failure.HowToResolve);
}
else
    Console.WriteLine ("License valid!");

then no validationFailures.Any() == true! but should be false.

When using the Assert.That then the error is detected as can be seen if the above code is run.

Either I did not understand the purpose of the When clause or it is an error.

In the former case, I'd be happy to learn the purpose and in the later to learn about the correction.

Cheers,

Peter

.net6+

Is there any plan to port to .net6+?

AssertThat throws error if Attribute or Feature deos not exist

In a .Net Framework (4.6.2) application.

If you have a licence which has been generated with an attribue of "Random1" and when you validate it you try to get an attribute of "Random2" you get a System.NullReferenceException when using .AssertThat(lic => lic.AdditionalAttributes.Get("Random2")

Same happens if you specify a feature which does not exist in the licence.

I would expect both of these to gracefully fail validation with a message similar to "Attribute 'Random2' not present in the licence."

Embed Validation folder in my project to prevent Standard.Licensing.dll from being replaced

Hi. I was wondering if by referencing the Standard.Licensing nuget package, it is possible for a "customer" to circumvent the licence validation by replacing the original Standard.Licensing.dll with a dummy version of it, that implements the same interfaces, classes and public methods but doesn't validate anything.
In order to make sure this doesn't happen, I consider including in my core project the classes that are in the Validation folder. Do you agree this is a good approach?

Thank you so much.

Soluton errors

Hi,

I retrieve the solution and try to compile it but I encounter the following errors :
1)

MSB4019	The imported project "C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\MSBuild\Microsoft\Portable\v4.5\Microsoft.Portable.CSharp.targets" was not found. Confirm that the expression in the Import declaration "C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\MSBuild\Microsoft\Portable\v4.5\Microsoft.Portable.CSharp.targets" is correct, and that the file exists on disk.	Standard.Licensing	C:\Program Files\dotnet\sdk\3.1.101\Sdks\Microsoft.NET.Sdk\Sdk\Sdk.targets	37	

=> I had to change the TargetFramework from this to this but don't think it's the right solution

    Before : <TargetFrameworks>netstandard2.0;netstandard1.3;netstandard1.0;net45;net40;portable-net45+win8+wpa81+wp8;portable-net403+sl50+win8+wp8;</TargetFrameworks>-->
    After :: <TargetFrameworks>netstandard2.0;netstandard1.3;netstandard1.0;net45;net40;</TargetFrameworks>
  1. The Standard.Licensing.Tests project wasn't in the solution.
    => I add it and add the following nuget packages : System.Xml.Linq, System.Xml.ReaderWriter, NUnit

  2. After that, the Standard.Licensing.Tests project compile but no test is runned

Is there a chance you can help me on these points?

License Exception - $exception {"illegal object in GetInstance: Org.BouncyCastle.Asn1.DerOctetString"} System.ArgumentException

While using any of the below code.

Version: .NET 4.6.1
Visual Studio 2019
WPF Windows Application
Visual Studio Running as Administrator account.

any of the below method cause exception.

validationFailures.ToList();

        if (validationFailures.Any())
        {

        }
        foreach (var failure in validationFailures)
        {
            Console.WriteLine(failure.GetType().Name + ": " + failure.Message + " - " + failure.HowToResolve);
        }

at Org.BouncyCastle.Asn1.DerBitString.GetInstance(Object obj)
at Org.BouncyCastle.Asn1.X509.SubjectPublicKeyInfo..ctor(Asn1Sequence seq)
at Org.BouncyCastle.Asn1.X509.SubjectPublicKeyInfo.GetInstance(Object obj)
at Org.BouncyCastle.Security.PublicKeyFactory.CreateKey(Byte[] keyInfoData)
at Standard.Licensing.License.VerifySignature(String publicKey)
at Standard.Licensing.Validation.LicenseValidationExtensions.<>c__DisplayClass4_0.b__0(License license)
at Standard.Licensing.Validation.ValidationChainBuilder.d__8.MoveNext()
at System.Collections.Generic.List1..ctor(IEnumerable1 collection)
at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
at ServerConfiguration.Views.LicenseView.SaveButton_Click(Object sender, RoutedEventArgs e) in LicenseView.xaml.cs:line 93
at System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object target, RoutedEventArgs routedEventArgs)
at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)
at System.Windows.UIElement.RaiseEventImpl(DependencyObject sender, RoutedEventArgs args)
at System.Windows.UIElement.RaiseEvent(RoutedEventArgs e)
at System.Windows.Controls.Primitives.ButtonBase.OnClick()
at System.Windows.Controls.Button.OnClick()
at System.Windows.Controls.Primitives.ButtonBase.OnMouseLeftButtonUp(MouseButtonEventArgs e)
at System.Windows.UIElement.OnMouseLeftButtonUpThunk(Object sender, MouseButtonEventArgs e)
at System.Windows.Input.MouseButtonEventArgs.InvokeEventHandler(Delegate genericHandler, Object genericTarget)
at System.Windows.RoutedEventArgs.InvokeHandler(Delegate handler, Object target)
at System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object target, RoutedEventArgs routedEventArgs)
at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)
at System.Windows.UIElement.ReRaiseEventAs(DependencyObject sender, RoutedEventArgs args, RoutedEvent newEvent)
at System.Windows.UIElement.OnMouseUpThunk(Object sender, MouseButtonEventArgs e)
at System.Windows.Input.MouseButtonEventArgs.InvokeEventHandler(Delegate genericHandler, Object genericTarget)
at System.Windows.RoutedEventArgs.InvokeHandler(Delegate handler, Object target)
at System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object target, RoutedEventArgs routedEventArgs)
at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)
at System.Windows.UIElement.RaiseEventImpl(DependencyObject sender, RoutedEventArgs args)
at System.Windows.UIElement.RaiseTrustedEvent(RoutedEventArgs args)
at System.Windows.UIElement.RaiseEvent(RoutedEventArgs args, Boolean trusted)
at System.Windows.Input.InputManager.ProcessStagingArea()
at System.Windows.Input.InputManager.ProcessInput(InputEventArgs input)
at System.Windows.Input.InputProviderSite.ReportInput(InputReport inputReport)
at System.Windows.Interop.HwndMouseInputProvider.ReportInput(IntPtr hwnd, InputMode mode, Int32 timestamp, RawMouseActions actions, Int32 x, Int32 y, Int32 wheel)
at System.Windows.Interop.HwndMouseInputProvider.FilterMessage(IntPtr hwnd, WindowMessage msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
at System.Windows.Interop.HwndSource.InputFilterMessage(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
at MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
at MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)
at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
at System.Windows.Threading.Dispatcher.LegacyInvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs)
at MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)
at MS.Win32.UnsafeNativeMethods.DispatchMessage(MSG& msg)
at System.Windows.Threading.Dispatcher.PushFrameImpl(DispatcherFrame frame)
at System.Windows.Threading.Dispatcher.PushFrame(DispatcherFrame frame)
at System.Windows.Application.RunDispatcher(Object ignore)
at System.Windows.Application.RunInternal(Window window)
at System.Windows.Application.Run(Window window)
at System.Windows.Application.Run()
at ServerConfiguration.App.Main()

Embed Standard.Licensing.dll failed

Hi.
I'd like to embed Standard.Licensing.dll and Device.Id into an exe.
I've added both dlls has references and put CopyLocal to false
I've added Device.Id and Standard.Licensing.dll to a Directory called dll
I've changed the property to RessourcesEmbeded
And in Main app I've this

[STAThread] static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); AppDomain.CurrentDomain.AssemblyResolve += OnResolveAssembly; Application.Run(new Frm_LicenseManager()); }

and this

`private static Assembly OnResolveAssembly(object sender, ResolveEventArgs args)
{
Assembly executingAssembly = Assembly.GetExecutingAssembly();
AssemblyName assemblyName = new AssemblyName(args.Name);

        var path = assemblyName.Name + ".dll";
        if (assemblyName.CultureInfo.Equals(CultureInfo.InvariantCulture) == false) path = String.Format(@"{0}\{1}", assemblyName.CultureInfo, path);

        using (Stream stream = executingAssembly.GetManifestResourceStream(path))
        {
            if (stream == null) return null;

            var assemblyRawBytes = new byte[stream.Length];
            stream.Read(assemblyRawBytes, 0, assemblyRawBytes.Length);
            return Assembly.Load(assemblyRawBytes);
        }
    }`

in Frm_LicenseManager, I can do generate a deviceId , it works,
But, when generate a license, I've got a crash!

`var dictionary = new Dictionary<string, string>
{
{ "MachineId", _deviceId }
};

            var license = Standard.Licensing.License.New()
            .As(LicenseType.Standard)
            .WithAdditionalAttributes(dictionary)
            .CreateAndSignWithPrivateKey(_publicKey, _productName);`

Can not Load Licence

When Load Licence show me below error

Data at the root level is invalid. Line 1, position 1.

The license signature and data does not match.

Followed the steps mentioned in the readme, however getting the error in the validationfailures.ToList(). Any Idea on this ?

The license signature and data does not match. This usually happens when a license file is corrupted or has been altered.

public static bool ValidateLicense(string publicKey)
        {
            var readAllLines = File.ReadAllText(@"D:\Work Items\KiteConnect\LicensingPOC\LicensingPOC\License.lic");
            var license = License.Load(readAllLines);

            var validationFailures = license.Validate()
                .ExpirationDate()
                .When(lic => lic.Type == LicenseType.Trial)
                .And()
                .Signature(publicKey).AssertValidLicense();

            List<IValidationFailure> failures = validationFailures.ToList();

            return !failures.Any();
        }

Enhanced license expiration time verification mechanism

Hello, this is a great library, it's great. But I want to know whether the verification mechanism of the license expiration time can be enhanced to add verification with the Internet time, because if the user modifies the local system time, the license can be made valid forever. If I missed something, please forgive me. Thanks

Way of setting the license type

Hi,

Not an issue, more of an enhancement...

Ability to set the license type to something other than Trial and Standard.
Maybe making it a string option instead of an enum, this way we can put whatever we like.
For example: Trial, Basic, Standard, Professional, Enterprise, "Anything we want really".

Thanks
Raff

Expiration Date is not correctly compared

The expiration date is set as Universal date when creating the License file (ToUniversalTime()).

But the LicenseValidationDate extension method ExpirationDate is comparing with DateTime.Now.
Shouldn't they both be consistently UniversalTime?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.