Skip to content

Commit

Permalink
Adding parsing of query signatures (#93)
Browse files Browse the repository at this point in the history
  • Loading branch information
Gekctek committed Sep 27, 2023
1 parent f73b67e commit 18886ba
Show file tree
Hide file tree
Showing 5 changed files with 237 additions and 18 deletions.
56 changes: 56 additions & 0 deletions src/Agent/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,11 @@
- [Int](#F-EdjCase-ICP-Agent-Standards-ICRC1-Models-MetaDataValueTag-Int 'EdjCase.ICP.Agent.Standards.ICRC1.Models.MetaDataValueTag.Int')
- [Nat](#F-EdjCase-ICP-Agent-Standards-ICRC1-Models-MetaDataValueTag-Nat 'EdjCase.ICP.Agent.Standards.ICRC1.Models.MetaDataValueTag.Nat')
- [Text](#F-EdjCase-ICP-Agent-Standards-ICRC1-Models-MetaDataValueTag-Text 'EdjCase.ICP.Agent.Standards.ICRC1.Models.MetaDataValueTag.Text')
- [NodeSignature](#T-EdjCase-ICP-Agent-Responses-NodeSignature 'EdjCase.ICP.Agent.Responses.NodeSignature')
- [#ctor(timestamp,signature,identity)](#M-EdjCase-ICP-Agent-Responses-NodeSignature-#ctor-EdjCase-ICP-Candid-Models-ICTimestamp,System-Byte[],EdjCase-ICP-Candid-Models-Principal- 'EdjCase.ICP.Agent.Responses.NodeSignature.#ctor(EdjCase.ICP.Candid.Models.ICTimestamp,System.Byte[],EdjCase.ICP.Candid.Models.Principal)')
- [Identity](#P-EdjCase-ICP-Agent-Responses-NodeSignature-Identity 'EdjCase.ICP.Agent.Responses.NodeSignature.Identity')
- [Signature](#P-EdjCase-ICP-Agent-Responses-NodeSignature-Signature 'EdjCase.ICP.Agent.Responses.NodeSignature.Signature')
- [Timestamp](#P-EdjCase-ICP-Agent-Responses-NodeSignature-Timestamp 'EdjCase.ICP.Agent.Responses.NodeSignature.Timestamp')
- [QueryRejectInfo](#T-EdjCase-ICP-Agent-Responses-QueryRejectInfo 'EdjCase.ICP.Agent.Responses.QueryRejectInfo')
- [Code](#P-EdjCase-ICP-Agent-Responses-QueryRejectInfo-Code 'EdjCase.ICP.Agent.Responses.QueryRejectInfo.Code')
- [ErrorCode](#P-EdjCase-ICP-Agent-Responses-QueryRejectInfo-ErrorCode 'EdjCase.ICP.Agent.Responses.QueryRejectInfo.ErrorCode')
Expand All @@ -206,6 +211,7 @@
- [Sender](#P-EdjCase-ICP-Agent-Requests-QueryRequest-Sender 'EdjCase.ICP.Agent.Requests.QueryRequest.Sender')
- [BuildHashableItem()](#M-EdjCase-ICP-Agent-Requests-QueryRequest-BuildHashableItem 'EdjCase.ICP.Agent.Requests.QueryRequest.BuildHashableItem')
- [QueryResponse](#T-EdjCase-ICP-Agent-Responses-QueryResponse 'EdjCase.ICP.Agent.Responses.QueryResponse')
- [Signatures](#P-EdjCase-ICP-Agent-Responses-QueryResponse-Signatures 'EdjCase.ICP.Agent.Responses.QueryResponse.Signatures')
- [Type](#P-EdjCase-ICP-Agent-Responses-QueryResponse-Type 'EdjCase.ICP.Agent.Responses.QueryResponse.Type')
- [AsRejected()](#M-EdjCase-ICP-Agent-Responses-QueryResponse-AsRejected 'EdjCase.ICP.Agent.Responses.QueryResponse.AsRejected')
- [AsReplied()](#M-EdjCase-ICP-Agent-Responses-QueryResponse-AsReplied 'EdjCase.ICP.Agent.Responses.QueryResponse.AsReplied')
Expand Down Expand Up @@ -2483,6 +2489,49 @@ Nat value

Text value

<a name='T-EdjCase-ICP-Agent-Responses-NodeSignature'></a>
## NodeSignature `type`

##### Namespace

EdjCase.ICP.Agent.Responses

##### Summary

Signature data from a replica node

<a name='M-EdjCase-ICP-Agent-Responses-NodeSignature-#ctor-EdjCase-ICP-Candid-Models-ICTimestamp,System-Byte[],EdjCase-ICP-Candid-Models-Principal-'></a>
### #ctor(timestamp,signature,identity) `constructor`

##### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| timestamp | [EdjCase.ICP.Candid.Models.ICTimestamp](#T-EdjCase-ICP-Candid-Models-ICTimestamp 'EdjCase.ICP.Candid.Models.ICTimestamp') | Timestamp when the signature was created |
| signature | [System.Byte[]](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Byte[] 'System.Byte[]') | The signature bytes |
| identity | [EdjCase.ICP.Candid.Models.Principal](#T-EdjCase-ICP-Candid-Models-Principal 'EdjCase.ICP.Candid.Models.Principal') | The identity of the signer |

<a name='P-EdjCase-ICP-Agent-Responses-NodeSignature-Identity'></a>
### Identity `property`

##### Summary

The identity of the signer

<a name='P-EdjCase-ICP-Agent-Responses-NodeSignature-Signature'></a>
### Signature `property`

##### Summary

The signature bytes

<a name='P-EdjCase-ICP-Agent-Responses-NodeSignature-Timestamp'></a>
### Timestamp `property`

##### Summary

Timestamp when the signature was created

<a name='T-EdjCase-ICP-Agent-Responses-QueryRejectInfo'></a>
## QueryRejectInfo `type`

Expand Down Expand Up @@ -2645,6 +2694,13 @@ EdjCase.ICP.Agent.Responses

A model representing the response data in the form of a variant

<a name='P-EdjCase-ICP-Agent-Responses-QueryResponse-Signatures'></a>
### Signatures `property`

##### Summary

Signatures from the replica node

<a name='P-EdjCase-ICP-Agent-Responses-QueryResponse-Type'></a>
### Type `property`

Expand Down
30 changes: 30 additions & 0 deletions src/Agent/API.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

31 changes: 31 additions & 0 deletions src/Agent/Responses/CborUtil.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using EdjCase.ICP.Candid.Encodings;
using EdjCase.ICP.Candid.Models;
using System;
using System.Collections.Generic;
using System.Formats.Cbor;
using System.Linq;
using System.Text;

namespace EdjCase.ICP.Agent.Responses
{
internal static class CborUtil
{
public static UnboundedUInt ReadNat(CborReader reader)
{
CborReaderState state = reader.PeekState();
switch (state)
{
case CborReaderState.UnsignedInteger:
return reader.ReadUInt64();
default:
byte[] codeBytes = reader.ReadByteString().ToArray();
return LEB128.DecodeUnsigned(codeBytes);
}
}

public static Principal ReadPrincipal(CborReader reader)
{
return Principal.FromBytes(reader.ReadByteString());
}
}
}
89 changes: 89 additions & 0 deletions src/Agent/Responses/NodeSignature.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
using EdjCase.ICP.Agent.Models;
using EdjCase.ICP.Candid.Models;
using Org.BouncyCastle.Crypto.Agreement;
using System;
using System.Collections.Generic;
using System.Formats.Cbor;
using System.Linq;
using System.Text;

namespace EdjCase.ICP.Agent.Responses
{
/// <summary>
/// Signature data from a replica node
/// </summary>
public class NodeSignature
{
/// <summary>
/// Timestamp when the signature was created
/// </summary>
public ICTimestamp Timestamp { get; }
/// <summary>
/// The signature bytes
/// </summary>
public byte[] Signature { get; }
/// <summary>
/// The identity of the signer
/// </summary>
public Principal Identity { get; }

/// <param name="timestamp">Timestamp when the signature was created</param>
/// <param name="signature">The signature bytes</param>
/// <param name="identity">The identity of the signer</param>
public NodeSignature(ICTimestamp timestamp, byte[] signature, Principal identity)
{
this.Timestamp = timestamp ?? throw new ArgumentNullException(nameof(timestamp));
this.Signature = signature ?? throw new ArgumentNullException(nameof(signature));
this.Identity = identity ?? throw new ArgumentNullException(nameof(identity));
}

internal static NodeSignature ReadCbor(CborReader reader)
{
ICTimestamp? timestamp = null;
byte[]? signature = null;
Principal? identity = null;
_ = reader.ReadStartMap();
while (reader.PeekState() != CborReaderState.EndMap)
{
string name = reader.ReadTextString();
switch (name)
{
case "timestamp":
timestamp = ICTimestamp.FromNanoSeconds(CborUtil.ReadNat(reader));
break;
case "signature":
signature = reader.ReadByteString();
break;
case "identity":
identity = CborUtil.ReadPrincipal(reader);
break;
default:
// Skip
reader.SkipValue();
break;
}
}
reader.ReadEndMap();

if (timestamp == null)
{
throw new Exception("Node signature is missing the timestamp field");
}
if (signature == null)
{
throw new Exception("Node signature is missing the signature field");
}
if (identity == null)
{
throw new Exception("Node signature is missing the identity field");
}


return new NodeSignature(
timestamp,
signature,
identity
);
}
}
}
49 changes: 31 additions & 18 deletions src/Agent/Responses/QueryResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
using EdjCase.ICP.Candid.Encodings;
using EdjCase.ICP.Candid.Models;
using EdjCase.ICP.Candid.Utilities;
using Org.BouncyCastle.Asn1.Ocsp;
using System;
using System.Collections.Generic;
using System.Formats.Cbor;
using System.Linq;
using System.Xml.Linq;
Expand All @@ -19,11 +21,16 @@ public class QueryResponse
/// to extract the variant data
/// </summary>
public QueryResponseType Type { get; }
/// <summary>
/// Signatures from the replica node
/// </summary>
public List<NodeSignature> Signatures { get; }
private readonly object value;

private QueryResponse(QueryResponseType type, object value)
private QueryResponse(QueryResponseType type, List<NodeSignature> signatures, object value)
{
this.Type = type;
this.Signatures = signatures;
this.value = value;
}

Expand All @@ -49,6 +56,7 @@ public QueryRejectInfo AsRejected()
return (QueryRejectInfo)this.value;
}


private void ThrowIfWrongType(QueryResponseType type)
{
if (this.Type != type)
Expand All @@ -73,14 +81,14 @@ public CandidArg ThrowOrGetReply()
return this.AsReplied();
}

internal static QueryResponse Rejected(RejectCode code, string? message, string? errorCode)
internal static QueryResponse Rejected(RejectCode code, List<NodeSignature> signatures, string? message, string? errorCode)
{
return new QueryResponse(QueryResponseType.Rejected, new QueryRejectInfo(code, message, errorCode));
return new QueryResponse(QueryResponseType.Rejected, signatures, new QueryRejectInfo(code, message, errorCode));
}

internal static QueryResponse Replied(CandidArg reply)
internal static QueryResponse Replied(CandidArg reply, List<NodeSignature> signatures)
{
return new QueryResponse(QueryResponseType.Replied, reply);
return new QueryResponse(QueryResponseType.Replied, signatures, reply);
}

internal static QueryResponse ReadCbor(CborReader reader)
Expand All @@ -90,6 +98,7 @@ internal static QueryResponse ReadCbor(CborReader reader)
UnboundedUInt? rejectCode = null;
string? rejectMessage = null;
string? errorCode = null;
List<NodeSignature> signatures = new ();

if (reader.ReadTag() != CborTag.SelfDescribeCbor)
{
Expand Down Expand Up @@ -120,26 +129,30 @@ internal static QueryResponse ReadCbor(CborReader reader)
reader.ReadEndMap();
break;
case "reject_code":
CborReaderState state = reader.PeekState();
switch (state)
{
case CborReaderState.UnsignedInteger:
rejectCode = reader.ReadUInt64();
break;
default:
byte[] codeBytes = reader.ReadByteString().ToArray();
rejectCode = LEB128.DecodeUnsigned(codeBytes);
break;
}
rejectCode = CborUtil.ReadNat(reader);
break;
case "reject_message":
rejectMessage = reader.ReadTextString();
break;
case "error_code":
errorCode = reader.ReadTextString();
break;
case "signatures":
reader.ReadStartArray();
while (reader.PeekState() != CborReaderState.EndArray)
{
signatures.Add(NodeSignature.ReadCbor(reader));
}
reader.ReadEndArray();
break;
default:
#if DEBUG
throw new NotImplementedException($"Cannot deserialize query response. Unknown field '{name}'");
#else
reader.SkipValue();
break;
#endif

}
}
reader.ReadEndMap();
Expand All @@ -160,14 +173,14 @@ internal static QueryResponse ReadCbor(CborReader reader)
string argHex = ByteUtil.ToHexString(replyArg!);
#endif
var arg = CandidArg.FromBytes(replyArg!);
return QueryResponse.Replied(arg);
return QueryResponse.Replied(arg, signatures);
case "rejected":
if (rejectCode == null)
{
throw new CborContentException("Missing field: reject_code");
}
RejectCode code = (RejectCode)(ulong)rejectCode!;
return QueryResponse.Rejected(code, rejectMessage, errorCode);
return QueryResponse.Rejected(code, signatures, rejectMessage, errorCode);
default:
throw new NotImplementedException($"Cannot deserialize query response with status '{status}'");
}
Expand Down

0 comments on commit 18886ba

Please sign in to comment.