diff --git a/Examples/ExampleScenarios.cs b/Examples/ExampleScenarios.cs
index 24dcd95..7d62dff 100644
--- a/Examples/ExampleScenarios.cs
+++ b/Examples/ExampleScenarios.cs
@@ -10,6 +10,9 @@ using MewtocolNet.Registers;
using System.Diagnostics;
using System.Text;
using Microsoft.Win32;
+using MewtocolNet.ComCassette;
+using System.Linq;
+using System.Net;
namespace Examples;
@@ -31,61 +34,6 @@ public class ExampleScenarios {
}
- [Scenario("Permament connection with poller")]
- public async Task RunCyclicPollerAsync () {
-
- Console.WriteLine("Starting poller scenario");
-
- int runTime = 10000;
- int remainingTime = runTime;
-
- //setting up a new PLC interface and register collection
- MewtocolInterface interf = new MewtocolInterface("192.168.115.210");
- TestRegisters registers = new TestRegisters();
-
- //attaching the register collection and an automatic poller
- interf.WithRegisterCollection(registers).WithPoller();
-
- await interf.ConnectAsync();
- await interf.AwaitFirstDataAsync();
-
- _ = Task.Factory.StartNew(async () => {
-
- while (interf.IsConnected) {
-
- //flip the bool register each tick and wait for it to be registered
- //await interf.SetRegisterAsync(nameof(registers.TestBool1), !registers.TestBool1);
-
- Console.Title =
- $"Speed UP: {interf.BytesPerSecondUpstream} B/s, " +
- $"Speed DOWN: {interf.BytesPerSecondDownstream} B/s, " +
- $"Poll cycle: {interf.PollerCycleDurationMs} ms, " +
- $"Queued MSGs: {interf.QueuedMessages}";
-
- Console.Clear();
- Console.WriteLine("Underlying registers on tick: \n");
-
- foreach (var register in interf.Registers)
- Console.WriteLine($"{register.ToString(true)}");
-
- Console.WriteLine($"{registers.TestBool1}");
- Console.WriteLine($"{registers.TestDuplicate}");
-
- remainingTime -= 1000;
-
- Console.WriteLine($"\nStopping in: {remainingTime}ms");
-
- await Task.Delay(1000);
-
- }
-
- });
-
- await Task.Delay(runTime);
- interf.Disconnect();
-
- }
-
[Scenario("Dispose and disconnect connection")]
public async Task RunDisposalAndDisconnectAsync () {
@@ -125,68 +73,7 @@ public class ExampleScenarios {
}
- [Scenario("Test auto enums and bitwise, needs the example program from MewtocolNet/PLC_Test")]
- public async Task RunEnumsBitwiseAsync () {
-
- Console.WriteLine("Starting auto enums and bitwise");
-
- //setting up a new PLC interface and register collection
- MewtocolInterface interf = new MewtocolInterface("192.168.115.210");
- TestRegistersEnumBitwise registers = new TestRegistersEnumBitwise();
-
- //attaching the register collection and an automatic poller
- interf.WithRegisterCollection(registers).WithPoller();
-
- registers.PropertyChanged += (s, e) => {
-
- Console.Clear();
-
- var props = registers.GetType().GetProperties();
-
- foreach (var prop in props) {
-
- var val = prop.GetValue(registers);
- string printVal = val?.ToString() ?? "null";
-
- if (val is BitArray bitarr) {
- printVal = bitarr.ToBitString();
- }
-
- Console.Write($"{prop.Name} - ");
-
- if(printVal == "True") {
- Console.ForegroundColor = ConsoleColor.Green;
- }
-
- Console.Write($"{printVal}");
-
- Console.ResetColor();
-
- Console.WriteLine();
-
- }
-
- };
-
- await interf.ConnectAsync();
-
- //use the async method to make sure the cycling is stopped
- //await interf.SetRegisterAsync(nameof(registers.StartCyclePLC), false);
-
- await Task.Delay(5000);
-
- //set the register without waiting for it async
- registers.StartCyclePLC = true;
-
- await Task.Delay(5000);
-
- registers.StartCyclePLC = false;
-
- await Task.Delay(2000);
-
- }
-
- [Scenario("Read register test")]
+ [Scenario("Read all kinds of example registers")]
public async Task RunReadTest () {
//setting up a new PLC interface and register collection
@@ -260,8 +147,8 @@ public class ExampleScenarios {
}
- [Scenario("Test multi frame")]
- public async Task MultiFrameTest() {
+ [Scenario("Test read speed 100 R registers")]
+ public async Task ReadRSpeedTest() {
var preLogLevel = Logger.LogLevel;
Logger.LogLevel = LogLevel.Critical;
@@ -273,11 +160,7 @@ public class ExampleScenarios {
//auto add all built registers to the interface
var builder = RegBuilder.ForInterface(interf);
- var r0reg = builder.FromPlcRegName("R0").Build();
- builder.FromPlcRegName("R1").Build();
- builder.FromPlcRegName("DT0").AsBytes(100).Build();
-
- for (int i = 1; i < 100; i++) {
+ for (int i = 0; i < 100; i++) {
builder.FromPlcRegName($"R{i}A").Build();
@@ -295,7 +178,7 @@ public class ExampleScenarios {
Console.WriteLine("Poller cycle finished");
- Console.WriteLine($"Single frame excec time: {sw.ElapsedMilliseconds:N0}ms for {cmdCount} commands");
+ Console.WriteLine($"Single frame excec time: {sw.ElapsedMilliseconds:N0}ms for {cmdCount} commands and {interf.Registers.Count()} R registers");
interf.Disconnect();
@@ -303,4 +186,38 @@ public class ExampleScenarios {
}
+ [Scenario("Find all COM5 cassettes in the network")]
+ public async Task FindCassettes () {
+
+ Console.Clear();
+
+ var casettes = await CassetteFinder.FindClientsAsync();
+
+ foreach (var cassette in casettes) {
+
+ Console.WriteLine($"{cassette.Name}");
+ Console.WriteLine($"IP: {cassette.IPAddress}");
+ Console.WriteLine($"Port: {cassette.Port}");
+ Console.WriteLine($"DHCP: {cassette.UsesDHCP}");
+ Console.WriteLine($"Subnet Mask: {cassette.SubnetMask}");
+ Console.WriteLine($"Gateway: {cassette.GatewayAddress}");
+ Console.WriteLine($"Mac: {cassette.MacAddress.ToHexString(":")}");
+ Console.WriteLine($"Firmware: {cassette.FirmwareVersion}");
+ Console.WriteLine($"Status: {cassette.Status}");
+ Console.WriteLine($"Endpoint: {cassette.EndpointName} - {cassette.Endpoint.Address}");
+ Console.WriteLine();
+
+ }
+
+ await Task.Delay(5000);
+
+ var found = casettes.FirstOrDefault(x => x.Endpoint.Address.ToString() == "10.237.191.75");
+
+ found.IPAddress = IPAddress.Parse($"192.168.1.{new Random().Next(20, 120)}");
+ found.Name = $"Rand{new Random().Next(5, 15)}";
+
+ await found.SendNewConfigAsync();
+
+ }
+
}
diff --git a/Examples/Program.cs b/Examples/Program.cs
index 3b2a56e..dc1b27c 100644
--- a/Examples/Program.cs
+++ b/Examples/Program.cs
@@ -7,6 +7,8 @@ using System.Reflection;
using System.Threading.Tasks;
using MewtocolNet.Logging;
using System.Text.RegularExpressions;
+using System.Globalization;
+using System.Threading;
namespace Examples;
@@ -16,13 +18,21 @@ class Program {
static void Main(string[] args) {
+ Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-us");
+
+ Console.Clear();
+
AppDomain.CurrentDomain.UnhandledException += (s,e) => {
- Console.WriteLine(e.ExceptionObject.ToString());
+ Console.ForegroundColor = ConsoleColor.Red;
+ Console.WriteLine($"Uncatched exception: {e.ExceptionObject.ToString()}");
+ Console.ResetColor();
};
- TaskScheduler.UnobservedTaskException += (s,e) => {
- Console.WriteLine(e.Exception.ToString());
- };
+ //TaskScheduler.UnobservedTaskException += (s,e) => {
+ // Console.ForegroundColor = ConsoleColor.Magenta;
+ // Console.WriteLine($"Unobserved Task Uncatched exception: {e.Exception.ToString()}");
+ // Console.ResetColor();
+ //};
ExampleSzenarios.SetupLogger();
diff --git a/MewtocolNet/ComCassette/CassetteFinder.cs b/MewtocolNet/ComCassette/CassetteFinder.cs
new file mode 100644
index 0000000..346b212
--- /dev/null
+++ b/MewtocolNet/ComCassette/CassetteFinder.cs
@@ -0,0 +1,161 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Net;
+using System.Net.NetworkInformation;
+using System.Net.Sockets;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MewtocolNet.ComCassette {
+
+ ///
+ /// Provides a interface to modify and find PLC network cassettes also known as COM5
+ ///
+ public class CassetteFinder {
+
+ public static async Task> FindClientsAsync (string ipSource = null, int timeoutMs = 100) {
+
+ var from = new IPEndPoint(IPAddress.Any, 0);
+
+ List cassettesFound = new List();
+ List>> interfacesTasks = new List>>();
+
+ var usableInterfaces = GetUseableNetInterfaces();
+
+ if (ipSource == null) {
+
+ var interfaces = NetworkInterface.GetAllNetworkInterfaces();
+
+ foreach (NetworkInterface netInterface in usableInterfaces) {
+
+ IPInterfaceProperties ipProps = netInterface.GetIPProperties();
+ var unicastInfo = ipProps.UnicastAddresses
+ .FirstOrDefault(x => x.Address.AddressFamily == AddressFamily.InterNetwork);
+
+ var ep = new IPEndPoint(unicastInfo.Address, 0);
+ interfacesTasks.Add(FindClientsForEndpoint(ep, timeoutMs, netInterface.Name));
+
+ }
+
+ } else {
+
+ from = new IPEndPoint(IPAddress.Parse(ipSource), 0);
+
+ var netInterface = usableInterfaces.FirstOrDefault(x => x.GetIPProperties().UnicastAddresses.Any(y => y.Address.ToString() == ipSource));
+
+ if (netInterface == null)
+ throw new NotSupportedException($"The host endpoint {ipSource}, is not available");
+
+ interfacesTasks.Add(FindClientsForEndpoint(from, timeoutMs, netInterface.Name));
+
+ }
+
+ //run the interface querys
+ var grouped = await Task.WhenAll(interfacesTasks);
+
+ foreach (var item in grouped)
+ cassettesFound.AddRange(item);
+
+ return cassettesFound;
+
+ }
+
+ private static IEnumerable GetUseableNetInterfaces () {
+
+ foreach (NetworkInterface netInterface in NetworkInterface.GetAllNetworkInterfaces()) {
+
+ bool isEthernet =
+ netInterface.NetworkInterfaceType == NetworkInterfaceType.Ethernet ||
+ netInterface.NetworkInterfaceType == NetworkInterfaceType.Ethernet3Megabit ||
+ netInterface.NetworkInterfaceType == NetworkInterfaceType.FastEthernetFx ||
+ netInterface.NetworkInterfaceType == NetworkInterfaceType.FastEthernetT ||
+ netInterface.NetworkInterfaceType == NetworkInterfaceType.GigabitEthernet;
+
+ bool isWlan = netInterface.NetworkInterfaceType == NetworkInterfaceType.Wireless80211;
+
+ bool isUsable = netInterface.OperationalStatus == OperationalStatus.Up;
+
+ if (!isUsable) continue;
+ if (!(isWlan || isEthernet)) continue;
+
+ IPInterfaceProperties ipProps = netInterface.GetIPProperties();
+ var hasUnicastInfo = ipProps.UnicastAddresses
+ .Any(x => x.Address.AddressFamily == AddressFamily.InterNetwork);
+
+ if (!hasUnicastInfo) continue;
+
+ yield return netInterface;
+
+ }
+
+ }
+
+ private static async Task> FindClientsForEndpoint (IPEndPoint from, int timeoutMs, string ipEndpointName) {
+
+ var cassettesFound = new List();
+
+ int plcPort = 9090;
+
+ // Byte msg to request the status transmission of all plcs
+ byte[] requestCode = new byte[] { 0x88, 0x40, 0x00 };
+
+ // The start code of the status transmission response
+ byte[] startCode = new byte[] { 0x88, 0xC0, 0x00 };
+
+ using(var udpClient = new UdpClient()) {
+
+ udpClient.EnableBroadcast = true;
+
+ udpClient.Client.Bind(from);
+
+ //broadcast packet to all devices (plc specific package)
+ udpClient.Send(requestCode, requestCode.Length, "255.255.255.255", plcPort);
+
+ //canceling after no new data was read
+ CancellationTokenSource tSource = new CancellationTokenSource();
+ var tm = new System.Timers.Timer(timeoutMs);
+ tm.Elapsed += (s, e) => {
+ tSource.Cancel();
+ tm.Stop();
+ };
+ tm.Start();
+
+ //wait for devices to send response
+ try {
+
+ byte[] recvBuffer = null;
+
+ while (!tSource.Token.IsCancellationRequested) {
+
+ var res = await udpClient.ReceiveAsync().WithCancellation(tSource.Token);
+
+ if (res.Buffer == null) break;
+
+ recvBuffer = res.Buffer;
+
+ if (recvBuffer.SearchBytePattern(startCode) == 0) {
+
+ tm.Stop();
+ tm.Start();
+
+ var parsed = CassetteInformation.FromBytes(recvBuffer, from, ipEndpointName);
+ if (parsed != null) cassettesFound.Add(parsed);
+
+ }
+
+ }
+
+ } catch (OperationCanceledException) { } catch (SocketException) { }
+
+ }
+
+ return cassettesFound;
+
+ }
+
+ }
+
+}
diff --git a/MewtocolNet/ComCassette/CassetteInformation.cs b/MewtocolNet/ComCassette/CassetteInformation.cs
new file mode 100644
index 0000000..fd5845f
--- /dev/null
+++ b/MewtocolNet/ComCassette/CassetteInformation.cs
@@ -0,0 +1,182 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+using System.Threading.Tasks;
+
+//WARNING! The whole UDP protocol was reverse engineered and is not fully implemented..
+
+namespace MewtocolNet.ComCassette {
+
+ ///
+ /// Information about the COM cassette
+ ///
+ public class CassetteInformation {
+
+ ///
+ /// Indicates if the cassette is currently configurating
+ ///
+ public bool IsConfigurating { get; private set; }
+
+ ///
+ /// Name of the COM cassette
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// Indicates the usage of DHCP
+ ///
+ public bool UsesDHCP { get; set; }
+
+ ///
+ /// IP Address of the COM cassette
+ ///
+ public IPAddress IPAddress { get; set; }
+
+ ///
+ /// Subnet mask of the cassette
+ ///
+ public IPAddress SubnetMask { get; set; }
+
+ ///
+ /// Default gateway of the cassette
+ ///
+ public IPAddress GatewayAddress { get; set; }
+
+ ///
+ /// Mac address of the cassette
+ ///
+ public byte[] MacAddress { get; private set; }
+
+ ///
+ /// The source endpoint the cassette is reachable from
+ ///
+ public IPEndPoint Endpoint { get; private set; }
+
+ ///
+ /// The name of the endpoint the device is reachable from, or null if not specifically defined
+ ///
+ public string EndpointName { get; private set; }
+
+ ///
+ /// Firmware version as string
+ ///
+ public string FirmwareVersion { get; private set; }
+
+ ///
+ /// The tcp port of the cassette
+ ///
+ public int Port { get; private set; }
+
+ ///
+ /// Status of the cassette
+ ///
+ public CassetteStatus Status { get; private set; }
+
+ internal static CassetteInformation FromBytes(byte[] bytes, IPEndPoint endpoint, string endpointName) {
+
+ // Receive data package explained:
+ // 0 3 4 8 12 17 22 24 27 29 31 32
+ // 88 C0 00 | 00 | C0 A8 73 D4 | FF FF FF 00 | C0 A8 73 3C | 00 | C0 8F 60 53 1C | 01 10 | 23 86 | 00 | 25 | 00 | 00 | 00 | 0D | (byte) * (n) NAME LEN
+ // Header |DHCP| IPv4 addr. | Subnet Mask | IPv4 Gatwy | | Mac Addr. | Ver. | Port | | | |STAT| | Name LEN | Name
+ // 1 or 0 Procuct Type? StatusCode Length of Name
+
+ //get ips / mac
+ var dhcpOn = bytes.Skip(3).First() != 0x00;
+ var ipAdd = new IPAddress(bytes.Skip(4).Take(4).ToArray());
+ var subnetMask = new IPAddress(bytes.Skip(8).Take(4).ToArray());
+ var gateWaysAdd = new IPAddress(bytes.Skip(12).Take(4).ToArray());
+ var macAdd = bytes.Skip(17).Take(5).ToArray();
+ var firmwareV = string.Join(".", bytes.Skip(22).Take(2).Select(x => x.ToString("X1")).ToArray());
+ var port = BitConverter.ToUInt16(bytes.Skip(24).Take(2).Reverse().ToArray(), 0);
+ var status = (CassetteStatus)bytes.Skip(29).First();
+
+ //missing blocks, later
+
+ //get name
+ var name = Encoding.ASCII.GetString(bytes.Skip(32).ToArray());
+
+ return new CassetteInformation {
+
+ Name = name,
+ UsesDHCP = dhcpOn,
+ IPAddress = ipAdd,
+ SubnetMask = subnetMask,
+ GatewayAddress = gateWaysAdd,
+ MacAddress = macAdd,
+ Endpoint = endpoint,
+ EndpointName = endpointName,
+ FirmwareVersion = firmwareV,
+ Port = port,
+ Status = status,
+
+ };
+
+ }
+
+ public async Task SendNewConfigAsync () {
+
+ if (IsConfigurating) return;
+
+ // this command gets sent to a specific plc ip address to overwrite the cassette config
+ // If dhcp is set to 1 the ip is ignored but still must be valid
+
+ // 88 41 00 | 00 | C0 8F 61 07 1B | 05 | 54 65 73 74 31 | 05 | 46 50 58 45 54 | 00 | C0 A8 01 07 | FF FF FF 00 | C0 A8 73 3C
+ // Header | | | 5 | T e s t 1 | 05 | F P X E T |0||1| 192.168.1.7 | 255.255... | 192.168.115.60
+ // Header | | Mac Address |LEN>| ASCII Name |LEN>| Static |DHCP| Target IP | Subnet Mask | Gateway
+
+ IsConfigurating = true;
+
+ List sendBytes = new List();
+
+ //add cmd header
+ sendBytes.AddRange(new byte[] { 0x88, 0x41, 0x00, 0x00 });
+
+ //add mac
+ sendBytes.AddRange(MacAddress);
+
+ //add name length
+ sendBytes.Add((byte)Name.Length);
+
+ //add name
+ sendBytes.AddRange(Encoding.ASCII.GetBytes(Name));
+
+ //FPXET
+ var subname = Encoding.ASCII.GetBytes("TESTFP");
+
+ //add sub name length
+ sendBytes.Add((byte)subname.Length);
+
+ //add subname
+ sendBytes.AddRange(subname);
+
+ //add dhcp 0 | 1
+ sendBytes.Add((byte)(UsesDHCP ? 0x01 : 0x00));
+
+ //add ip address
+ sendBytes.AddRange(IPAddress.GetAddressBytes());
+
+ //add subnet mask ip address
+ sendBytes.AddRange(SubnetMask.GetAddressBytes());
+
+ //add gateway ip
+ sendBytes.AddRange(GatewayAddress.GetAddressBytes());
+
+ var sendBytesArr = sendBytes.ToArray();
+
+ using(var udpClient = new UdpClient()) {
+
+ udpClient.Client.Bind(Endpoint);
+
+ //broadcast packet to all devices (plc specific package)
+ await udpClient.SendAsync(sendBytesArr, sendBytesArr.Length, "255.255.255.255", 9090);
+
+ }
+
+ }
+
+ }
+
+}
diff --git a/MewtocolNet/ComCassette/CassetteStatus.cs b/MewtocolNet/ComCassette/CassetteStatus.cs
new file mode 100644
index 0000000..4f84666
--- /dev/null
+++ b/MewtocolNet/ComCassette/CassetteStatus.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace MewtocolNet.ComCassette {
+
+ ///
+ /// Needs a list of all status codes.. hard to reverse engineer
+ ///
+ public enum CassetteStatus {
+
+ ///
+ /// Cassette is running as intended
+ ///
+ Normal = 0,
+ ///
+ /// Cassette DHCP resolution error
+ ///
+ DHCPError = 2,
+
+ }
+
+}
diff --git a/MewtocolNet/Extensions/AsyncExtensions.cs b/MewtocolNet/Extensions/AsyncExtensions.cs
new file mode 100644
index 0000000..463ad8a
--- /dev/null
+++ b/MewtocolNet/Extensions/AsyncExtensions.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+using System.Threading;
+using System.Net.Sockets;
+
+namespace MewtocolNet {
+
+ internal static class AsyncExtensions {
+
+ public static async Task WithCancellation(this Task task, CancellationToken cancellationToken) {
+
+ var tcs = new TaskCompletionSource();
+ using (cancellationToken.Register(s => ((TaskCompletionSource)s).TrySetResult(true), tcs)) {
+ if (task != await Task.WhenAny(task, tcs.Task)) {
+ throw new OperationCanceledException(cancellationToken);
+ }
+ }
+
+ return task.Result;
+
+ }
+
+ }
+
+}
diff --git a/MewtocolNet/MewtocolHelpers.cs b/MewtocolNet/MewtocolHelpers.cs
index 99be553..1b4f071 100644
--- a/MewtocolNet/MewtocolHelpers.cs
+++ b/MewtocolNet/MewtocolHelpers.cs
@@ -59,6 +59,29 @@ namespace MewtocolNet {
#region Byte and string operation helpers
+ ///
+ /// Searches a byte array for a pattern
+ ///
+ ///
+ ///
+ /// The start index of the found pattern or -1
+ public static int SearchBytePattern(this byte[] src, byte[] pattern) {
+
+ int maxFirstCharSlot = src.Length - pattern.Length + 1;
+ for (int i = 0; i < maxFirstCharSlot; i++) {
+ if (src[i] != pattern[0]) // compare only first byte
+ continue;
+
+ // found a match on first byte, now try to match rest of the pattern
+ for (int j = pattern.Length - 1; j >= 1; j--) {
+ if (src[i + j] != pattern[j]) break;
+ if (j == 1) return i;
+ }
+ }
+ return -1;
+
+ }
+
///
/// Converts a string (after converting to upper case) to ascii bytes
///
@@ -204,7 +227,7 @@ namespace MewtocolNet {
///
/// Seperator between the hex numbers
/// The byte array
- internal static string ToHexString (this byte[] arr, string seperator = "") {
+ public static string ToHexString (this byte[] arr, string seperator = "") {
StringBuilder sb = new StringBuilder();
diff --git a/MewtocolNet/MewtocolInterface.cs b/MewtocolNet/MewtocolInterface.cs
index cb48916..793720c 100644
--- a/MewtocolNet/MewtocolInterface.cs
+++ b/MewtocolNet/MewtocolInterface.cs
@@ -466,7 +466,7 @@ namespace MewtocolNet {
await stream.ReadAsync(responseBuffer, 0, responseBuffer.Length);
bool terminatorReceived = responseBuffer.Any(x => x == (byte)CR);
- var delimiterTerminatorIdx = SearchBytePattern(responseBuffer, new byte[] { (byte)DELIMITER, (byte)CR });
+ var delimiterTerminatorIdx = responseBuffer.SearchBytePattern(new byte[] { (byte)DELIMITER, (byte)CR });
if (terminatorReceived && delimiterTerminatorIdx == -1) {
cmdState = CommandState.Complete;
@@ -557,23 +557,6 @@ namespace MewtocolNet {
}
- private int SearchBytePattern (byte[] src, byte[] pattern) {
-
- int maxFirstCharSlot = src.Length - pattern.Length + 1;
- for (int i = 0; i < maxFirstCharSlot; i++) {
- if (src[i] != pattern[0]) // compare only first byte
- continue;
-
- // found a match on first byte, now try to match rest of the pattern
- for (int j = pattern.Length - 1; j >= 1; j--) {
- if (src[i + j] != pattern[j]) break;
- if (j == 1) return i;
- }
- }
- return -1;
-
- }
-
#endregion
#region Disposing