Skip to content

Commit db74473

Browse files
blackspherefollowerqdot
authored andcommitted
Correcting the handing of old messages arriving at a new parser
I'm not sure if this is actually possible in the wild, since the client/server relationship is resticted to server >= client. In testing, I serialised DeviceAdded to Version0, then deserialised it, causing an error since it didn't match the current object version. At this time, the client does not send any messages that the server would choke on, but this builds that in for future cases. Also more tests and some ReShaper work.
1 parent 40d9677 commit db74473

8 files changed

Lines changed: 339 additions & 48 deletions

File tree

Buttplug.Core/App_Packages/LibLog.4.2/LibLog.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
#define LIBLOG_PUBLIC
4444

4545
using System.Diagnostics.CodeAnalysis;
46+
// ReSharper disable All
4647

4748
[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "Buttplug.Logging")]
4849
[assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "Buttplug.Logging.Logger.#Invoke(Buttplug.Logging.LogLevel,System.Func`1<System.String>,System.Exception,System.Object[])")]

Buttplug.Core/ButtplugJsonMessageParser.cs

Lines changed: 52 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,18 @@ public class ButtplugJsonMessageParser
1616
{
1717
[NotNull]
1818
private readonly Dictionary<string, Type> _messageTypes;
19-
[NotNull]
2019
private readonly IButtplugLog _bpLogger;
2120
[NotNull]
2221
private readonly JsonSchema4 _schema;
2322

23+
[NotNull]
24+
private JsonSerializer _serializer;
25+
2426
public ButtplugJsonMessageParser(IButtplugLogManager aLogManager = null)
2527
{
2628
_bpLogger = aLogManager.GetLogger(GetType());
2729
_bpLogger?.Info($"Setting up {GetType().Name}");
30+
_serializer = new JsonSerializer { MissingMemberHandling = MissingMemberHandling.Error };
2831
IEnumerable<Type> allTypes;
2932

3033
// Some classes in the library may not load on certain platforms due to missing symbols.
@@ -131,56 +134,77 @@ public ButtplugMessage[] Deserialize(string aJsonMsg)
131134
continue;
132135
}
133136

134-
var s = new JsonSerializer { MissingMemberHandling = MissingMemberHandling.Error };
135-
136137
// This specifically could fail due to object conversion.
137-
try
138-
{
139-
var r = o[msgName].Value<JObject>();
140-
res.Add((ButtplugMessage)r.ToObject(_messageTypes[msgName], s));
141-
_bpLogger?.Trace($"Message successfully parsed as {msgName} type");
142-
}
143-
catch (InvalidCastException e)
144-
{
145-
var err = new Error($"Could not create message for JSON {aJsonMsg}: {e.Message}", ErrorClass.ERROR_MSG, ButtplugConsts.SystemMsgId);
146-
_bpLogger?.LogErrorMsg(err);
147-
res.Add(err);
148-
}
149-
catch (JsonSerializationException e)
150-
{
151-
var err = new Error($"Could not create message for JSON {aJsonMsg}: {e.Message}", ErrorClass.ERROR_MSG, ButtplugConsts.SystemMsgId);
152-
_bpLogger?.LogErrorMsg(err);
153-
res.Add(err);
154-
}
138+
res.Add(DeserializeAs(o, _messageTypes[msgName], msgName, aJsonMsg));
155139
}
156140

157141
return res.ToArray();
158142
}
159143

144+
private ButtplugMessage DeserializeAs(JObject aObject, Type aMsgType, string aMsgName, string aJsonMsg)
145+
{
146+
try
147+
{
148+
var r = aObject[aMsgName].Value<JObject>();
149+
var msg = (ButtplugMessage)r.ToObject(aMsgType, _serializer);
150+
_bpLogger?.Trace($"Message successfully parsed as {aMsgName} type");
151+
return msg;
152+
}
153+
catch (InvalidCastException e)
154+
{
155+
var err = new Error($"Could not create message for JSON {aJsonMsg}: {e.Message}", ErrorClass.ERROR_MSG, ButtplugConsts.SystemMsgId);
156+
_bpLogger?.LogErrorMsg(err);
157+
return err;
158+
}
159+
catch (JsonSerializationException e)
160+
{
161+
// Object didn't fit. Downgrade?
162+
var tmp = (ButtplugMessage)aMsgType.GetConstructor(
163+
BindingFlags.NonPublic | BindingFlags.Instance,
164+
null, Type.EmptyTypes, null)?.Invoke(null);
165+
if (tmp?.PreviousType != null)
166+
{
167+
var msg = DeserializeAs(aObject, tmp.PreviousType, aMsgName, aJsonMsg);
168+
if (!(msg is Error))
169+
{
170+
return msg;
171+
}
172+
}
173+
174+
var err = new Error($"Could not create message for JSON {aJsonMsg}: {e.Message}", ErrorClass.ERROR_MSG, ButtplugConsts.SystemMsgId);
175+
_bpLogger?.LogErrorMsg(err);
176+
return err;
177+
}
178+
}
179+
160180
public string Serialize([NotNull] ButtplugMessage aMsg, uint clientSchemaVersion)
161181
{
162182
// Warning: Any log messages in this function must be localOnly. They will possibly recurse.
163183

164184
// Support downgrading messages
165185
var tmp = aMsg;
166-
while (tmp.SchemaVersion > clientSchemaVersion)
186+
while (tmp == null || tmp.SchemaVersion > clientSchemaVersion)
167187
{
168-
if (tmp.PreviousType == null)
188+
if (tmp?.PreviousType == null)
169189
{
170-
if (tmp.Id == ButtplugConsts.SystemMsgId)
190+
if (aMsg.Id == ButtplugConsts.SystemMsgId)
171191
{
172192
// There's no previous version of this system message
173193
_bpLogger?.Warn($"No messages serialized!");
174194
return null;
175195
}
176196

177-
tmp = new Error($"No backwards compatible version for message #{tmp.GetType().Name}!",
178-
ErrorClass.ERROR_MSG, tmp.Id);
179-
continue;
197+
var err = new Error($"No backwards compatible version for message #{aMsg.GetType().Name}!",
198+
ErrorClass.ERROR_MSG, aMsg.Id);
199+
var eo = new JObject(new JProperty(err.GetType().Name, JObject.FromObject(err)));
200+
var ea = new JArray(eo);
201+
_bpLogger?.Error(err.ErrorMessage, true);
202+
_bpLogger?.Trace($"Message serialized to: {ea.ToString(Formatting.None)}", true);
203+
return ea.ToString(Formatting.None);
180204
}
181205

182206
tmp = (ButtplugMessage)aMsg.PreviousType.GetConstructor(
183-
new Type[] { tmp.GetType() }).Invoke(new object[] { tmp });
207+
new Type[] { tmp.GetType() })?.Invoke(new object[] { tmp });
184208
}
185209

186210
var o = new JObject(new JProperty(aMsg.GetType().Name, JObject.FromObject(tmp)));

Buttplug.Core/ButtplugMessage.cs

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
namespace Buttplug.Core
55
{
6-
public class ButtplugMessage
6+
public abstract class ButtplugMessage
77
{
88
/*
99
* Message schema versions
@@ -27,29 +27,17 @@ public class ButtplugMessage
2727
[JsonIgnore]
2828
public uint SchemaVersion
2929
{
30-
get
31-
{
32-
return _schemaVersion;
33-
}
30+
get => _schemaVersion;
3431

35-
protected set
36-
{
37-
_schemaVersion = value;
38-
}
32+
protected set => _schemaVersion = value;
3933
}
4034

4135
[JsonIgnore]
4236
public Type PreviousType
4337
{
44-
get
45-
{
46-
return _previousType;
47-
}
38+
get => _previousType;
4839

49-
protected set
50-
{
51-
_previousType = value;
52-
}
40+
protected set => _previousType = value;
5341
}
5442

5543
// Base class starts at version 0
@@ -60,7 +48,7 @@ protected set
6048
[JsonIgnore]
6149
private Type _previousType = null;
6250

63-
public ButtplugMessage(uint aId, uint aSchemaVersion = 0, Type aPreviousType = null)
51+
protected ButtplugMessage(uint aId, uint aSchemaVersion = 0, Type aPreviousType = null)
6452
{
6553
Id = aId;
6654
SchemaVersion = aSchemaVersion;

Buttplug.Core/Messages/Messages.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,10 +130,15 @@ public class DeviceList : ButtplugMessage, IButtplugMessageOutgoingOnly
130130
public readonly DeviceMessageInfo[] Devices = new DeviceMessageInfo[0];
131131

132132
public DeviceList(DeviceMessageInfo[] aDeviceList, uint aId)
133-
: base(aId, 1, typeof(DeviceListVersion0))
133+
: base(aId, 1, typeof(DeviceListVersion0))
134134
{
135135
Devices = aDeviceList;
136136
}
137+
138+
internal DeviceList()
139+
: base(0, 1, typeof(DeviceListVersion0))
140+
{
141+
}
137142
}
138143

139144
public class DeviceListVersion0 : ButtplugMessage, IButtplugMessageOutgoingOnly
@@ -187,6 +192,11 @@ public DeviceAdded(uint aIndex, string aName,
187192
DeviceName = aName;
188193
DeviceMessages = aMessages;
189194
}
195+
196+
internal DeviceAdded()
197+
: base(0, 0, 1, typeof(DeviceAddedVersion0))
198+
{
199+
}
190200
}
191201

192202
public class DeviceAddedVersion0 : ButtplugDeviceMessage, IButtplugMessageOutgoingOnly
@@ -414,9 +424,13 @@ public FleshlightLaunchFW12Cmd(uint aDeviceIndex, uint aSpeed, uint aPosition, u
414424
// ReSharper disable once UnusedMember.Global
415425
public class LovenseCmd : ButtplugDeviceMessage
416426
{
427+
[JsonProperty(Required = Required.Always)]
428+
public string Command;
429+
417430
public LovenseCmd(uint aDeviceIndex, string aDeviceCmd, uint aId = ButtplugConsts.DefaultMsgId)
418431
: base(aId, aDeviceIndex)
419432
{
433+
Command = aDeviceCmd;
420434
}
421435
}
422436

Buttplug.Server.Managers.ErosTek/ET312Protocol.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
using System;
2+
using System.Diagnostics.CodeAnalysis;
23

34
namespace Buttplug.Server.Managers.ETSerialManager
45
{
6+
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Based on 3rd party documentation.")]
57
public static class ET312Protocol
68
{
79
/// <summary>

Buttplug.Server.Test/Buttplug.Server.Test.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
<Compile Include="AssemblyGitVersionTests.cs" />
6262
<Compile Include="ButtplugDeviceTests.cs" />
6363
<Compile Include="ButtplugJsonMessageParserTests.cs" />
64+
<Compile Include="ButtplugMessagesTests.cs" />
6465
<Compile Include="ButtplugMessageTests.cs" />
6566
<Compile Include="ButtplugServerTests.cs" />
6667
<Compile Include="FleshlightHelperTests.cs" />

0 commit comments

Comments
 (0)