Skip to content
Draft
85 changes: 85 additions & 0 deletions designs/session-serialization.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Session serialization

Session serialization is provided through the `ISessionSerializer` type. There are two modes that are available:

## Common structure

```mermaid
packet-beta
0: "M"
1-10: "Session Id (Variable length)"
11: "N"
12: "A"
13: "R"
14: "T"
15: "C"
16-24: "Key 1 Blob"
25-33: "Key 2 Blob"
34-42: "..."
43-50: "Flags (variable)"
```

Where:
- *M*: Mode
- *N*: New session
- *A*: Abandoned
- *R*: Readonly
- *T*: Timeout
- *C*: Key count

## Flags

Flags allow for additional information to be sent either direction that may not be known initially. This field was added v2 but is backwards compatible with the v1 deserializer and will operate as a no-op as it just reads the things it knows about and doesn't look for the end of a payload.

Structure:

```mermaid
packet-beta
0: "C"
1: "F1"
2: "F1L"
3-10: "Flag1 specific payload"
11: "F2"
12: "F2L"
13-20: "Flag2 specific payload"
21-25: "..."
```

Where:
- *Fn*: Flag `n`

Where `C` is the count of flags, and each `Fn` is a flag identifier an int with 7bit encoding. Each f

An example is the flag section used to indicate that there is support for diffing a session state on the server:

```mermaid
packet-beta
0: "1"
1: "100"
2: "0"
```

## Full Copy (Mode = 1)

The following is the structure of the key blobs when the full state is serialized:

```mermaid
packet-beta
0-10: "Key name"
11-20: "Serialized value"
```

## Diffing Support (Mode = 2)

The following is the structure of the key blobs when only the difference is serialized:

```mermaid
packet-beta
0-10: "Key name"
11: "S"
12-20: "Serialized value"
```

Where:
- *S*: A value indicating the change the key has undergone from the values in `SessionItemChangeState`

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Licensed to the .NET Foundation under one or more agreements.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.AspNetCore.SystemWebAdapters.SessionState.Serialization;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Web;
using Microsoft.AspNetCore.SystemWebAdapters.SessionState.Serialization;

namespace Microsoft.AspNetCore.SystemWebAdapters.SessionState.RemoteSession;

Expand All @@ -22,11 +23,43 @@ public static void CopyTo(this ISessionState result, HttpSessionStateBase state)
}

state.Timeout = result.Timeout;

if (result is ISessionStateChangeset changes)
{
UpdateFromChanges(changes, state);
}
else
{
Replace(result, state);
}
}

private static void UpdateFromChanges(ISessionStateChangeset from, HttpSessionStateBase state)
{
foreach (var change in from.Changes)
{
if (change.State is SessionItemChangeState.Changed or SessionItemChangeState.New)
{
state[change.Key] = from[change.Key];
}
else if (change.State is SessionItemChangeState.Removed)
{
state.Remove(change.Key);
}
else if (change.State is SessionItemChangeState.Unknown)
{

}
}
}

private static void Replace(ISessionState from, HttpSessionStateBase state)
{
state.Clear();

foreach (var key in result.Keys)
foreach (var key in from.Keys)
{
state[key] = result[key];
state[key] = from[key];
}
}
}
118 changes: 118 additions & 0 deletions src/Services/SessionState/BinarySessionSerializer.ChangesetWriter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace Microsoft.AspNetCore.SystemWebAdapters.SessionState.Serialization;

[System.Diagnostics.CodeAnalysis.SuppressMessage("Maintainability", "CA1510:Use ArgumentNullException throw helper", Justification = "Source shared with .NET Framework that does not have the method")]
internal partial class BinarySessionSerializer : ISessionSerializer
{
private readonly struct ChangesetWriter(ISessionKeySerializer serializer)
{
public List<string>? Write(ISessionStateChangeset state, BinaryWriter writer)
{
writer.Write(ModeDelta);
writer.Write(state.SessionID);

writer.Write(state.IsNewSession);
writer.Write(state.IsAbandoned);
writer.Write(state.IsReadOnly);

writer.Write7BitEncodedInt(state.Timeout);
writer.Write7BitEncodedInt(state.Count);

List<string>? unknownKeys = null;

foreach (var item in state.Changes)
{
writer.Write(item.Key);

// New with V2 serializer
if (item.State is SessionItemChangeState.NoChange or SessionItemChangeState.Removed)
{
writer.Write7BitEncodedInt((int)item.State);
}
else if (serializer.TrySerialize(item.Key, state[item.Key], out var result))
{
writer.Write7BitEncodedInt((int)item.State);
writer.Write7BitEncodedInt(result.Length);
writer.Write(result);
}
else
{
(unknownKeys ??= []).Add(item.Key);
writer.Write7BitEncodedInt((int)SessionItemChangeState.Unknown);
}
}

writer.WriteFlags([]);

return unknownKeys;
}

public SessionStateCollection Read(BinaryReader reader)
{
var state = SessionStateCollection.CreateTracking(serializer);

state.SessionID = reader.ReadString();
state.IsNewSession = reader.ReadBoolean();
state.IsAbandoned = reader.ReadBoolean();
state.IsReadOnly = reader.ReadBoolean();
state.Timeout = reader.Read7BitEncodedInt();

var count = reader.Read7BitEncodedInt();

for (var index = count; index > 0; index--)
{
var key = reader.ReadString();
var changeState = (SessionItemChangeState)reader.Read7BitEncodedInt();

if (changeState is SessionItemChangeState.NoChange)
{
state.MarkUnchanged(key);
}
else if (changeState is SessionItemChangeState.Removed)
{
state.MarkRemoved(key);
}
else if (changeState is SessionItemChangeState.Unknown)
{
state.AddUnknownKey(key);
}
else if (changeState is SessionItemChangeState.New or SessionItemChangeState.Changed)
{
var length = reader.Read7BitEncodedInt();
var bytes = reader.ReadBytes(length);

if (serializer.TryDeserialize(key, bytes, out var result))
{
if (result is not null)
{
state[key] = result;
}
}
else
{
state.AddUnknownKey(key);
}
}
}

foreach (var (flag, payload) in reader.ReadFlags())
{
// No flags are currently read
}

return state;
}
}
}
Loading