I am developing a custom transport agent in C#.I am using microsoft exhange server 2010 for this. Whenever an emails comes i need to decide if it should allow to go or not on the basis of some condition. If it should not be allowed to go i store whole email(.eml) as blob in database.Now i want not to use database for holding rejected emails. i want to hold those emails in some mailbox. I want to know if it is possible to redirect an email to mailbox?
public class FIMFactory : RoutingAgentFactory
{
public override RoutingAgent CreateAgent(SmtpServer server)
{
return new FIMAgent(server);
}
}
public class FIMAgent : RoutingAgent
{
private readonly SmtpServer _server;
public FIMAgent(SmtpServer server)
{
_server = server ?? throw new ArgumentNullException(nameof(server));
OnResolvedMessage += FIMAgent_OnResolvedMessage;
}
private void FIMAgent_OnResolvedMessage(ResolvedMessageEventSource source, QueuedMessageEventArgs e)
{
var mi = e.MailItem;
var emailMessage = EmailMessage.Create(BodyFormat.Text, false, "utf-8");
emailMessage.Sender = new EmailRecipient(mi.Message.Sender.DisplayName, mi.Message.Sender.SmtpAddress);
emailMessage.To.Add(new EmailRecipient(mi.Message.Sender.DisplayName, mi.Message.Sender.SmtpAddress));
emailMessage.Subject = "...";
using (var stream1 = emailMessage.Body.GetContentWriteStream())
using (var sw = new StreamWriter(stream1, new UTF8Encoding(false)))
{
sw.WriteLine("...");
}
_server.SubmitMessage(emailMessage);
}
}
Related
ModbusFactory modbus = new ModbusFactory();
TcpClient tcpClient = new TcpClient("127.0.0.1", 5000);
var master = modbus.CreateMaster(tcpClient);
var data = master.ReadHoldingRegisters(0, 0, 2);
This is my NModbus code. In this case, I expect to get the result data of only response ReadHoldingRegisters(). However, I want to get not only response data but also the request byte array.
The above picture is the ModbusSlave program's communication log. That shows RX/TX with a timestamp. How can I get like this RX/TX log in my C# NModbus code?
There are no available logging methods in a document of NModbus GitHub.
public class GetLogMessage : ModbusLogger
{
public List<string> _message { get; set; } = new List<string>();
public GetLogMessage(LoggingLevel minimumLoggingLevel) : base(minimumLoggingLevel)
{
}
protected override void LogCore(LoggingLevel level, string message)
{
_message.Add(message);
if (level == LoggingLevel.Trace) _message.Add(message);
Trace.WriteLine($"[{level}]".PadRight(4) + message);
}
}
I found how can get a trace log.
First, we create a new class that inherits ModbusLogger. Then create LogCore and Constructure.
Now we can get RX/TX data in _message.
GetLogMessage logger = new GetLogMessage(LoggingLevel.Trace);
ModbusFactory modbus = new ModbusFactory(null,true, logger);
TcpClient client = new TcpClient("127.0.0.1", 5000);
IModbusMaster master = modbus.CreateMaster(client);
ushort[] res = master.ReadHoldingRegisters(0,0,2);
var getlog = logger._message;
Console.WriteLine(getlog[0]);
Console.WriteLine(getlog[5]);
Results
When my AWS Credentials File (see docs) is updated by an external process the AmazonSQSClient doesn't re-read it, SendMessageAsync fails with a security/token error.
We use a custom powershell script to refresh the local AWS cred's file periodically. The script works fine, the file is refreshed prior to the credentials expiring on AWS. However, if my app is running when the file is refreshed the new credentials are not re-read from the file, the "client" will show that the previous credentials are still in use.
The AWS docs list several AWSCredential providers but none of them seem to be the correct choice...I think..
Restarting the app works, the new credentials are read correctly and messages are sent until the next time the cred's file is updated.
using (var client = new AmazonSQSClient(Amazon.RegionEndpoint.EUWest1))
{
return client.SendMessageAsync(request);
}
I don't think there is a way for a running app to pick up the default credentials being refreshed in credentials file. There is a solution for Node.js loading credentials from a JSON file. You can create a similar solution in C#. You can also run a local DB to store credentials so whenever credentials file is updated DB table or JSON file is also updated. You will need to use access key and secret key in your SQS client constructor as opposed to using default credentials.
// Load these from JSON file or DB.
var accessKey = "";
var secretKey = "";
using (var client = new AmazonSQSClient(accessKey, secretKey, Amazon.RegionEndpoint.EUWest1))
{
return client.SendMessageAsync(request);
}
The following works "ok" but I've only tested it with one profile and the file watcher is not as timely as you'd like so I'd recommend you wrap your usage inside a Retry mechanism.
// Usage..
var credentials = new AwsCredentialsFile();
using (var client = new AmazonSQSClient(credentials, Amazon.RegionEndpoint.EUWest1))
{
return client.SendMessageAsync(request);
}
public class AwsCredentialsFile : AWSCredentials
{
// https://docs.aws.amazon.com/sdk-for-net/v2/developer-guide/net-dg-config-creds.html#creds-file
private const string DefaultProfileName = "default";
private static ConcurrentDictionary<string, ImmutableCredentials> _credentials = new ConcurrentDictionary<string, ImmutableCredentials>(StringComparer.OrdinalIgnoreCase);
private static FileSystemWatcher _watcher = BuildFileSystemWatcher();
private readonly System.Text.Encoding _encoding;
private readonly string _profileName;
public AwsCredentialsFile()
: this(AwsCredentialsFile.DefaultProfileName, System.Text.Encoding.UTF8)
{
}
public AwsCredentialsFile(string profileName)
: this(profileName, System.Text.Encoding.UTF8)
{
}
public AwsCredentialsFile(string profileName, System.Text.Encoding encoding)
{
_profileName = profileName;
_encoding = encoding;
}
private static FileSystemWatcher BuildFileSystemWatcher()
{
var watcher = new FileSystemWatcher
{
Path = Path.GetDirectoryName(GetDefaultCredentialsFilePath()),
NotifyFilter = NotifyFilters.LastWrite,
Filter = "credentials"
};
watcher.Changed += (object source, FileSystemEventArgs e) => { _credentials?.Clear(); };
watcher.EnableRaisingEvents = true;
return watcher;
}
public static string GetDefaultCredentialsFilePath()
{
return System.Environment.ExpandEnvironmentVariables(#"C:\Users\%USERNAME%\.aws\credentials");
}
public static (string AccessKey, string SecretAccessKey, string Token) ReadCredentialsFromFile(string profileName, System.Text.Encoding encoding)
{
var profile = $"[{profileName}]";
string awsAccessKeyId = null;
string awsSecretAccessKey = null;
string token = null;
var lines = File.ReadAllLines(GetDefaultCredentialsFilePath(), encoding);
for (int i = 0; i < lines.Length; i++)
{
var text = lines[i];
if (text.Equals(profile, StringComparison.OrdinalIgnoreCase))
{
awsAccessKeyId = lines[i + 1].Replace("aws_access_key_id = ", string.Empty);
awsSecretAccessKey = lines[i + 2].Replace("aws_secret_access_key = ", string.Empty);
if (lines.Length >= i + 3)
{
token = lines[i + 3].Replace("aws_session_token = ", string.Empty);
}
break;
}
}
var result = (AccessKey: awsAccessKeyId, SecretAccessKey: awsSecretAccessKey, Token: token);
return result;
}
public override ImmutableCredentials GetCredentials()
{
if (_credentials.TryGetValue(_profileName, out ImmutableCredentials value))
{
return value;
}
else
{
var (AccessKey, SecretAccessKey, Token) = ReadCredentialsFromFile(_profileName, _encoding);
var credentials = new ImmutableCredentials(AccessKey, SecretAccessKey, Token);
_credentials.TryAdd(_profileName, credentials);
return credentials;
}
}
}
Hello I creating a webapp that has a working SSH terminal similar to Putty. I'm using SSH Library as a means of handling the ssh stream. However there is a problem. I can log into a Cisco 2950 and type in commands but it comes out jumbled and in one line.
Also when I try "conf t" it gets into the configuration terminal but then you can't do anything and this pops up "Line has invalid autocommand "?".
Here is the code I have so far:
This is the SSH.cs that interacts with the library.
public class SSH
{
public string cmdInput { get; set; }
public string SSHConnect()
{
var PasswordConnection = new PasswordAuthenticationMethod("username", "password");
var KeyboardInteractive = new KeyboardInteractiveAuthenticationMethod("username");
// jmccarthy is the username
var connectionInfo = new ConnectionInfo("10.56.1.2", 22, "username", PasswordConnection, KeyboardInteractive);
var ssh = new SshClient(connectionInfo);
ssh.Connect();
var cmd = ssh.CreateCommand(cmdInput);
var asynch = cmd.BeginExecute(delegate(IAsyncResult ar)
{
//Console.WriteLine("Finished.");
}, null);
var reader = new StreamReader(cmd.OutputStream);
var myData = "";
while (!asynch.IsCompleted)
{
var result = reader.ReadToEnd();
if (string.IsNullOrEmpty(result))
continue;
myData = result;
}
cmd.EndExecute(asynch);
return myData;
}
}
This the code in the .aspx.cs that displays the code on the web page.
protected void CMD(object sender, EventArgs e)
{
SSH s = new SSH();
s.cmdInput = input.Text;
output.Text = s.SSHConnect();
}
Any help would be appreciated.
From looking through the test cases in the code for the SSH.NET library, you can use the RunCommand method instead of CreateCommand, which will synchronously process the command. I also added a using block for the SshClient ssh object since it implements iDisposable. Remember to call Disconnect as well so you don't get stuck with open connections.
Also the SshCommand.Result property (used in the command.Result call below), encapsulates the logic to pull the results from the OutputSteam, and uses this._session.ConnectionInfo.Encoding to read the OutputStream using the proper encoding. This should help with the jumbled lines you were receiving.
Here is an example:
public string SSHConnect() {
var PasswordConnection = new PasswordAuthenticationMethod("username", "password");
var KeyboardInteractive = new KeyboardInteractiveAuthenticationMethod("username");
string myData = null;
var connectionInfo = new ConnectionInfo("10.56.1.2", 22, "username", PasswordConnection, KeyboardInteractive);
using (SshClient ssh = new SshClient(connectionInfo)){
ssh.Connect();
var command = ssh.RunCommand(cmdInput);
myData = command.Result;
ssh.Disconnect();
}
return myData;
}
We are developing an application that will install on PC and it will perform some background upload and download to/from our server. One of the requirement is to detect if the internet connection is currently busy (say above 50% utilization) and if it is, it needs to back-off and try another time. The main reason is to ensure the app does not interfere with user experience if they are in the middle of gaming, watching online movie or aggressively downloading files
After much thinking and research on google and of course SO, I still haven't found a good way on how to implement this, so decided to throw this out here. The application is implemented in C#, .NET 4.0 and I am looking for all forms of responses - either implementation in C# or other languages, pseudo-logic or approach on how to achieve - measuring of internet traffic utilization on local PC with good enough accuracy.
To avoid duplication of effort, so far I have tried these (and why they aren't suitable)
Use WMI to get network statistic. Most SO posts and solutions out there since to refer to this as the approach but it doesn't meet our requirement as measuring of bytes sent/received against network interface capacity (e.g. 1GB Ethernet Card) for utilisation will yield a good measure for LAN traffic but not for internet traffic (where the actual internet bandwidth might only be say 8Mbps)
Use of .NET Network Information Statistics or performance counter - yield similar readings to the above hence have the same shortcomings
Use ICMP (Ping) and measure RTT. It was suggested that 400ms RTT is considered as slow and good indication for busy network, however I was told that user with modem (yes we have to support that), use of reverse proxy or microwave link often get ping above that hence not a good measure
Start downloading a known file and measure the speed - this itself generate traffic which we are trying to avoid, also if this check is done often enough, our application will end up creating a lot of internet traffic - which again not ideal
MOD: Using BITS - this service can be disabled on user pc, requires group policy changes and assume server to be IIS (with custom configuration) and in our case our server is not IIS
So here it is, I'm all confuse and looking for some advice. I highlighted the question text so that you guys don't get lost reading this and wondering what the question is. Thanks.
You could use UPnP to query the router, and retrive the number of bytes sent and received over the network. You could keep checking this value on the router to determine what the activity is. Unfortunately this functionality doesn't seem to be well-documented, but it is possible to implement UPnP communication functionality within a C# application. You will need to use UDP to query for the router (UPnP discover), and once you have found the device, query its functionality, and then query the number of packets sent and received for the Internet Gateway Device using a WebClient (TCP).
Code for a UPnP library:
using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Xml;
using System.IO;
namespace UPNPLib
{
public class RouterElement
{
public RouterElement()
{
}
public override string ToString()
{
return Name;
}
public List children = new List();
public RouterElement parent;
public string Name;
public string Value;
public RouterElement this[string name] {
get
{
foreach (RouterElement et in children)
{
if (et.Name.ToLower().Contains(name.ToLower()))
{
return et;
}
}
foreach (RouterElement et in children)
{
Console.WriteLine(et.Name);
}
throw new KeyNotFoundException("Unable to find the specified entry");
}
}
public RouterElement(XmlNode node, RouterElement _parent)
{
Name = node.Name;
if (node.ChildNodes.Count
/// Gets the root URL of the device
///
///
public static string GetRootUrl()
{
StringBuilder mbuilder = new StringBuilder();
mbuilder.Append("M-SEARCH * HTTP/1.1\r\n");
mbuilder.Append("HOST: 239.255.255.250:1900\r\n");
mbuilder.Append("ST:upnp:rootdevice\r\n");
mbuilder.Append("MAN:\"ssdp:discover\"\r\n");
mbuilder.Append("MX:3\r\n\r\n");
UdpClient mclient = new UdpClient();
byte[] dgram = Encoding.ASCII.GetBytes(mbuilder.ToString());
mclient.Send(dgram,dgram.Length,new IPEndPoint(IPAddress.Broadcast,1900));
IPEndPoint mpoint = new IPEndPoint(IPAddress.Any, 0);
rootsearch:
dgram = mclient.Receive(ref mpoint);
string mret = Encoding.ASCII.GetString(dgram);
string orig = mret;
mret = mret.ToLower();
string url = orig.Substring(mret.IndexOf("location:") + "location:".Length, mret.IndexOf("\r", mret.IndexOf("location:")) - (mret.IndexOf("location:") + "location:".Length));
WebClient wclient = new WebClient();
try
{
Console.WriteLine("POLL:" + url);
string reply = wclient.DownloadString(url);
if (!reply.ToLower().Contains("router"))
{
goto rootsearch;
}
}
catch (Exception)
{
goto rootsearch;
}
return url;
}
public static RouterElement enumRouterFunctions(string url)
{
XmlReader mreader = XmlReader.Create(url);
XmlDocument md = new XmlDocument();
md.Load(mreader);
XmlNodeList rootnodes = md.GetElementsByTagName("serviceList");
RouterElement elem = new RouterElement();
foreach (XmlNode et in rootnodes)
{
RouterElement el = new RouterElement(et, null);
elem.children.Add(el);
}
return elem;
}
public static RouterElement getRouterInformation(string url)
{
XmlReader mreader = XmlReader.Create(url);
XmlDocument md = new XmlDocument();
md.Load(mreader);
XmlNodeList rootnodes = md.GetElementsByTagName("device");
return new RouterElement(rootnodes[0], null);
}
}
public class RouterMethod
{
string url;
public string MethodName;
string parentname;
string MakeRequest(string URL, byte[] data, string[] headers)
{
Uri mri = new Uri(URL);
TcpClient mclient = new TcpClient();
mclient.Connect(mri.Host, mri.Port);
Stream mstream = mclient.GetStream();
StreamWriter textwriter = new StreamWriter(mstream);
textwriter.Write("POST "+mri.PathAndQuery+" HTTP/1.1\r\n");
textwriter.Write("Connection: Close\r\n");
textwriter.Write("Content-Type: text/xml; charset=\"utf-8\"\r\n");
foreach (string et in headers)
{
textwriter.Write(et + "\r\n");
}
textwriter.Write("Content-Length: " + (data.Length).ToString()+"\r\n");
textwriter.Write("Host: " + mri.Host+":"+mri.Port+"\r\n");
textwriter.Write("\r\n");
textwriter.Flush();
Stream reqstream = mstream;
reqstream.Write(data, 0, data.Length);
reqstream.Flush();
StreamReader reader = new StreamReader(mstream);
while (reader.ReadLine().Length > 2)
{
}
return reader.ReadToEnd();
}
public RouterElement Invoke(string[] args)
{
MemoryStream mstream = new MemoryStream();
StreamWriter mwriter = new StreamWriter(mstream);
//TODO: Implement argument list
string arglist = "";
mwriter.Write("" + "" + "");
mwriter.Write("");//" + arglist + "");
mwriter.Write("");
mwriter.Flush();
List headers = new List();
headers.Add("SOAPAction: \"" + parentschema + "#" + MethodName + "\"");
mstream.Position = 0;
byte[] dgram = new byte[mstream.Length];
mstream.Read(dgram, 0, dgram.Length);
XmlDocument mdoc = new XmlDocument();
string txt = MakeRequest(url, dgram, headers.ToArray());
mdoc.LoadXml(txt);
try
{
RouterElement elem = new RouterElement(mdoc.ChildNodes[0], null);
return elem["Body"].children[0];
}
catch (Exception er)
{
RouterElement elem = new RouterElement(mdoc.ChildNodes[1], null);
return elem["Body"].children[0];
}
}
public List parameters = new List();
string baseurl;
string parentschema;
public RouterMethod(string svcurl, RouterElement element,string pname, string baseURL, string svcpdsc)
{
parentschema = svcpdsc;
baseurl = baseURL;
parentname = pname;
url = svcurl;
MethodName = element["name"].Value;
try
{
foreach (RouterElement et in element["argumentList"].children)
{
parameters.Add(et.children[0].Value);
}
}
catch (KeyNotFoundException)
{
}
}
}
public class RouterService
{
string url;
public string ServiceName;
public List methods = new List();
public RouterMethod GetMethodByNonCaseSensitiveName(string name)
{
foreach (RouterMethod et in methods)
{
if (et.MethodName.ToLower() == name.ToLower())
{
return et;
}
}
throw new KeyNotFoundException();
}
public RouterService(RouterElement element, string baseurl)
{
ServiceName = element["serviceId"].Value;
url = element["controlURL"].Value;
WebClient mclient = new WebClient();
string turtle = element["SCPDURL"].Value;
if (!turtle.ToLower().Contains("http"))
{
turtle = baseurl + turtle;
}
Console.WriteLine("service URL " + turtle);
string axml = mclient.DownloadString(turtle);
XmlDocument mdoc = new XmlDocument();
if (!url.ToLower().Contains("http"))
{
url = baseurl + url;
}
mdoc.LoadXml(axml);
XmlNode mainnode = mdoc.GetElementsByTagName("actionList")[0];
RouterElement actions = new RouterElement(mainnode, null);
foreach (RouterElement et in actions.children)
{
RouterMethod method = new RouterMethod(url, et,ServiceName,baseurl,element["serviceType"].Value);
methods.Add(method);
}
}
}
}
Code for a bandwidth meter:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using UPNPLib;
using System.IO;
namespace bandwidthmeter
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
BinaryReader mreader = new BinaryReader(File.Open("bandwidthlog.txt", FileMode.OpenOrCreate));
if (mreader.BaseStream.Length > 0)
{
prevsent = mreader.ReadInt64();
prevrecv = mreader.ReadInt64();
}
mreader.Close();
List services = new List();
string fullurl = UPNP.GetRootUrl();
RouterElement router = UPNP.enumRouterFunctions(fullurl);
Console.WriteLine("Router feature enumeration complete");
foreach (RouterElement et in router.children)
{
services.Add(new RouterService(et.children[0], fullurl.Substring(0, fullurl.IndexOf("/", "http://".Length+1))));
}
getReceiveDelegate = services[1].GetMethodByNonCaseSensitiveName("GetTotalBytesReceived");
getSentDelegate = services[1].GetMethodByNonCaseSensitiveName("GetTotalBytesSent");
Console.WriteLine("Invoking " + getReceiveDelegate.MethodName);
//Console.WriteLine(services[1].GetMethodByNonCaseSensitiveName("GetTotalPacketsSent").Invoke(null));
Timer mymer = new Timer();
mymer.Tick += new EventHandler(mymer_Tick);
mymer.Interval = 1000;
mymer.Start();
FormClosed += new FormClosedEventHandler(Form1_FormClosed);
}
long prevsent = 0;
long prevrecv = 0;
void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
BinaryWriter mwriter = new BinaryWriter(File.Open("bandwidthlog.txt", FileMode.OpenOrCreate));
mwriter.Write(getsent());
mwriter.Write(getreceived());
mwriter.Flush();
mwriter.Close();
}
long getsent()
{
long retval = Convert.ToInt64(getSentDelegate.Invoke(null).children[0].Value);
if (prevsent > retval)
{
retval = prevsent + retval;
}
return retval;
}
long getreceived()
{
long retval = Convert.ToInt64(getReceiveDelegate.Invoke(null).children[0].Value);
if (prevrecv > retval)
{
retval = prevrecv + retval;
}
return retval;
}
void mymer_Tick(object sender, EventArgs e)
{
label1.Text = "Sent: "+(getsent()/1024/1024).ToString()+"MB\nReceived: "+(getreceived()/1024/1024).ToString()+"MB";
}
RouterMethod getSentDelegate;
RouterMethod getReceiveDelegate;
}
}
Have you considered using Background Intelligent Transfer Service (BITS). It's designed to do this job already:
Background Intelligent Transfer Service (BITS) transfers files (downloads or uploads) between a client and server and provides progress information related to the transfers. You can also download files from a peer.
and,
Preserve the responsiveness of other network applications.
I'm not sure if there's a managed interface to it (I can see reference to Powershell Cmdlets), so you might have to use COM interop to use it.
Making the assumption that you are targetting Windows PC's (as you said you were developing in C#), have you looked at BITS, the Background Intelligent Transfer Service?
There's examples of how to hook into it using C# on MSDN and elsewhere, e.g. http://msdn.microsoft.com/en-us/magazine/cc188766.aspx
I need to send a mail including the exception details (Yellow Screen Of Death) as attachment.
I could get the YSOD as follows:
string YSODmarkup = lastErrorWrapper.GetHtmlErrorMessage();
if (!string.IsNullOrEmpty(YSODmarkup))
{
Attachment YSOD = Attachment.CreateAttachmentFromString(YSODmarkup, "YSOD.htm");
mm.Attachments.Add(YSOD);
}
mm is of type MailMessage, but the mail is not sent.
Here
System.Net.Mail.MailMessage MyMailMessage = new System.Net.Mail.MailMessage("from", "to", "Exception-Details", htmlEmail.ToString());
is used to bind the body of the mail.
After this only the attachment is added.
While removing the attachment, mail is sent.
Can anyone help me out?
As per the comments from Mr. Albin and Mr. Paul am updating the following
string YSODmarkup = Ex_Details.GetHtmlErrorMessage();
string p = System.IO.Directory.GetCurrentDirectory();
p = p + "\\trial.txt";
StreamWriter sw = new StreamWriter(p);
sw.WriteLine(YSODmarkup);
sw.Close();
Attachment a = new Attachment(p);
if (!string.IsNullOrEmpty(YSODmarkup))
{
Attachment YSOD = Attachment.CreateAttachmentFromString(YSODmarkup, "YSOD.html");
System.Net.Mail.Attachment(server.mappath("C:\\Documents and Settings\\user\\Desktop\\xml.docx"));
MyMailMessage.Attachments.Add(a);
}
Here i attached the contents to a text file and tried the same. So the mail was not sent. Is there any issue with sending mails which contains HTML tags in it. Because i was able to attach a normal text file.
namespace SendAttachmentMail
{
class Program
{
static void Main(string[] args)
{
var myAddress = new MailAddress("jhered#yahoo.com","James Peckham");
MailMessage message = new MailMessage(myAddress, myAddress);
message.Body = "Hello";
message.Attachments.Add(new Attachment(#"Test.txt"));
var client = new YahooMailClient();
client.Send(message);
}
}
public class YahooMailClient : SmtpClient
{
public YahooMailClient()
: base("smtp.mail.yahoo.com", 25)
{
Credentials = new YahooCredentials();
}
}
public class YahooCredentials : ICredentialsByHost
{
public NetworkCredential GetCredential(string host, int port, string authenticationType)
{
return new NetworkCredential("jhered#yahoo.com", "mypwd");
}
}
}