// SPDX-License-Identifier: AGPL-3.0-only
/**
* Digital Voice Modem - Fixed Network Equipment Core Library
* AGPLv3 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* @package DVM / Fixed Network Equipment Core Library
* @license AGPLv3 License (https://opensource.org/licenses/AGPL-3.0)
*
* Copyright (C) 2022,2024 Bryan Biedenkapp, N2PLL
*
*/
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Runtime.Intrinsics.X86;
using System.Security.Cryptography;
using System.Threading.Tasks;
namespace fnecore
{
///
/// Structure representing a raw UDP packet frame.
///
/// "Frame" is used loosely here...
public struct UdpFrame
{
///
///
///
public IPEndPoint Endpoint;
///
///
///
public byte[] Message;
} // public struct UDPFrame
///
/// Base class from which all UDP classes are derived.
///
public abstract class UdpBase
{
protected const ushort AES_WRAPPED_PCKT_MAGIC = 0xC0FE;
protected const int AES_BLOCK_SIZE = 128;
protected UdpClient client;
protected bool isCryptoWrapped;
protected byte[] presharedKey;
/*
** Methods
*/
///
/// Initializes a new instance of the class.
///
protected UdpBase()
{
client = new UdpClient();
isCryptoWrapped = false;
presharedKey = null;
}
///
/// Sets the preshared encryption key.
///
///
public void SetPresharedKey(byte[] presharedKey)
{
if (presharedKey != null) {
this.presharedKey = presharedKey;
isCryptoWrapped = true;
} else {
this.presharedKey = null;
isCryptoWrapped = false;
}
}
///
///
///
///
public async Task Receive()
{
UdpReceiveResult res = await client.ReceiveAsync();
byte[] buffer = res.Buffer;
// are we crypto wrapped?
if (isCryptoWrapped)
{
if (presharedKey == null)
throw new InvalidOperationException("tried to read datagram encrypted with no key? this shouldn't happen BUGBUG");
// does the network packet contain the appropriate magic leader?
ushort magic = FneUtils.ToUInt16(res.Buffer, 0);
if (magic == AES_WRAPPED_PCKT_MAGIC)
{
int cryptedLen = res.Buffer.Length - 2;
byte[] cryptoBuffer = new byte[cryptedLen];
Buffer.BlockCopy(res.Buffer, 2, cryptoBuffer, 0, cryptedLen);
// decrypt
byte[] decrypted = Decrypt(cryptoBuffer, presharedKey);
// finalize, cleanup buffers, and replace with new
if (decrypted != null)
{
buffer = decrypted;
}
else
{
buffer = new byte[0];
}
}
else
{
buffer = new byte[0]; // this will effectively discard packets without the packet magic
}
}
return new UdpFrame()
{
Message = buffer,
Endpoint = res.RemoteEndPoint
};
}
///
///
///
///
///
///
protected static byte[] Encrypt(byte[] buffer, byte[] key)
{
byte[] encrypted = null;
using (AesManaged aes = new AesManaged()
{
KeySize = 256,
Key = key,
BlockSize = AES_BLOCK_SIZE,
Mode = CipherMode.ECB,
Padding = PaddingMode.Zeros,
IV = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
})
{
ICryptoTransform encryptor = aes.CreateEncryptor();
using (MemoryStream ms = new MemoryStream())
using (CryptoStream cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
{
cs.Write(buffer, 0, buffer.Length);
encrypted = ms.ToArray();
}
}
return encrypted;
}
///
///
///
///
///
///
protected static byte[] Decrypt(byte[] buffer, byte[] key)
{
using (AesManaged aes = new AesManaged()
{
KeySize = 256,
Key = key,
BlockSize = 128,
Mode = CipherMode.ECB,
Padding = PaddingMode.Zeros,
IV = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
})
{
ICryptoTransform decryptor = aes.CreateDecryptor();
using (MemoryStream ms = new MemoryStream(buffer))
using (CryptoStream cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
using (MemoryStream resultStream = new MemoryStream())
{
cs.CopyTo(resultStream);
return resultStream.ToArray();
}
}
}
} // public abstract class UDPBase
///
///
///
public class UdpReceiver : UdpBase
{
private IPEndPoint endpoint;
/*
** Properties
*/
///
/// Gets the for this .
///
public IPEndPoint EndPoint => endpoint;
/*
** Methods
*/
///
/// Initializes a new instance of the class.
///
public UdpReceiver()
{
/* stub */
}
///
///
///
///
///
///
public void Connect(string hostName, int port)
{
try
{
try
{
endpoint = new IPEndPoint(IPAddress.Parse(hostName), port);
}
catch
{
IPHostEntry entry = Dns.GetHostEntry(hostName);
if (entry.AddressList.Length > 0)
{
IPAddress address = entry.AddressList[0];
endpoint = new IPEndPoint(address, port);
}
}
}
catch
{
return;
}
client.Connect(endpoint.Address.ToString(), endpoint.Port);
}
///
///
///
///
///
public void Connect(IPEndPoint endpoint)
{
UdpReceiver recv = new UdpReceiver();
this.endpoint = endpoint;
client.Connect(endpoint.Address.ToString(), endpoint.Port);
}
///
///
///
///
public void Send(UdpFrame frame)
{
byte[] buffer = frame.Message;
// are we crypto wrapped?
if (isCryptoWrapped)
{
if (presharedKey == null)
throw new InvalidOperationException("tried to read datagram encrypted with no key? this shouldn't happen BUGBUG");
// calculate the length of the encrypted data
int cryptedLen = buffer.Length;
// calculate the padding needed to make the buffer a multiple of AES_BLOCK_SIZE
int paddingLen = AES_BLOCK_SIZE - (cryptedLen % AES_BLOCK_SIZE);
if (paddingLen == AES_BLOCK_SIZE)
{
paddingLen = 0; // no padding needed if already a multiple of AES_BLOCK_SIZE
}
byte[] cryptoBuffer = new byte[cryptedLen + paddingLen];
Buffer.BlockCopy(buffer, 0, cryptoBuffer, 0, buffer.Length);
// encrypt the buffer
byte[] crypted = Encrypt(cryptoBuffer, presharedKey);
// create the final buffer with the magic number and encrypted data
buffer = new byte[crypted.Length + 2];
Buffer.BlockCopy(crypted, 0, buffer, 2, crypted.Length);
FneUtils.WriteBytes(AES_WRAPPED_PCKT_MAGIC, ref buffer, 0);
// set the length to the actual length of the buffer to be sent
frame.Message = buffer;
}
client.Send(frame.Message, frame.Message.Length);
}
} // public class UdpReceiver : UdpBase
} // namespace fnecore