Skip to content

Commit 88d40f7

Browse files
committed
Various improvements to the API
1 parent d7f167c commit 88d40f7

File tree

14 files changed

+313
-26
lines changed

14 files changed

+313
-26
lines changed

Synsharp.Tests/TestNodeHelper.cs

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
1+
using System;
12
using System.Collections.Generic;
23
using System.Linq;
34
using System.Net;
5+
using System.Threading.Channels;
46
using System.Threading.Tasks;
57
using NUnit.Framework;
68
using Synsharp.Forms;
79
using Synsharp.Types;
810
using CryptoX509Cert = Synsharp.Forms.CryptoX509Cert;
911
using InetIPv6 = Synsharp.Forms.InetIPv6;
12+
using InetUrl = Synsharp.Forms.InetUrl;
1013

1114
namespace Synsharp.Tests;
1215

@@ -30,12 +33,12 @@ public async Task TestAddNodeWithTags()
3033
Assert.NotNull(SynapseClient);
3134

3235
var inetIPv6 = InetIPv6.Parse("2001:0db8:85a3:0000:0000:8a2e:0370:7334");
33-
inetIPv6.Tags = new HashSet<string>() { "di.test", "random.tag" };
36+
inetIPv6.Tags.Add("di.test", "random.tag");
3437

3538
var response = await SynapseClient.Nodes.Add(inetIPv6);
3639

3740
Assert.IsNotNull(response);
38-
Assert.AreEqual(4, response.Tags.Count);
41+
Assert.AreEqual(4, response.Tags.Count());
3942
Assert.That(response.Tags.Contains("di"));
4043
Assert.That(response.Tags.Contains("di.test"));
4144
Assert.That(response.Tags.Contains("random"));
@@ -55,4 +58,52 @@ public async Task TestAddCryptoX509CertNode()
5558
Assert.IsNotNull(response);
5659
Assert.AreEqual("ebff56c59290e26d64050e0b68ec6575", response.MD5.ToString());
5760
}
61+
62+
63+
[Test]
64+
public async Task TestAddTag()
65+
{
66+
Assert.NotNull(SynapseClient);
67+
68+
var inetIPv6 = InetIPv6.Parse("2001:0db8:85a3:0000:0000:8a2e:0370:7334");
69+
Console.WriteLine($"'{string.Join(",", inetIPv6.Tags)}'");
70+
inetIPv6 = await SynapseClient.Nodes.Add(inetIPv6);
71+
72+
await SynapseClient.Nodes.AddTag(inetIPv6.Iden, "di.test");
73+
var response = await SynapseClient.Nodes.GetAsync<InetIPv6>(inetIPv6.Iden);
74+
Console.WriteLine($"'{string.Join(",", response.Tags)}'");
75+
Assert.That(response.Tags.Contains("di.test"));
76+
}
77+
78+
[Test]
79+
public async Task TestDelete()
80+
{
81+
Assert.NotNull(SynapseClient);
82+
83+
var inetIPv6 = InetIPv6.Parse("2001:0db8:85a3:0000:0000:8a2e:0370:7334");
84+
inetIPv6 = await SynapseClient.Nodes.Add(inetIPv6);
85+
86+
var response = SynapseClient.StormAsync<InetIPv6>("inet:ipv6=2001:0db8:85a3:0000:0000:8a2e:0370:7334");
87+
Assert.AreEqual(1, await response.CountAsync());
88+
89+
await SynapseClient.Nodes.Remove(inetIPv6.Iden);
90+
91+
response = SynapseClient.StormAsync<InetIPv6>("inet:ipv6=2001:0db8:85a3:0000:0000:8a2e:0370:7334");
92+
Assert.AreEqual(0, await response.CountAsync());
93+
}
94+
95+
[Test]
96+
public async Task TestGetByProperty()
97+
{
98+
Assert.NotNull(SynapseClient);
99+
100+
var url = InetUrl.Parse("https://www.welivesecurity.com/2020/03/12/tracking-turla-new-backdoor-armenian-watering-holes/");
101+
_ = await SynapseClient.Nodes.Add(url);
102+
103+
var response = SynapseClient.Nodes.GetAsyncByProperty<InetUrl>(new Dictionary<string, string>()
104+
{
105+
{ "fqdn", "www.welivesecurity.com" }
106+
});
107+
Assert.AreEqual(1, await response.CountAsync());
108+
}
58109
}

Synsharp/Forms/HashSHA1.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,10 @@ namespace Synsharp.Forms;
2222
[SynapseForm("hash:sha1")]
2323
public class HashSHA1 : SynapseObject<Hex>
2424
{
25+
public static HashSHA1 Parse(string str)
26+
{
27+
var hash = new HashSHA1();
28+
hash.SetValue(Hex.Parse(str));
29+
return hash;
30+
}
2531
}

Synsharp/Forms/HashSHA256.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,10 @@ namespace Synsharp.Forms;
2222
[SynapseForm("hash:sha256")]
2323
public class HashSHA256 : SynapseObject<Hex>
2424
{
25+
public static HashSHA256 Parse(string str)
26+
{
27+
var hash = new HashSHA256();
28+
hash.SetValue(Hex.Parse(str));
29+
return hash;
30+
}
2531
}

Synsharp/Forms/InetFqdn.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,12 @@ namespace Synsharp.Forms;
2222
[SynapseForm("inet:fqdn")]
2323
public class InetFqdn : SynapseObject<Str>
2424
{
25+
public InetFqdn() : base()
26+
{
27+
}
28+
29+
public InetFqdn(string s)
30+
{
31+
SetValue(s);
32+
}
2533
}

Synsharp/Forms/InetIPv4.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ public InetIPv4() : base()
2727
{
2828
}
2929

30+
public InetIPv4(IPAddress s)
31+
{
32+
SetValue(s);
33+
}
34+
3035
public InetIPv4(string s)
3136
{
3237
SetValue(s);

Synsharp/Forms/InetUrl.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,32 @@
1414
* limitations under the License.
1515
*/
1616

17+
using System;
1718
using Synsharp.Attribute;
1819

1920
namespace Synsharp.Forms;
2021

2122
[SynapseForm("inet:url")]
2223
public class InetUrl : SynapseObject<Types.InetUrl>
2324
{
25+
protected bool Equals(InetUrl other)
26+
{
27+
return base.Equals(other) && Equals(Value, other.Value);
28+
}
29+
30+
public override bool Equals(object obj)
31+
{
32+
if (ReferenceEquals(null, obj)) return false;
33+
if (ReferenceEquals(this, obj)) return true;
34+
if (obj.GetType() != this.GetType()) return false;
35+
return Equals((InetUrl)obj);
36+
}
37+
38+
public override int GetHashCode()
39+
{
40+
return Value.GetHashCode();
41+
}
42+
2443
public InetUrl() : base()
2544
{
2645
}
@@ -39,4 +58,11 @@ public InetUrl(string str) : base()
3958
[SynapseProperty("port")] public Types.InetPort Port { get; set; }
4059
[SynapseProperty("proto")] public Types.Str Proto { get; set; }
4160
[SynapseProperty("user")] public Types.InetUser User { get; set; }
61+
62+
public static InetUrl Parse(string str)
63+
{
64+
var address = new InetUrl();
65+
address.SetValue(str);
66+
return address;
67+
}
4268
}

Synsharp/Forms/SynForm.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright 2022 Antoine Cailliau
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
using Synsharp.Attribute;
18+
using Synsharp.Types;
19+
20+
namespace Synsharp.Forms;
21+
22+
[SynapseForm("syn:form")]
23+
public class SynForm : SynapseObject<Str>
24+
{
25+
26+
}

Synsharp/Helpers/NodeHelper.cs

Lines changed: 90 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,35 @@ public NodeHelper(SynapseClient synapseClient, ILogger<NodeHelper> logger)
4040
public async Task<T> Add<T>(T synapseObject, string view = null) where T : SynapseObject
4141
{
4242
var value = GetCoreValueDynamic(synapseObject);
43+
var propertyDict = GetPropertyDict(synapseObject);
4344

45+
var tagRegex = new Regex(@"[a-zA-Z0-9\.]+");
46+
_logger.LogDebug($"Got {synapseObject.Tags.Count()}");
47+
if (synapseObject.Tags.Any(_ => !tagRegex.Match(_).Success))
48+
throw new SynapseException("Tags should match [a-zA-Z0-9.]+");
49+
50+
// Build the storm command and execute
51+
var attribute = synapseObject.GetType().GetCustomAttribute<SynapseFormAttribute>();
52+
if (attribute != null)
53+
{
54+
var type = attribute.Name;
55+
var attributes = string.Join(" ", propertyDict.Select(_ => $":{_.Key}={_.Value}"));
56+
var tags = string.Join(" ", synapseObject.Tags.Select(_ => $"+#{_}"));
57+
58+
var command = $"[ {type}=\"{StringHelpers.Escape(value)}\" {attributes} {tags} ]";
59+
var results = await _synapseClient.StormAsync<T>(command,new ApiStormQueryOpts(){View= view}).ToListAsync();
60+
return results.FirstOrDefault();
61+
}
62+
else
63+
{
64+
throw new SynapseException($"Could not infer form type for '{synapseObject.GetType().FullName}'");
65+
}
66+
}
67+
68+
private Dictionary<string, string> GetPropertyDict<T>(T synapseObject) where T : SynapseObject
69+
{
4470
var propertyDict = new Dictionary<string, string>();
45-
71+
4672
// Get the form properties
4773
var fields = SynapseConverter.GetFields(synapseObject.GetType());
4874
foreach (var field in fields)
@@ -62,7 +88,7 @@ public async Task<T> Add<T>(T synapseObject, string view = null) where T : Synap
6288
}
6389
}
6490
}
65-
91+
6692
var properties = SynapseConverter.GetProperties(synapseObject.GetType());
6793
foreach (var propertyInfo in properties)
6894
{
@@ -79,31 +105,14 @@ public async Task<T> Add<T>(T synapseObject, string view = null) where T : Synap
79105
}
80106
else
81107
{
82-
throw new SynapseException($"The property '{propertyInfo.Name}' has no value or value is not a SynapseType.");
108+
throw new SynapseException(
109+
$"The property '{propertyInfo.Name}' has no value or value is not a SynapseType.");
83110
}
84111
}
85112
}
86113
}
87114

88-
var tagRegex = new Regex(@"[a-zA-Z0-9\.]+");
89-
if (synapseObject.Tags.Any(_ => !tagRegex.Match(_).Success))
90-
throw new SynapseException("Tags should match [a-zA-Z0-9.]+");
91-
92-
// Build the storm command and execute
93-
var attribute = synapseObject.GetType().GetCustomAttribute<SynapseFormAttribute>();
94-
if (attribute != null)
95-
{
96-
var type = attribute.Name;
97-
var attributes = string.Join(" ", propertyDict.Select(_ => $":{_.Key}={_.Value}"));
98-
var tags = string.Join(" ", synapseObject.Tags.Select(_ => $"+#{_}"));
99-
var command = $"[ {type}={value} {attributes} {tags} ]";
100-
var results = await _synapseClient.StormAsync<T>(command,new ApiStormQueryOpts(){View= view}).ToListAsync();
101-
return results.FirstOrDefault();
102-
}
103-
else
104-
{
105-
throw new SynapseException($"Could not infer form type for '{synapseObject.GetType().FullName}'");
106-
}
115+
return propertyDict;
107116
}
108117

109118
private static string GetCoreValueDynamic<T>(T synapseObject) where T : SynapseObject
@@ -164,6 +173,15 @@ public async Task AddLightEdge(SynapseObject o1, SynapseObject o2, string @ref,
164173
_ = await _synapseClient.StormAsync<object>(command, new ApiStormQueryOpts(){View= view}).ToListAsync();
165174
}
166175

176+
public async Task RemoveLightEdge(SynapseObject o1, SynapseObject o2, string @ref, string view = null)
177+
{
178+
var (t1, v1) = GetSelector(o1);
179+
var (t2, v2) = GetSelector(o2);
180+
181+
var command = $"{t1}={v1} [ <({@ref})- {{ {t2}={v2} }} ]";
182+
_ = await _synapseClient.StormAsync<object>(command, new ApiStormQueryOpts(){View= view}).ToListAsync();
183+
}
184+
167185
/// <summary>
168186
/// Returns the node identified by the specified iden
169187
/// </summary>
@@ -184,4 +202,54 @@ public async Task<T> GetAsync<T>(string iden, string view = null)
184202
})
185203
.SingleOrDefaultAsync();
186204
}
205+
206+
public async Task Remove(string iden, string viewIden = null)
207+
{
208+
if (iden == null) throw new ArgumentNullException(nameof(iden));
209+
_ = (await _synapseClient.StormAsync<SynapseObject>(
210+
$"| delnode",
211+
new ApiStormQueryOpts() { View = viewIden, Idens = new[] { iden } })
212+
.ToListAsync());
213+
}
214+
215+
public async Task AddTag(string iden, string tagName, string viewIden = null)
216+
{
217+
if (iden == null) throw new ArgumentNullException(nameof(iden));
218+
var tagRegex = new Regex(@"[a-zA-Z0-9\.]+");
219+
if (!tagRegex.Match(tagName).Success)
220+
throw new SynapseException("Tags should match [a-zA-Z0-9.]+");
221+
_ = (await _synapseClient.StormAsync<SynapseObject>(
222+
$"[ +#{tagName} ]",
223+
new ApiStormQueryOpts() { View = viewIden, Idens = new[] { iden } })
224+
.ToListAsync());
225+
}
226+
227+
public IAsyncEnumerable<T> GetAsyncByProperty<T>(Dictionary<string,string> propertyValues, string view = null)
228+
{
229+
var attribute = typeof(T).GetCustomAttribute<SynapseFormAttribute>();
230+
if (attribute != null)
231+
{
232+
var type = attribute.Name;
233+
234+
string selector = "{type}";
235+
if (propertyValues.Count == 1)
236+
{
237+
var p = propertyValues.Single();
238+
selector = $"{type}:{p.Key}={p.Value}";
239+
}
240+
else if (propertyValues.Count > 1)
241+
{
242+
var plist = propertyValues.ToList();
243+
var p = plist.First();
244+
var attributes = string.Join(" ", plist.Skip(1).Select(_ => $"+:{_.Key}={_.Value}"));
245+
selector = $"{type}:{p.Key}={p.Value} {attributes}";
246+
}
247+
248+
return _synapseClient.StormAsync<T>(selector,new ApiStormQueryOpts(){View= view});
249+
}
250+
else
251+
{
252+
throw new SynapseException($"Could not infer form type for '{typeof(T).FullName}'");
253+
}
254+
}
187255
}

Synsharp/Helpers/ViewHelper.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ public IAsyncEnumerable<T> Execute<T>(string iden, string query)
9191
return _client.StormAsync<T>($"view.exec {iden} {{ {query} }}");
9292
}
9393

94-
public object Merge(string iden)
94+
public Task Merge(string iden)
9595
{
9696
var command = $"$view = $lib.view.get({iden}) $view.merge()";
9797
return _client.StormCallAsync(command);

Synsharp/SynapseObject.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
using System.Net;
2121
using System.Reflection;
2222
using System.Text;
23+
using Newtonsoft.Json;
2324
using Synsharp.Attribute;
2425
using Synsharp.Forms;
2526
using Synsharp.Types;
@@ -34,7 +35,7 @@ public abstract class SynapseObject
3435
[SynapseProperty(".created")] public Time Created { set; get; }
3536
[SynapseProperty("iden")] public Str Iden { set; get; }
3637

37-
public HashSet<string> Tags { get; set; } = new();
38+
public TagTree Tags { get; set; } = new();
3839

3940
public abstract string GetCoreValue();
4041
}

0 commit comments

Comments
 (0)