diff --git a/README.md b/README.md index 737a85d..20defe0 100644 --- a/README.md +++ b/README.md @@ -110,49 +110,45 @@ static void Main(string[] args) @"e:\speficic-samples\sample1.exe" // file }; - // Initialize yara context - using (YaraContext ctx = new YaraContext()) + + // Compile list of yara rules + CompiledRules rules = null; + using (var compiler = new Compiler()) { - // Compile list of yara rules - CompiledRules rules = null; - using (var compiler = new Compiler()) + foreach (var yara in ruleFiles) { - foreach (var yara in ruleFiles) - { - compiler.AddRuleFile(yara); - } + compiler.AddRuleFile(yara); + } - rules = compiler.Compile(); + rules = compiler.Compile(); - Console.WriteLine($"* Compiled"); - } + Console.WriteLine($"* Compiled"); + } - if (rules != null) - { - // Initialize the scanner - var scanner = new Scanner(); + if (rules != null) + { + // Initialize the scanner + var scanner = new Scanner(); - // Go through all samples - foreach (var sample in samples) + // Go through all samples + foreach (var sample in samples) + { + // If item is file, scan the file + if (File.Exists(sample)) { - // If item is file, scan the file - if (File.Exists(sample)) - { - ScanFile(scanner, sample, rules); - } - // If item is directory, scan the directory - else + ScanFile(scanner, sample, rules); + } + // If item is directory, scan the directory + else + { + if (Directory.Exists(sample)) { - if (Directory.Exists(sample)) - { - DirectoryInfo dirInfo = new DirectoryInfo(sample); + DirectoryInfo dirInfo = new DirectoryInfo(sample); - foreach (FileInfo fi in dirInfo.EnumerateFiles("*", SearchOption.AllDirectories)) - ScanFile(scanner, fi.FullName, rules); - } + foreach (FileInfo fi in dirInfo.EnumerateFiles("*", SearchOption.AllDirectories)) + ScanFile(scanner, fi.FullName, rules); } } - } } diff --git a/Samples/YaraInteractive/Program.cs b/Samples/YaraInteractive/Program.cs index de3413c..beb0db4 100644 --- a/Samples/YaraInteractive/Program.cs +++ b/Samples/YaraInteractive/Program.cs @@ -7,26 +7,22 @@ class Program { static void Main(string[] args) { - using (var ctx = new YaraContext()) - { - Console.WriteLine("# Welcome to Yara Interactive Console..."); + Console.WriteLine("# Welcome to Yara Interactive Console..."); - while (true) - { - Console.Write("> "); + while (true) + { + Console.Write("> "); - string command = Console.ReadLine(); + string command = Console.ReadLine(); - if (string.IsNullOrWhiteSpace(command)) - continue; + if (string.IsNullOrWhiteSpace(command)) + continue; - bool isManagedCmd = CmdHandler.ExecuteCmd(command); + bool isManagedCmd = CmdHandler.ExecuteCmd(command); - if (!isManagedCmd) - Console.WriteLine(":Err: Unknown command..."); - } + if (!isManagedCmd) + Console.WriteLine(":Err: Unknown command..."); } } - } } diff --git a/UnitTests/dnYara.UnitTests/ScanTests.cs b/UnitTests/dnYara.UnitTests/ScanTests.cs index 17fd9d2..9aba8d8 100644 --- a/UnitTests/dnYara.UnitTests/ScanTests.cs +++ b/UnitTests/dnYara.UnitTests/ScanTests.cs @@ -11,120 +11,106 @@ public class ScanTests [Fact] public void CheckStringMatchTest() { - // Initialize yara context - using (YaraContext ctx = new YaraContext()) - { - // Compile yara rules - CompiledRules rules = null; + // Compile yara rules + CompiledRules rules = null; - using (var compiler = new Compiler()) - { - compiler.AddRuleString("rule foo: bar {strings: $a = \"lmn\" condition: $a}"); + using (var compiler = new Compiler()) + { + compiler.AddRuleString("rule foo: bar {strings: $a = \"lmn\" condition: $a}"); - rules = compiler.Compile(); - } + rules = compiler.Compile(); + } - if (rules != null) - { - // Initialize the scanner - var scanner = new Scanner(); + if (rules != null) + { + // Initialize the scanner + var scanner = new Scanner(); - List scanResult = scanner.ScanString("abcdefgjiklmnoprstuvwxyz", rules); + List scanResult = scanner.ScanString("abcdefgjiklmnoprstuvwxyz", rules); - Assert.True(scanResult.Count > 0); - } + Assert.True(scanResult.Count > 0); } } [Fact] public void CheckStringNotMatchTest() { - // Initialize yara context - using (YaraContext ctx = new YaraContext()) - { - // Compile yara rules - CompiledRules rules = null; + // Compile yara rules + CompiledRules rules = null; - using (var compiler = new Compiler()) - { - compiler.AddRuleString("rule foo: bar {strings: $a = \"nml\" condition: $a}"); + using (var compiler = new Compiler()) + { + compiler.AddRuleString("rule foo: bar {strings: $a = \"nml\" condition: $a}"); - rules = compiler.Compile(); - } + rules = compiler.Compile(); + } - if (rules != null) - { - // Initialize the scanner - var scanner = new Scanner(); - List scanResult = scanner.ScanString("abcdefgjiklmnoprstuvwxyz", rules); + if (rules != null) + { + // Initialize the scanner + var scanner = new Scanner(); + List scanResult = scanner.ScanString("abcdefgjiklmnoprstuvwxyz", rules); - Assert.True(scanResult.Count == 0); - } + Assert.True(scanResult.Count == 0); } } [Fact] public void CheckMemoryMatchTest() { - // Initialize yara context - using (YaraContext ctx = new YaraContext()) - { - // Compile yara rules - CompiledRules rules = null; + // Compile yara rules + CompiledRules rules = null; - using (var compiler = new Compiler()) - { - compiler.AddRuleString("rule foo: bar {strings: $a = \"lmn\" condition: $a}"); + using (var compiler = new Compiler()) + { + compiler.AddRuleString("rule foo: bar {strings: $a = \"lmn\" condition: $a}"); - rules = compiler.Compile(); - } + rules = compiler.Compile(); + } - if (rules != null) - { - // Initialize the scanner - var scanner = new Scanner(); + if (rules != null) + { + // Initialize the scanner + var scanner = new Scanner(); - Encoding encoding = Encoding.ASCII; + Encoding encoding = Encoding.ASCII; - byte[] buffer = encoding.GetBytes("abcdefgjiklmnoprstuvwxyz"); + byte[] buffer = encoding.GetBytes("abcdefgjiklmnoprstuvwxyz"); - List scanResult = scanner.ScanMemory(ref buffer, rules); + List scanResult = scanner.ScanMemory(ref buffer, rules); - Assert.True(scanResult.Count > 0); - } + Assert.True(scanResult.Count > 0); } + } [Fact] public void CheckMemoryNotMatchTest() { - // Initialize yara context - using (YaraContext ctx = new YaraContext()) - { - // Compile yara rules - CompiledRules rules = null; + // Compile yara rules + CompiledRules rules = null; - using (var compiler = new Compiler()) - { - compiler.AddRuleString("rule foo: bar {strings: $a = \"nml\" condition: $a}"); + using (var compiler = new Compiler()) + { + compiler.AddRuleString("rule foo: bar {strings: $a = \"nml\" condition: $a}"); - rules = compiler.Compile(); - } + rules = compiler.Compile(); + } - if (rules != null) - { - // Initialize the scanner - var scanner = new Scanner(); + if (rules != null) + { + // Initialize the scanner + var scanner = new Scanner(); - Encoding encoding = Encoding.ASCII; + Encoding encoding = Encoding.ASCII; - byte[] buffer = encoding.GetBytes("abcdefgjiklmnoprstuvwxyz"); + byte[] buffer = encoding.GetBytes("abcdefgjiklmnoprstuvwxyz"); - List scanResult = scanner.ScanMemory(ref buffer, rules); + List scanResult = scanner.ScanMemory(ref buffer, rules); - Assert.True(scanResult.Count == 0); - } + Assert.True(scanResult.Count == 0); } + } [Fact] @@ -141,71 +127,64 @@ public void CheckIterateRulesTest() $a }"; - // Initialize yara context - using (YaraContext ctx = new YaraContext()) + // Compile yara rules + CompiledRules rules = null; + + using (var compiler = new Compiler()) { - // Compile yara rules - CompiledRules rules = null; - - using (var compiler = new Compiler()) - { - compiler.AddRuleString(ruleText); - - rules = compiler.Compile(); - } - - if (rules != null) - { - var rule = rules.Rules.ToList()[0]; - Assert.NotEmpty(rules.Rules); - Assert.Equal("foo", rule.Identifier); - Assert.Equal("bar", rule.Tags[0]); - Assert.Equal(true, rule.Metas["bool_meta"]); - Assert.Equal((long)10, rule.Metas["int_meta"]); - Assert.Equal("what a long, drawn-out thing this is!", rule.Metas["string_meta"]); - } + compiler.AddRuleString(ruleText); + + rules = compiler.Compile(); } + + if (rules != null) + { + var rule = rules.Rules.ToList()[0]; + Assert.NotEmpty(rules.Rules); + Assert.Equal("foo", rule.Identifier); + Assert.Equal("bar", rule.Tags[0]); + Assert.Equal(true, rule.Metas["bool_meta"]); + Assert.Equal((long)10, rule.Metas["int_meta"]); + Assert.Equal("what a long, drawn-out thing this is!", rule.Metas["string_meta"]); + } + } [Fact] public void CheckSaveLoadRuleTest() { - // Initialize yara context - using (YaraContext ctx = new YaraContext()) + using (var compiler = new Compiler()) { - using (var compiler = new Compiler()) - { - compiler.AddRuleString("rule foo: bar {strings: $a = \"lmn\" condition: $a}"); - CompiledRules compiledRules = compiler.Compile(); - Assert.True(compiledRules.RuleCount == 1); + compiler.AddRuleString("rule foo: bar {strings: $a = \"lmn\" condition: $a}"); + CompiledRules compiledRules = compiler.Compile(); + Assert.True(compiledRules.RuleCount == 1); - Encoding encoding = Encoding.ASCII; - byte[] buffer = encoding.GetBytes("abcdefgjiklmnoprstuvwxyz"); + Encoding encoding = Encoding.ASCII; + byte[] buffer = encoding.GetBytes("abcdefgjiklmnoprstuvwxyz"); - // Initialize the scanner - var scanner = new Scanner(); + // Initialize the scanner + var scanner = new Scanner(); - List compiledScanResults = scanner.ScanMemory(ref buffer, compiledRules); - Assert.True(compiledScanResults.Count == 1); - Assert.Equal("foo", compiledScanResults[0].MatchingRule.Identifier); + List compiledScanResults = scanner.ScanMemory(ref buffer, compiledRules); + Assert.True(compiledScanResults.Count == 1); + Assert.Equal("foo", compiledScanResults[0].MatchingRule.Identifier); - //save the rule to disk - string tempfile = System.IO.Path.GetTempFileName(); - bool saved = compiledRules.Save(tempfile); - Assert.True(saved); + //save the rule to disk + string tempfile = System.IO.Path.GetTempFileName(); + bool saved = compiledRules.Save(tempfile); + Assert.True(saved); - //load the saved rule to a new ruleset - CompiledRules loadedRules = new CompiledRules(tempfile); + //load the saved rule to a new ruleset + CompiledRules loadedRules = new CompiledRules(tempfile); - List loadedScanResults = scanner.ScanMemory(ref buffer, loadedRules); + List loadedScanResults = scanner.ScanMemory(ref buffer, loadedRules); - Assert.True(loadedScanResults.Count == 1); - Assert.Equal("foo", loadedScanResults[0].MatchingRule.Identifier); + Assert.True(loadedScanResults.Count == 1); + Assert.Equal("foo", loadedScanResults[0].MatchingRule.Identifier); - System.IO.File.Delete(tempfile); - } + System.IO.File.Delete(tempfile); } } @@ -213,40 +192,33 @@ public void CheckSaveLoadRuleTest() [Fact] public void CheckExternalVariableRuleTest() { - // Initialize yara context - using (YaraContext ctx = new YaraContext()) + using (var compiler = new Compiler()) { - using (var compiler = new Compiler()) - { - //must declare this or the compiler will complain it doesn't exist when a rule references it - compiler.DeclareExternalStringVariable("filename"); - - //declare rule with an external variable available - compiler.AddRuleString("rule foo: bar {strings: $a = \"lmn\" condition: $a and filename matches /\\.txt$/is}"); - CompiledRules compiledRules = compiler.Compile(); - - Assert.True(compiledRules.RuleCount == 1); - - Encoding encoding = Encoding.ASCII; - byte[] buffer = encoding.GetBytes("abcdefgjiklmnoprstuvwxyz"); + //must declare this or the compiler will complain it doesn't exist when a rule references it + compiler.DeclareExternalStringVariable("filename"); - // Initialize a customscanner we can add variables to - var scanner = new CustomScanner(compiledRules); + //declare rule with an external variable available + compiler.AddRuleString("rule foo: bar {strings: $a = \"lmn\" condition: $a and filename matches /\\.txt$/is}"); + CompiledRules compiledRules = compiler.Compile(); - ExternalVariables externalVariables = new ExternalVariables(); - externalVariables.StringVariables.Add("filename", "Alphabet.txt"); + Assert.True(compiledRules.RuleCount == 1); - List compiledScanResults = scanner.ScanMemory(ref buffer, externalVariables); + Encoding encoding = Encoding.ASCII; + byte[] buffer = encoding.GetBytes("abcdefgjiklmnoprstuvwxyz"); - Assert.True(compiledScanResults.Count == 1); - Assert.Equal("foo", compiledScanResults[0].MatchingRule.Identifier); + // Initialize a customscanner we can add variables to + var scanner = new CustomScanner(compiledRules); - //release before falling out of the yara context - scanner.Release(); - } + ExternalVariables externalVariables = new ExternalVariables(); + externalVariables.StringVariables.Add("filename", "Alphabet.txt"); + List compiledScanResults = scanner.ScanMemory(ref buffer, externalVariables); + Assert.True(compiledScanResults.Count == 1); + Assert.Equal("foo", compiledScanResults[0].MatchingRule.Identifier); + //release before falling out of the yara context + scanner.Release(); } } } diff --git a/dnYara/Compiler.cs b/dnYara/Compiler.cs index e2f797a..1d4fcef 100644 --- a/dnYara/Compiler.cs +++ b/dnYara/Compiler.cs @@ -21,6 +21,8 @@ public class Compiler public Compiler() { + YaraContext.Instance.EnsureInitialized(); + ErrorUtility.ThrowOnError(Methods.yr_compiler_create(out compilerPtr)); compilationErrors = new List(); @@ -49,6 +51,8 @@ public void Dispose() public void AddRuleFile(string path) { + YaraContext.Instance.EnsureInitialized(); + compilationErrors.Clear(); try @@ -81,6 +85,8 @@ public void AddRuleFile(string path) public void AddRuleString(string rule) { + YaraContext.Instance.EnsureInitialized(); + compilationErrors.Clear(); var errors = Methods.yr_compiler_add_string( @@ -94,6 +100,8 @@ public void AddRuleString(string rule) public void DeclareExternalStringVariable(string name, string defaultValue = "") { + YaraContext.Instance.EnsureInitialized(); + var errors = Methods.yr_compiler_define_string_variable( compilerPtr, name, @@ -105,6 +113,8 @@ public void DeclareExternalStringVariable(string name, string defaultValue = "") public void DeclareExternalIntVariable(string name, long defaultValue = 0) { + YaraContext.Instance.EnsureInitialized(); + var errors = Methods.yr_scanner_define_integer_variable( compilerPtr, name, @@ -116,6 +126,8 @@ public void DeclareExternalIntVariable(string name, long defaultValue = 0) public void DeclareExternalFloatVariable(string name, double defaultValue = 0) { + YaraContext.Instance.EnsureInitialized(); + var errors = Methods.yr_scanner_define_float_variable( compilerPtr, name, @@ -127,6 +139,8 @@ public void DeclareExternalFloatVariable(string name, double defaultValue = 0) public void DeclareExternalBooleanVariable(string name, bool defaultValue = false) { + YaraContext.Instance.EnsureInitialized(); + var errors = Methods.yr_compiler_define_boolean_variable( compilerPtr, name, @@ -138,6 +152,8 @@ public void DeclareExternalBooleanVariable(string name, bool defaultValue = fals public CompiledRules Compile() { + YaraContext.Instance.EnsureInitialized(); + IntPtr rulesPtr = new IntPtr(); ErrorUtility.ThrowOnError( @@ -148,6 +164,8 @@ public CompiledRules Compile() public static CompiledRules CompileRulesFile(string path) { + YaraContext.Instance.EnsureInitialized(); + Compiler yc = new Compiler(); yc.AddRuleFile(path); @@ -156,6 +174,8 @@ public static CompiledRules CompileRulesFile(string path) public static CompiledRules CompileRulesString(string rule) { + YaraContext.Instance.EnsureInitialized(); + Compiler yc = new Compiler(); yc.AddRuleString(rule); diff --git a/dnYara/CustomScanner.cs b/dnYara/CustomScanner.cs index a5beb63..bf82c9e 100644 --- a/dnYara/CustomScanner.cs +++ b/dnYara/CustomScanner.cs @@ -30,12 +30,16 @@ public CustomScanner(CompiledRules rules, int flags = 0, int timeout = YR_TIMEOU //must be called before the context is destroyed (ie: falling out of a using()) public void Release() { + YaraContext.Instance.EnsureInitialized(); + Methods.yr_scanner_destroy(customScannerPtr); customScannerPtr = IntPtr.Zero; } private void CreateNewScanner(CompiledRules rules, YR_SCAN_FLAGS flags, int timeout) { + YaraContext.Instance.EnsureInitialized(); + ErrorUtility.ThrowOnError( Methods.yr_scanner_create(rules.BasePtr, out IntPtr newScanner)); @@ -45,8 +49,17 @@ private void CreateNewScanner(CompiledRules rules, YR_SCAN_FLAGS flags, int time SetTimeout(timeout); } - public virtual void SetFlags(YR_SCAN_FLAGS flags) => Methods.yr_scanner_set_flags(customScannerPtr, (int)flags); - public virtual void SetTimeout(int timeout) => Methods.yr_scanner_set_timeout(customScannerPtr, timeout); + public virtual void SetFlags(YR_SCAN_FLAGS flags) + { + YaraContext.Instance.EnsureInitialized(); + Methods.yr_scanner_set_flags(customScannerPtr, (int)flags); + } + + public virtual void SetTimeout(int timeout) + { + YaraContext.Instance.EnsureInitialized(); + Methods.yr_scanner_set_timeout(customScannerPtr, timeout); + } private bool TestAllVariablesUnique(ExternalVariables externalVariables, out string duplicatesListString) { @@ -73,6 +86,8 @@ private bool TestAllVariablesUnique(ExternalVariables externalVariables, out str private void SetExternalVariables(ExternalVariables externalVariables) { + YaraContext.Instance.EnsureInitialized(); + if (!TestAllVariablesUnique(externalVariables, out string duplicates)) { throw new InvalidDataException("Duplicate external variable names declared: " + duplicates); @@ -99,6 +114,8 @@ private void SetExternalVariables(ExternalVariables externalVariables) //a new scanner should be created if it's imporant for them not to exist private void ClearExternalVariables(ExternalVariables externalVariables) { + YaraContext.Instance.EnsureInitialized(); + foreach (KeyValuePair variable in externalVariables.StringVariables) ErrorUtility.ThrowOnError( Methods.yr_scanner_define_string_variable(customScannerPtr, variable.Key, String.Empty)); @@ -119,6 +136,8 @@ private void ClearExternalVariables(ExternalVariables externalVariables) public virtual List ScanFile(string path, ExternalVariables externalVariables) { + YaraContext.Instance.EnsureInitialized(); + if (customScannerPtr == IntPtr.Zero) throw new NullReferenceException("Custom Scanner has not been initialised"); @@ -212,6 +231,8 @@ public virtual List ScanMemory( ExternalVariables externalVariables, YR_SCAN_FLAGS flags) { + YaraContext.Instance.EnsureInitialized(); + YR_CALLBACK_FUNC scannerCallback = new YR_CALLBACK_FUNC(HandleMessage); List scanResults = new List(); GCHandleHandler resultsHandle = new GCHandleHandler(scanResults); diff --git a/dnYara/Exceptions/YaraContextNotInitializedException.cs b/dnYara/Exceptions/YaraContextNotInitializedException.cs new file mode 100644 index 0000000..4e89bc8 --- /dev/null +++ b/dnYara/Exceptions/YaraContextNotInitializedException.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace dnYara.Exceptions +{ + public class YaraContextNotInitializedException : InvalidOperationException + { + public YaraContextNotInitializedException() + : base("YaraContext has been cleaned up.") + { + } + } +} diff --git a/dnYara/ScanResult.cs b/dnYara/ScanResult.cs index 5145bb3..304f0f5 100644 --- a/dnYara/ScanResult.cs +++ b/dnYara/ScanResult.cs @@ -5,7 +5,6 @@ namespace dnYara { - public class ScanResult { public Rule MatchingRule; diff --git a/dnYara/Scanner.cs b/dnYara/Scanner.cs index 110da11..d2c6394 100644 --- a/dnYara/Scanner.cs +++ b/dnYara/Scanner.cs @@ -15,6 +15,7 @@ public class Scanner public Scanner() { + YaraContext.Instance.EnsureInitialized(); callbackPtr = new YR_CALLBACK_FUNC(HandleMessage); } @@ -31,6 +32,8 @@ public virtual List ScanFile( if (!File.Exists(path)) throw new FileNotFoundException(path); + YaraContext.Instance.EnsureInitialized(); + var results = new List(); var nativePath = path; @@ -60,6 +63,8 @@ public virtual List ScanProcess( CompiledRules rules, YR_SCAN_FLAGS flags) { + YaraContext.Instance.EnsureInitialized(); + var results = new List(); GCHandleHandler resultsHandle = new GCHandleHandler(results); @@ -144,6 +149,8 @@ public virtual List ScanMemory( CompiledRules rules, YR_SCAN_FLAGS flags) { + YaraContext.Instance.EnsureInitialized(); + var results = new List(); GCHandleHandler resultsHandle = new GCHandleHandler(results); diff --git a/dnYara/YaraContext.cs b/dnYara/YaraContext.cs index 59473dd..0aefd2c 100644 --- a/dnYara/YaraContext.cs +++ b/dnYara/YaraContext.cs @@ -6,23 +6,50 @@ namespace dnYara /// /// RAII wrapper for Yara context, must be used with 'using' keyword. /// - public sealed class YaraContext - : IDisposable + public sealed class YaraContext { - public YaraContext() + private static readonly Lazy lazy = + new Lazy(() => new YaraContext()); + + public static YaraContext Instance { get { return lazy.Value; } } + + private bool isCleanedUp = false; + + private YaraContext() { ErrorUtility.ThrowOnError(Methods.yr_initialize()); } ~YaraContext() { - Dispose(); + Cleanup(); + } + + public void Cleanup() + { + if (!isCleanedUp) + { + Methods.yr_finalize(); + isCleanedUp = true; + } } - public void Dispose() + public void EnsureInitialized() { - Methods.yr_finalize(); + if (isCleanedUp) + { + throw new InvalidOperationException("YaraContext has been cleaned up."); + } + } + + public void Reinitialize() + { + if (isCleanedUp) + { + ErrorUtility.ThrowOnError(Methods.yr_initialize()); + isCleanedUp = false; + } } } - + } diff --git a/dnYara/dnYara.csproj b/dnYara/dnYara.csproj index dd6f1e9..b9cf0f6 100644 --- a/dnYara/dnYara.csproj +++ b/dnYara/dnYara.csproj @@ -4,10 +4,10 @@ netstandard2.0 7.3 true - Sylvain B. for Airbus CERT + Sylvain Bruyere, Airbus CERT Airbus CERT - 2.1.0.0 - 2.1.0.0 + 2.2.0.0 + 2.2.0.0 dnYara (for Yara v4.0.0 to v4.1.1) is a .Net Standard wrapper for the native Yara library. It lets you use all the features of Yara that the native C library exposes ! It is built in C# / .Net Standard to ensure compatibility with a maximum of .Net frameworks, and to be cross-platform. @@ -18,11 +18,12 @@ You can also compile the library yourself for your platform with cmake (instruct https://github.com/airbus-cert/dnYara https://github.com/airbus-cert/dnYara yara, DFIR, detection, malware - 2.1.0.0 + 2.2.0.0 true Apache-2.0 dnYara dnYara (for Yara 4.0.0 to 4.1.1) + README.md @@ -30,6 +31,13 @@ You can also compile the library yourself for your platform with cmake (instruct TRACE; + + + True + \ + + +