Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support ReadOnlySequence as input #106

Merged
merged 10 commits into from
Nov 17, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion README.md → ReadMe.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ finally scalar code processes the rest (including padding).

Basically the entry to encoder / decoder is `Base64.Default` for _base64_, and `Base64.Url` for _base64Url_.

See [demo](./demo/gfoidl.Base64.Demo/Program.cs) for further examples.

### Encoding

```c#
Expand Down Expand Up @@ -99,7 +101,27 @@ status = Base64.Default.Decode(base64.Slice(consumed), decoded.Slice
decoded = decoded.Slice(0, written + written1);
```

See [demo](./demo/gfoidl.Base64.Demo/Program.cs) for further examples.
### ReadOnlySequence / IBufferWriter

Encoding / decoding with `ReadOnlySequence<byte>` and `IBufferWriter<byte>` can be used together with `System.IO.Pipelines`.

```c#
var pipeOptions = PipeOptions.Default;
var pipe = new Pipe(pipeOptions);

var rnd = new Random(42);
var data = new byte[4097];
rnd.NextBytes(data);

pipe.Writer.Write(data);
await pipe.Writer.CompleteAsync();

ReadResult readResult = await pipe.Reader.ReadAsync();

var resultPipe = new Pipe();
Base64.Default.Encode(readResult.Buffer, resultPipe.Writer, out long consumed, out long written);
await resultPipe.Writer.CompleteAsync();
```

## (Functional) Comparison to classes in .NET

Expand Down
52 changes: 42 additions & 10 deletions demo/gfoidl.Base64.Demo/Program.cs
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
using System;
using System.Buffers;
using System.Diagnostics;
using System.IO.Pipelines;
using System.Threading.Tasks;

namespace gfoidl.Base64.Demo
{
class Program
{
static void Main()
static async Task Main()
{
Action[] demos = { RunGuidEncoding, RunGuidDecoding, RunBufferChainEncode, RunDetectEncoding };
Func<Task>[] demos = { RunGuidEncoding, RunGuidDecoding, RunBufferChainEncoding, RunDetectEncoding, RunSequenceEncoding };

foreach (Action demo in demos)
foreach (Func<Task> demo in demos)
{
Console.Write($"{demo.Method.Name}...");
demo();
await demo();
Console.WriteLine("done");
}
}
//---------------------------------------------------------------------
private static void RunGuidEncoding()
private static Task RunGuidEncoding()
{
byte[] guid = Guid.NewGuid().ToByteArray();

Expand All @@ -38,9 +40,11 @@ private static void RunGuidEncoding()
Debug.Assert(status == OperationStatus.Done);
Debug.Assert(consumed == guid.Length);
Debug.Assert(written == guidBase64UrlUTF8.Length);

return Task.CompletedTask;
}
//---------------------------------------------------------------------
private static void RunGuidDecoding()
private static Task RunGuidDecoding()
{
Guid guid = Guid.NewGuid();

Expand Down Expand Up @@ -68,9 +72,11 @@ private static void RunGuidDecoding()
Debug.Assert(status == OperationStatus.Done);
Debug.Assert(consumed == guidBase64Url.Length);
Debug.Assert(written == guidBase64UrlDecodedBuffer.Length);

return Task.CompletedTask;
}
//---------------------------------------------------------------------
private static void RunBufferChainEncode()
private static Task RunBufferChainEncoding()
{
var rnd = new Random();
Span<byte> data = new byte[1000];
Expand All @@ -80,23 +86,25 @@ private static void RunBufferChainEncode()
Span<char> base64 = new char[5000];

OperationStatus status = Base64.Default.Encode(data.Slice(0, 400), base64, out int consumed, out int written, isFinalBlock: false);
Debug.Assert(status == OperationStatus.NeedMoreData);
Debug.Assert(status == OperationStatus.NeedMoreData); // 400 is not a multiple of 3, so more data is needed
status = Base64.Default.Encode(data.Slice(consumed), base64.Slice(written), out consumed, out int written1, isFinalBlock: true);
Debug.Assert(status == OperationStatus.Done);

base64 = base64.Slice(0, written + written1);

Span<byte> decoded = new byte[5000];
status = Base64.Default.Decode(base64.Slice(0, 100), decoded, out consumed, out written, isFinalBlock: false);
Debug.Assert(status == OperationStatus.Done); // 100 encoded bytes can be decoded to 75 bytes, so Done
Debug.Assert(status == OperationStatus.Done); // 100 encoded bytes can be decoded to 75 bytes, so Done
status = Base64.Default.Decode(base64.Slice(consumed), decoded.Slice(written), out consumed, out written1, isFinalBlock: true);
Debug.Assert(status == OperationStatus.Done);

decoded = decoded.Slice(0, written + written1);
Debug.Assert(data.SequenceEqual(decoded));

return Task.CompletedTask;
}
//---------------------------------------------------------------------
private static void RunDetectEncoding()
private static Task RunDetectEncoding()
{
// Let's assume we don't know whether this string is base64 or base64Url
string encodedString = "a-_9";
Expand All @@ -121,6 +129,30 @@ private static void RunDetectEncoding()
data = data.Slice(0, written);

Debug.Assert(data.Length == 3);

return Task.CompletedTask;
}
//---------------------------------------------------------------------
private static async Task RunSequenceEncoding()
{
var pipeOptions = PipeOptions.Default;
var pipe = new Pipe(pipeOptions);

var rnd = new Random(42);
var data = new byte[4097];
rnd.NextBytes(data);

pipe.Writer.Write(data);
await pipe.Writer.CompleteAsync();

ReadResult readResult = await pipe.Reader.ReadAsync();

var resultPipe = new Pipe();
Base64.Default.Encode(readResult.Buffer, resultPipe.Writer, out long consumed, out long written);
await resultPipe.Writer.CompleteAsync();

Debug.Assert(consumed == data.Length);
Debug.Assert(written == (data.Length + 2) / 3 * 4);
}
}
}
4 changes: 4 additions & 0 deletions demo/gfoidl.Base64.Demo/gfoidl.Base64.Demo.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
<TargetFramework>netcoreapp3.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="System.IO.Pipelines" Version="4.6.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\source\gfoidl.Base64\gfoidl.Base64.csproj" />
</ItemGroup>
Expand Down
18 changes: 18 additions & 0 deletions demo/gfoidl.Base64.WebDemo/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;

namespace gfoidl.Base64.WebDemo
{
public static class Program
{
public static Task Main(string[] args) => CreateHostBuilder(args).Build().RunAsync();
//---------------------------------------------------------------------
public static IHostBuilder CreateHostBuilder(string[] args)
=> Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}
27 changes: 27 additions & 0 deletions demo/gfoidl.Base64.WebDemo/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"iisSettings": {
"windowsAuthentication" : false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:61968",
"sslPort" : 44327
}
},
"profiles": {
"IIS Express": {
"commandName" : "IISExpress",
"launchBrowser" : true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"gfoidl.Base64.WebDemo": {
"commandName" : "Project",
"launchBrowser" : true,
"applicationUrl" : "https://localhost:5001;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
47 changes: 47 additions & 0 deletions demo/gfoidl.Base64.WebDemo/Startup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System.IO.Pipelines;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace gfoidl.Base64.WebDemo
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
}
//---------------------------------------------------------------------
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}

app.UseRouting();

app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("POST data");
});

endpoints.MapPost("/", async context =>
{
// Very simple and hacky example

ReadResult readResult = await context.Request.BodyReader.ReadAsync();

await context.Response.WriteAsync("base64:\n");
Base64.Default.Encode(readResult.Buffer, context.Response.BodyWriter, out long _, out long written);

await context.Response.WriteAsync("\nbase64Url:\n");
Base64.Url.Encode(readResult.Buffer, context.Response.BodyWriter, out long _, out written);
});
});
}
}
}
9 changes: 9 additions & 0 deletions demo/gfoidl.Base64.WebDemo/appsettings.Development.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default" : "Debug",
"System" : "Information",
"Microsoft": "Information"
}
}
}
10 changes: 10 additions & 0 deletions demo/gfoidl.Base64.WebDemo/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"Logging": {
"LogLevel": {
"Default" : "Information",
"Microsoft" : "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}
11 changes: 11 additions & 0 deletions demo/gfoidl.Base64.WebDemo/gfoidl.Base64.WebDemo.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\source\gfoidl.Base64\gfoidl.Base64.csproj" />
</ItemGroup>

</Project>
1 change: 0 additions & 1 deletion fuzz/gfoidl.Base64.FuzzTests/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,6 @@ private static bool ContainsInvalidData(ReadOnlySpan<byte> encoded, Base64 encod
{
ReadOnlySpan<sbyte> decodingMap = default;


if (encoder is Base64Encoder)
{
decodingMap = Base64Encoder.DecodingMap;
Expand Down
9 changes: 9 additions & 0 deletions gfoidl.Base64.sln
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "scripts", "scripts", "{6920
fuzz\setup.sh = fuzz\setup.sh
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gfoidl.Base64.WebDemo", "demo\gfoidl.Base64.WebDemo\gfoidl.Base64.WebDemo.csproj", "{66E1E4B6-BD9B-40FF-85A9-070B0D497E59}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -116,6 +118,12 @@ Global
{DC515055-B8E7-4C25-B4CF-703292C4F975}.Fuzz|Any CPU.ActiveCfg = Release|Any CPU
{DC515055-B8E7-4C25-B4CF-703292C4F975}.Fuzz|Any CPU.Build.0 = Release|Any CPU
{DC515055-B8E7-4C25-B4CF-703292C4F975}.Release|Any CPU.ActiveCfg = Release|Any CPU
{66E1E4B6-BD9B-40FF-85A9-070B0D497E59}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{66E1E4B6-BD9B-40FF-85A9-070B0D497E59}.Debug|Any CPU.Build.0 = Debug|Any CPU
{66E1E4B6-BD9B-40FF-85A9-070B0D497E59}.Fuzz|Any CPU.ActiveCfg = Debug|Any CPU
{66E1E4B6-BD9B-40FF-85A9-070B0D497E59}.Fuzz|Any CPU.Build.0 = Debug|Any CPU
{66E1E4B6-BD9B-40FF-85A9-070B0D497E59}.Release|Any CPU.ActiveCfg = Release|Any CPU
{66E1E4B6-BD9B-40FF-85A9-070B0D497E59}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -133,6 +141,7 @@ Global
{CD033330-6EDE-4E8E-976C-5CB7C58217E8} = {403238BD-BE8A-4E78-924A-C841CD8C7955}
{3F0FD9C1-1B52-4BC6-9BD8-8FD3B0162E88} = {CD033330-6EDE-4E8E-976C-5CB7C58217E8}
{692006B6-CD1B-4A91-97C5-0A98C25EAD23} = {89ADD037-F9FC-4ADE-99B8-D89307057409}
{66E1E4B6-BD9B-40FF-85A9-070B0D497E59} = {A52BCD9C-4216-42E5-AB37-FD5F6C46A9E7}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {A902DC31-2F3E-4397-93D1-5004F325B71C}
Expand Down
Loading