├── shell_crcbrute
├── Makefile
├── brute
└── brute.c
├── SPIlog
├── App.config
├── Properties
│ └── AssemblyInfo.cs
├── SPIlog.csproj
├── Program.cs
└── Options.cs
├── CRSF_Debug
├── App.config
├── Properties
│ └── AssemblyInfo.cs
├── CRSF_Debug.csproj
└── Program.cs
└── CRSF_Debug.sln
/shell_crcbrute/Makefile:
--------------------------------------------------------------------------------
1 |
2 |
3 | all: brute.c
4 | gcc -O3 brute.c -o brute -fopenmp
5 |
--------------------------------------------------------------------------------
/shell_crcbrute/brute:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/g3gg0/CRSF_RevEng/master/shell_crcbrute/brute
--------------------------------------------------------------------------------
/SPIlog/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/CRSF_Debug/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/SPIlog/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // Allgemeine Informationen über eine Assembly werden über die folgenden
6 | // Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern,
7 | // die einer Assembly zugeordnet sind.
8 | [assembly: AssemblyTitle("SPIlog")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("SPIlog")]
13 | [assembly: AssemblyCopyright("Copyright © 2021")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Durch Festlegen von ComVisible auf FALSE werden die Typen in dieser Assembly
18 | // für COM-Komponenten unsichtbar. Wenn Sie auf einen Typ in dieser Assembly von
19 | // COM aus zugreifen müssen, sollten Sie das ComVisible-Attribut für diesen Typ auf "True" festlegen.
20 | [assembly: ComVisible(false)]
21 |
22 | // Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird
23 | [assembly: Guid("517de142-fc87-4e09-bd61-2426ccb0c732")]
24 |
25 | // Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten:
26 | //
27 | // Hauptversion
28 | // Nebenversion
29 | // Buildnummer
30 | // Revision
31 | //
32 | // Sie können alle Werte angeben oder Standardwerte für die Build- und Revisionsnummern verwenden,
33 | // indem Sie "*" wie unten gezeigt eingeben:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/CRSF_Debug/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // Allgemeine Informationen über eine Assembly werden über die folgenden
6 | // Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern,
7 | // die einer Assembly zugeordnet sind.
8 | [assembly: AssemblyTitle("CRSF_Debug")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("CRSF_Debug")]
13 | [assembly: AssemblyCopyright("Copyright © 2021")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Durch Festlegen von ComVisible auf FALSE werden die Typen in dieser Assembly
18 | // für COM-Komponenten unsichtbar. Wenn Sie auf einen Typ in dieser Assembly von
19 | // COM aus zugreifen müssen, sollten Sie das ComVisible-Attribut für diesen Typ auf "True" festlegen.
20 | [assembly: ComVisible(false)]
21 |
22 | // Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird
23 | [assembly: Guid("7cf0de11-2c7e-421f-acf2-edf807124555")]
24 |
25 | // Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten:
26 | //
27 | // Hauptversion
28 | // Nebenversion
29 | // Buildnummer
30 | // Revision
31 | //
32 | // Sie können alle Werte angeben oder Standardwerte für die Build- und Revisionsnummern verwenden,
33 | // indem Sie "*" wie unten gezeigt eingeben:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/CRSF_Debug.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.0.32112.339
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CRSF_Debug", "CRSF_Debug\CRSF_Debug.csproj", "{7CF0DE11-2C7E-421F-ACF2-EDF807124555}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SPIlog", "SPIlog\SPIlog.csproj", "{517DE142-FC87-4E09-BD61-2426CCB0C732}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Release|Any CPU = Release|Any CPU
14 | EndGlobalSection
15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
16 | {7CF0DE11-2C7E-421F-ACF2-EDF807124555}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17 | {7CF0DE11-2C7E-421F-ACF2-EDF807124555}.Debug|Any CPU.Build.0 = Debug|Any CPU
18 | {7CF0DE11-2C7E-421F-ACF2-EDF807124555}.Release|Any CPU.ActiveCfg = Release|Any CPU
19 | {7CF0DE11-2C7E-421F-ACF2-EDF807124555}.Release|Any CPU.Build.0 = Release|Any CPU
20 | {517DE142-FC87-4E09-BD61-2426CCB0C732}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {517DE142-FC87-4E09-BD61-2426CCB0C732}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {517DE142-FC87-4E09-BD61-2426CCB0C732}.Release|Any CPU.ActiveCfg = Release|Any CPU
23 | {517DE142-FC87-4E09-BD61-2426CCB0C732}.Release|Any CPU.Build.0 = Release|Any CPU
24 | EndGlobalSection
25 | GlobalSection(SolutionProperties) = preSolution
26 | HideSolutionNode = FALSE
27 | EndGlobalSection
28 | GlobalSection(ExtensibilityGlobals) = postSolution
29 | SolutionGuid = {45A0F764-8198-4AF8-9F92-D4643BF65DA4}
30 | EndGlobalSection
31 | EndGlobal
32 |
--------------------------------------------------------------------------------
/CRSF_Debug/CRSF_Debug.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {7CF0DE11-2C7E-421F-ACF2-EDF807124555}
8 | Exe
9 | CRSF_Debug
10 | CRSF_Debug
11 | v4.6
12 | 512
13 | true
14 | true
15 |
16 |
17 | AnyCPU
18 | true
19 | full
20 | false
21 | bin\Debug\
22 | DEBUG;TRACE
23 | prompt
24 | 4
25 |
26 |
27 | AnyCPU
28 | pdbonly
29 | true
30 | bin\Release\
31 | TRACE
32 | prompt
33 | 4
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/SPIlog/SPIlog.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {517DE142-FC87-4E09-BD61-2426CCB0C732}
8 | Exe
9 | SPIlog
10 | SPIlog
11 | v4.6
12 | 512
13 | true
14 | true
15 |
16 |
17 | AnyCPU
18 | true
19 | full
20 | false
21 | bin\Debug\
22 | DEBUG;TRACE
23 | prompt
24 | 4
25 |
26 |
27 | AnyCPU
28 | pdbonly
29 | true
30 | bin\Release\
31 | TRACE
32 | prompt
33 | 4
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/CRSF_Debug/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics;
4 | using System.IO.Ports;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading;
8 | using System.Threading.Tasks;
9 |
10 | namespace CRSF_Debug
11 | {
12 | class Program
13 | {
14 | enum eCrsfState
15 | {
16 | STATE_WAIT_SYNC = 0,
17 | STATE_RECEIVE_LEN = 1,
18 | STATE_RECEIVE_DATA = 2
19 | };
20 |
21 | enum eCrsfFrameType
22 | {
23 | CRSF_FRAMETYPE_GPS = 0x02,
24 | CRSF_FRAMETYPE_BATTERY_SENSOR = 0x08,
25 | CRSF_FRAMETYPE_LINK_STATISTICS = 0x14,
26 | CRSF_FRAMETYPE_RC_CHANNELS_PACKED = 0x16,
27 | CRSF_FRAMETYPE_ATTITUDE = 0x1E,
28 | CRSF_FRAMETYPE_FLIGHT_MODE = 0x21,
29 | // Extended Header Frames, range: 0x28 to 0x96
30 | CRSF_FRAMETYPE_DEVICE_PING = 0x28,
31 | CRSF_FRAMETYPE_DEVICE_INFO = 0x29,
32 | CRSF_FRAMETYPE_PARAMETER_SETTINGS_ENTRY = 0x2B,
33 | CRSF_FRAMETYPE_PARAMETER_READ = 0x2C,
34 | CRSF_FRAMETYPE_PARAMETER_WRITE = 0x2D,
35 | CRSF_FRAMETYPE_COMMAND = 0x32,
36 | // MSP commands
37 | CRSF_FRAMETYPE_MSP_REQ = 0x7A, // response request using msp sequence as command
38 | CRSF_FRAMETYPE_MSP_RESP = 0x7B, // reply with 58 byte chunked binary
39 | CRSF_FRAMETYPE_MSP_WRITE = 0x7C // write with 8 byte chunked binary (OpenTX outbound telemetry buffer limit)
40 | };
41 |
42 |
43 | static byte crc8(byte[] data, int start, int length)
44 | {
45 | uint crc = 0;
46 | for (uint i = 0; i < length; i++)
47 | {
48 | uint inbyte = data[start + i];
49 | for (uint j = 0; j < 8; j++)
50 | {
51 | uint mix = (crc ^ inbyte) & 0x80;
52 | crc <<= 1;
53 | if (mix != 0)
54 | {
55 | crc ^= 0xD5;
56 | }
57 | inbyte <<= 1;
58 | }
59 | }
60 | return (byte)crc;
61 | }
62 |
63 | public static string ByteArrayToString(byte[] ba, int start, int length)
64 | {
65 | StringBuilder hex = new StringBuilder(length * 2);
66 | for (int pos = 0; pos < length; pos++)
67 | {
68 | hex.AppendFormat("{0:x2}", ba[start + pos]);
69 | }
70 | return hex.ToString();
71 | }
72 |
73 | private static SerialPort Port;
74 |
75 | static void Main(string[] args)
76 | {
77 | Port = new SerialPort("COM16", 420000, Parity.None, 8, StopBits.One);
78 | eCrsfState state = eCrsfState.STATE_WAIT_SYNC;
79 |
80 | Port.Open();
81 |
82 | int received = 0;
83 | byte[] buffer = new byte[0];
84 |
85 | while (true)
86 | {
87 | int data = Port.ReadByte();
88 |
89 | switch (state)
90 | {
91 | case eCrsfState.STATE_WAIT_SYNC:
92 | if (data == 0xC8)
93 | {
94 | state = eCrsfState.STATE_RECEIVE_LEN;
95 | }
96 | break;
97 |
98 | case eCrsfState.STATE_RECEIVE_LEN:
99 | buffer = new byte[data];
100 | received = 0;
101 | state = eCrsfState.STATE_RECEIVE_DATA;
102 | break;
103 |
104 | case eCrsfState.STATE_RECEIVE_DATA:
105 | buffer[received++] = (byte)data;
106 | if (received >= buffer.Length)
107 | {
108 | byte crc = buffer[buffer.Length - 1];
109 | byte calcCrc = crc8(buffer, 0, buffer.Length - 1);
110 | bool crcFail = (calcCrc != crc);
111 |
112 | if (crcFail)
113 | {
114 | Console.WriteLine("CRC Failed");
115 | }
116 | else
117 | {
118 | Process(buffer, 0, buffer.Length - 1);
119 | }
120 |
121 |
122 | state = eCrsfState.STATE_WAIT_SYNC;
123 | }
124 | break;
125 | }
126 | }
127 | }
128 |
129 | private static byte dummy_crc = 0;
130 |
131 | private static Dictionary ReceivedTypes = new Dictionary();
132 | private static List ReceivedTypesIndex = new List();
133 |
134 | private static DateTime lastTime = DateTime.Now;
135 | private static int framesSent = 0;
136 | private static int framesReceived = 0;
137 | private static int framesReceivedLast = 0;
138 |
139 | private static void Process(byte[] buffer, int start, int length)
140 | {
141 | int pos = 8;
142 | DateTime nowTime = DateTime.Now;
143 |
144 | if (Enum.IsDefined(typeof(eCrsfFrameType), (int)buffer[start + 0]))
145 | {
146 | eCrsfFrameType type = (eCrsfFrameType)buffer[start + 0];
147 |
148 | if (!ReceivedTypes.ContainsKey(type))
149 | {
150 | ReceivedTypes.Add(type, 0);
151 | ReceivedTypesIndex.Add(type);
152 | }
153 |
154 | ReceivedTypes[type]++;
155 | pos = ReceivedTypesIndex.IndexOf(type);
156 |
157 | if (type == eCrsfFrameType.CRSF_FRAMETYPE_RC_CHANNELS_PACKED)
158 | {
159 | uint[] channelValues = new uint[16];
160 | byte chanBits = 0;
161 | byte channel = 0;
162 | uint value = 0;
163 |
164 | framesReceived++;
165 |
166 | /* go through all payload bytes */
167 | for (int bitPos = 0; bitPos < 22; bitPos++)
168 | {
169 | /* fetch 8 bits */
170 | value |= ((uint)buffer[start + 1 + bitPos]) << chanBits;
171 | chanBits += 8;
172 |
173 | /* when we got enough (11) bits, treat this as a sample */
174 | if (chanBits >= 11)
175 | {
176 | channelValues[channel++] = (value & 0x7FF);
177 | /* keep remaining bits */
178 | value >>= 11;
179 | chanBits -= 11;
180 | }
181 | }
182 | Console.SetCursorPosition(4, 2 + 4 * pos);
183 | Console.Write("Received " + ReceivedTypes[type].ToString().PadLeft(6) + "x 0x" + buffer[start + 0].ToString("X2") + " (" + Enum.GetName(typeof(eCrsfFrameType), type) + ")");
184 |
185 | Console.SetCursorPosition(4, 2 + 4 * pos + 2);
186 | Console.Write(" parsed " + string.Join(" ", channelValues.Select(v => v.ToString("D4"))));
187 | }
188 | else
189 | {
190 | Console.SetCursorPosition(4, 2 + 4 * pos);
191 | Console.Write("Received " + ReceivedTypes[type].ToString().PadLeft(6) + "x 0x" + buffer[start + 0].ToString("X2") + " (" + Enum.GetName(typeof(eCrsfFrameType), type) + ")");
192 | }
193 | }
194 | else if ((int)buffer[start + 0] == 0xED)
195 | {
196 | int ms = 0;
197 |
198 | ms |= (int)buffer[start + 1];
199 | ms |= (int)buffer[start + 2] << 8;
200 | ms |= (int)buffer[start + 3] << 16;
201 | ms |= (int)buffer[start + 4] << 24;
202 |
203 | Console.SetCursorPosition(20, 0);
204 | Console.Write("latency: " + (nowTime.Millisecond - ms) + " ms ");
205 | }
206 | else
207 | {
208 | Console.SetCursorPosition(4, 2 + 4 * pos);
209 | Console.Write("Received 0x" + buffer[start + 0].ToString("X2") + " ");
210 | }
211 |
212 |
213 | if ((nowTime - lastTime).TotalMilliseconds > 500)
214 | {
215 | /* calc rate */
216 | int rate = (framesReceived - framesReceivedLast) * 2;
217 | Console.SetCursorPosition(0, 0);
218 | Console.Write(" rate " + rate + " Hz ");
219 | framesReceivedLast = framesReceived;
220 |
221 | /* send custom telemetry */
222 | framesSent++;
223 | lastTime = nowTime;
224 | byte[] buf = new byte[] { 0xC8, 0x06, 0xEC, 0xEC, 0xAA, 0x55, 0x11, 0x00 };
225 | buf[3] = (byte) (nowTime.Millisecond & 0xFF);
226 | buf[4] = (byte)((nowTime.Millisecond >> 8) & 0xFF);
227 | buf[5] = (byte)((nowTime.Millisecond >> 16) & 0xFF);
228 | buf[6] = (byte)((nowTime.Millisecond >> 24) & 0xFF);
229 | buf[7] = crc8(buf, 2, 5);
230 |
231 | Port.Write(buf, 0, buf.Length);
232 | Console.SetCursorPosition(20, 0);
233 | Console.Write(" sent " + framesSent);
234 | }
235 |
236 | var lines = ByteArrayToString(buffer, start, length).Split(60);
237 | int linenum = 0;
238 | foreach (string l in lines)
239 | {
240 | Console.SetCursorPosition(6, 2 + 4 * pos + 1 + linenum);
241 | Console.Write(l);
242 | }
243 | }
244 | }
245 |
246 | public static class Extensions
247 | {
248 | public static IEnumerable Split(this string str, int n)
249 | {
250 | if (String.IsNullOrEmpty(str) || n < 1)
251 | {
252 | throw new ArgumentException();
253 | }
254 |
255 | return Enumerable.Range(0, (str.Length + n - 1) / n).Select(i => str.Substring(i * n, Math.Min(n, str.Length - i * n)));
256 | }
257 | }
258 | }
259 |
--------------------------------------------------------------------------------
/shell_crcbrute/brute.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 |
7 | #define COUNT(x) (sizeof(x)/sizeof((x)[0]))
8 |
9 |
10 | uint8_t packets_crc8[][13] = {
11 | { 0x03, 0x25, 0xED, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23 },
12 | { 0x03, 0x25, 0xED, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68 },
13 | { 0x03, 0x53, 0xD4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x71 },
14 | { 0x03, 0x59, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC1 },
15 | { 0x03, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42 },
16 | { 0x03, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x86 },
17 | { 0x03, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xCD },
18 | { 0x03, 0x74, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6E },
19 | { 0x03, 0x9E, 0xF1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF1 },
20 | { 0x03, 0x9F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x39 },
21 | { 0x03, 0xBF, 0xA6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08 },
22 | { 0x03, 0xDC, 0x5F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8B },
23 | { 0x03, 0xE8, 0xC8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x67 },
24 | { 0x01, 0x9E, 0xF1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4B },
25 | { 0x83, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF }
26 | };
27 |
28 | uint8_t packets_crc16[][23] = {
29 | { 0x03, 0x17, 0xFE, 0xE7, 0x5F, 0x80, 0xA1, 0x84, 0x12, 0x4A, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF1, 0xBE },
30 | { 0x03, 0xA1, 0xFC, 0xE7, 0x1F, 0x80, 0xA1, 0x84, 0x12, 0x4A, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5A, 0x03 },
31 | { 0x03, 0x3A, 0xFD, 0xE7, 0x1F, 0x80, 0xA1, 0x84, 0x12, 0x4A, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD9, 0x22 },
32 | { 0x03, 0x39, 0xFD, 0xE7, 0x5F, 0x80, 0xA1, 0x84, 0x12, 0x4A, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB6, 0x68 },
33 | { 0x03, 0x3A, 0xFD, 0xE7, 0x5F, 0x80, 0xA1, 0x84, 0x12, 0x4A, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5D, 0x01 },
34 | { 0x03, 0x3A, 0xFD, 0xE7, 0x5F, 0x80, 0xA1, 0x84, 0x12, 0x4A, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBB, 0x40 },
35 | { 0x03, 0x3A, 0xFD, 0xE7, 0x5F, 0x80, 0xA1, 0x84, 0x12, 0x4A, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5D, 0x01 },
36 | { 0x03, 0x3A, 0xFD, 0xE7, 0x5F, 0x80, 0xA1, 0x84, 0x12, 0x4A, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBB, 0x40 },
37 | { 0x03, 0xD3, 0xFD, 0xE7, 0x5F, 0x80, 0xA1, 0x84, 0x12, 0x4A, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD9, 0xED },
38 | { 0x03, 0xD3, 0xFD, 0xE7, 0x5F, 0x80, 0xA1, 0x84, 0x12, 0x4A, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD9, 0xED },
39 | { 0x03, 0xD3, 0xFD, 0xE7, 0x5F, 0x80, 0xA1, 0x84, 0x12, 0x4A, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xCD },
40 | { 0x03, 0xD3, 0xFD, 0xE7, 0x9F, 0x80, 0xA1, 0x84, 0x12, 0x4A, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA6, 0xA9 },
41 | { 0x03, 0xD3, 0xFD, 0xE7, 0x9F, 0x80, 0xA1, 0x84, 0x12, 0x4A, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0xE8 },
42 | { 0x03, 0x6A, 0xFE, 0xE7, 0x5F, 0x80, 0xA1, 0x84, 0x12, 0x4A, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE9, 0x26 },
43 | { 0x03, 0x6A, 0xFE, 0xE7, 0x5F, 0x80, 0xA1, 0x84, 0x12, 0x4A, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1A, 0x06 },
44 | { 0x03, 0x6A, 0xFE, 0xE7, 0x9F, 0x80, 0xA1, 0x84, 0x12, 0x4A, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x96, 0x62 },
45 | { 0x03, 0x6A, 0xFE, 0xE7, 0x9F, 0x80, 0xA1, 0x84, 0x12, 0x4A, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0x42 },
46 | { 0x03, 0x6A, 0xFE, 0xE7, 0x5F, 0x80, 0xA1, 0x84, 0x12, 0x4A, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1A, 0x06 },
47 | { 0x03, 0x6A, 0xFE, 0xE7, 0x5F, 0x80, 0xA1, 0x84, 0x12, 0x4A, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE9, 0x26 },
48 | { 0x03, 0x17, 0xFE, 0xE7, 0x9F, 0x80, 0xA1, 0x84, 0x12, 0x4A, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9B, 0x9B },
49 | { 0x03, 0x17, 0xFE, 0xE7, 0x9F, 0x80, 0xA1, 0x84, 0xF2, 0x5F, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x1E }
50 | };
51 |
52 | uint8_t crc8(uint8_t *data, int start, int length, uint8_t init)
53 | {
54 | uint32_t crc = init;
55 |
56 | for (uint32_t i = 0; i < length; i++)
57 | {
58 | uint32_t inData = data[start + i];
59 | for (uint32_t j = 0; j < 8; j++)
60 | {
61 | uint32_t mix = (crc ^ inData) & 0x80;
62 | crc <<= 1;
63 | if (mix != 0)
64 | {
65 | //crc ^= 0xd5;
66 | //crc ^= 0x9b;
67 | //crc ^= 0x1d;
68 | crc ^= 0x07;
69 | //crc ^= 0x9b;
70 | //crc ^= 0x9b;
71 | }
72 | inData <<= 1;
73 | }
74 | }
75 | return (uint8_t)crc;
76 | }
77 |
78 | uint16_t reflect(uint16_t inData, int width)
79 | {
80 | uint16_t resByte = 0;
81 |
82 | for (uint8_t i = 0; i < width; i++)
83 | {
84 | if ((inData & (1 << i)) != 0)
85 | {
86 | resByte |= ( (1 << (width-1 - i)) & 0xFFFF);
87 | }
88 | }
89 |
90 | return resByte;
91 | }
92 |
93 | uint16_t crc16(uint8_t *data, int start, int length, uint16_t init)
94 | {
95 | uint16_t table[256] =
96 | {
97 | 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF,
98 | 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE,
99 | 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485, 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D,
100 | 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4, 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC,
101 | 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B,
102 | 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A,
103 | 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49,
104 | 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78,
105 | 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
106 | 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256,
107 | 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
108 | 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634,
109 | 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3,
110 | 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92,
111 | 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1,
112 | 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0
113 | };
114 | uint16_t crc = init;
115 |
116 | for (int i = 0; i < length; ++i)
117 | {
118 | crc = (crc ^ (reflect(data[start + i], 8)<< (16 - 8)));
119 | int pos = (crc >> (16 - 8)) & 0xFF;
120 | crc = (crc << 8);
121 | crc = (crc ^ table[pos]);
122 | }
123 | return reflect(crc, 16);
124 | }
125 |
126 | void brute_crc8()
127 | {
128 | uint8_t buffer[32];
129 | int pkt_start = 0;
130 | int pkt_len = 12;
131 |
132 | memcpy(&buffer[pkt_start], packets_crc8[0], pkt_len + 1);
133 |
134 | for(uint64_t test_value = 0; test_value < 0x100; test_value++)
135 | {
136 | if(crc8(buffer, 0, pkt_start + pkt_len, test_value) == buffer[pkt_start + pkt_len])
137 | {
138 | uint8_t buffer_check[32];
139 |
140 | for(int pkt = 1; pkt < COUNT(packets_crc8); pkt++)
141 | {
142 | memcpy(&buffer_check[pkt_start], packets_crc8[pkt], pkt_len + 1);
143 | if(crc8(buffer_check, 0, pkt_start + pkt_len, test_value) == buffer_check[pkt_start + pkt_len])
144 | {
145 | printf("match: 0/%02d 0x%02X\n", pkt, (uint32_t)test_value);
146 | }
147 | }
148 | }
149 | }
150 | }
151 |
152 | /*
153 | ch @ hop
154 | > FSK, DA3A0B, 49 @ 61 Rx 03 A2 F8 E7 DF 7F A1 84 12 4A 28 00 00 00 00 00 00 00 00 00 00 E0 07
155 | > FSK, DD7A0B, 49 @ 61 Tx 03 4C 5F 00 00 00 00 00 00 00 00 00 C4 crci: 82
156 | > FSK, DA0820, 46 @ 62 Rx 03 A2 FC E7 1F 80 A1 84 12 4A 28 00 00 00 00 00 00 00 00 00 00 E7 45
157 | > FSK, DD4820, 46 @ 62 Tx 03 5F 04 00 00 00 00 00 00 00 00 00 D5 crci: A6
158 | > FSK, D7A072, 09 @ 63 Rx 03 A2 F8 E7 DF 7F A1 84 12 4A 28 70 DA 0F 02 1D 4A 61 CF 00 00 46 CC
159 | > FSK, DAE072, 09 @ 63 Tx 03 C7 96 00 00 00 00 00 00 00 00 00 E9 crci: D8
160 | > FSK, D80449, 15 @ 64 Rx 03 A2 F8 E7 DF 7F A1 84 12 4A 28 00 00 00 00 00 00 00 00 00 00 3F CD
161 | > FSK, DB4449, 15 @ 64 Tx 83 00 6A 00 00 00 00 00 00 00 00 00 ED crci: 38
162 | > FSK, D9E6D9, 44 @ 65 Rx 03 A2 F8 E7 1F 80 A1 84 12 4A 28 00 00 00 00 00 00 00 00 00 00 4A 42
163 | > FSK, DD26D9, 44 @ 65 Tx 03 44 5F 00 00 00 00 00 00 00 00 00 F1 crci: A8
164 | > FSK, DA2968, 48 @ 66 Rx 03 A2 FC E7 1F 80 A1 84 12 4A 28 00 00 00 00 00 00 00 00 00 00 C8 6C
165 | > FSK, DD6968, 48 @ 66 Tx 03 5F 04 00 00 00 00 00 00 00 00 00 2C crci: 77
166 |
167 | */
168 |
169 | uint8_t packets_crc8_payload[][13] = {
170 | { 0x03, 0x4C, 0x5F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC4 },
171 | { 0x03, 0x5F, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD5 },
172 | { 0x03, 0xC7, 0x96, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE9 },
173 | { 0x83, 0x00, 0x6A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xED },
174 | { 0x03, 0x44, 0x5F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF1 },
175 | { 0x03, 0x5F, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2C }
176 | };
177 |
178 | uint8_t packets_crc8_hopnum[] = { 61, 62, 63, 64, 65, 66 };
179 |
180 | void brute_crc8_payload()
181 | {
182 | uint8_t buffer[32];
183 | int pkt_start = 4;
184 | int pkt_len = 12;
185 |
186 | #define off(x) ( ((x) + omp_get_thread_num())%4)
187 |
188 | printf("thread #%d\n", omp_get_thread_num());
189 | memcpy(&buffer[pkt_start], packets_crc8_payload[0], pkt_len + 1);
190 |
191 | for(uint64_t test_value = 0; test_value < 0x100000000; test_value++)
192 | {
193 | buffer[off(0)] = test_value >> 24;
194 | buffer[off(1)] = test_value >> 16;
195 | buffer[off(2)] = test_value >> 8;
196 | buffer[off(3)] = test_value + packets_crc8_hopnum[0];
197 |
198 | if(crc8(buffer, 0, pkt_start + pkt_len, 0) == buffer[pkt_start + pkt_len])
199 | {
200 | uint8_t buffer_check[32];
201 | uint8_t valid = 1;
202 |
203 | for(int pkt = 1; pkt < COUNT(packets_crc8_payload); pkt++)
204 | {
205 | buffer_check[off(0)] = test_value >> 24;
206 | buffer_check[off(1)] = test_value >> 16;
207 | buffer_check[off(2)] = test_value >> 8;
208 | buffer_check[off(3)] = test_value + packets_crc8_hopnum[pkt];
209 |
210 | memcpy(&buffer_check[pkt_start], packets_crc8_payload[pkt], pkt_len + 1);
211 | if(crc8(buffer_check, 0, pkt_start + pkt_len, 0) == buffer_check[pkt_start + pkt_len])
212 | {
213 |
214 | }
215 | else
216 | {
217 | valid = 0;
218 | }
219 | }
220 |
221 | if(valid)
222 | {
223 | printf("match: 0x%08X\n", (uint32_t)test_value);
224 | }
225 | }
226 | }
227 | }
228 |
229 |
230 | void brute_crc16()
231 | {
232 | uint8_t buffer[32];
233 | int pkt_start = 0;
234 | int pkt_len = 21;
235 |
236 | memcpy(&buffer[pkt_start], packets_crc16[2], pkt_len + 2);
237 |
238 | for(uint64_t test_value = 0; test_value < 0x10000; test_value++)
239 | {
240 | uint16_t crc = crc16(buffer, 0, pkt_start + pkt_len, test_value);
241 | uint16_t crct = *((uint16_t*)&buffer[pkt_start + pkt_len]);
242 |
243 | if(crc == crct)
244 | {
245 | uint8_t buffer_check[32];
246 |
247 | for(int pkt = 1; pkt < 21; pkt++)
248 | {
249 | memcpy(&buffer_check[pkt_start], packets_crc16[pkt], pkt_len + 2);
250 | uint16_t ccrc = crc16(buffer_check, 0, pkt_start + pkt_len, test_value);
251 | uint16_t ccrct = *((uint16_t*)&buffer_check[pkt_start + pkt_len]);
252 |
253 | if(ccrc == ccrct)
254 | {
255 | printf("match: 0/%d 0x%08X\n", pkt, (uint32_t)test_value);
256 | }
257 | }
258 | }
259 | }
260 | }
261 |
262 |
263 |
264 |
265 | void brute_crc16_header()
266 | {
267 | uint8_t buffer[32];
268 | int pkt_start = 3;
269 | int pkt_len = 21;
270 | uint16_t crc_init = 0xFFFF;
271 |
272 | memcpy(&buffer[pkt_start], packets_crc16[1], pkt_len + 2);
273 |
274 | for(uint64_t test_value = 0; test_value < 0x10000; test_value++)
275 | {
276 | buffer[0] = test_value;
277 | buffer[1] = test_value >> 8;
278 | //buffer[2] = test_value >> 16;
279 | //buffer[3] = test_value >> 24;
280 |
281 | uint16_t crc = crc16(buffer, 0, pkt_start + pkt_len, crc_init);
282 | uint16_t crct = *((uint16_t*)&buffer[pkt_start + pkt_len]);
283 |
284 | if(crc == crct)
285 | {
286 | uint8_t buffer_check[32];
287 |
288 | for(int pkt = 1; pkt < 21; pkt++)
289 | {
290 | buffer_check[0] = test_value;
291 | buffer_check[1] = test_value >> 8;
292 | //buffer_check[2] = test_value >> 16;
293 | //buffer_check[3] = test_value >> 24;
294 |
295 | memcpy(&buffer_check[pkt_start], packets_crc16[pkt], pkt_len + 2);
296 | uint16_t ccrc = crc16(buffer_check, 0, pkt_start + pkt_len, crc_init);
297 | uint16_t ccrct = *((uint16_t*)&buffer_check[pkt_start + pkt_len]);
298 |
299 | if(ccrc == ccrct)
300 | {
301 | printf("match: 0/%d 0x%08X\n", pkt, (uint32_t)test_value);
302 | }
303 | }
304 | }
305 | }
306 | }
307 |
308 |
309 |
310 | int main(int argc, char *argv[])
311 | {
312 | #pragma omp parallel num_threads(4)
313 | brute_crc8_payload();
314 | return 0;
315 | }
--------------------------------------------------------------------------------
/SPIlog/Program.cs:
--------------------------------------------------------------------------------
1 | using Mono.Options;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.ComponentModel;
5 | using System.IO;
6 | using System.IO.Ports;
7 | using System.Linq;
8 | using System.Net.Sockets;
9 | using System.Runtime.InteropServices.ComTypes;
10 | using System.Runtime.Serialization.Formatters.Binary;
11 | using System.Text;
12 | using System.Threading;
13 | using System.Threading.Tasks;
14 |
15 | namespace SPIlog
16 | {
17 | class Program
18 | {
19 | public static SerialPort Port { get; private set; }
20 | public static bool IsLora
21 | {
22 | get
23 | {
24 | return (RegistersFsk[1] & 0x80) != 0;
25 | }
26 | }
27 |
28 | public static int[] HopSequenceDetected = null;
29 | public static byte[] RegistersFsk = new byte[255];
30 | public static byte[] RegistersLora = new byte[255];
31 | public static uint[] SharedRegisters = new uint[] { 0x00, 0x01, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x4B, 0x58, 0x5A, 0x5C, 0x5E, 0x63, 0x6C, 0x70 };
32 | public static byte[] hopDownCrc = new byte[150];
33 | public static ushort[] hopUpCrc = new ushort[150];
34 | public static ulong ChannelSpacing = 260000;
35 | public static List Frequencies = new List();
36 | public static List FrequenciesBinary = new List();
37 | public static List LastWrittenFrequenciesBinary = new List();
38 | public static List LastUplinkFrequenciesBinary = new List();
39 | public static List> FrequenciesLog = new List>();
40 | public static ulong MinFreq = 0xFFFFFFFF;
41 | public static ulong MaxFreq = 0;
42 |
43 | public enum SX1272RegsFsk
44 | {
45 | RegFifo = 0x00,
46 | RegOpMode = 0x01,
47 | RegBitrateMsb = 0x02,
48 | RegBitrateLsb = 0x03,
49 | RegFdevMsb = 0x04,
50 | RegFdevLsb = 0x05,
51 | RegFrfMsb = 0x06,
52 | RegFrfMid = 0x07,
53 | RegFrfLsb = 0x08,
54 | RegPaConfig = 0x09,
55 | RegPaRamp = 0x0A,
56 | RegOcp = 0x0B,
57 | RegLna = 0x0C,
58 | RegRxConfig = 0x0D,
59 | RegRssiConfig = 0x0E,
60 | RegRssiCollision = 0x0F,
61 | RegRssiThresh = 0x10,
62 | RegRssiValue = 0x11,
63 | RegRxBw = 0x12,
64 | RegAfcBw = 0x13,
65 | RegOokPeak = 0x14,
66 | RegOokFix = 0x15,
67 | RegOokAvg = 0x16,
68 | RegAfcFei = 0x1A,
69 | RegAfcMsb = 0x1B,
70 | RegAfcLsb = 0x1C,
71 | RegFeiMsb = 0x1D,
72 | RegFeiLsb = 0x1E,
73 | RegPreambleDetect = 0x1F,
74 | RegRxTimeout1 = 0x20,
75 | RegRxTimeout2 = 0x21,
76 | RegRxTimeout3 = 0x22,
77 | RegRxDelay = 0x23,
78 | RegOsc = 0x24,
79 | RegPreambleMsb = 0x25,
80 | RegPreambleLsb = 0x26,
81 | RegSyncConfig = 0x27,
82 | RegSyncValue1 = 0x28,
83 | RegSyncValue2 = 0x29,
84 | RegSyncValue3 = 0x2A,
85 | RegSyncValue4 = 0x2B,
86 | RegSyncValue5 = 0x2C,
87 | RegSyncValue6 = 0x2D,
88 | RegSyncValue7 = 0x2E,
89 | RegSyncValue8 = 0x2F,
90 | RegPacketConfig1 = 0x30,
91 | RegPacketConfig2 = 0x31,
92 | RegPayloadLength = 0x32,
93 | RegNodeAdrs = 0x33,
94 | RegBroadcastAdrs = 0x34,
95 | RegFifoThresh = 0x35,
96 | RegSeqConfig1 = 0x36,
97 | RegSeqConfig2 = 0x37,
98 | RegTimerResol = 0x38,
99 | RegTimer1Coef = 0x39,
100 | RegTimer2Coef = 0x3A,
101 | RegImageCal = 0x3B,
102 | RegTemp = 0x3C,
103 | RegLowBat = 0x3D,
104 | RegIrqFlags1 = 0x3E,
105 | RegIrqFlags2 = 0x3F,
106 | RegDioMapping1 = 0x40,
107 | RegDioMapping2 = 0x41,
108 | RegVersion = 0x42,
109 | RegAgcRef = 0x43,
110 | RegAgcThresh1 = 0x44,
111 | RegAgcThresh2 = 0x45,
112 | RegAgcThresh3 = 0x46,
113 | RegPllHop = 0x4B,
114 | RegTcxo = 0x58,
115 | RegPaDac = 0x5A,
116 | RegPll = 0x5C,
117 | RegPllLowPn = 0x5E,
118 | RegPaManual = 0x63,
119 | RegFormerTemp = 0x6C,
120 | RegBitRateFrac = 0x70
121 | }
122 |
123 | public enum SX1272RegsLoRa
124 | {
125 | RegFifo = 0x00,
126 | RegOpMode = 0x01,
127 | RegFrfMsb = 0x06,
128 | RegFrfMid = 0x07,
129 | RegFrfLsb = 0x08,
130 | RegPaConfig = 0x09,
131 | RegPaRamp = 0x0A,
132 | RegOcp = 0x0B,
133 | RegLna = 0x0C,
134 | RegFifoAddrPtr = 0x0D,
135 | RegFifoTxBaseAddr = 0x0E,
136 | RegFifoRxBaseAddr = 0x0F,
137 | RegFifoRxCurrentAddr = 0x10,
138 | RegIrqFlagsMask = 0x11,
139 | RegIrqFlags = 0x12,
140 | RegRxNbBytes = 0x13,
141 | RegRxHeaderCntValueMsb = 0x14,
142 | RegRxHeaderCntValueLsb = 0x15,
143 | RegRxPacketCntMsb = 0x16,
144 | RegRxPacketCntLsb = 0x17,
145 | RegModemStat = 0x18,
146 | RegPktSnrValue = 0x19,
147 | RegPktRssiValue = 0x1A,
148 | RegRssiValue = 0x1B,
149 | RegHopChannel = 0x1C,
150 | RegModemConfig1 = 0x1D,
151 | RegModemConfig2 = 0x1E,
152 | RegSymbTimeoutLsb = 0x1F,
153 | RegPreambleMsb = 0x20,
154 | RegPreambleLsb = 0x21,
155 | RegPayloadLength = 0x22,
156 | RegMaxPayloadLength = 0x23,
157 | RegHopPeriod = 0x24,
158 | RegFifoRxByteAddr = 0x25,
159 | RegFeiMsb = 0x28,
160 | RegFeiMib = 0x29,
161 | RegFeiLsb = 0x2A,
162 | RegRssiWideband = 0x2C,
163 | RegDetectOptimize = 0x31,
164 | RegInvertIQ = 0x33,
165 | RegDetectionThreshold = 0x37,
166 | RegSyncWord = 0x39,
167 | RegInvertIQ2 = 0x3B,
168 | RegDioMapping1 = 0x40,
169 | RegDioMapping2 = 0x41,
170 | RegVersion = 0x42,
171 | RegAgcRef = 0x43,
172 | RegAgcThresh1 = 0x44,
173 | RegAgcThresh2 = 0x45,
174 | RegAgcThresh3 = 0x46,
175 | RegPllHop = 0x4B,
176 | RegTcxo = 0x58,
177 | RegPaDac = 0x5A,
178 | RegPll = 0x5C,
179 | RegPllLowPn = 0x5E,
180 | RegPaManual = 0x63,
181 | RegFormerTemp = 0x6C,
182 | RegBitRateFrac = 0x70
183 | }
184 |
185 | public static byte crc8(byte[] data, int start, int length, byte init)
186 | {
187 | uint crc = init;
188 |
189 | if (data == null || data.Length < start + length)
190 | {
191 | return 0;
192 | }
193 |
194 | for (uint i = 0; i < length; i++)
195 | {
196 | uint inData = data[start + i];
197 | for (uint j = 0; j < 8; j++)
198 | {
199 | uint mix = (crc ^ inData) & 0x80;
200 | crc <<= 1;
201 | if (mix != 0)
202 | {
203 | crc ^= 0x07;
204 | }
205 | inData <<= 1;
206 | }
207 | }
208 | return (byte)crc;
209 | }
210 |
211 | public static ushort reflect(int inData, int width)
212 | {
213 | int resByte = 0;
214 |
215 | for (int i = 0; i < width; i++)
216 | {
217 | if ((inData & (1 << i)) != 0)
218 | {
219 | resByte |= ((1 << (width - 1 - i)) & 0xFFFF);
220 | }
221 | }
222 |
223 | return (ushort)resByte;
224 | }
225 |
226 | public static ushort[] crc16_table =
227 | {
228 | 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF,
229 | 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE,
230 | 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485, 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D,
231 | 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4, 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC,
232 | 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B,
233 | 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A,
234 | 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49,
235 | 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78,
236 | 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
237 | 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256,
238 | 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
239 | 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634,
240 | 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3,
241 | 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92,
242 | 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1,
243 | 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0
244 | };
245 |
246 | public static ushort crc16(byte[] data, int start, int length, ushort init)
247 | {
248 | int crc = init;
249 |
250 | for (int i = 0; i < length; ++i)
251 | {
252 | crc ^= (reflect(data[start + i], 8) << (16 - 8));
253 | int pos = (crc >> (16 - 8)) & 0xFF;
254 | crc <<= 8;
255 | crc ^= crc16_table[pos];
256 | }
257 | return reflect(crc, 16);
258 | }
259 |
260 | static ulong GetFrequencyRaw()
261 | {
262 | return (GetRegister(SX1272RegsFsk.RegFrfMsb) << 16) | (GetRegister(SX1272RegsFsk.RegFrfMid) << 8) | (GetRegister(SX1272RegsFsk.RegFrfLsb) << 0);
263 | }
264 |
265 | ///
266 | /// Return the frequency in MHz
267 | ///
268 | static decimal GetFrequency()
269 | {
270 | return FreqToMhz(GetFrequencyRaw());
271 | }
272 |
273 | static int GetBitrateRaw()
274 | {
275 | return (RegistersFsk[(int)SX1272RegsFsk.RegBitrateMsb] << 8) | RegistersFsk[(int)SX1272RegsFsk.RegBitrateLsb];
276 | }
277 |
278 | static decimal GetBitrate()
279 | {
280 | if (IsFsk())
281 | {
282 | int br = GetBitrateRaw();
283 | decimal rate = (br > 0 ? (32000000.0m / br) : 0) / 1000.0m;
284 |
285 | return Math.Round(rate, 2);
286 | }
287 |
288 | return 0;
289 | }
290 |
291 | static int GetChiprate()
292 | {
293 | if (IsFsk())
294 | {
295 | return 0;
296 | }
297 | else
298 | {
299 | int rateReg = (RegistersLora[0x1E] >> 4) & 0x0F;
300 | /* check if anything was configured at all */
301 | if (rateReg < 6 || rateReg > 12)
302 | {
303 | return 0;
304 | }
305 | int rate = 1 << rateReg;
306 |
307 | return rate;
308 | }
309 | }
310 |
311 | static int GetFreqShiftRaw()
312 | {
313 | return (RegistersFsk[(int)SX1272RegsFsk.RegFdevMsb] << 8) | RegistersFsk[(int)SX1272RegsFsk.RegFdevLsb];
314 | }
315 |
316 | static bool IsFsk()
317 | {
318 | return ((GetRegister(SX1272RegsFsk.RegOpMode) & 0x80) == 0);
319 | }
320 |
321 | static decimal GetFreqShift()
322 | {
323 | if (IsFsk())
324 | {
325 | decimal width = (GetFreqShiftRaw() * 32000000.0m / (1 << 19)) / 1000.0m;
326 |
327 | return Math.Round(width, 2);
328 | }
329 | else
330 | {
331 | /* check if anything was configured at all */
332 | if(((RegistersLora[0x1D] >> 3) & 7) == 0)
333 | {
334 | return 0;
335 | }
336 | int width = 0;
337 | switch(RegistersLora[0x1D] >> 6)
338 | {
339 | case 0:
340 | width = 125000;
341 | break;
342 | case 1:
343 | width = 250000;
344 | break;
345 | case 2:
346 | width = 500000;
347 | break;
348 |
349 | }
350 |
351 | return width / 1000;
352 | }
353 | }
354 |
355 | static string GetModulationParameters()
356 | {
357 | if(IsFsk())
358 | {
359 | return "FSK " + " F:" + GetFrequency().ToString("000.000") + "MHz S:" + GetFreqShift().ToString("00.000") + "kHz B:" + GetBitrate().ToString("00.00") + "k";
360 | }
361 | return "LoRa" + " F:" + GetFrequency().ToString("000.000") + "MHz S:" + GetFreqShift().ToString("00.000") + "kHz B:" + GetChiprate().ToString("00") + "c/s";
362 | }
363 |
364 | static void Main(string[] args)
365 | {
366 | string port = null;
367 | string outfile = null;
368 | string infile = null;
369 | bool show_help = false;
370 | bool hasKeys = true;
371 | int displayMode = 1;
372 | int sleepDelay = 0;
373 | string lastModulationParameters = "";
374 |
375 | Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.InvariantCulture;
376 |
377 | var p = new OptionSet() {
378 | "Usage: SPILog [-p port] [-o log.bin] [-i log.bin]",
379 | "",
380 | "Options:",
381 | { "p|port=", "Serial port of the FPGA SPI logger.", v => port = v },
382 | { "o|output=", "Filename where to write SPI log to.", v => outfile = v },
383 | { "i|input=", "Filename where to read SPI log data from.", v => infile = v },
384 | { "d|display=", "Display mode (0 = nothing, 1 = register r/w, 2 = FIFO payload only, 3 = SPI data, 4 = raw SPI data).", v => displayMode = int.Parse(v) },
385 | { "s|sleep=", "sleep delay in ms after payload r/w in playback mode (0 = disable).", v => sleepDelay = int.Parse(v) },
386 | { "h|help", "Show this message and exit", v => show_help = v != null },
387 | };
388 | List extra;
389 |
390 | try
391 | {
392 | extra = p.Parse(args);
393 | }
394 | catch (OptionException e)
395 | {
396 | Console.Write("SPILog: ");
397 | Console.WriteLine(e.Message);
398 | Console.WriteLine("Try `SPILog --help' for more information.");
399 | return;
400 | }
401 |
402 | if (show_help)
403 | {
404 | p.WriteOptionDescriptions(Console.Out);
405 | return;
406 | }
407 |
408 | if (port != null && infile != null)
409 | {
410 | Console.Write("SPILog: ");
411 | Console.WriteLine("You cannot specify both -p and -i");
412 | Console.WriteLine("Try `SPILog --help' for more information.");
413 | return;
414 | }
415 |
416 | BinaryWriter logWriter = null;
417 | BinaryReader logReader = null;
418 |
419 | if (outfile != null)
420 | {
421 | Console.WriteLine("SPILog: Writing log to " + outfile);
422 | if (File.Exists(outfile))
423 | {
424 | File.Delete(outfile);
425 | }
426 | logWriter = new BinaryWriter(File.OpenWrite(outfile));
427 | }
428 |
429 | if (infile != null)
430 | {
431 | Console.WriteLine("SPILog: Reading log from " + infile);
432 | logReader = new BinaryReader(File.OpenRead(infile));
433 | }
434 |
435 | if (port != null)
436 | {
437 | Console.WriteLine("SPILog: Reading data from " + port);
438 | Port = new SerialPort(port, 8000000, Parity.None, 8, StopBits.One);
439 | Port.ReadBufferSize = 8192000;
440 | Port.Open();
441 |
442 | logReader = new BinaryReader(Port.BaseStream);
443 | logReader.BaseStream.ReadTimeout = 50;
444 | }
445 |
446 | if (logReader == null)
447 | {
448 | Console.Write("SPILog: ");
449 | Console.WriteLine("You have to specify either -p and -i");
450 | Console.WriteLine("Try `SPILog --help' for more information.");
451 | return;
452 | }
453 |
454 | SX1272RegsFsk lastFskRegister = 0;
455 | SX1272RegsLoRa lastLoRaRegister = 0;
456 | List fifoData = new List();
457 | bool write = false;
458 | uint currentRegister = 0;
459 | int currentHop = 0;
460 | int hopsMissing = 0;
461 | int hopsSuccess = 0;
462 | bool running = true;
463 | byte fwLastCounter = 0;
464 | string fifoWriteLine = null;
465 |
466 | DateTime StartTime = DateTime.Now;
467 | DateTime lastUpdate = DateTime.Now;
468 |
469 | try
470 | {
471 | bool test = Console.KeyAvailable;
472 | }
473 | catch(Exception ex)
474 | {
475 | hasKeys = false;
476 | }
477 |
478 | switch (displayMode)
479 | {
480 | case 5:
481 | Console.Clear();
482 | break;
483 | }
484 |
485 | try
486 | {
487 | while (running)
488 | {
489 | DateTime thisTime = DateTime.Now;
490 |
491 | if (hasKeys && Console.KeyAvailable)
492 | {
493 | string line = Console.In.ReadLine().Trim();
494 |
495 | if (line.Length == 0)
496 | {
497 | continue;
498 | }
499 |
500 | switch (line.Split(' ')[0])
501 | {
502 | case "help":
503 | Console.WriteLine("Available commands:");
504 | Console.WriteLine(" help, display , stats, regs, quit");
505 | break;
506 |
507 | case "stats":
508 | PrintStatistics();
509 | continue;
510 |
511 | case "quit":
512 | running = false;
513 | continue;
514 |
515 | case "regs":
516 | Console.WriteLine(" Reg FSK LoRa");
517 | Console.WriteLine("-------------------");
518 | for (uint reg = 0; reg < 0x80; reg++)
519 | {
520 | if (SharedRegisters.Contains(reg))
521 | {
522 | Console.WriteLine(" 0x" + reg.ToString("X2") + ": 0x" + RegistersFsk[reg].ToString("X2"));
523 | }
524 | else
525 | {
526 | Console.WriteLine(" 0x" + reg.ToString("X2") + ": 0x" + RegistersFsk[reg].ToString("X2") + " 0x" + RegistersLora[reg].ToString("X2"));
527 | }
528 | }
529 | break;
530 |
531 | case "display":
532 | try
533 | {
534 | displayMode = int.Parse(line.Split(' ')[1]);
535 | Console.Clear();
536 | }
537 | catch (Exception ex)
538 | {
539 | Console.WriteLine("Usage: display where n = 0..4");
540 | }
541 | break;
542 | }
543 |
544 | }
545 |
546 | try
547 | {
548 | byte[] buf = logReader.ReadBytes(2);
549 |
550 | if (buf == null || buf.Length == 0)
551 | {
552 | if ((logReader.BaseStream is FileStream))
553 | {
554 | running = false;
555 | }
556 | continue;
557 | }
558 | byte type = buf[0];
559 | byte data = buf[1];
560 |
561 | if (logWriter != null)
562 | {
563 | logWriter.Write(buf);
564 | }
565 |
566 | /* chip enable */
567 | if (type == 0xDE)
568 | {
569 | bool wasFifo = false;
570 |
571 | switch (lastFskRegister)
572 | {
573 | case SX1272RegsFsk.RegFifo:
574 | wasFifo = true;
575 | break;
576 | }
577 | switch (lastLoRaRegister)
578 | {
579 | case SX1272RegsLoRa.RegFifo:
580 | wasFifo = true;
581 | break;
582 | }
583 |
584 | if (wasFifo)
585 | {
586 | ulong frf = GetFrequencyRaw();
587 | int arfcn = FreqToArfcn(frf);
588 | string payload = string.Join(" ", fifoData.Select(d => d.ToString("X2")));
589 | string miss = "@" + currentHop.ToString().PadLeft(3);
590 |
591 | if (HopSequenceDetected != null)
592 | {
593 | if (arfcn == HopSequenceDetected[currentHop])
594 | {
595 | currentHop++;
596 | hopsSuccess++;
597 | currentHop %= HopSequenceDetected.Length;
598 | hopsMissing = 0;
599 | }
600 | else
601 | {
602 | currentHop = 0;
603 | currentHop %= HopSequenceDetected.Length;
604 | hopsMissing++;
605 | miss = hopsMissing.ToString().PadLeft(4);
606 | hopsSuccess = 0;
607 | }
608 | }
609 |
610 | if (frf != 0 || payload.Length > 0)
611 | {
612 | byte[] binData = fifoData.Select(d => (byte)d).ToArray();
613 | bool crc8InitZero = false;
614 |
615 | if (binData.Length > 1)
616 | {
617 | byte crc = crc8(binData, 0, binData.Length - 1, 0);
618 | crc8InitZero = (crc == binData[binData.Length - 1]);
619 | }
620 |
621 | string modulationParameters = GetModulationParameters();
622 |
623 | if (lastModulationParameters != modulationParameters)
624 | {
625 | lastModulationParameters = modulationParameters;
626 | //Console.WriteLine("Modulation: " + modulationParameters);
627 | }
628 |
629 | fifoWriteLine = "> " + modulationParameters + " " + arfcn.ToString("00") + " " + miss + " " + (write ? "Tx" : "Rx") + " " + payload + (crc8InitZero ? " (CRC8 zero-init)" : "");
630 |
631 | if (!write && !IsFsk() && crc8InitZero)
632 | {
633 | /* then check for a fw packet 00000101 / 00001101 */
634 | if ((binData[0] == 0x05) || (binData[0] == 0x0D))
635 | {
636 | byte len = binData[1];
637 | byte[] fwData = binData.Skip(2).Take(len).ToArray();
638 |
639 | if (fwLastCounter != binData[0])
640 | {
641 | fwLastCounter = binData[0];
642 | //Console.WriteLine("FW> " + string.Join(" ", fwData.Select(d => d.ToString("X2"))));
643 | }
644 | }
645 |
646 | }
647 | }
648 |
649 | if (hopsSuccess > 10 && displayMode == 5)
650 | {
651 | if (write)
652 | {
653 | for (int test_value = 0; test_value < 0x100; test_value++)
654 | {
655 | int pkt_start = 0;
656 | int pkt_len = 12;
657 | byte[] buffer = fifoData.Select(d => (byte)d).ToArray();
658 | byte initValue = (byte)(test_value + hopDownCrc[currentHop / 2]);
659 |
660 | if (crc8(buffer, 0, pkt_start + pkt_len, initValue) == buffer[pkt_start + pkt_len])
661 | {
662 | hopDownCrc[currentHop / 2] = initValue;
663 | break;
664 | }
665 | }
666 | }
667 | else
668 | {
669 | for (int test_value = 0; test_value < 0x10000; test_value++)
670 | {
671 | int pkt_start = 0;
672 | int pkt_len = 21;
673 | byte[] buffer = fifoData.Select(d => (byte)d).ToArray();
674 | ushort initValue = (ushort)(test_value + hopUpCrc[currentHop / 2]);
675 | ushort pktCrc = (ushort)((buffer[pkt_start + pkt_len + 1] << 8) | buffer[pkt_start + pkt_len]);
676 |
677 | if (crc16(buffer, 0, pkt_start + pkt_len, initValue) == pktCrc)
678 | {
679 | hopUpCrc[currentHop / 2] = initValue;
680 | break;
681 | }
682 | }
683 | }
684 | }
685 | }
686 |
687 | if(fifoWriteLine != null)
688 | {
689 | switch (displayMode)
690 | {
691 | case 0:
692 | case 1:
693 | case 2:
694 | case 3:
695 | case 4:
696 | Console.WriteLine(fifoWriteLine);
697 | break;
698 | case 5:
699 | break;
700 | }
701 | fifoWriteLine = null;
702 | }
703 |
704 | fifoData.Clear();
705 | currentRegister = data & ~0x80U;
706 | write = (data & 0x80) != 0;
707 |
708 | lastFskRegister = (SX1272RegsFsk) 0xff;
709 | lastLoRaRegister = (SX1272RegsLoRa) 0xff;
710 |
711 | if (IsFsk())
712 | {
713 | lastFskRegister = (SX1272RegsFsk)currentRegister;
714 | }
715 | else
716 | {
717 | lastLoRaRegister = (SX1272RegsLoRa)currentRegister;
718 | }
719 | }
720 | else if (type == 0xDD)
721 | {
722 | /* data */
723 | string regName = "unk";
724 |
725 | if (IsFsk())
726 | {
727 | if (Enum.IsDefined(typeof(SX1272RegsFsk), (int)currentRegister))
728 | {
729 | regName = Enum.GetName(typeof(SX1272RegsFsk), (int)currentRegister);
730 | }
731 | }
732 | else
733 | {
734 | if (Enum.IsDefined(typeof(SX1272RegsLoRa), (int)currentRegister))
735 | {
736 | regName = Enum.GetName(typeof(SX1272RegsLoRa), (int)currentRegister);
737 | }
738 | }
739 |
740 | if (currentRegister == 0)
741 | {
742 | fifoData.Add(data);
743 | }
744 | else if(write)
745 | {
746 | WriteRegister(currentRegister, data);
747 | }
748 |
749 | switch (displayMode)
750 | {
751 | case 0:
752 | break;
753 |
754 | case 1:
755 | {
756 | if (write)
757 | {
758 | Console.Write("W Reg: 0x" + currentRegister.ToString("X2") + " " + regName.PadRight(16) + " < " + data.ToString("X2"));
759 | Console.WriteLine();
760 |
761 | if (IsLora)
762 | {
763 | switch ((SX1272RegsLoRa)currentRegister)
764 | {
765 | case SX1272RegsLoRa.RegOpMode:
766 | string[] modes = { "SLEEP", "STDBY", "Frequency Synthesis TX", "Transmit", "Frequency Synthesis RX", "Receive continuous", "Receive single", "Channel Activity Detection" };
767 | Console.WriteLine(" => Mode " + (((data & 0x80) == 0) ? "FSK " : "LoRa"));
768 | Console.WriteLine(" => Mode " + modes[data & 7]);
769 | break;
770 |
771 | case SX1272RegsLoRa.RegFrfLsb:
772 | Console.WriteLine(" => Frequency " + GetFrequency() + " MHz");
773 | break;
774 |
775 | case SX1272RegsLoRa.RegModemConfig2:
776 | {
777 | Console.WriteLine(" => SpreadingFactor " + (1U << (data >> 4)).ToString());
778 | Console.WriteLine(" => TxContinuousMode " + ((data & (1 << 3)) != 0));
779 | Console.WriteLine(" => AgcAutoOn " + ((data & (1 << 2)) != 0));
780 | Console.WriteLine(" => SymbTimeout(9:8) " + ((data >> 0) & 3).ToString());
781 | }
782 | break;
783 |
784 | case SX1272RegsLoRa.RegModemConfig1:
785 | {
786 | Console.WriteLine(" => Bw " + (125 * (1 << (data >> 6))).ToString() + " kHz");
787 | Console.WriteLine(" => CodingRate 4/" + (4 + ((data >> 6) & 7)));
788 | Console.WriteLine(" => ImplHdrMode " + ((data & (1 << 2)) != 0));
789 | Console.WriteLine(" => PayloadCRC " + ((data & (1 << 1)) != 0));
790 | Console.WriteLine(" => LowDataRateOpt " + ((data & (1 << 0)) != 0));
791 | }
792 | break;
793 | }
794 | }
795 | else
796 | {
797 | switch ((SX1272RegsFsk)currentRegister)
798 | {
799 | case SX1272RegsFsk.RegOpMode:
800 | string[] modes = { "SLEEP", "STDBY", "Frequency Synthesis TX", "Transmit", "Frequency Synthesis RX", "Receive", "", "" };
801 |
802 | Console.WriteLine(" => Mode " + (((data & 0x80) == 0) ? "FSK " : "LoRa"));
803 | Console.WriteLine(" => Mode " + modes[data & 7]);
804 | break;
805 |
806 | case SX1272RegsFsk.RegFrfLsb:
807 | Console.WriteLine(" => Frequency " + GetFrequency() + " MHz");
808 | break;
809 | }
810 | }
811 | break;
812 | }
813 | else
814 | {
815 | Console.Write("R Reg: 0x" + currentRegister.ToString("X2") + " " + regName.PadRight(16) + " > " + data.ToString("X2"));
816 | Console.WriteLine();
817 | }
818 | break;
819 | }
820 |
821 | case 2:
822 | break;
823 |
824 | case 3:
825 | Console.Write(" " + data.ToString("X2"));
826 | Console.WriteLine("");
827 | Console.Write("" + (((data & 0x80) != 0) ? "W" : "R") + " " + (data & 0x7F).ToString("X2"));
828 | break;
829 |
830 | case 4:
831 | Console.Write(" " + data.ToString("X2")); Console.WriteLine("");
832 | Console.Write("" + data.ToString("X2"));
833 | break;
834 |
835 | case 5:
836 |
837 | if ((thisTime - lastUpdate).TotalMilliseconds > 50)
838 | {
839 | lastUpdate = thisTime;
840 | var frfs = LastWrittenFrequenciesBinary.Distinct().ToList();
841 | List deltas = new List();
842 |
843 | frfs.Sort();
844 |
845 | for (int pos = 0; pos < frfs.Count - 1; pos++)
846 | {
847 | ulong delta = frfs[pos + 1] - frfs[pos];
848 | deltas.Add(delta);
849 | }
850 | decimal avgDelta = 0;
851 |
852 | if (deltas.Count > 0)
853 | {
854 | avgDelta = (decimal)deltas.Average(s => (double)s);
855 | }
856 | decimal avgDeltaFreq = (avgDelta * 32000000.0m / (1 << 19));
857 |
858 | ChannelSpacing = (ulong)avgDeltaFreq;
859 |
860 | Console.SetCursorPosition(0, 0);
861 | Console.WriteLine("Hop: " + currentHop.ToString().PadLeft(3));
862 | Console.WriteLine("");
863 | Console.WriteLine("Freq min: 0x" + MinFreq.ToString("X6") + " (" + FreqToMhzString(MinFreq) + " MHz) ");
864 | Console.WriteLine("Freq max: 0x" + MaxFreq.ToString("X6") + " (" + FreqToMhzString(MaxFreq) + " MHz) ");
865 | Console.WriteLine("Spacing: " + (avgDeltaFreq / 1000.0m).ToString("0.00") + " kHz ");
866 | Console.WriteLine("FreqShift: 0x" + GetFreqShiftRaw().ToString("X6") + " (" + GetFreqShift().ToString("0.00") + " kHz) ");
867 | Console.WriteLine("Bitrate: 0x" + GetBitrateRaw().ToString("X6") + " (" + GetBitrate().ToString("0.00") + " kBaud) ");
868 | Console.WriteLine("");
869 | Console.WriteLine("");
870 | Console.WriteLine(" hop sequence");
871 | Console.WriteLine("--------------");
872 | for (int hopNum = 0; hopNum < 300; hopNum++)
873 | {
874 | if ((hopNum % 30) == 0)
875 | {
876 | Console.WriteLine();
877 | Console.Write(" " + hopNum.ToString().PadLeft(3) + " ");
878 | }
879 | if (HopSequenceDetected != null)
880 | {
881 | Console.Write(" " + HopSequenceDetected[hopNum].ToString().PadLeft(2));
882 | }
883 | }
884 | Console.WriteLine("");
885 | Console.WriteLine("");
886 | Console.WriteLine(" Downlink CRC8 table per hop");
887 | Console.WriteLine("-----------------------------");
888 | for (int hopNum = 0; hopNum < 150; hopNum++)
889 | {
890 | if ((hopNum % 30) == 0)
891 | {
892 | Console.WriteLine();
893 | Console.Write(" " + hopNum.ToString().PadLeft(3) + " ");
894 | }
895 | Console.Write(" " + hopDownCrc[hopNum].ToString("X2"));
896 | }
897 | Console.WriteLine("");
898 | Console.WriteLine("");
899 | Console.WriteLine(" Uplink CRC16 table per hop");
900 | Console.WriteLine("----------------------------");
901 | for (int hopNum = 0; hopNum < 150; hopNum++)
902 | {
903 | if ((hopNum % 20) == 0)
904 | {
905 | Console.WriteLine();
906 | Console.Write(" " + hopNum.ToString().PadLeft(3) + " ");
907 | }
908 | Console.Write(" " + hopUpCrc[hopNum].ToString("X4"));
909 | }
910 | Console.WriteLine("");
911 | }
912 | break;
913 | }
914 |
915 | if (write)
916 | {
917 | /* shared between lora and fsk */
918 | switch ((SX1272RegsFsk)currentRegister)
919 | {
920 | case SX1272RegsFsk.RegFrfLsb:
921 | ulong frf = (GetRegister(SX1272RegsFsk.RegFrfMsb) << 16) + (GetRegister(SX1272RegsFsk.RegFrfMid) << 8) + (GetRegister(SX1272RegsFsk.RegFrfLsb) << 0);
922 | ulong freq = (ulong)(GetFrequency() * 1000000.0m);
923 |
924 | /* delay a few ms */
925 | if ((logReader.BaseStream is FileStream) && sleepDelay > 0)
926 | {
927 | Thread.Sleep(sleepDelay);
928 | }
929 |
930 | if (frf != 0)
931 | {
932 | int arfcn = FreqToArfcn(frf);
933 |
934 | Frequencies.Add(freq);
935 | FrequenciesBinary.Add(frf);
936 | FrequenciesLog.Add(new Tuple((DateTime.Now - StartTime).TotalMilliseconds, (byte)((frf - 0xD70AB0) / 4260)));
937 |
938 | if ((FrequenciesBinary.Count % 2) == 0)
939 | {
940 | int width = FrequenciesBinary.Count / 2;
941 |
942 | var part1 = FrequenciesBinary.Take(width);
943 | var part2 = FrequenciesBinary.Skip(width);
944 |
945 | //if(Enumerable.SequenceEqual(part1, part2))
946 | {
947 | //PrintStatistics();
948 | }
949 |
950 | }
951 |
952 | //Console.WriteLine(" => Frequency: " + (freq / 1000) + " kHz");
953 |
954 |
955 | LastWrittenFrequenciesBinary.Add(frf);
956 |
957 | if (MinFreq != LastWrittenFrequenciesBinary.Min())
958 | {
959 | MinFreq = LastWrittenFrequenciesBinary.Min();
960 | HopSequenceDetected = null;
961 | }
962 | if (MaxFreq != LastWrittenFrequenciesBinary.Max())
963 | {
964 | MaxFreq = LastWrittenFrequenciesBinary.Max();
965 | HopSequenceDetected = null;
966 | }
967 |
968 | if (LastWrittenFrequenciesBinary.Count > 300)
969 | {
970 | /* if the pattern does not repeat, reset the hop list */
971 | if (LastWrittenFrequenciesBinary.First() != LastWrittenFrequenciesBinary.Last())
972 | {
973 | HopSequenceDetected = null;
974 | }
975 |
976 | /* only update the hop sequence if the current (and first) ARFCN is zero */
977 | if (HopSequenceDetected == null && arfcn == 0)
978 | {
979 | HopSequenceDetected = LastWrittenFrequenciesBinary.Take(300).Select(v => FreqToArfcn(v)).ToArray();
980 | }
981 |
982 | LastWrittenFrequenciesBinary.RemoveAt(0);
983 | }
984 | }
985 | break;
986 | }
987 | }
988 |
989 | if (currentRegister != 0)
990 | {
991 | lastFskRegister = (SX1272RegsFsk)0xff;
992 | lastLoRaRegister = (SX1272RegsLoRa)0xff;
993 |
994 | if (IsFsk())
995 | {
996 | lastFskRegister = (SX1272RegsFsk)currentRegister;
997 | }
998 | else
999 | {
1000 | lastLoRaRegister = (SX1272RegsLoRa)currentRegister;
1001 | }
1002 | currentRegister++;
1003 | }
1004 | }
1005 | else
1006 | {
1007 | Console.WriteLine("Lost a few bytes");
1008 | lastFskRegister = 0;
1009 | fifoData.Clear();
1010 | logReader.ReadByte();
1011 | }
1012 | }
1013 | catch (TimeoutException ex)
1014 | {
1015 | }
1016 | }
1017 | }
1018 | catch (EndOfStreamException ex)
1019 | {
1020 | Console.WriteLine("End of file");
1021 | }
1022 | }
1023 |
1024 | private static int FreqToArfcn(ulong frf)
1025 | {
1026 | double spacingFrf = ChannelSpacing * (1 << 19) / 32000000; //4260.0f
1027 | return (int)Math.Round((frf - MinFreq) / spacingFrf, 0);
1028 | }
1029 |
1030 | private static decimal FreqToMhz(ulong frf)
1031 | {
1032 | return Math.Round(((frf * 32000000) >> 19) / 1000000.0m, 3);
1033 | }
1034 |
1035 | private static string FreqToMhzString(ulong frf)
1036 | {
1037 | return FreqToMhz(frf).ToString("0.000");
1038 | }
1039 |
1040 | private static void PrintStatistics()
1041 | {
1042 | ulong min = FrequenciesBinary.Min();
1043 | ulong max = FrequenciesBinary.Max();
1044 | decimal minFreq = ((min * 32000000) >> 19) / 1000000.0m;
1045 | decimal maxFreq = ((max * 32000000) >> 19) / 1000000.0m;
1046 |
1047 | var freqs = FrequenciesBinary.OrderBy(s => s).Distinct();
1048 |
1049 | List deltas = new List();
1050 |
1051 | ulong prevFreq = freqs.First();
1052 |
1053 | foreach (var freq in freqs.Skip(1))
1054 | {
1055 | ulong delta = freq - prevFreq;
1056 |
1057 | deltas.Add(delta);
1058 |
1059 | prevFreq = freq;
1060 | }
1061 |
1062 | if (deltas.Count > 0)
1063 | {
1064 | Console.WriteLine("");
1065 | Console.WriteLine("Min: " + min.ToString("X6") + " (" + minFreq.ToString("0.000") + ")" + " Max: " + max.ToString("X6") + " (" + maxFreq.ToString("0.000") + ")" + " Distance: " + deltas.First() + " Hz");
1066 |
1067 | if (!IsLora)
1068 | {
1069 | uint frac = GetRegister(0x70);
1070 | uint bitRate = (GetRegister(2) << 8 | GetRegister(3));
1071 |
1072 | if (bitRate > 0)
1073 | {
1074 | Console.WriteLine("Rate: " + (32000000 / (bitRate + frac / 16)));
1075 | }
1076 | }
1077 | }
1078 |
1079 | /*
1080 |
1081 | Console.WriteLine("Frequencies: ");
1082 | foreach (ulong frf in FrequenciesBinary)
1083 | {
1084 | var chan = (frf - FrequenciesBinary.Min()) / deltas.First();
1085 | Console.Write("0x" + chan.ToString("X2")+", ");
1086 | }
1087 | Console.WriteLine("");
1088 |
1089 | */
1090 |
1091 | Console.WriteLine("Frequencies: ");
1092 | foreach (var pair in FrequenciesLog.OrderBy(p => p.Item1))
1093 | {
1094 | Console.Write(pair.Item1 + " " + pair.Item2.ToString("X2") + ", ");
1095 | }
1096 | Console.WriteLine("");
1097 | }
1098 |
1099 | private static void WriteRegister(uint currentRegister, uint data)
1100 | {
1101 | if (IsLora && !SharedRegisters.Contains(currentRegister))
1102 | {
1103 | RegistersLora[currentRegister] = (byte)data;
1104 | }
1105 | else
1106 | {
1107 | RegistersFsk[currentRegister] = (byte)data;
1108 | }
1109 | }
1110 |
1111 | private static uint GetRegister(SX1272RegsFsk reg)
1112 | {
1113 | return GetRegister((uint)reg);
1114 | }
1115 |
1116 | private static uint GetRegister(SX1272RegsLoRa reg)
1117 | {
1118 | return GetRegister((uint)reg);
1119 | }
1120 |
1121 | private static uint GetRegister(uint currentRegister)
1122 | {
1123 | if (IsLora && !SharedRegisters.Contains(currentRegister))
1124 | {
1125 | return RegistersLora[currentRegister];
1126 | }
1127 | return RegistersFsk[currentRegister];
1128 | }
1129 | }
1130 | }
--------------------------------------------------------------------------------
/SPIlog/Options.cs:
--------------------------------------------------------------------------------
1 |
2 | //
3 | // Options.cs
4 | //
5 | // Authors:
6 | // Jonathan Pryor ,
7 | // Federico Di Gregorio
8 | // Rolf Bjarne Kvinge
9 | //
10 | // Copyright (C) 2008 Novell (http://www.novell.com)
11 | // Copyright (C) 2009 Federico Di Gregorio.
12 | // Copyright (C) 2012 Xamarin Inc (http://www.xamarin.com)
13 | // Copyright (C) 2017 Microsoft Corporation (http://www.microsoft.com)
14 | //
15 | // Permission is hereby granted, free of charge, to any person obtaining
16 | // a copy of this software and associated documentation files (the
17 | // "Software"), to deal in the Software without restriction, including
18 | // without limitation the rights to use, copy, modify, merge, publish,
19 | // distribute, sublicense, and/or sell copies of the Software, and to
20 | // permit persons to whom the Software is furnished to do so, subject to
21 | // the following conditions:
22 | //
23 | // The above copyright notice and this permission notice shall be
24 | // included in all copies or substantial portions of the Software.
25 | //
26 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
27 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
28 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
29 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
30 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
31 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
32 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33 | //
34 |
35 | // Compile With:
36 | // mcs -debug+ -r:System.Core Options.cs -o:Mono.Options.dll -t:library
37 | // mcs -debug+ -d:LINQ -r:System.Core Options.cs -o:Mono.Options.dll -t:library
38 | //
39 | // The LINQ version just changes the implementation of
40 | // OptionSet.Parse(IEnumerable), and confers no semantic changes.
41 |
42 | //
43 | // A Getopt::Long-inspired option parsing library for C#.
44 | //
45 | // Mono.Options.OptionSet is built upon a key/value table, where the
46 | // key is a option format string and the value is a delegate that is
47 | // invoked when the format string is matched.
48 | //
49 | // Option format strings:
50 | // Regex-like BNF Grammar:
51 | // name: .+
52 | // type: [=:]
53 | // sep: ( [^{}]+ | '{' .+ '}' )?
54 | // aliases: ( name type sep ) ( '|' name type sep )*
55 | //
56 | // Each '|'-delimited name is an alias for the associated action. If the
57 | // format string ends in a '=', it has a required value. If the format
58 | // string ends in a ':', it has an optional value. If neither '=' or ':'
59 | // is present, no value is supported. `=' or `:' need only be defined on one
60 | // alias, but if they are provided on more than one they must be consistent.
61 | //
62 | // Each alias portion may also end with a "key/value separator", which is used
63 | // to split option values if the option accepts > 1 value. If not specified,
64 | // it defaults to '=' and ':'. If specified, it can be any character except
65 | // '{' and '}' OR the *string* between '{' and '}'. If no separator should be
66 | // used (i.e. the separate values should be distinct arguments), then "{}"
67 | // should be used as the separator.
68 | //
69 | // Options are extracted either from the current option by looking for
70 | // the option name followed by an '=' or ':', or is taken from the
71 | // following option IFF:
72 | // - The current option does not contain a '=' or a ':'
73 | // - The current option requires a value (i.e. not a Option type of ':')
74 | //
75 | // The `name' used in the option format string does NOT include any leading
76 | // option indicator, such as '-', '--', or '/'. All three of these are
77 | // permitted/required on any named option.
78 | //
79 | // Option bundling is permitted so long as:
80 | // - '-' is used to start the option group
81 | // - all of the bundled options are a single character
82 | // - at most one of the bundled options accepts a value, and the value
83 | // provided starts from the next character to the end of the string.
84 | //
85 | // This allows specifying '-a -b -c' as '-abc', and specifying '-D name=value'
86 | // as '-Dname=value'.
87 | //
88 | // Option processing is disabled by specifying "--". All options after "--"
89 | // are returned by OptionSet.Parse() unchanged and unprocessed.
90 | //
91 | // Unprocessed options are returned from OptionSet.Parse().
92 | //
93 | // Examples:
94 | // int verbose = 0;
95 | // OptionSet p = new OptionSet ()
96 | // .Add ("v", v => ++verbose)
97 | // .Add ("name=|value=", v => Console.WriteLine (v));
98 | // p.Parse (new string[]{"-v", "--v", "/v", "-name=A", "/name", "B", "extra"});
99 | //
100 | // The above would parse the argument string array, and would invoke the
101 | // lambda expression three times, setting `verbose' to 3 when complete.
102 | // It would also print out "A" and "B" to standard output.
103 | // The returned array would contain the string "extra".
104 | //
105 | // C# 3.0 collection initializers are supported and encouraged:
106 | // var p = new OptionSet () {
107 | // { "h|?|help", v => ShowHelp () },
108 | // };
109 | //
110 | // System.ComponentModel.TypeConverter is also supported, allowing the use of
111 | // custom data types in the callback type; TypeConverter.ConvertFromString()
112 | // is used to convert the value option to an instance of the specified
113 | // type:
114 | //
115 | // var p = new OptionSet () {
116 | // { "foo=", (Foo f) => Console.WriteLine (f.ToString ()) },
117 | // };
118 | //
119 | // Random other tidbits:
120 | // - Boolean options (those w/o '=' or ':' in the option format string)
121 | // are explicitly enabled if they are followed with '+', and explicitly
122 | // disabled if they are followed with '-':
123 | // string a = null;
124 | // var p = new OptionSet () {
125 | // { "a", s => a = s },
126 | // };
127 | // p.Parse (new string[]{"-a"}); // sets v != null
128 | // p.Parse (new string[]{"-a+"}); // sets v != null
129 | // p.Parse (new string[]{"-a-"}); // sets v == null
130 | //
131 |
132 | //
133 | // Mono.Options.CommandSet allows easily having separate commands and
134 | // associated command options, allowing creation of a *suite* along the
135 | // lines of **git**(1), **svn**(1), etc.
136 | //
137 | // CommandSet allows intermixing plain text strings for `--help` output,
138 | // Option values -- as supported by OptionSet -- and Command instances,
139 | // which have a name, optional help text, and an optional OptionSet.
140 | //
141 | // var suite = new CommandSet ("suite-name") {
142 | // // Use strings and option values, as with OptionSet
143 | // "usage: suite-name COMMAND [OPTIONS]+",
144 | // { "v:", "verbosity", (int? v) => Verbosity = v.HasValue ? v.Value : Verbosity+1 },
145 | // // Commands may also be specified
146 | // new Command ("command-name", "command help") {
147 | // Options = new OptionSet {/*...*/},
148 | // Run = args => { /*...*/},
149 | // },
150 | // new MyCommandSubclass (),
151 | // };
152 | // return suite.Run (new string[]{...});
153 | //
154 | // CommandSet provides a `help` command, and forwards `help COMMAND`
155 | // to the registered Command instance by invoking Command.Invoke()
156 | // with `--help` as an option.
157 | //
158 |
159 | using System;
160 | using System.Collections;
161 | using System.Collections.Generic;
162 | using System.Collections.ObjectModel;
163 | using System.ComponentModel;
164 | using System.Globalization;
165 | using System.IO;
166 | #if PCL
167 | using System.Reflection;
168 | #else
169 | using System.Runtime.Serialization;
170 | using System.Security.Permissions;
171 | #endif
172 | using System.Text;
173 | using System.Text.RegularExpressions;
174 |
175 | #if LINQ
176 | using System.Linq;
177 | #endif
178 |
179 | #if TEST
180 | using NDesk.Options;
181 | #endif
182 |
183 | #if PCL
184 | using MessageLocalizerConverter = System.Func;
185 | #else
186 | using MessageLocalizerConverter = System.Converter;
187 | #endif
188 |
189 | #if NDESK_OPTIONS
190 | namespace NDesk.Options
191 | #else
192 | namespace Mono.Options
193 | #endif
194 | {
195 | static class StringCoda
196 | {
197 |
198 | public static IEnumerable WrappedLines(string self, params int[] widths)
199 | {
200 | IEnumerable w = widths;
201 | return WrappedLines(self, w);
202 | }
203 |
204 | public static IEnumerable WrappedLines(string self, IEnumerable widths)
205 | {
206 | if (widths == null)
207 | throw new ArgumentNullException("widths");
208 | return CreateWrappedLinesIterator(self, widths);
209 | }
210 |
211 | private static IEnumerable CreateWrappedLinesIterator(string self, IEnumerable widths)
212 | {
213 | if (string.IsNullOrEmpty(self))
214 | {
215 | yield return string.Empty;
216 | yield break;
217 | }
218 | using (IEnumerator ewidths = widths.GetEnumerator())
219 | {
220 | bool? hw = null;
221 | int width = GetNextWidth(ewidths, int.MaxValue, ref hw);
222 | int start = 0, end;
223 | do
224 | {
225 | end = GetLineEnd(start, width, self);
226 | // endCorrection is 1 if the line end is '\n', and might be 2 if the line end is '\r\n'.
227 | int endCorrection = 1;
228 | if (end >= 2 && self.Substring(end - 2, 2).Equals("\r\n"))
229 | endCorrection = 2;
230 | char c = self[end - endCorrection];
231 | if (char.IsWhiteSpace(c))
232 | end -= endCorrection;
233 | bool needContinuation = end != self.Length && !IsEolChar(c);
234 | string continuation = "";
235 | if (needContinuation)
236 | {
237 | --end;
238 | continuation = "-";
239 | }
240 | string line = self.Substring(start, end - start) + continuation;
241 | yield return line;
242 | start = end;
243 | if (char.IsWhiteSpace(c))
244 | start += endCorrection;
245 | width = GetNextWidth(ewidths, width, ref hw);
246 | } while (start < self.Length);
247 | }
248 | }
249 |
250 | private static int GetNextWidth(IEnumerator ewidths, int curWidth, ref bool? eValid)
251 | {
252 | if (!eValid.HasValue || (eValid.HasValue && eValid.Value))
253 | {
254 | curWidth = (eValid = ewidths.MoveNext()).Value ? ewidths.Current : curWidth;
255 | // '.' is any character, - is for a continuation
256 | const string minWidth = ".-";
257 | if (curWidth < minWidth.Length)
258 | throw new ArgumentOutOfRangeException("widths",
259 | string.Format("Element must be >= {0}, was {1}.", minWidth.Length, curWidth));
260 | return curWidth;
261 | }
262 | // no more elements, use the last element.
263 | return curWidth;
264 | }
265 |
266 | private static bool IsEolChar(char c)
267 | {
268 | return !char.IsLetterOrDigit(c);
269 | }
270 |
271 | private static int GetLineEnd(int start, int length, string description)
272 | {
273 | int end = System.Math.Min(start + length, description.Length);
274 | int sep = -1;
275 | for (int i = start; i < end; ++i)
276 | {
277 | if (i + 2 <= description.Length && description.Substring(i, 2).Equals("\r\n"))
278 | return i + 2;
279 | if (description[i] == '\n')
280 | return i + 1;
281 | if (IsEolChar(description[i]))
282 | sep = i + 1;
283 | }
284 | if (sep == -1 || end == description.Length)
285 | return end;
286 | return sep;
287 | }
288 | }
289 |
290 | public class OptionValueCollection : IList, IList
291 | {
292 |
293 | List values = new List();
294 | OptionContext c;
295 |
296 | internal OptionValueCollection(OptionContext c)
297 | {
298 | this.c = c;
299 | }
300 |
301 | #region ICollection
302 | void ICollection.CopyTo(Array array, int index) { (values as ICollection).CopyTo(array, index); }
303 | bool ICollection.IsSynchronized { get { return (values as ICollection).IsSynchronized; } }
304 | object ICollection.SyncRoot { get { return (values as ICollection).SyncRoot; } }
305 | #endregion
306 |
307 | #region ICollection
308 | public void Add(string item) { values.Add(item); }
309 | public void Clear() { values.Clear(); }
310 | public bool Contains(string item) { return values.Contains(item); }
311 | public void CopyTo(string[] array, int arrayIndex) { values.CopyTo(array, arrayIndex); }
312 | public bool Remove(string item) { return values.Remove(item); }
313 | public int Count { get { return values.Count; } }
314 | public bool IsReadOnly { get { return false; } }
315 | #endregion
316 |
317 | #region IEnumerable
318 | IEnumerator IEnumerable.GetEnumerator() { return values.GetEnumerator(); }
319 | #endregion
320 |
321 | #region IEnumerable
322 | public IEnumerator GetEnumerator() { return values.GetEnumerator(); }
323 | #endregion
324 |
325 | #region IList
326 | int IList.Add(object value) { return (values as IList).Add(value); }
327 | bool IList.Contains(object value) { return (values as IList).Contains(value); }
328 | int IList.IndexOf(object value) { return (values as IList).IndexOf(value); }
329 | void IList.Insert(int index, object value) { (values as IList).Insert(index, value); }
330 | void IList.Remove(object value) { (values as IList).Remove(value); }
331 | void IList.RemoveAt(int index) { (values as IList).RemoveAt(index); }
332 | bool IList.IsFixedSize { get { return false; } }
333 | object IList.this[int index] { get { return this[index]; } set { (values as IList)[index] = value; } }
334 | #endregion
335 |
336 | #region IList
337 | public int IndexOf(string item) { return values.IndexOf(item); }
338 | public void Insert(int index, string item) { values.Insert(index, item); }
339 | public void RemoveAt(int index) { values.RemoveAt(index); }
340 |
341 | private void AssertValid(int index)
342 | {
343 | if (c.Option == null)
344 | throw new InvalidOperationException("OptionContext.Option is null.");
345 | if (index >= c.Option.MaxValueCount)
346 | throw new ArgumentOutOfRangeException("index");
347 | if (c.Option.OptionValueType == OptionValueType.Required &&
348 | index >= values.Count)
349 | throw new OptionException(string.Format(
350 | c.OptionSet.MessageLocalizer("Missing required value for option '{0}'."), c.OptionName),
351 | c.OptionName);
352 | }
353 |
354 | public string this[int index]
355 | {
356 | get
357 | {
358 | AssertValid(index);
359 | return index >= values.Count ? null : values[index];
360 | }
361 | set
362 | {
363 | values[index] = value;
364 | }
365 | }
366 | #endregion
367 |
368 | public List ToList()
369 | {
370 | return new List(values);
371 | }
372 |
373 | public string[] ToArray()
374 | {
375 | return values.ToArray();
376 | }
377 |
378 | public override string ToString()
379 | {
380 | return string.Join(", ", values.ToArray());
381 | }
382 | }
383 |
384 | public class OptionContext
385 | {
386 | private Option option;
387 | private string name;
388 | private int index;
389 | private OptionSet set;
390 | private OptionValueCollection c;
391 |
392 | public OptionContext(OptionSet set)
393 | {
394 | this.set = set;
395 | this.c = new OptionValueCollection(this);
396 | }
397 |
398 | public Option Option
399 | {
400 | get { return option; }
401 | set { option = value; }
402 | }
403 |
404 | public string OptionName
405 | {
406 | get { return name; }
407 | set { name = value; }
408 | }
409 |
410 | public int OptionIndex
411 | {
412 | get { return index; }
413 | set { index = value; }
414 | }
415 |
416 | public OptionSet OptionSet
417 | {
418 | get { return set; }
419 | }
420 |
421 | public OptionValueCollection OptionValues
422 | {
423 | get { return c; }
424 | }
425 | }
426 |
427 | public enum OptionValueType
428 | {
429 | None,
430 | Optional,
431 | Required,
432 | }
433 |
434 | public abstract class Option
435 | {
436 | string prototype, description;
437 | string[] names;
438 | OptionValueType type;
439 | int count;
440 | string[] separators;
441 | bool hidden;
442 |
443 | protected Option(string prototype, string description)
444 | : this(prototype, description, 1, false)
445 | {
446 | }
447 |
448 | protected Option(string prototype, string description, int maxValueCount)
449 | : this(prototype, description, maxValueCount, false)
450 | {
451 | }
452 |
453 | protected Option(string prototype, string description, int maxValueCount, bool hidden)
454 | {
455 | if (prototype == null)
456 | throw new ArgumentNullException("prototype");
457 | if (prototype.Length == 0)
458 | throw new ArgumentException("Cannot be the empty string.", "prototype");
459 | if (maxValueCount < 0)
460 | throw new ArgumentOutOfRangeException("maxValueCount");
461 |
462 | this.prototype = prototype;
463 | this.description = description;
464 | this.count = maxValueCount;
465 | this.names = (this is OptionSet.Category)
466 | // append GetHashCode() so that "duplicate" categories have distinct
467 | // names, e.g. adding multiple "" categories should be valid.
468 | ? new[] { prototype + this.GetHashCode() }
469 | : prototype.Split('|');
470 |
471 | if (this is OptionSet.Category || this is CommandOption)
472 | return;
473 |
474 | this.type = ParsePrototype();
475 | this.hidden = hidden;
476 |
477 | if (this.count == 0 && type != OptionValueType.None)
478 | throw new ArgumentException(
479 | "Cannot provide maxValueCount of 0 for OptionValueType.Required or " +
480 | "OptionValueType.Optional.",
481 | "maxValueCount");
482 | if (this.type == OptionValueType.None && maxValueCount > 1)
483 | throw new ArgumentException(
484 | string.Format("Cannot provide maxValueCount of {0} for OptionValueType.None.", maxValueCount),
485 | "maxValueCount");
486 | if (Array.IndexOf(names, "<>") >= 0 &&
487 | ((names.Length == 1 && this.type != OptionValueType.None) ||
488 | (names.Length > 1 && this.MaxValueCount > 1)))
489 | throw new ArgumentException(
490 | "The default option handler '<>' cannot require values.",
491 | "prototype");
492 | }
493 |
494 | public string Prototype { get { return prototype; } }
495 | public string Description { get { return description; } }
496 | public OptionValueType OptionValueType { get { return type; } }
497 | public int MaxValueCount { get { return count; } }
498 | public bool Hidden { get { return hidden; } }
499 |
500 | public string[] GetNames()
501 | {
502 | return (string[])names.Clone();
503 | }
504 |
505 | public string[] GetValueSeparators()
506 | {
507 | if (separators == null)
508 | return new string[0];
509 | return (string[])separators.Clone();
510 | }
511 |
512 | protected static T Parse(string value, OptionContext c)
513 | {
514 | Type tt = typeof(T);
515 | #if PCL
516 | TypeInfo ti = tt.GetTypeInfo ();
517 | #else
518 | Type ti = tt;
519 | #endif
520 | bool nullable =
521 | ti.IsValueType &&
522 | ti.IsGenericType &&
523 | !ti.IsGenericTypeDefinition &&
524 | ti.GetGenericTypeDefinition() == typeof(Nullable<>);
525 | #if PCL
526 | Type targetType = nullable ? tt.GenericTypeArguments [0] : tt;
527 | #else
528 | Type targetType = nullable ? tt.GetGenericArguments()[0] : tt;
529 | #endif
530 | T t = default(T);
531 | try
532 | {
533 | if (value != null)
534 | {
535 | #if PCL
536 | if (targetType.GetTypeInfo ().IsEnum)
537 | t = (T) Enum.Parse (targetType, value, true);
538 | else
539 | t = (T) Convert.ChangeType (value, targetType);
540 | #else
541 | TypeConverter conv = TypeDescriptor.GetConverter(targetType);
542 | t = (T)conv.ConvertFromString(value);
543 | #endif
544 | }
545 | }
546 | catch (Exception e)
547 | {
548 | throw new OptionException(
549 | string.Format(
550 | c.OptionSet.MessageLocalizer("Could not convert string `{0}' to type {1} for option `{2}'."),
551 | value, targetType.Name, c.OptionName),
552 | c.OptionName, e);
553 | }
554 | return t;
555 | }
556 |
557 | internal string[] Names { get { return names; } }
558 | internal string[] ValueSeparators { get { return separators; } }
559 |
560 | static readonly char[] NameTerminator = new char[] { '=', ':' };
561 |
562 | private OptionValueType ParsePrototype()
563 | {
564 | char type = '\0';
565 | List seps = new List();
566 | for (int i = 0; i < names.Length; ++i)
567 | {
568 | string name = names[i];
569 | if (name.Length == 0)
570 | throw new ArgumentException("Empty option names are not supported.", "prototype");
571 |
572 | int end = name.IndexOfAny(NameTerminator);
573 | if (end == -1)
574 | continue;
575 | names[i] = name.Substring(0, end);
576 | if (type == '\0' || type == name[end])
577 | type = name[end];
578 | else
579 | throw new ArgumentException(
580 | string.Format("Conflicting option types: '{0}' vs. '{1}'.", type, name[end]),
581 | "prototype");
582 | AddSeparators(name, end, seps);
583 | }
584 |
585 | if (type == '\0')
586 | return OptionValueType.None;
587 |
588 | if (count <= 1 && seps.Count != 0)
589 | throw new ArgumentException(
590 | string.Format("Cannot provide key/value separators for Options taking {0} value(s).", count),
591 | "prototype");
592 | if (count > 1)
593 | {
594 | if (seps.Count == 0)
595 | this.separators = new string[] { ":", "=" };
596 | else if (seps.Count == 1 && seps[0].Length == 0)
597 | this.separators = null;
598 | else
599 | this.separators = seps.ToArray();
600 | }
601 |
602 | return type == '=' ? OptionValueType.Required : OptionValueType.Optional;
603 | }
604 |
605 | private static void AddSeparators(string name, int end, ICollection seps)
606 | {
607 | int start = -1;
608 | for (int i = end + 1; i < name.Length; ++i)
609 | {
610 | switch (name[i])
611 | {
612 | case '{':
613 | if (start != -1)
614 | throw new ArgumentException(
615 | string.Format("Ill-formed name/value separator found in \"{0}\".", name),
616 | "prototype");
617 | start = i + 1;
618 | break;
619 | case '}':
620 | if (start == -1)
621 | throw new ArgumentException(
622 | string.Format("Ill-formed name/value separator found in \"{0}\".", name),
623 | "prototype");
624 | seps.Add(name.Substring(start, i - start));
625 | start = -1;
626 | break;
627 | default:
628 | if (start == -1)
629 | seps.Add(name[i].ToString());
630 | break;
631 | }
632 | }
633 | if (start != -1)
634 | throw new ArgumentException(
635 | string.Format("Ill-formed name/value separator found in \"{0}\".", name),
636 | "prototype");
637 | }
638 |
639 | public void Invoke(OptionContext c)
640 | {
641 | OnParseComplete(c);
642 | c.OptionName = null;
643 | c.Option = null;
644 | c.OptionValues.Clear();
645 | }
646 |
647 | protected abstract void OnParseComplete(OptionContext c);
648 |
649 | internal void InvokeOnParseComplete(OptionContext c)
650 | {
651 | OnParseComplete(c);
652 | }
653 |
654 | public override string ToString()
655 | {
656 | return Prototype;
657 | }
658 | }
659 |
660 | public abstract class ArgumentSource
661 | {
662 |
663 | protected ArgumentSource()
664 | {
665 | }
666 |
667 | public abstract string[] GetNames();
668 | public abstract string Description { get; }
669 | public abstract bool GetArguments(string value, out IEnumerable replacement);
670 |
671 | #if !PCL || NETSTANDARD1_3
672 | public static IEnumerable GetArgumentsFromFile(string file)
673 | {
674 | return GetArguments(File.OpenText(file), true);
675 | }
676 | #endif
677 |
678 | public static IEnumerable GetArguments(TextReader reader)
679 | {
680 | return GetArguments(reader, false);
681 | }
682 |
683 | // Cribbed from mcs/driver.cs:LoadArgs(string)
684 | static IEnumerable GetArguments(TextReader reader, bool close)
685 | {
686 | try
687 | {
688 | StringBuilder arg = new StringBuilder();
689 |
690 | string line;
691 | while ((line = reader.ReadLine()) != null)
692 | {
693 | int t = line.Length;
694 |
695 | for (int i = 0; i < t; i++)
696 | {
697 | char c = line[i];
698 |
699 | if (c == '"' || c == '\'')
700 | {
701 | char end = c;
702 |
703 | for (i++; i < t; i++)
704 | {
705 | c = line[i];
706 |
707 | if (c == end)
708 | break;
709 | arg.Append(c);
710 | }
711 | }
712 | else if (c == ' ')
713 | {
714 | if (arg.Length > 0)
715 | {
716 | yield return arg.ToString();
717 | arg.Length = 0;
718 | }
719 | }
720 | else
721 | arg.Append(c);
722 | }
723 | if (arg.Length > 0)
724 | {
725 | yield return arg.ToString();
726 | arg.Length = 0;
727 | }
728 | }
729 | }
730 | finally
731 | {
732 | if (close)
733 | reader.Dispose();
734 | }
735 | }
736 | }
737 |
738 | #if !PCL || NETSTANDARD1_3
739 | public class ResponseFileSource : ArgumentSource
740 | {
741 |
742 | public override string[] GetNames()
743 | {
744 | return new string[] { "@file" };
745 | }
746 |
747 | public override string Description
748 | {
749 | get { return "Read response file for more options."; }
750 | }
751 |
752 | public override bool GetArguments(string value, out IEnumerable replacement)
753 | {
754 | if (string.IsNullOrEmpty(value) || !value.StartsWith("@"))
755 | {
756 | replacement = null;
757 | return false;
758 | }
759 | replacement = ArgumentSource.GetArgumentsFromFile(value.Substring(1));
760 | return true;
761 | }
762 | }
763 | #endif
764 |
765 | #if !PCL
766 | [Serializable]
767 | #endif
768 | public class OptionException : Exception
769 | {
770 | private string option;
771 |
772 | public OptionException()
773 | {
774 | }
775 |
776 | public OptionException(string message, string optionName)
777 | : base(message)
778 | {
779 | this.option = optionName;
780 | }
781 |
782 | public OptionException(string message, string optionName, Exception innerException)
783 | : base(message, innerException)
784 | {
785 | this.option = optionName;
786 | }
787 |
788 | #if !PCL
789 | protected OptionException(SerializationInfo info, StreamingContext context)
790 | : base(info, context)
791 | {
792 | this.option = info.GetString("OptionName");
793 | }
794 | #endif
795 |
796 | public string OptionName
797 | {
798 | get { return this.option; }
799 | }
800 |
801 | #if !PCL
802 | #pragma warning disable 618 // SecurityPermissionAttribute is obsolete
803 | [SecurityPermission(SecurityAction.LinkDemand, SerializationFormatter = true)]
804 | #pragma warning restore 618
805 | public override void GetObjectData(SerializationInfo info, StreamingContext context)
806 | {
807 | base.GetObjectData(info, context);
808 | info.AddValue("OptionName", option);
809 | }
810 | #endif
811 | }
812 |
813 | public delegate void OptionAction(TKey key, TValue value);
814 |
815 | public class OptionSet : KeyedCollection
816 | {
817 | public OptionSet()
818 | : this(null, null)
819 | {
820 | }
821 |
822 | public OptionSet(MessageLocalizerConverter localizer)
823 | : this(localizer, null)
824 | {
825 | }
826 |
827 | public OptionSet(StringComparer comparer)
828 | : this(null, comparer)
829 | {
830 | }
831 |
832 | public OptionSet(MessageLocalizerConverter localizer, StringComparer comparer)
833 | : base(comparer)
834 | {
835 | this.roSources = new ReadOnlyCollection(sources);
836 | this.localizer = localizer;
837 | if (this.localizer == null)
838 | {
839 | this.localizer = delegate (string f) {
840 | return f;
841 | };
842 | }
843 | }
844 |
845 | MessageLocalizerConverter localizer;
846 |
847 | public MessageLocalizerConverter MessageLocalizer
848 | {
849 | get { return localizer; }
850 | internal set { localizer = value; }
851 | }
852 |
853 | List sources = new List();
854 | ReadOnlyCollection roSources;
855 |
856 | public ReadOnlyCollection ArgumentSources
857 | {
858 | get { return roSources; }
859 | }
860 |
861 |
862 | protected override string GetKeyForItem(Option item)
863 | {
864 | if (item == null)
865 | throw new ArgumentNullException("option");
866 | if (item.Names != null && item.Names.Length > 0)
867 | return item.Names[0];
868 | // This should never happen, as it's invalid for Option to be
869 | // constructed w/o any names.
870 | throw new InvalidOperationException("Option has no names!");
871 | }
872 |
873 | [Obsolete("Use KeyedCollection.this[string]")]
874 | protected Option GetOptionForName(string option)
875 | {
876 | if (option == null)
877 | throw new ArgumentNullException("option");
878 | try
879 | {
880 | return base[option];
881 | }
882 | catch (KeyNotFoundException)
883 | {
884 | return null;
885 | }
886 | }
887 |
888 | protected override void InsertItem(int index, Option item)
889 | {
890 | base.InsertItem(index, item);
891 | AddImpl(item);
892 | }
893 |
894 | protected override void RemoveItem(int index)
895 | {
896 | Option p = Items[index];
897 | base.RemoveItem(index);
898 | // KeyedCollection.RemoveItem() handles the 0th item
899 | for (int i = 1; i < p.Names.Length; ++i)
900 | {
901 | Dictionary.Remove(p.Names[i]);
902 | }
903 | }
904 |
905 | protected override void SetItem(int index, Option item)
906 | {
907 | base.SetItem(index, item);
908 | AddImpl(item);
909 | }
910 |
911 | private void AddImpl(Option option)
912 | {
913 | if (option == null)
914 | throw new ArgumentNullException("option");
915 | List added = new List(option.Names.Length);
916 | try
917 | {
918 | // KeyedCollection.InsertItem/SetItem handle the 0th name.
919 | for (int i = 1; i < option.Names.Length; ++i)
920 | {
921 | Dictionary.Add(option.Names[i], option);
922 | added.Add(option.Names[i]);
923 | }
924 | }
925 | catch (Exception)
926 | {
927 | foreach (string name in added)
928 | Dictionary.Remove(name);
929 | throw;
930 | }
931 | }
932 |
933 | public OptionSet Add(string header)
934 | {
935 | if (header == null)
936 | throw new ArgumentNullException("header");
937 | Add(new Category(header));
938 | return this;
939 | }
940 |
941 | internal sealed class Category : Option
942 | {
943 |
944 | // Prototype starts with '=' because this is an invalid prototype
945 | // (see Option.ParsePrototype(), and thus it'll prevent Category
946 | // instances from being accidentally used as normal options.
947 | public Category(string description)
948 | : base("=:Category:= " + description, description)
949 | {
950 | }
951 |
952 | protected override void OnParseComplete(OptionContext c)
953 | {
954 | throw new NotSupportedException("Category.OnParseComplete should not be invoked.");
955 | }
956 | }
957 |
958 |
959 | public new OptionSet Add(Option option)
960 | {
961 | base.Add(option);
962 | return this;
963 | }
964 |
965 | sealed class ActionOption : Option
966 | {
967 | Action action;
968 |
969 | public ActionOption(string prototype, string description, int count, Action action)
970 | : this(prototype, description, count, action, false)
971 | {
972 | }
973 |
974 | public ActionOption(string prototype, string description, int count, Action action, bool hidden)
975 | : base(prototype, description, count, hidden)
976 | {
977 | if (action == null)
978 | throw new ArgumentNullException("action");
979 | this.action = action;
980 | }
981 |
982 | protected override void OnParseComplete(OptionContext c)
983 | {
984 | action(c.OptionValues);
985 | }
986 | }
987 |
988 | public OptionSet Add(string prototype, Action action)
989 | {
990 | return Add(prototype, null, action);
991 | }
992 |
993 | public OptionSet Add(string prototype, string description, Action action)
994 | {
995 | return Add(prototype, description, action, false);
996 | }
997 |
998 | public OptionSet Add(string prototype, string description, Action action, bool hidden)
999 | {
1000 | if (action == null)
1001 | throw new ArgumentNullException("action");
1002 | Option p = new ActionOption(prototype, description, 1,
1003 | delegate (OptionValueCollection v) { action(v[0]); }, hidden);
1004 | base.Add(p);
1005 | return this;
1006 | }
1007 |
1008 | public OptionSet Add(string prototype, OptionAction action)
1009 | {
1010 | return Add(prototype, null, action);
1011 | }
1012 |
1013 | public OptionSet Add(string prototype, string description, OptionAction action)
1014 | {
1015 | return Add(prototype, description, action, false);
1016 | }
1017 |
1018 | public OptionSet Add(string prototype, string description, OptionAction action, bool hidden)
1019 | {
1020 | if (action == null)
1021 | throw new ArgumentNullException("action");
1022 | Option p = new ActionOption(prototype, description, 2,
1023 | delegate (OptionValueCollection v) { action(v[0], v[1]); }, hidden);
1024 | base.Add(p);
1025 | return this;
1026 | }
1027 |
1028 | sealed class ActionOption : Option
1029 | {
1030 | Action action;
1031 |
1032 | public ActionOption(string prototype, string description, Action action)
1033 | : base(prototype, description, 1)
1034 | {
1035 | if (action == null)
1036 | throw new ArgumentNullException("action");
1037 | this.action = action;
1038 | }
1039 |
1040 | protected override void OnParseComplete(OptionContext c)
1041 | {
1042 | action(Parse(c.OptionValues[0], c));
1043 | }
1044 | }
1045 |
1046 | sealed class ActionOption : Option
1047 | {
1048 | OptionAction action;
1049 |
1050 | public ActionOption(string prototype, string description, OptionAction action)
1051 | : base(prototype, description, 2)
1052 | {
1053 | if (action == null)
1054 | throw new ArgumentNullException("action");
1055 | this.action = action;
1056 | }
1057 |
1058 | protected override void OnParseComplete(OptionContext c)
1059 | {
1060 | action(
1061 | Parse(c.OptionValues[0], c),
1062 | Parse(c.OptionValues[1], c));
1063 | }
1064 | }
1065 |
1066 | public OptionSet Add(string prototype, Action action)
1067 | {
1068 | return Add(prototype, null, action);
1069 | }
1070 |
1071 | public OptionSet Add(string prototype, string description, Action action)
1072 | {
1073 | return Add(new ActionOption(prototype, description, action));
1074 | }
1075 |
1076 | public OptionSet Add(string prototype, OptionAction action)
1077 | {
1078 | return Add(prototype, null, action);
1079 | }
1080 |
1081 | public OptionSet Add(string prototype, string description, OptionAction action)
1082 | {
1083 | return Add(new ActionOption(prototype, description, action));
1084 | }
1085 |
1086 | public OptionSet Add(ArgumentSource source)
1087 | {
1088 | if (source == null)
1089 | throw new ArgumentNullException("source");
1090 | sources.Add(source);
1091 | return this;
1092 | }
1093 |
1094 | protected virtual OptionContext CreateOptionContext()
1095 | {
1096 | return new OptionContext(this);
1097 | }
1098 |
1099 | public List Parse(IEnumerable arguments)
1100 | {
1101 | if (arguments == null)
1102 | throw new ArgumentNullException("arguments");
1103 | OptionContext c = CreateOptionContext();
1104 | c.OptionIndex = -1;
1105 | bool process = true;
1106 | List unprocessed = new List();
1107 | Option def = Contains("<>") ? this["<>"] : null;
1108 | ArgumentEnumerator ae = new ArgumentEnumerator(arguments);
1109 | foreach (string argument in ae)
1110 | {
1111 | ++c.OptionIndex;
1112 | if (argument == "--")
1113 | {
1114 | process = false;
1115 | continue;
1116 | }
1117 | if (!process)
1118 | {
1119 | Unprocessed(unprocessed, def, c, argument);
1120 | continue;
1121 | }
1122 | if (AddSource(ae, argument))
1123 | continue;
1124 | if (!Parse(argument, c))
1125 | Unprocessed(unprocessed, def, c, argument);
1126 | }
1127 | if (c.Option != null)
1128 | c.Option.Invoke(c);
1129 | return unprocessed;
1130 | }
1131 |
1132 | class ArgumentEnumerator : IEnumerable
1133 | {
1134 | List> sources = new List>();
1135 |
1136 | public ArgumentEnumerator(IEnumerable arguments)
1137 | {
1138 | sources.Add(arguments.GetEnumerator());
1139 | }
1140 |
1141 | public void Add(IEnumerable arguments)
1142 | {
1143 | sources.Add(arguments.GetEnumerator());
1144 | }
1145 |
1146 | public IEnumerator GetEnumerator()
1147 | {
1148 | do
1149 | {
1150 | IEnumerator c = sources[sources.Count - 1];
1151 | if (c.MoveNext())
1152 | yield return c.Current;
1153 | else
1154 | {
1155 | c.Dispose();
1156 | sources.RemoveAt(sources.Count - 1);
1157 | }
1158 | } while (sources.Count > 0);
1159 | }
1160 |
1161 | IEnumerator IEnumerable.GetEnumerator()
1162 | {
1163 | return GetEnumerator();
1164 | }
1165 | }
1166 |
1167 | bool AddSource(ArgumentEnumerator ae, string argument)
1168 | {
1169 | foreach (ArgumentSource source in sources)
1170 | {
1171 | IEnumerable replacement;
1172 | if (!source.GetArguments(argument, out replacement))
1173 | continue;
1174 | ae.Add(replacement);
1175 | return true;
1176 | }
1177 | return false;
1178 | }
1179 |
1180 | private static bool Unprocessed(ICollection extra, Option def, OptionContext c, string argument)
1181 | {
1182 | if (def == null)
1183 | {
1184 | extra.Add(argument);
1185 | return false;
1186 | }
1187 | c.OptionValues.Add(argument);
1188 | c.Option = def;
1189 | c.Option.Invoke(c);
1190 | return false;
1191 | }
1192 |
1193 | private readonly Regex ValueOption = new Regex(
1194 | @"^(?--|-|/)(?[^:=]+)((?[:=])(?.*))?$");
1195 |
1196 | protected bool GetOptionParts(string argument, out string flag, out string name, out string sep, out string value)
1197 | {
1198 | if (argument == null)
1199 | throw new ArgumentNullException("argument");
1200 |
1201 | flag = name = sep = value = null;
1202 | Match m = ValueOption.Match(argument);
1203 | if (!m.Success)
1204 | {
1205 | return false;
1206 | }
1207 | flag = m.Groups["flag"].Value;
1208 | name = m.Groups["name"].Value;
1209 | if (m.Groups["sep"].Success && m.Groups["value"].Success)
1210 | {
1211 | sep = m.Groups["sep"].Value;
1212 | value = m.Groups["value"].Value;
1213 | }
1214 | return true;
1215 | }
1216 |
1217 | protected virtual bool Parse(string argument, OptionContext c)
1218 | {
1219 | if (c.Option != null)
1220 | {
1221 | ParseValue(argument, c);
1222 | return true;
1223 | }
1224 |
1225 | string f, n, s, v;
1226 | if (!GetOptionParts(argument, out f, out n, out s, out v))
1227 | return false;
1228 |
1229 | Option p;
1230 | if (Contains(n))
1231 | {
1232 | p = this[n];
1233 | c.OptionName = f + n;
1234 | c.Option = p;
1235 | switch (p.OptionValueType)
1236 | {
1237 | case OptionValueType.None:
1238 | c.OptionValues.Add(n);
1239 | c.Option.Invoke(c);
1240 | break;
1241 | case OptionValueType.Optional:
1242 | case OptionValueType.Required:
1243 | ParseValue(v, c);
1244 | break;
1245 | }
1246 | return true;
1247 | }
1248 | // no match; is it a bool option?
1249 | if (ParseBool(argument, n, c))
1250 | return true;
1251 | // is it a bundled option?
1252 | if (ParseBundledValue(f, string.Concat(n + s + v), c))
1253 | return true;
1254 |
1255 | return false;
1256 | }
1257 |
1258 | private void ParseValue(string option, OptionContext c)
1259 | {
1260 | if (option != null)
1261 | foreach (string o in c.Option.ValueSeparators != null
1262 | ? option.Split(c.Option.ValueSeparators, c.Option.MaxValueCount - c.OptionValues.Count, StringSplitOptions.None)
1263 | : new string[] { option })
1264 | {
1265 | c.OptionValues.Add(o);
1266 | }
1267 | if (c.OptionValues.Count == c.Option.MaxValueCount ||
1268 | c.Option.OptionValueType == OptionValueType.Optional)
1269 | c.Option.Invoke(c);
1270 | else if (c.OptionValues.Count > c.Option.MaxValueCount)
1271 | {
1272 | throw new OptionException(localizer(string.Format(
1273 | "Error: Found {0} option values when expecting {1}.",
1274 | c.OptionValues.Count, c.Option.MaxValueCount)),
1275 | c.OptionName);
1276 | }
1277 | }
1278 |
1279 | private bool ParseBool(string option, string n, OptionContext c)
1280 | {
1281 | Option p;
1282 | string rn;
1283 | if (n.Length >= 1 && (n[n.Length - 1] == '+' || n[n.Length - 1] == '-') &&
1284 | Contains((rn = n.Substring(0, n.Length - 1))))
1285 | {
1286 | p = this[rn];
1287 | string v = n[n.Length - 1] == '+' ? option : null;
1288 | c.OptionName = option;
1289 | c.Option = p;
1290 | c.OptionValues.Add(v);
1291 | p.Invoke(c);
1292 | return true;
1293 | }
1294 | return false;
1295 | }
1296 |
1297 | private bool ParseBundledValue(string f, string n, OptionContext c)
1298 | {
1299 | if (f != "-")
1300 | return false;
1301 | for (int i = 0; i < n.Length; ++i)
1302 | {
1303 | Option p;
1304 | string opt = f + n[i].ToString();
1305 | string rn = n[i].ToString();
1306 | if (!Contains(rn))
1307 | {
1308 | if (i == 0)
1309 | return false;
1310 | throw new OptionException(string.Format(localizer(
1311 | "Cannot use unregistered option '{0}' in bundle '{1}'."), rn, f + n), null);
1312 | }
1313 | p = this[rn];
1314 | switch (p.OptionValueType)
1315 | {
1316 | case OptionValueType.None:
1317 | Invoke(c, opt, n, p);
1318 | break;
1319 | case OptionValueType.Optional:
1320 | case OptionValueType.Required:
1321 | {
1322 | string v = n.Substring(i + 1);
1323 | c.Option = p;
1324 | c.OptionName = opt;
1325 | ParseValue(v.Length != 0 ? v : null, c);
1326 | return true;
1327 | }
1328 | default:
1329 | throw new InvalidOperationException("Unknown OptionValueType: " + p.OptionValueType);
1330 | }
1331 | }
1332 | return true;
1333 | }
1334 |
1335 | private static void Invoke(OptionContext c, string name, string value, Option option)
1336 | {
1337 | c.OptionName = name;
1338 | c.Option = option;
1339 | c.OptionValues.Add(value);
1340 | option.Invoke(c);
1341 | }
1342 |
1343 | private const int OptionWidth = 29;
1344 | private const int Description_FirstWidth = 80 - OptionWidth;
1345 | private const int Description_RemWidth = 80 - OptionWidth - 2;
1346 |
1347 | static readonly string CommandHelpIndentStart = new string(' ', OptionWidth);
1348 | static readonly string CommandHelpIndentRemaining = new string(' ', OptionWidth + 2);
1349 |
1350 | public void WriteOptionDescriptions(TextWriter o)
1351 | {
1352 | foreach (Option p in this)
1353 | {
1354 | int written = 0;
1355 |
1356 | if (p.Hidden)
1357 | continue;
1358 |
1359 | Category c = p as Category;
1360 | if (c != null)
1361 | {
1362 | WriteDescription(o, p.Description, "", 80, 80);
1363 | continue;
1364 | }
1365 | CommandOption co = p as CommandOption;
1366 | if (co != null)
1367 | {
1368 | WriteCommandDescription(o, co.Command, co.CommandName);
1369 | continue;
1370 | }
1371 |
1372 | if (!WriteOptionPrototype(o, p, ref written))
1373 | continue;
1374 |
1375 | if (written < OptionWidth)
1376 | o.Write(new string(' ', OptionWidth - written));
1377 | else
1378 | {
1379 | o.WriteLine();
1380 | o.Write(new string(' ', OptionWidth));
1381 | }
1382 |
1383 | WriteDescription(o, p.Description, new string(' ', OptionWidth + 2),
1384 | Description_FirstWidth, Description_RemWidth);
1385 | }
1386 |
1387 | foreach (ArgumentSource s in sources)
1388 | {
1389 | string[] names = s.GetNames();
1390 | if (names == null || names.Length == 0)
1391 | continue;
1392 |
1393 | int written = 0;
1394 |
1395 | Write(o, ref written, " ");
1396 | Write(o, ref written, names[0]);
1397 | for (int i = 1; i < names.Length; ++i)
1398 | {
1399 | Write(o, ref written, ", ");
1400 | Write(o, ref written, names[i]);
1401 | }
1402 |
1403 | if (written < OptionWidth)
1404 | o.Write(new string(' ', OptionWidth - written));
1405 | else
1406 | {
1407 | o.WriteLine();
1408 | o.Write(new string(' ', OptionWidth));
1409 | }
1410 |
1411 | WriteDescription(o, s.Description, new string(' ', OptionWidth + 2),
1412 | Description_FirstWidth, Description_RemWidth);
1413 | }
1414 | }
1415 |
1416 | internal void WriteCommandDescription(TextWriter o, Command c, string commandName)
1417 | {
1418 | var name = new string(' ', 8) + (commandName ?? c.Name);
1419 | if (name.Length < OptionWidth - 1)
1420 | {
1421 | WriteDescription(o, name + new string(' ', OptionWidth - name.Length) + c.Help, CommandHelpIndentRemaining, 80, Description_RemWidth);
1422 | }
1423 | else
1424 | {
1425 | WriteDescription(o, name, "", 80, 80);
1426 | WriteDescription(o, CommandHelpIndentStart + c.Help, CommandHelpIndentRemaining, 80, Description_RemWidth);
1427 | }
1428 | }
1429 |
1430 | void WriteDescription(TextWriter o, string value, string prefix, int firstWidth, int remWidth)
1431 | {
1432 | bool indent = false;
1433 | foreach (string line in GetLines(localizer(GetDescription(value)), firstWidth, remWidth))
1434 | {
1435 | if (indent)
1436 | o.Write(prefix);
1437 | o.WriteLine(line);
1438 | indent = true;
1439 | }
1440 | }
1441 |
1442 | bool WriteOptionPrototype(TextWriter o, Option p, ref int written)
1443 | {
1444 | string[] names = p.Names;
1445 |
1446 | int i = GetNextOptionIndex(names, 0);
1447 | if (i == names.Length)
1448 | return false;
1449 |
1450 | if (names[i].Length == 1)
1451 | {
1452 | Write(o, ref written, " -");
1453 | Write(o, ref written, names[0]);
1454 | }
1455 | else
1456 | {
1457 | Write(o, ref written, " --");
1458 | Write(o, ref written, names[0]);
1459 | }
1460 |
1461 | for (i = GetNextOptionIndex(names, i + 1);
1462 | i < names.Length; i = GetNextOptionIndex(names, i + 1))
1463 | {
1464 | Write(o, ref written, ", ");
1465 | Write(o, ref written, names[i].Length == 1 ? "-" : "--");
1466 | Write(o, ref written, names[i]);
1467 | }
1468 |
1469 | if (p.OptionValueType == OptionValueType.Optional ||
1470 | p.OptionValueType == OptionValueType.Required)
1471 | {
1472 | if (p.OptionValueType == OptionValueType.Optional)
1473 | {
1474 | Write(o, ref written, localizer("["));
1475 | }
1476 | Write(o, ref written, localizer("=" + GetArgumentName(0, p.MaxValueCount, p.Description)));
1477 | string sep = p.ValueSeparators != null && p.ValueSeparators.Length > 0
1478 | ? p.ValueSeparators[0]
1479 | : " ";
1480 | for (int c = 1; c < p.MaxValueCount; ++c)
1481 | {
1482 | Write(o, ref written, localizer(sep + GetArgumentName(c, p.MaxValueCount, p.Description)));
1483 | }
1484 | if (p.OptionValueType == OptionValueType.Optional)
1485 | {
1486 | Write(o, ref written, localizer("]"));
1487 | }
1488 | }
1489 | return true;
1490 | }
1491 |
1492 | static int GetNextOptionIndex(string[] names, int i)
1493 | {
1494 | while (i < names.Length && names[i] == "<>")
1495 | {
1496 | ++i;
1497 | }
1498 | return i;
1499 | }
1500 |
1501 | static void Write(TextWriter o, ref int n, string s)
1502 | {
1503 | n += s.Length;
1504 | o.Write(s);
1505 | }
1506 |
1507 | static string GetArgumentName(int index, int maxIndex, string description)
1508 | {
1509 | var matches = Regex.Matches(description ?? "", @"(?<=(? 1
1520 | if (maxIndex > 1 && parts.Length == 2 &&
1521 | parts[0] == index.ToString(CultureInfo.InvariantCulture))
1522 | {
1523 | argName = parts[1];
1524 | }
1525 | }
1526 |
1527 | if (string.IsNullOrEmpty(argName))
1528 | {
1529 | argName = maxIndex == 1 ? "VALUE" : "VALUE" + (index + 1);
1530 | }
1531 | return argName;
1532 | }
1533 |
1534 | private static string GetDescription(string description)
1535 | {
1536 | if (description == null)
1537 | return string.Empty;
1538 | StringBuilder sb = new StringBuilder(description.Length);
1539 | int start = -1;
1540 | for (int i = 0; i < description.Length; ++i)
1541 | {
1542 | switch (description[i])
1543 | {
1544 | case '{':
1545 | if (i == start)
1546 | {
1547 | sb.Append('{');
1548 | start = -1;
1549 | }
1550 | else if (start < 0)
1551 | start = i + 1;
1552 | break;
1553 | case '}':
1554 | if (start < 0)
1555 | {
1556 | if ((i + 1) == description.Length || description[i + 1] != '}')
1557 | throw new InvalidOperationException("Invalid option description: " + description);
1558 | ++i;
1559 | sb.Append("}");
1560 | }
1561 | else
1562 | {
1563 | sb.Append(description.Substring(start, i - start));
1564 | start = -1;
1565 | }
1566 | break;
1567 | case ':':
1568 | if (start < 0)
1569 | goto default;
1570 | start = i + 1;
1571 | break;
1572 | default:
1573 | if (start < 0)
1574 | sb.Append(description[i]);
1575 | break;
1576 | }
1577 | }
1578 | return sb.ToString();
1579 | }
1580 |
1581 | private static IEnumerable GetLines(string description, int firstWidth, int remWidth)
1582 | {
1583 | return StringCoda.WrappedLines(description, firstWidth, remWidth);
1584 | }
1585 | }
1586 |
1587 | public class Command
1588 | {
1589 | public string Name { get; }
1590 | public string Help { get; }
1591 |
1592 | public OptionSet Options { get; set; }
1593 | public Action> Run { get; set; }
1594 |
1595 | public CommandSet CommandSet { get; internal set; }
1596 |
1597 | public Command(string name, string help = null)
1598 | {
1599 | if (string.IsNullOrEmpty(name))
1600 | throw new ArgumentNullException(nameof(name));
1601 |
1602 | Name = NormalizeCommandName(name);
1603 | Help = help;
1604 | }
1605 |
1606 | static string NormalizeCommandName(string name)
1607 | {
1608 | var value = new StringBuilder(name.Length);
1609 | var space = false;
1610 | for (int i = 0; i < name.Length; ++i)
1611 | {
1612 | if (!char.IsWhiteSpace(name, i))
1613 | {
1614 | space = false;
1615 | value.Append(name[i]);
1616 | }
1617 | else if (!space)
1618 | {
1619 | space = true;
1620 | value.Append(' ');
1621 | }
1622 | }
1623 | return value.ToString();
1624 | }
1625 |
1626 | public virtual int Invoke(IEnumerable arguments)
1627 | {
1628 | var rest = Options?.Parse(arguments) ?? arguments;
1629 | Run?.Invoke(rest);
1630 | return 0;
1631 | }
1632 | }
1633 |
1634 | class CommandOption : Option
1635 | {
1636 | public Command Command { get; }
1637 | public string CommandName { get; }
1638 |
1639 | // Prototype starts with '=' because this is an invalid prototype
1640 | // (see Option.ParsePrototype(), and thus it'll prevent Category
1641 | // instances from being accidentally used as normal options.
1642 | public CommandOption(Command command, string commandName = null, bool hidden = false)
1643 | : base("=:Command:= " + (commandName ?? command?.Name), (commandName ?? command?.Name), maxValueCount: 0, hidden: hidden)
1644 | {
1645 | if (command == null)
1646 | throw new ArgumentNullException(nameof(command));
1647 | Command = command;
1648 | CommandName = commandName ?? command.Name;
1649 | }
1650 |
1651 | protected override void OnParseComplete(OptionContext c)
1652 | {
1653 | throw new NotSupportedException("CommandOption.OnParseComplete should not be invoked.");
1654 | }
1655 | }
1656 |
1657 | class HelpOption : Option
1658 | {
1659 | Option option;
1660 | CommandSet commands;
1661 |
1662 | public HelpOption(CommandSet commands, Option d)
1663 | : base(d.Prototype, d.Description, d.MaxValueCount, d.Hidden)
1664 | {
1665 | this.commands = commands;
1666 | this.option = d;
1667 | }
1668 |
1669 | protected override void OnParseComplete(OptionContext c)
1670 | {
1671 | commands.showHelp = true;
1672 |
1673 | option?.InvokeOnParseComplete(c);
1674 | }
1675 | }
1676 |
1677 | class CommandOptionSet : OptionSet
1678 | {
1679 | CommandSet commands;
1680 |
1681 | public CommandOptionSet(CommandSet commands, MessageLocalizerConverter localizer)
1682 | : base(localizer)
1683 | {
1684 | this.commands = commands;
1685 | }
1686 |
1687 | protected override void SetItem(int index, Option item)
1688 | {
1689 | if (ShouldWrapOption(item))
1690 | {
1691 | base.SetItem(index, new HelpOption(commands, item));
1692 | return;
1693 | }
1694 | base.SetItem(index, item);
1695 | }
1696 |
1697 | bool ShouldWrapOption(Option item)
1698 | {
1699 | if (item == null)
1700 | return false;
1701 | var help = item as HelpOption;
1702 | if (help != null)
1703 | return false;
1704 | foreach (var n in item.Names)
1705 | {
1706 | if (n == "help")
1707 | return true;
1708 | }
1709 | return false;
1710 | }
1711 |
1712 | protected override void InsertItem(int index, Option item)
1713 | {
1714 | if (ShouldWrapOption(item))
1715 | {
1716 | base.InsertItem(index, new HelpOption(commands, item));
1717 | return;
1718 | }
1719 | base.InsertItem(index, item);
1720 | }
1721 | }
1722 |
1723 | public class CommandSet : KeyedCollection
1724 | {
1725 | readonly string suite;
1726 |
1727 | OptionSet options;
1728 | TextWriter outWriter;
1729 | TextWriter errorWriter;
1730 |
1731 | internal List NestedCommandSets;
1732 |
1733 | internal HelpCommand help;
1734 |
1735 | internal bool showHelp;
1736 |
1737 | internal OptionSet Options => options;
1738 |
1739 | #if !PCL || NETSTANDARD1_3
1740 | public CommandSet(string suite, MessageLocalizerConverter localizer = null)
1741 | : this(suite, Console.Out, Console.Error, localizer)
1742 | {
1743 | }
1744 | #endif
1745 |
1746 | public CommandSet(string suite, TextWriter output, TextWriter error, MessageLocalizerConverter localizer = null)
1747 | {
1748 | if (suite == null)
1749 | throw new ArgumentNullException(nameof(suite));
1750 | if (output == null)
1751 | throw new ArgumentNullException(nameof(output));
1752 | if (error == null)
1753 | throw new ArgumentNullException(nameof(error));
1754 |
1755 | this.suite = suite;
1756 | options = new CommandOptionSet(this, localizer);
1757 | outWriter = output;
1758 | errorWriter = error;
1759 | }
1760 |
1761 | public string Suite => suite;
1762 | public TextWriter Out => outWriter;
1763 | public TextWriter Error => errorWriter;
1764 | public MessageLocalizerConverter MessageLocalizer => options.MessageLocalizer;
1765 |
1766 | protected override string GetKeyForItem(Command item)
1767 | {
1768 | return item?.Name;
1769 | }
1770 |
1771 | public new CommandSet Add(Command value)
1772 | {
1773 | if (value == null)
1774 | throw new ArgumentNullException(nameof(value));
1775 | AddCommand(value);
1776 | options.Add(new CommandOption(value));
1777 | return this;
1778 | }
1779 |
1780 | void AddCommand(Command value)
1781 | {
1782 | if (value.CommandSet != null && value.CommandSet != this)
1783 | {
1784 | throw new ArgumentException("Command instances can only be added to a single CommandSet.", nameof(value));
1785 | }
1786 | value.CommandSet = this;
1787 | if (value.Options != null)
1788 | {
1789 | value.Options.MessageLocalizer = options.MessageLocalizer;
1790 | }
1791 |
1792 | base.Add(value);
1793 |
1794 | help = help ?? value as HelpCommand;
1795 | }
1796 |
1797 | public CommandSet Add(string header)
1798 | {
1799 | options.Add(header);
1800 | return this;
1801 | }
1802 |
1803 | public CommandSet Add(Option option)
1804 | {
1805 | options.Add(option);
1806 | return this;
1807 | }
1808 |
1809 | public CommandSet Add(string prototype, Action action)
1810 | {
1811 | options.Add(prototype, action);
1812 | return this;
1813 | }
1814 |
1815 | public CommandSet Add(string prototype, string description, Action action)
1816 | {
1817 | options.Add(prototype, description, action);
1818 | return this;
1819 | }
1820 |
1821 | public CommandSet Add(string prototype, string description, Action action, bool hidden)
1822 | {
1823 | options.Add(prototype, description, action, hidden);
1824 | return this;
1825 | }
1826 |
1827 | public CommandSet Add(string prototype, OptionAction action)
1828 | {
1829 | options.Add(prototype, action);
1830 | return this;
1831 | }
1832 |
1833 | public CommandSet Add(string prototype, string description, OptionAction action)
1834 | {
1835 | options.Add(prototype, description, action);
1836 | return this;
1837 | }
1838 |
1839 | public CommandSet Add(string prototype, string description, OptionAction action, bool hidden)
1840 | {
1841 | options.Add(prototype, description, action, hidden);
1842 | return this;
1843 | }
1844 |
1845 | public CommandSet Add(string prototype, Action action)
1846 | {
1847 | options.Add(prototype, null, action);
1848 | return this;
1849 | }
1850 |
1851 | public CommandSet Add(string prototype, string description, Action action)
1852 | {
1853 | options.Add(prototype, description, action);
1854 | return this;
1855 | }
1856 |
1857 | public CommandSet Add(string prototype, OptionAction action)
1858 | {
1859 | options.Add(prototype, action);
1860 | return this;
1861 | }
1862 |
1863 | public CommandSet Add(string prototype, string description, OptionAction action)
1864 | {
1865 | options.Add(prototype, description, action);
1866 | return this;
1867 | }
1868 |
1869 | public CommandSet Add(ArgumentSource source)
1870 | {
1871 | options.Add(source);
1872 | return this;
1873 | }
1874 |
1875 | public CommandSet Add(CommandSet nestedCommands)
1876 | {
1877 | if (nestedCommands == null)
1878 | throw new ArgumentNullException(nameof(nestedCommands));
1879 |
1880 | if (NestedCommandSets == null)
1881 | {
1882 | NestedCommandSets = new List();
1883 | }
1884 |
1885 | if (!AlreadyAdded(nestedCommands))
1886 | {
1887 | NestedCommandSets.Add(nestedCommands);
1888 | foreach (var o in nestedCommands.options)
1889 | {
1890 | if (o is CommandOption c)
1891 | {
1892 | options.Add(new CommandOption(c.Command, $"{nestedCommands.Suite} {c.CommandName}"));
1893 | }
1894 | else
1895 | {
1896 | options.Add(o);
1897 | }
1898 | }
1899 | }
1900 |
1901 | nestedCommands.options = this.options;
1902 | nestedCommands.outWriter = this.outWriter;
1903 | nestedCommands.errorWriter = this.errorWriter;
1904 |
1905 | return this;
1906 | }
1907 |
1908 | bool AlreadyAdded(CommandSet value)
1909 | {
1910 | if (value == this)
1911 | return true;
1912 | if (NestedCommandSets == null)
1913 | return false;
1914 | foreach (var nc in NestedCommandSets)
1915 | {
1916 | if (nc.AlreadyAdded(value))
1917 | return true;
1918 | }
1919 | return false;
1920 | }
1921 |
1922 | public IEnumerable GetCompletions(string prefix = null)
1923 | {
1924 | string rest;
1925 | ExtractToken(ref prefix, out rest);
1926 |
1927 | foreach (var command in this)
1928 | {
1929 | if (command.Name.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
1930 | {
1931 | yield return command.Name;
1932 | }
1933 | }
1934 |
1935 | if (NestedCommandSets == null)
1936 | yield break;
1937 |
1938 | foreach (var subset in NestedCommandSets)
1939 | {
1940 | if (subset.Suite.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
1941 | {
1942 | foreach (var c in subset.GetCompletions(rest))
1943 | {
1944 | yield return $"{subset.Suite} {c}";
1945 | }
1946 | }
1947 | }
1948 | }
1949 |
1950 | static void ExtractToken(ref string input, out string rest)
1951 | {
1952 | rest = "";
1953 | input = input ?? "";
1954 |
1955 | int top = input.Length;
1956 | for (int i = 0; i < top; i++)
1957 | {
1958 | if (char.IsWhiteSpace(input[i]))
1959 | continue;
1960 |
1961 | for (int j = i; j < top; j++)
1962 | {
1963 | if (char.IsWhiteSpace(input[j]))
1964 | {
1965 | rest = input.Substring(j).Trim();
1966 | input = input.Substring(i, j).Trim();
1967 | return;
1968 | }
1969 | }
1970 | rest = "";
1971 | if (i != 0)
1972 | input = input.Substring(i).Trim();
1973 | return;
1974 | }
1975 | }
1976 |
1977 | public int Run(IEnumerable arguments)
1978 | {
1979 | if (arguments == null)
1980 | throw new ArgumentNullException(nameof(arguments));
1981 |
1982 | this.showHelp = false;
1983 | if (help == null)
1984 | {
1985 | help = new HelpCommand();
1986 | AddCommand(help);
1987 | }
1988 | Action setHelp = v => showHelp = v != null;
1989 | if (!options.Contains("help"))
1990 | {
1991 | options.Add("help", "", setHelp, hidden: true);
1992 | }
1993 | if (!options.Contains("?"))
1994 | {
1995 | options.Add("?", "", setHelp, hidden: true);
1996 | }
1997 | var extra = options.Parse(arguments);
1998 | if (extra.Count == 0)
1999 | {
2000 | if (showHelp)
2001 | {
2002 | return help.Invoke(extra);
2003 | }
2004 | Out.WriteLine(options.MessageLocalizer($"Use `{Suite} help` for usage."));
2005 | return 1;
2006 | }
2007 | var command = GetCommand(extra);
2008 | if (command == null)
2009 | {
2010 | help.WriteUnknownCommand(extra[0]);
2011 | return 1;
2012 | }
2013 | if (showHelp)
2014 | {
2015 | if (command.Options?.Contains("help") ?? true)
2016 | {
2017 | extra.Add("--help");
2018 | return command.Invoke(extra);
2019 | }
2020 | command.Options.WriteOptionDescriptions(Out);
2021 | return 0;
2022 | }
2023 | return command.Invoke(extra);
2024 | }
2025 |
2026 | internal Command GetCommand(List extra)
2027 | {
2028 | return TryGetLocalCommand(extra) ?? TryGetNestedCommand(extra);
2029 | }
2030 |
2031 | Command TryGetLocalCommand(List extra)
2032 | {
2033 | var name = extra[0];
2034 | if (Contains(name))
2035 | {
2036 | extra.RemoveAt(0);
2037 | return this[name];
2038 | }
2039 | for (int i = 1; i < extra.Count; ++i)
2040 | {
2041 | name = name + " " + extra[i];
2042 | if (!Contains(name))
2043 | continue;
2044 | extra.RemoveRange(0, i + 1);
2045 | return this[name];
2046 | }
2047 | return null;
2048 | }
2049 |
2050 | Command TryGetNestedCommand(List extra)
2051 | {
2052 | if (NestedCommandSets == null)
2053 | return null;
2054 |
2055 | var nestedCommands = NestedCommandSets.Find(c => c.Suite == extra[0]);
2056 | if (nestedCommands == null)
2057 | return null;
2058 |
2059 | var extraCopy = new List(extra);
2060 | extraCopy.RemoveAt(0);
2061 | if (extraCopy.Count == 0)
2062 | return null;
2063 |
2064 | var command = nestedCommands.GetCommand(extraCopy);
2065 | if (command != null)
2066 | {
2067 | extra.Clear();
2068 | extra.AddRange(extraCopy);
2069 | return command;
2070 | }
2071 | return null;
2072 | }
2073 | }
2074 |
2075 | public class HelpCommand : Command
2076 | {
2077 | public HelpCommand()
2078 | : base("help", help: "Show this message and exit")
2079 | {
2080 | }
2081 |
2082 | public override int Invoke(IEnumerable arguments)
2083 | {
2084 | var extra = new List(arguments ?? new string[0]);
2085 | var _ = CommandSet.Options.MessageLocalizer;
2086 | if (extra.Count == 0)
2087 | {
2088 | CommandSet.Options.WriteOptionDescriptions(CommandSet.Out);
2089 | return 0;
2090 | }
2091 | var command = CommandSet.GetCommand(extra);
2092 | if (command == this || extra.Contains("--help"))
2093 | {
2094 | CommandSet.Out.WriteLine(_($"Usage: {CommandSet.Suite} COMMAND [OPTIONS]"));
2095 | CommandSet.Out.WriteLine(_($"Use `{CommandSet.Suite} help COMMAND` for help on a specific command."));
2096 | CommandSet.Out.WriteLine();
2097 | CommandSet.Out.WriteLine(_($"Available commands:"));
2098 | CommandSet.Out.WriteLine();
2099 | var commands = GetCommands();
2100 | commands.Sort((x, y) => string.Compare(x.Key, y.Key, StringComparison.OrdinalIgnoreCase));
2101 | foreach (var c in commands)
2102 | {
2103 | if (c.Key == "help")
2104 | {
2105 | continue;
2106 | }
2107 | CommandSet.Options.WriteCommandDescription(CommandSet.Out, c.Value, c.Key);
2108 | }
2109 | CommandSet.Options.WriteCommandDescription(CommandSet.Out, CommandSet.help, "help");
2110 | return 0;
2111 | }
2112 | if (command == null)
2113 | {
2114 | WriteUnknownCommand(extra[0]);
2115 | return 1;
2116 | }
2117 | if (command.Options != null)
2118 | {
2119 | command.Options.WriteOptionDescriptions(CommandSet.Out);
2120 | return 0;
2121 | }
2122 | return command.Invoke(new[] { "--help" });
2123 | }
2124 |
2125 | List> GetCommands()
2126 | {
2127 | var commands = new List>();
2128 |
2129 | foreach (var c in CommandSet)
2130 | {
2131 | commands.Add(new KeyValuePair(c.Name, c));
2132 | }
2133 |
2134 | if (CommandSet.NestedCommandSets == null)
2135 | return commands;
2136 |
2137 | foreach (var nc in CommandSet.NestedCommandSets)
2138 | {
2139 | AddNestedCommands(commands, "", nc);
2140 | }
2141 |
2142 | return commands;
2143 | }
2144 |
2145 | void AddNestedCommands(List> commands, string outer, CommandSet value)
2146 | {
2147 | foreach (var v in value)
2148 | {
2149 | commands.Add(new KeyValuePair($"{outer}{value.Suite} {v.Name}", v));
2150 | }
2151 | if (value.NestedCommandSets == null)
2152 | return;
2153 | foreach (var nc in value.NestedCommandSets)
2154 | {
2155 | AddNestedCommands(commands, $"{outer}{value.Suite} ", nc);
2156 | }
2157 | }
2158 |
2159 | internal void WriteUnknownCommand(string unknownCommand)
2160 | {
2161 | CommandSet.Error.WriteLine(CommandSet.Options.MessageLocalizer($"{CommandSet.Suite}: Unknown command: {unknownCommand}"));
2162 | CommandSet.Error.WriteLine(CommandSet.Options.MessageLocalizer($"{CommandSet.Suite}: Use `{CommandSet.Suite} help` for usage."));
2163 | }
2164 | }
2165 | }
2166 |
2167 |
--------------------------------------------------------------------------------