-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
15 changed files
with
528 additions
and
116 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
using System.Text; | ||
|
||
namespace Unichain.P2P.Packets; | ||
|
||
/// <summary> | ||
/// Represents a collection of headers and a payload | ||
/// </summary> | ||
public struct Content { | ||
|
||
/// <summary> | ||
/// The headers of this content. | ||
/// </summary> | ||
public Dictionary<string,string> Headers { get; set; } | ||
|
||
/// <summary> | ||
/// The binary payload of this content. | ||
/// </summary> | ||
public byte[] Payload { get; set; } | ||
|
||
/// <summary> | ||
/// Writes the current content to the stream | ||
/// </summary> | ||
/// <param name="s">The stream to be written onto</param> | ||
/// <exception cref="NotSupportedException">If the stream is non writable</exception> | ||
internal readonly void Write(Stream s) { | ||
if (!s.CanWrite) { | ||
throw new NotSupportedException("Cannot write to stream"); | ||
} | ||
|
||
using BinaryWriter bw = new(s, Encoding.UTF8, true); | ||
|
||
bw.Write(Headers.Count); | ||
foreach (var header in Headers) { | ||
bw.Write(header.Key); | ||
bw.Write(header.Value); | ||
} | ||
bw.Write((uint)Payload.Length); | ||
bw.Write(Payload); | ||
} | ||
|
||
/// <summary> | ||
/// Reads and creates a new content from the stream | ||
/// </summary> | ||
/// <param name="s">The stream that has the data</param> | ||
/// <returns>The newly created content</returns> | ||
/// <exception cref="NotSupportedException">If the stream is non readable</exception> | ||
internal static Content Read(Stream s) { | ||
if (!s.CanRead) { | ||
throw new NotSupportedException("Cannot read from stream"); | ||
} | ||
|
||
using BinaryReader br = new(s, Encoding.UTF8, true); | ||
|
||
var headers = new Dictionary<string, string>(); | ||
int headerCount = br.ReadInt32(); | ||
for (int i = 0; i < headerCount; i++) { | ||
string key = br.ReadString(); | ||
string value = br.ReadString(); | ||
headers.Add(key, value); | ||
} | ||
uint payloadSize = br.ReadUInt32(); | ||
byte[] payload = br.ReadBytes((int)payloadSize); | ||
return new Content { | ||
Headers = headers, | ||
Payload = payload | ||
}; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
namespace Unichain.P2P.Packets; | ||
|
||
/// <summary> | ||
/// A class to create new contents for requests and responses. | ||
/// </summary> | ||
public class ContentBuilder { | ||
private readonly Dictionary<string, string> headers; | ||
private byte[] payload; | ||
|
||
/// <summary> | ||
/// Instantiates a new builder for <see cref="Content"/> objects with default information. | ||
/// </summary> | ||
public ContentBuilder() { | ||
headers = new(); | ||
payload = Array.Empty<byte>(); | ||
} | ||
|
||
/// <summary> | ||
/// Adds a new header to the content. | ||
/// </summary> | ||
/// <param name="key">The key of the header</param> | ||
/// <param name="value">The value of the header</param> | ||
public ContentBuilder WithHeader(string key, string value) { | ||
headers.Add(key, value); | ||
return this; | ||
} | ||
|
||
/// <summary> | ||
/// Defines what the payload will be. | ||
/// </summary> | ||
/// <param name="payload">The payload</param> | ||
public ContentBuilder WithPayload(byte[] payload) { | ||
this.payload = payload; | ||
return this; | ||
} | ||
|
||
/// <summary> | ||
/// Builds the final <see cref="Content"/> object | ||
/// </summary> | ||
public Content Build() { | ||
return new() { | ||
Headers = headers, | ||
Payload = payload | ||
}; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
# Unichain.P2P | ||
|
||
## Request | ||
|
||
A request packet is used to ask for information from | ||
another node or send important data. It also can be a broadcast, | ||
where the sender should not expect a response and must send | ||
the same request for all its known peers. | ||
|
||
A request is made of primarily of many parts: | ||
|
||
* The current protocol version that the sender is using. | ||
* A [RequestMethod](./RequestMethod.cs). Similar to HTTP's GET, POST, etc. | ||
* A Route. It is a path that identifies what endpoint in the node this | ||
request should be sent to. | ||
* Information about the sender. For maximum compatibility, it should include | ||
private and public IP, port and a unique node indentifier. This information | ||
is used to enable nodes in the same computer or same networks to efficiently | ||
communicate with each other. | ||
* Whether this request is originating from a broadcast request or not. | ||
* A collection of contents. Each content has a set of headers and a payload. | ||
|
||
### Structure | ||
|
||
The sequence structure of a request is as follows: | ||
|
||
> **Note:** All strings in the payload have a size prefix, so you | ||
shouldn't read string until a null terminator(\0) is found. | ||
|
||
| Order | Description | Type | Size | | ||
| ----- | ----------- | ---- | ---- | | ||
| 1 | Protocol Version | int | 4 bytes | | ||
| 2 | Request Method | int | 4 bytes | | ||
| 3 | Route | string | Variable | | ||
| 4 | Ip Version(IPV4/IPV6) | bool | 1 byte | | ||
| 5 | Public Ip | string | Variable | | ||
| 6 | Private Ip(empty if IPV6) | string | Variable | | ||
| 7 | Is Broadcast | bool | 1 byte | | ||
| 8 | Contents Count | int | 4 bytes | | ||
| 9 | Contents | Content[] | Variable | | ||
|
||
## Content | ||
|
||
A packet content is merely a collection of headers and a payload. | ||
An entire content can be read/written with funcions from the | ||
struct [Content](./Content.cs) itself. | ||
|
||
### Structure | ||
|
||
| Order | Description | Type | Size | | ||
| ----- | ----------- | ---- | ---- | | ||
| 1 | Headers Count | int | 4 bytes | | ||
| 2 | Headers | Header[] | Variable | | ||
| 3 | Payload Length | int | 4 bytes | | ||
| 4 | Payload | byte[] | Variable | | ||
|
||
## Content Headers | ||
|
||
The headers are included in each content to identify the content's type | ||
and any other metadata that is needed to process the content. It can be | ||
the file name, in case it is an attachment, the priority of a new | ||
transaction, compression algorithm of the payload, encryption, etc. | ||
|
||
A header is constituted of a key and a value. Both are strings. Beware | ||
that can be a limit of key/value sizes, as it is prudent to keep them | ||
at a minimum size to ensure fast communication. | ||
|
||
### Structure | ||
|
||
The strings here could omit the size prefix, as they are already | ||
included in the content structure, but are included for simplicity. | ||
|
||
| Order | Description | Type | Size | | ||
| ----- | ----------- | ---- | ---- | | ||
| 1 | Key | string | Variable | | ||
| 2 | Value | string | Variable | | ||
|
||
|
||
## Content Payload | ||
|
||
The content payload is basically the data being sent. It is an unsigned | ||
integer indicating the size of the payload, followed by the payload itself. | ||
Note that the payload may be compressed and/or encrypted, depending on the | ||
headers information. | ||
|
||
## Response | ||
|
||
A response is simpler than a request as it has a limitation of only one | ||
content. It also has a status code, indicating whether the request was | ||
successful or an error occurred. | ||
|
||
### Structure | ||
|
||
| Order | Description | Type | Size | | ||
| ----- | ----------- | ---- | ---- | | ||
| 1 | Protocol Version | int | 4 bytes | | ||
| 2 | Status Code | int | 4 bytes | | ||
| 3 | Content | Content | Variable | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
namespace Unichain.P2P.Packets; | ||
|
||
/// <summary> | ||
/// Represents a request method from a node | ||
/// </summary> | ||
public struct Request | ||
{ | ||
/// <summary> | ||
/// The protocol version that the sender is using. | ||
/// </summary> | ||
public int ProtocolVersion { get; set; } | ||
|
||
/// <summary> | ||
/// The method of the request | ||
/// </summary> | ||
public RequestMethod Method { get; set; } | ||
|
||
/// <summary> | ||
/// The URI of the request | ||
/// </summary> | ||
public Route Route { get; set; } | ||
|
||
/// <summary> | ||
/// Identification information of the sender | ||
/// </summary> | ||
public Address Sender { get; set; } | ||
|
||
/// <summary> | ||
/// Defines if the current request is a broadcast request. If true, | ||
/// the node should not send a response and should spread it across | ||
/// all its known peers. | ||
/// </summary> | ||
public bool IsBroadcast { get; set; } | ||
|
||
/// <summary> | ||
/// A list with all the <see cref="Content"/> present in this request. | ||
/// </summary> | ||
public List<Content> Contents { get; set; } | ||
|
||
// TODO: implement read/write in request | ||
// must implement Address read/write first and public/private IP stuff | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
namespace Unichain.P2P.Packets; | ||
|
||
/// <summary> | ||
/// A class to craft new requests. | ||
/// </summary> | ||
public class RequestBuilder { | ||
private int protocolVersion; | ||
private RequestMethod method; | ||
private Route? route; | ||
private Address? sender; | ||
private bool isBroadcast; | ||
private List<Content> contents; | ||
|
||
/// <summary> | ||
/// Creates a new builder for <see cref="Request"/> objects with default information. | ||
/// </summary> | ||
public RequestBuilder() | ||
{ | ||
contents = new(); | ||
} | ||
|
||
/// <summary> | ||
/// Defines the protocol version that the request will use | ||
/// </summary> | ||
/// <param name="protocolVersion">The protocol version</param> | ||
public RequestBuilder WithProtocolVersion(int protocolVersion) { | ||
this.protocolVersion = protocolVersion; | ||
return this; | ||
} | ||
|
||
/// <summary> | ||
/// Defines the method that the request will use | ||
/// </summary> | ||
/// <param name="method">The method</param> | ||
public RequestBuilder WithMethod(RequestMethod method) { | ||
this.method = method; | ||
return this; | ||
} | ||
|
||
/// <summary> | ||
/// Defines the route of the request | ||
/// </summary> | ||
/// <param name="route">The route that this request will be forwarded</param> | ||
public RequestBuilder WithRoute(Route route) { | ||
this.route = route; | ||
return this; | ||
} | ||
|
||
/// <summary> | ||
/// Defines the sender of the request | ||
/// </summary> | ||
/// <param name="sender">The sender</param> | ||
public RequestBuilder WithSender(Address sender) { | ||
this.sender = sender; | ||
return this; | ||
} | ||
|
||
/// <summary> | ||
/// Defines a request to be broadcasted across the entire network | ||
/// </summary> | ||
public RequestBuilder WithBroadcast() { | ||
this.isBroadcast = true; | ||
return this; | ||
} | ||
|
||
/// <summary> | ||
/// Sets a specific state for the <see cref="Request"/> to be broadcasted or not | ||
/// </summary> | ||
/// <param name="isBroadcast">If the request should be a broadcast or not</param> | ||
public RequestBuilder WithBroadcast(bool isBroadcast) { | ||
this.isBroadcast = isBroadcast; | ||
return this; | ||
} | ||
|
||
/// <summary> | ||
/// Adds many <see cref="Content"/> to the request at once. | ||
/// <b>This method will override any previous contents added to the request.</b> | ||
/// </summary> | ||
/// <param name="contents">The contents to be added</param> | ||
public RequestBuilder WithContents(List<Content> contents) { | ||
this.contents = contents; | ||
return this; | ||
} | ||
|
||
/// <summary> | ||
/// Adds many <see cref="Content"/> to the request at once | ||
/// using an <see cref="Action"/>, preferably a lambda expression | ||
/// </summary> | ||
/// <param name="contents">The action that adds all contents</param> | ||
public RequestBuilder WithContents(Action<List<Content>> contents) { | ||
contents(this.contents); | ||
return this; | ||
} | ||
|
||
/// <summary> | ||
/// Adds a <see cref="Content"/> to the request | ||
/// </summary> | ||
/// <param name="content">The content to be added</param> | ||
public RequestBuilder WithContent(Content content) { | ||
this.contents.Add(content); | ||
return this; | ||
} | ||
|
||
/// <summary> | ||
/// Creates a <see cref="Request"/> object with the current builder configuration | ||
/// </summary> | ||
/// <exception cref="InvalidOperationException">If the route or sender is not defined</exception> | ||
public Request Build() { | ||
if(route is null) { | ||
throw new InvalidOperationException("The route must be defined"); | ||
} | ||
|
||
if(sender is null) { | ||
throw new InvalidOperationException("The sender must be defined"); | ||
} | ||
|
||
return new Request { | ||
ProtocolVersion = protocolVersion, | ||
Method = method, | ||
Route = route, | ||
Sender = sender, | ||
IsBroadcast = isBroadcast, | ||
Contents = contents | ||
}; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
namespace Unichain.P2P; | ||
|
||
public enum RequestMethod { | ||
INVALID, | ||
GET, | ||
POST | ||
} |
Oops, something went wrong.