• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

meshtastic / c-sharp / 17021068173

17 Aug 2025 12:34PM UTC coverage: 53.023% (-6.7%) from 59.738%
17021068173

Pull #123

github

web-flow
Merge 33f1eb888 into 50bb5fc3a
Pull Request #123: Testing framework WIP and XeDSA signing infra

224 of 696 branches covered (32.18%)

Branch coverage included in aggregate %.

167 of 461 new or added lines in 4 files covered. (36.23%)

1 existing line in 1 file now uncovered.

1118 of 1835 relevant lines covered (60.93%)

4.62 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

72.55
/Meshtastic/Data/MessageFactories/NodeInfoMessageFactory.cs
1
using Google.Protobuf;
2
using Meshtastic.Data;
3
using Meshtastic.Protobufs;
4
using Meshtastic.Crypto;
5
using Org.BouncyCastle.Security;
6

7
namespace Meshtastic.Data.MessageFactories;
8

9
/// <summary>
10
/// Factory for creating NodeInfo (User) MeshPackets with optional XEdDSA signatures
11
/// </summary>
12
public static class NodeInfoMessageFactory
13
{
14
    /// <summary>
15
    /// Create a NodeInfo MeshPacket with the user's information
16
    /// </summary>
17
    /// <param name="deviceStateContainer">Device state containing configuration</param>
18
    /// <param name="user">User information to broadcast</param>
19
    /// <param name="signPacket">Whether to sign the packet with XEdDSA</param>
20
    /// <param name="useShortHash">Use SHA-256 instead of SHA-512 for signatures</param>
21
    /// <returns>MeshPacket containing the NodeInfo</returns>
22
    public static MeshPacket CreateNodeInfoMessage(
23
        DeviceStateContainer deviceStateContainer,
24
        User user,
25
        bool signPacket = false,
26
        bool useShortHash = true)
27
    {
11✔
28
        if (deviceStateContainer == null)
11✔
29
            throw new ArgumentNullException(nameof(deviceStateContainer));
1✔
30
        if (user == null)
10✔
31
            throw new ArgumentNullException(nameof(user));
1✔
32

33
        // Create the data payload
34
        var data = new Protobufs.Data
9✔
35
        {
9✔
36
            Portnum = PortNum.NodeinfoApp,
9✔
37
            Payload = user.ToByteString()
9✔
38
        };
9✔
39

40
        // Create the base mesh packet
41
        var meshPacket = new MeshPacket
9!
42
        {
9✔
43
            From = deviceStateContainer.MyNodeInfo?.MyNodeNum ?? 0,
9✔
44
            To = 0xffffffff, // Broadcast address
9✔
45
            Id = GeneratePacketId(),
9✔
46
            Channel = 0,
9✔
47
            HopLimit = deviceStateContainer.LocalConfig?.Lora?.HopLimit ?? 3,
9✔
48
            WantAck = false,
9✔
49
            Priority = MeshPacket.Types.Priority.Background,
9✔
50
            Decoded = data
9✔
51
        };
9✔
52

53
        // Add signature if requested and we have the necessary keys
54
        if (signPacket && HasSigningCapability(deviceStateContainer))
9!
NEW
55
        {
×
56
            try
NEW
57
            {
×
NEW
58
                AddXEdDSASignature(meshPacket, deviceStateContainer, useShortHash);
×
NEW
59
            }
×
NEW
60
            catch (Exception)
×
NEW
61
            {
×
62
                // Log error but don't fail packet creation
63
                // Swallow exception: failed to sign NodeInfo packet, but don't fail packet creation
NEW
64
            }
×
NEW
65
        }
×
66

67
        return meshPacket;
9✔
68
    }
9✔
69

70
    /// <summary>
71
    /// Create a minimal NodeInfo packet for size comparison testing
72
    /// </summary>
73
    /// <param name="longName">User's long name</param>
74
    /// <param name="shortName">User's short name</param>
75
    /// <param name="id">User ID</param>
76
    /// <param name="hardwareModel">Hardware model</param>
77
    /// <param name="publicKey">Optional public key</param>
78
    /// <returns>Minimal User object for testing</returns>
79
    public static User CreateTestUser(
80
        string longName = "Test User",
81
        string shortName = "TU",
82
        string id = "!12345678",
83
        HardwareModel hardwareModel = HardwareModel.Unset,
84
        byte[]? publicKey = null)
85
    {
10✔
86
        var user = new User
10✔
87
        {
10✔
88
            LongName = longName,
10✔
89
            ShortName = shortName,
10✔
90
            Id = id,
10✔
91
            HwModel = hardwareModel,
10✔
92
            IsUnmessagable = false,
10✔
93
            // Macaddr = ByteString.CopyFrom([0xFF, 0xAA, 0x88, 0x99, 0x55, 0x66]), // Obsolete field
10✔
94
        };
10✔
95

96
        if (publicKey != null)
10✔
97
        {
1✔
98
            user.PublicKey = ByteString.CopyFrom(publicKey);
1✔
99
        }
1✔
100

101
        return user;
10✔
102
    }
10✔
103

104
    /// <summary>
105
    /// Verify the XEdDSA signature on a received NodeInfo packet
106
    /// </summary>
107
    /// <param name="meshPacket">Received mesh packet</param>
108
    /// <param name="senderPublicKey">Public key of the sender</param>
109
    /// <param name="useShortHash">Use SHA-256 instead of SHA-512</param>
110
    /// <returns>True if signature is valid</returns>
111
    public static bool VerifyNodeInfoSignature(
112
        MeshPacket meshPacket,
113
        byte[] senderPublicKey,
114
        bool useShortHash = true)
115
    {
1✔
116
        if (meshPacket?.Decoded?.Payload == null)
1!
NEW
117
            return false;
×
118

119
        // For now, we'll simulate signature verification since the protobuf doesn't have signature fields yet
120
        // This would be replaced with actual signature verification once the firmware PR is merged
121

122
        try
123
        {
1✔
124
            var payload = meshPacket.Decoded.Payload.ToByteArray();
1✔
125

126
            // In a real implementation, we would extract the signature from the packet
127
            // For now, we'll demonstrate the verification process
128
            var mockSignature = new byte[64]; // This would come from the packet
1✔
129

130
            return XEdDSASigning.Verify(payload, mockSignature, senderPublicKey, useShortHash);
1✔
131
        }
NEW
132
        catch
×
NEW
133
        {
×
NEW
134
            return false;
×
135
        }
136
    }
1✔
137

138
    private static bool HasSigningCapability(DeviceStateContainer deviceStateContainer)
139
    {
5✔
140
        // Check if we have the necessary keys for signing
141
        // This would be based on the device's PKI configuration
142
        return deviceStateContainer.LocalConfig?.Security?.PublicKey != null;
5!
143
    }
5✔
144

145
    private static void AddXEdDSASignature(MeshPacket meshPacket, DeviceStateContainer deviceStateContainer, bool useShortHash)
NEW
146
    {
×
NEW
147
        if (meshPacket.Decoded?.Payload == null)
×
NEW
148
            return;
×
149

150
        // Get the device's private key (this would be from secure storage)
NEW
151
        var privateKey = GetDevicePrivateKey(deviceStateContainer);
×
NEW
152
        if (privateKey == null)
×
NEW
153
            return;
×
154

155
        // Generate Ed25519 keys from X25519 private key
NEW
156
        var (edPrivateKey, edPublicKey) = XEdDSASigning.GenerateEdDSAKeysFromX25519(privateKey);
×
157

158
        // Sign the payload
NEW
159
        var payload = meshPacket.Decoded.Payload.ToByteArray();
×
NEW
160
        var signature = XEdDSASigning.Sign(payload, edPrivateKey, edPublicKey, useShortHash);
×
161

162
        // Note: In the actual implementation, we would add the signature to the packet
163
        // For now, we'll store it in a custom field or metadata
164
        // Once the firmware PR is merged, this would be:
165
        // meshPacket.Decoded.XeddsaSignature = ByteString.CopyFrom(signature);
166
        // meshPacket.Decoded.HasXeddsaSignature = true;
167

NEW
168
    }
×
169

170
    private static byte[]? GetDevicePrivateKey(DeviceStateContainer deviceStateContainer)
NEW
171
    {
×
172
        // In a real implementation, this would retrieve the device's private key
173
        // from secure storage or generate one if it doesn't exist
174

175
        // For testing, we'll generate a mock private key
NEW
176
        var random = new Random();
×
NEW
177
        var privateKey = new byte[32];
×
NEW
178
        new SecureRandom().NextBytes(privateKey);
×
NEW
179
        return privateKey;
×
NEW
180
    }
×
181

182
    /// <summary>
183
    /// Analyze payload sizes for NodeInfo messages with and without signatures
184
    /// </summary>
185
    /// <param name="user">User to analyze</param>
186
    /// <returns>Payload analysis</returns>
187
    public static NodeInfoPayloadAnalysis AnalyzePayloadSizes(User user)
188
    {
2✔
189
        // Create a test device state container with proper configuration for signing
190
        var testDeviceState = new DeviceStateContainer();
2✔
191
        testDeviceState.MyNodeInfo = new MyNodeInfo { MyNodeNum = 0x12345678 };
2✔
192

193
        // Create unsigned packet
194
        var unsignedPacket = CreateNodeInfoMessage(
2✔
195
            deviceStateContainer: testDeviceState,
2✔
196
            user: user,
2✔
197
            signPacket: false);
2✔
198

199
        // Create signed packets - these will use mock signatures for testing
200
        var signedSha256Packet = CreateNodeInfoMessage(
2✔
201
            deviceStateContainer: testDeviceState,
2✔
202
            user: user,
2✔
203
            signPacket: true,
2✔
204
            useShortHash: true);
2✔
205

206
        var signedSha512Packet = CreateNodeInfoMessage(
2✔
207
            deviceStateContainer: testDeviceState,
2✔
208
            user: user,
2✔
209
            signPacket: true,
2✔
210
            useShortHash: false);
2✔
211

212
        // Since signing currently fails silently due to no private key,
213
        // we'll simulate the signature overhead by creating packets with mock signatures
214
        var unsignedSize = unsignedPacket.ToByteArray().Length;
2✔
215
        var userDataSize = user.ToByteArray().Length;
2✔
216
        
217
        // Ed25519 signatures are always 64 bytes, so we simulate the overhead
218
        var signatureSizeOverhead = 64;
2✔
219
        var signedSha256Size = unsignedSize + signatureSizeOverhead;
2✔
220
        var signedSha512Size = unsignedSize + signatureSizeOverhead;
2✔
221

222
        return new NodeInfoPayloadAnalysis
2✔
223
        {
2✔
224
            UserDataSize = userDataSize,
2✔
225
            BasePacketSize = unsignedSize - userDataSize,
2✔
226
            UnsignedTotalSize = unsignedSize,
2✔
227
            SignedSha256TotalSize = signedSha256Size,
2✔
228
            SignedSha512TotalSize = signedSha512Size,
2✔
229
            SignatureSizeOverhead = signatureSizeOverhead,
2✔
230
            Sha256HashSize = 32,
2✔
231
            Sha512HashSize = 64
2✔
232
        };
2✔
233
    }
2✔
234

235
    private static uint GeneratePacketId()
236
    {
9✔
237
        var bytes = new byte[4];
9✔
238
        new SecureRandom().NextBytes(bytes);
9✔
239
        return BitConverter.ToUInt32(bytes, 0);
9✔
240
    }
9✔
241
}
242

243
/// <summary>
244
/// Analysis of NodeInfo payload sizes with and without signatures
245
/// </summary>
246
public class NodeInfoPayloadAnalysis
247
{
248
    /// <summary>
249
    /// Size of the User data in bytes
250
    /// </summary>
251
    public int UserDataSize { get; set; }
252

253
    /// <summary>
254
    /// Size of the base MeshPacket without User data in bytes
255
    /// </summary>
256
    public int BasePacketSize { get; set; }
257

258
    /// <summary>
259
    /// Total size of unsigned packet in bytes
260
    /// </summary>
261
    public int UnsignedTotalSize { get; set; }
262

263
    /// <summary>
264
    /// Total size of packet signed with SHA-256 in bytes
265
    /// </summary>
266
    public int SignedSha256TotalSize { get; set; }
267

268
    /// <summary>
269
    /// Total size of packet signed with SHA-512 in bytes
270
    /// </summary>
271
    public int SignedSha512TotalSize { get; set; }
272

273
    /// <summary>
274
    /// Size overhead of the signature in bytes (always 64 for Ed25519)
275
    /// </summary>
276
    public int SignatureSizeOverhead { get; set; }
277

278
    /// <summary>
279
    /// Size of SHA-256 hash in bytes
280
    /// </summary>
281
    public int Sha256HashSize { get; set; }
282

283
    /// <summary>
284
    /// Size of SHA-512 hash in bytes
285
    /// </summary>
286
    public int Sha512HashSize { get; set; }
287

288
    /// <summary>
289
    /// Signature overhead for SHA-256 (same as SignatureSizeOverhead)
290
    /// </summary>
291
    public int Sha256Overhead => SignatureSizeOverhead;
3✔
292

293
    /// <summary>
294
    /// Signature overhead for SHA-512 (same as SignatureSizeOverhead)
295
    /// </summary>
296
    public int Sha512Overhead => SignatureSizeOverhead;
3✔
297

298
    /// <summary>
299
    /// Percentage overhead for SHA-256 signatures
300
    /// </summary>
301
    public double Sha256OverheadPercentage => (double)Sha256Overhead / UnsignedTotalSize * 100;
2✔
302

303
    /// <summary>
304
    /// Percentage overhead for SHA-512 signatures
305
    /// </summary>
306
    public double Sha512OverheadPercentage => (double)Sha512Overhead / UnsignedTotalSize * 100;
2✔
307
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc