C# Issue sending and receiving data - c#

I know that this is a very discussed topic, but i'm facing a strange behavior from my code.
Making some tests I found that everything goes perfectly for some clients but others simply can't receive and send data through my custom Client design TCPClient based.
I can't confirm if this issue is caused by VPN or NATs connections although many Users can access my server from this connection types.
I' ve been reading about TCPClient and TCPListener in .Net and searching issues like mine but no luck. Everything I got are poor examples.
I will appreciate any help, even book references for network advanced programming.
Here is my code. Most stable up to date
Cliente.cs
using System;
using System.Collections.Generic;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Net;
using System.IO;
using System.Threading.Tasks;
using System.Threading;
using System.Runtime.Serialization.Formatters.Binary;
using System.Linq;
using System.IO.Compression;
namespace netComunicator
{
public class Client:IDisposable
{
int BufferSize { get { return 16384; } }
/// <summary>
/// .NET TcpClient for receive and send methods
/// </summary>
public TcpClient NetClient = new TcpClient();
/// <summary>
/// Externl buffer to store incoming data
/// This buffers get clear when EOS secuence is present on it
/// </summary>
List<byte> ExternBuffer = new List<byte>();
/// <summary>
/// Remote port
/// </summary>
public string Port { get; set; }
/// <summary>
/// MAC address from client
/// </summary>
public string ClientMac { get; set; }
/// <summary>
/// Client IP
/// </summary>
public string ClientIp { get; set; }
/// <summary>
/// Cifrado
/// Use NETSID as public key and his first 128 bits as IV
/// </summary>
private NetCipher Cipher { get; set; }
/// <summary>
/// Shared network session id
/// </summary>
public string NETSID { get; set; }
#region delegados y eventos
public delegate void NoConnection(string noConnectedClient);
public delegate void ObjectDataReceived(object data, Client client);
public delegate void ClientDisconnected(Client client);
public event ClientDisconnected OnClientDisconnected;
public event ObjectDataReceived OnObjectDataReceived;
public event NoConnection OnFailConnectTo;
#endregion
public NetworkStream NetStream;
CancellationTokenSource CTS;
CancellationToken Token;
CancellationTokenSource CancelKAS;
CancellationToken CancelKASToken;
/// <summary>
/// Max lost count of KEEP_ALIVE
/// </summary>
const int MAX_KEEP_ALIVE_COUNT = 30;
/// <summary>
/// Current lost KEEP_ALIVE_SIGNAL
/// </summary>
int CURRENT_KEEP_ALIVE_SIGNAL_COUNT = 0;
public Client(byte[] NETSID)
{
CTS = new CancellationTokenSource();
Token = CTS.Token;
CancelKAS = new CancellationTokenSource();
CancelKASToken = CancelKAS.Token;
ClientMac = GetMac();
if (NETSID != null)
{
Cipher = new NetCipher(NETSID);
this.NETSID = new Guid(NETSID).ToString();
}
}
public void StartClient()
{
try
{
Task.Factory.StartNew(() => Receive(), Token);
}
catch { }
}
public void StopClient()
{
CTS.Cancel();
}
public void StartKeepAliveSignal()
{
Task.Factory.StartNew(() =>
{
while (!CancelKASToken.IsCancellationRequested)
{
if (CURRENT_KEEP_ALIVE_SIGNAL_COUNT == MAX_KEEP_ALIVE_COUNT)
{
OnClientDisconnected?.BeginInvoke(this, null, null);
Dispose();
}
Send("KEEP_ALIVE");
CURRENT_KEEP_ALIVE_SIGNAL_COUNT++;
Thread.Sleep(1000);
}
}, CancelKASToken);
}
public void StoptKeepAliveSignal()
{
CancelKAS.Cancel();
}
static string GetLocalIPAddress()
{
var host = Dns.GetHostEntry(Dns.GetHostName());
foreach (var ip in host.AddressList)
if (ip.AddressFamily == AddressFamily.InterNetwork)
return ip.ToString();
throw new Exception("No network adapters with an IPv4 address in the system!");
}
string GetMac()
{
NetworkInterface[] nics = NetworkInterface.GetAllNetworkInterfaces();
var mac = nics.Where(n => n.OperationalStatus == OperationalStatus.Up).FirstOrDefault();
if (mac != null)
return mac.GetPhysicalAddress().ToString();
return "";
}
void CloseConnection()
{
NetClient.Close();
}
public void Dispose()
{
try
{
StoptKeepAliveSignal();
StopClient();
CloseConnection();
}
catch{ }
}
async public Task<bool> Connect(string ip, int port, bool initKAS = true)
{
return await Task.Factory.StartNew(() =>
{
try
{
NetClient.Connect(ip, port);
Guid guid = new Guid(NETSID);
if (NetStream == null)
{
NetStream = NetClient.GetStream();
Cipher = new NetCipher(guid.ToByteArray());
}
List<byte> initdata = new List<byte>();
initdata.AddRange(NetComTools.wellcomeMsg);
initdata.AddRange(System.Text.Encoding.ASCII.GetBytes(ClientMac));
initdata.AddRange(guid.ToByteArray());
NetStream.Write(initdata.ToArray(), 0, initdata.Count);
StartClient();
// if required KEEP_ALIVE_SIGNAL on
if (initKAS)
StartKeepAliveSignal();
ClientIp = ip;
return true;
}
catch
{
// Notificar que no se pudo establecer conexion
OnFailConnectTo?.Invoke(ip);
}
return false;
});
}
public void Receive()
{
while (!Token.IsCancellationRequested)
{
try
{
byte[] buff = new byte[BufferSize];
int bytesReaded = NetStream.Read(buff, 0, buff.Length);
byte[] bytesFromBuffer = buff.Take(bytesReaded).ToArray();
CURRENT_KEEP_ALIVE_SIGNAL_COUNT = 0;
ExternBuffer.AddRange(bytesFromBuffer);
// Determine if current received bytes have End Of Secuence byte array
bool isEOS = IsEndOfSecuence(ExternBuffer);
if (isEOS)
{
// Secuence separator takes inside EOS and passing ExternBuffer
// split current buffer in N subsecuences.
// This because in one buffer read, can be more than one message delimited by EOS
List<byte[]> secuences = SecuenceSeparator(ExternBuffer);
ExternBuffer.Clear();
foreach (byte[] secuence in secuences)
{
byte[] decompress = Decompress(secuence);
byte[] decipher = Cipher.Decrypt(decompress);
object data = Serializer.Deserialize(decipher);
OnObjectDataReceived?.BeginInvoke(data, this, null, null);
}
}
}
catch
{
Dispose();
OnClientDisconnected?.Invoke(this);
return;
}
Thread.Sleep(10);
}
}
/// <summary>
/// If bytes[] have an EOS secuence return true
/// </summary>
bool IsEndOfSecuence(byte[] bytes)
{
int secuencelen = bytes.Length - 1;
int checkSum = NetComTools.EOS.Length - 1;
int EOSLen = checkSum;
int trys = 0;
for (int i = secuencelen; i >= 0; i--)
{
if (trys <= EOSLen)
{
if (checkSum == 0)
return true;
if (bytes[i] == NetComTools.EOS[checkSum])
checkSum--;
else
checkSum = NetComTools.EOS.Length - 1;
}
else
return false;
trys++;
}
return false;
}
bool IsEndOfSecuence(List<byte> bytes)
{
int secuencelen = bytes.Count - 1;
int checkSum = NetComTools.EOS.Length - 1;
int EOSLen = checkSum;
int trys = 0;
for (int i = secuencelen; i >= 0; i--)
{
if (trys <= EOSLen)
{
if (checkSum == 0)
return true;
if (bytes[i] == NetComTools.EOS[checkSum])
checkSum--;
else
checkSum = NetComTools.EOS.Length - 1;
}
else
return false;
trys++;
}
return false;
}
List<byte[]> SecuenceSeparator(byte[] data)
{
List<byte[]> subsecuences = new List<byte[]>();
List<byte> externBuff = new List<byte>(data);
int inc = 0;
int secuencelen = externBuff.Count;
int lastpos = 0;
//40 = 15 + datos, 5 + eos, 15 + datos, 5 + eos
for (int i = 0; i < secuencelen; i++)
{
if (externBuff[i] == NetComTools.EOS[inc])
{
inc++;
if (inc == NetComTools.EOS.Length)
{
List<byte> sub_nsecuence = new List<byte>();
int elements = i + 1 - lastpos - NetComTools.EOS.Length;
byte[] matchsecuence = new byte[elements];
externBuff.CopyTo(lastpos, matchsecuence, 0, elements);
sub_nsecuence.AddRange(matchsecuence);
subsecuences.Add(sub_nsecuence.ToArray());
inc = 0;
lastpos = i + 1;
}
}
else
inc = 0;
}
return subsecuences;
}
List<byte[]> SecuenceSeparator(List<byte> externBuff)
{
List<byte[]> subsecuences = new List<byte[]>();
int inc = 0;
int secuencelen = externBuff.Count;
int lastpos = 0;
for (int i = 0; i < secuencelen; i++)
{
if (externBuff[i] == NetComTools.EOS[inc])
{
inc++;
if (inc == NetComTools.EOS.Length)
{
List<byte> sub_nsecuence = new List<byte>();
int elements = i + 1 - lastpos - NetComTools.EOS.Length;
byte[] matchsecuence = new byte[elements];
externBuff.CopyTo(lastpos, matchsecuence, 0, elements);
sub_nsecuence.AddRange(matchsecuence);
subsecuences.Add(sub_nsecuence.ToArray());
inc = 0;
lastpos = i + 1;
}
}
else
inc = 0;
}
return subsecuences;
}
public void Send(object data)
{
try
{
byte[] objDatabyte = Serializer.Serialize(data);
byte[] cipher = Cipher.Encrypt(objDatabyte);
byte[] compressed = Compress(cipher);
NetStream.Write(compressed, 0, compressed.Length);
NetStream.Write(NetComTools.EOS, 0, NetComTools.EOS.Length);
}
catch
{
OnClientDisconnected?.Invoke(this);
Dispose();
}
}
public static byte[] Compress(byte[] raw)
{
using (MemoryStream msw = new MemoryStream())
{
using (GZipStream gzip = new GZipStream(msw, CompressionMode.Compress))
gzip.Write(raw, 0, raw.Length);
return msw.ToArray();
}
}
public static byte[] Decompress(byte[] gzipraw)
{
using (GZipStream msw = new GZipStream(new MemoryStream(gzipraw), CompressionMode.Decompress))
{
using (MemoryStream ms = new MemoryStream())
{
int readed;
while ((readed = msw.ReadByte()) != -1)
ms.WriteByte((byte)readed);
return ms.ToArray();
}
}
}
}
static class Serializer
{
public static byte[] Serialize(object anySerializableObject)
{
using (var memoryStream = new MemoryStream())
{
(new BinaryFormatter()).Serialize(memoryStream, anySerializableObject);
return memoryStream.ToArray();
}
}
public static object Deserialize(byte[] message)
{
using (var memoryStream = new MemoryStream(message))
{
BinaryFormatter b = new BinaryFormatter();
memoryStream.Position = 0;
return b.Deserialize(memoryStream);
}
}
}
}
Server.cs
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Collections.Concurrent;
using System.Threading.Tasks;
using System.Threading;
using System.Linq;
namespace netComunicator
{
public class Server
{
/// <summary>
/// Connected Clients
/// </summary>
public ConcurrentBag<Client> Clients = new ConcurrentBag<Client>();
/// <summary>
/// .NET TcpListenet
/// </summary>
public TcpListener Host;
CancellationTokenSource CTS;
CancellationToken Token;
#region Delegados y Eventos
public delegate void ClientConnected(Client client);
public delegate void ClientDisconnected(Client client);
public event ClientConnected OnClientConnected;
public event ClientDisconnected OnClientDisconnected;
#endregion
public Server(int port)
{
CTS = new CancellationTokenSource();
Token = CTS.Token;
try
{
Host = new TcpListener(IPAddress.Parse(NetComTools.thisClientIP), port);
Host.Start();
try { Task.Factory.StartNew(() => AcceptIncoming(), Token); } catch (Exception ex) {
throw ex;
}
}
catch (Exception ec)
{
throw ec;
}
}
public void SendTo(Client client, object data)
{
Client cl = Clients.Where(c => c.Equals(client)).FirstOrDefault();
cl?.Send(data);
}
public void SendAll(object data)
{
Task.Factory.StartNew(() =>
{
foreach (Client client in Clients)
client.Send(data);
});
}
void AcceptIncoming()
{
while (!Token.IsCancellationRequested)
{
try
{
AsyncCallback acb = new AsyncCallback(PerformConnection);
Host.BeginAcceptSocket(acb, null);
Thread.Sleep(10);
}
catch (Exception ex)
{
throw ex;
}
}
}
void PerformConnection(IAsyncResult res)
{
TcpClient tempClient = null;
try
{
tempClient = Host.EndAcceptTcpClient(res);
NetworkStream ns;
ns = tempClient.GetStream();
byte[] initData = new byte[NetComTools.wellcomeMsg.Length + 28];
ns.Read(initData, 0, initData.Length);
byte[] wellcome = new byte[NetComTools.wellcomeMsg.Length];
byte[] mac = new byte[12];
byte[] netsidfromclient = new byte[16];
List<byte> ini = new List<byte>(initData);
ini.CopyTo(0, wellcome, 0, 4);
ini.CopyTo(4, mac, 0, 12);
ini.CopyTo(16, netsidfromclient, 0, 16);
if (WellcomeOk(wellcome))
{
Client newClient = new Client(netsidfromclient)
{
ClientIp = tempClient.Client.RemoteEndPoint.ToString().Split(':')[0],
Port = tempClient.Client.RemoteEndPoint.ToString().Split(':')[1],
NetStream = ns,
ClientMac = System.Text.Encoding.ASCII.GetString(mac),
};
Clients.Add(newClient);
newClient.NetClient = tempClient;
newClient.OnClientDisconnected += newClient_OnClientDisconnected;
newClient.StartClient();
OnClientConnected?.BeginInvoke(newClient, null, null);
}
else
tempClient.Close();
}
catch {if( tempClient != null) tempClient.Close(); return; }
}
bool WellcomeOk(byte[] wellcomeFromNet)
{
if (wellcomeFromNet.Length != NetComTools.wellcomeMsg.Length)
return false;
else
{
for (int i = 0; i < wellcomeFromNet.Length; i++)
if (wellcomeFromNet[i] != NetComTools.wellcomeMsg[i])
return false;
}
return true;
}
void newClient_OnClientDisconnected(Client client)
{
client.OnClientDisconnected -= newClient_OnClientDisconnected;
Clients = RemoveClientFromList(client);
OnClientDisconnected?.Invoke(client);
}
ConcurrentBag<Client> RemoveClientFromList(Client client)
{
ConcurrentBag<Client> Acl = new ConcurrentBag<Client>();
return new ConcurrentBag<Client>(Clients.Where(c => c != client));
}
public void StopListener()
{
Host.Stop();
CTS.Cancel();
}
}
}

Related

GraphServiceClient: LargeFileUploadTask with a Stream

I'm using a library to encrypt files.
The library takes an input Stream and an output Stream.
I'd like to use the library to encrypt data and upload them to OneDrive.
I should use LargeFileUploadTask to upload the file to onedrive but I don't know if it is possible to avoid writing encrypted data to a temporary file before doing the actual upload.
Is there a way I can get a Stream to just write to while uploading the data to OneDrive?
var encrypter = new StreamEncrypter("password");
using (var sourceStream = await OpenStream(input)) {
using (var destStream = await OpenStreamForWrite(output)) {
await encrypter.Encrypt(sourceStream, destStream);
}
}
Found a way, as suggested I implemented my own Stream. The size of the destination file must be known before the upload (in my scenario I can compute the final size because I'm using AES encryption).
For now I didn't bother to implement the Seek method (probably it gets called only when the upload is resumed because I see it called just once with 0 as the requested position).
The test progam:
var appOptions = new PublicClientApplicationOptions()
{
ClientName = appName,
ClientId = appId,
TenantId = tenantId,
RedirectUri = redirectUri
};
var app = PublicClientApplicationBuilder.CreateWithApplicationOptions(appOptions).Build();
var storageProperties = new StorageCreationPropertiesBuilder("UserTokenCache.txt", "./cache")
.WithLinuxKeyring(
"com.contoso.devtools.tokencache",
MsalCacheHelper.LinuxKeyRingDefaultCollection,
"MSAL token cache for all Contoso dev tool apps.",
new KeyValuePair<string, string>("Version", "1"),
new KeyValuePair<string, string>("ProductGroup", "MyApps"))
.WithMacKeyChain(
"myapp_msal_service",
"myapp_msal_account")
.Build();
var cacheHelper = await MsalCacheHelper.CreateAsync(storageProperties);
cacheHelper.VerifyPersistence();
cacheHelper.RegisterCache(app.UserTokenCache);
var accounts = await app.GetAccountsAsync();
AuthenticationResult result;
try {
result = await app.AcquireTokenSilent(scopes, accounts.FirstOrDefault())
.ExecuteAsync();
} catch (MsalUiRequiredException) {
result = await app.AcquireTokenInteractive(scopes)
.ExecuteAsync();
}
var graphClient = new GraphServiceClient(new AuthenticationProvider(result.AccessToken));
using (var fileStream = System.IO.File.OpenRead(#"c:\test\test.zip")) {
UploadSession uploadSession = await graphClient.Me.Drive.Root.ItemWithPath("/test/test.zip").CreateUploadSession().Request().PostAsync();
using (var oneDriveStream = new OneDriveStream(uploadSession, fileStream.Length)) {
byte[] buffer = new byte[4096];
int read;
while ((read = await fileStream.ReadAsync(buffer, 0, buffer.Length)) > 0) {
await oneDriveStream.WriteAsync(buffer, 0, read);
}
await oneDriveStream.WaitTask();
}
}
The OneDriveStream class:
internal class OneDriveStream : Stream
{
private readonly Task? _writeTask;
private long _pos;
private readonly long _cacheSize = 1310720;
private readonly List<byte> _buffer = new();
private readonly SemaphoreSlim _bufferSema = new(1,1);
public WriterStream(UploadSession session, long length)
{
Length = length;
_writeTask = new LargeFileUploadTask<DriveItem>(session, this, (int)_cacheSize).UploadAsync();
}
public override bool CanRead => true;
public override bool CanSeek => true;
public override bool CanWrite => true;
public override long Length { get; }
public override long Position {
get => _pos;
set => _pos = value;
}
protected override void Dispose(bool disposing)
{
_writeTask?.Dispose();
}
public override void Flush()
{
}
public override int Read(byte[] buffer, int offset, int count)
{
if (_pos >= Length)
return 0;
_bufferSema.Wait();
int readCount = 0;
for (int i = 0; i < count; i++) {
if (_buffer.Count > 0) {
buffer[offset + i] = _buffer[0];
_buffer.RemoveAt(0);
_pos++;
readCount++;
if (_pos >= Length)
break;
} else {
_bufferSema.Release();
Thread.Sleep(20);
_bufferSema.Wait();
i--;
}
}
_bufferSema.Release();
return readCount;
}
public override long Seek(long offset, SeekOrigin origin)
{
return offset;
}
public override void SetLength(long value)
{
throw new NotImplementedException();
}
public override void Write(byte[] buffer, int offset, int count)
{
while(_buffer.Count > 0)
Thread.Sleep(10);
_bufferSema.Wait();
for (int i = 0; i < count; i++) {
_buffer.Add(buffer[offset + i]);
}
_bufferSema.Release();
}
public async Task WaitTask()
{
if (_writeTask != null)
await _writeTask;
}
}

how to create last file with renaming messages where message batch size is not greater than equal to 240

In below application,
Producer method adding messages to a blocking collection.
In Consumer method, I'm consuming blocking collection and adding messages to a list and when size >= 240, writing that list to json file.
At some point I don't have any new messages in blocking collection, but in Consumer, I have a list of messages which is not >=240 in size, then in this case , the app is not able to write to a new JSON file (rest of the data).
How can I let the Consumer know that no new messages coming up, write whatever left with you in a new file?
Is this possible? let say Consumer will wait for 1 minute and if there is no new messages, then write whatever left in an new file?
Here is the code (here I'm adding 11 messages. Till 9 messages the batch size is 240 and it's generates a file, but message no 10 & 11 not able to write in new file),
class Program
{
private static List<Batch> batchList = new List<Batch>();
private static BlockingCollection<Message> messages = new BlockingCollection<Message>();
private static int maxbatchsize = 240;
private static int currentsize = 0;
private static void Producer()
{
int ctr = 1;
while (ctr <= 11)
{
messages.Add(new Message { Id = ctr, Name = $"Name-{ctr}" });
Thread.Sleep(1000);
ctr++;
}
}
private static void Consumer()
{
foreach (var message in messages.GetConsumingEnumerable())
{
var msg = JsonConvert.SerializeObject(message);
Console.WriteLine(msg);
if (currentsize + msg.Length >= maxbatchsize)
{
WriteToFile(batchList);
}
batchList.Add(new Batch { Message = message });
currentsize += msg.Length;
}
}
private static void WriteToFile(List<Batch> batchList)
{
using (StreamWriter outFile = System.IO.File.CreateText(Path.Combine(#"C:\TEMP", $"{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.json")))
{
outFile.Write(JsonConvert.SerializeObject(batchList));
}
batchList.Clear();
currentsize = 0;
}
static void Main(string[] args)
{
var producer = Task.Factory.StartNew(() => Producer());
var consumer = Task.Factory.StartNew(() => Consumer());
Console.Read();
}
}
}
Supporting classes,
public class Message
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Batch
{
public Message Message { get; set; }
}
Update:
class Program
{
private static readonly List<Batch> BatchList = new List<Batch>();
private static readonly BlockingCollection<Message> Messages = new BlockingCollection<Message>();
private const int Maxbatchsize = 240;
private static int _currentsize;
private static void Producer()
{
int ctr = 1;
while (ctr <= 11)
{
Messages.Add(new Message { Id = ctr, Name = $"Name-{ctr}" });
Thread.Sleep(1000);
ctr++;
}
Messages.CompleteAdding();
}
private static void Consumer()
{
foreach (var message in Messages.GetConsumingEnumerable())
{
if (_currentsize >= Maxbatchsize)
{
var listToWrite = new Batch[BatchList.Count];
BatchList.CopyTo(listToWrite);
BatchList.Clear();
_currentsize = 0;
WriteToFile(listToWrite.ToList());
}
else
{
Thread.Sleep(1000);
if (Messages.IsAddingCompleted)
{
var remainSize = Messages.Select(JsonConvert.SerializeObject).Sum(x => x.Length);
if (remainSize == 0)
{
var lastMsg = JsonConvert.SerializeObject(message);
BatchList.Add(new Batch { Message = message });
_currentsize += lastMsg.Length;
Console.WriteLine(lastMsg);
var additionListToWrite = new Batch[BatchList.Count];
BatchList.CopyTo(additionListToWrite);
BatchList.Clear();
_currentsize = 0;
WriteToFile(additionListToWrite.ToList());
break;
}
}
}
var msg = JsonConvert.SerializeObject(message);
BatchList.Add(new Batch { Message = message });
_currentsize += msg.Length;
Console.WriteLine(msg);
}
}
private static void WriteToFile(List<Batch> listToWrite)
{
using (StreamWriter outFile = System.IO.File.CreateText(Path.Combine(#"C:\TEMP", $"{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.json")))
{
outFile.Write(JsonConvert.SerializeObject(listToWrite));
}
}
static void Main(string[] args)
{
var producer = Task.Factory.StartNew(() => Producer());
var consumer = Task.Factory.StartNew(() => Consumer());
Console.Read();
}
}

How to make background service to track GPS location even if application is closed in xamarin.forms (android)?

I am working in xamarin.forms. I want to create background service for Android.
My requirement is to track device location (Latitude, longitude, place name, Employee code) and inserted to the web server every after 5 min. After successful login, service has been started and for a particular employee, data is start inserting. Even if the app is closed still service should be running in background and data is keep inserting.
My code is
using Android.App;
using Android.Content;
using Android.Locations;
using Android.Net;
using Android.OS;
using HRMS;
using HRMS.Interface;
using HRMS.TableAttributes;
using Newtonsoft.Json;
using Plugin.Geolocator;
using Plugin.Geolocator.Abstractions;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Threading;
using System.Xml.Linq;
using Xamarin.Forms;
using static HRMS.ServiceLayer.ServiceClasses;
using HRMS.MenuController;
using HRMS.Droid;
using Android.Provider;
namespace SilverHRMS.Droid.Service
{
[Service]
public class GPSTrackingService : Android.App.Service
{
#region Private Variables
static readonly string TAG = "X:" + typeof(GPSTrackingService).Name;
static int TimerWait = 300000; //150000;//25000 25 Sec; // 10 min 600000 and 20 Min 1200000
Timer _timer;
readonly string logTag = "GPSTrackingService";
private string deviceId = "";
private string googleAPI = "http://maps.googleapis.com/maps/api/geocode/xml?latlng={0},{1}&sensor=false";
#endregion
/// <summary>
///
/// </summary>
/// <param name="intent"></param>
/// <param name="flags"></param>
/// <param name="startId"></param>
/// <returns></returns>
public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId)
{
try
{
// Log.Debug(TAG, "OnStartCommand called at {2}, flags={0}, startid={1}", flags, startId, DateTime.UtcNow);
//_timer = new Timer(o => { Log.Debug(TAG, "Hello from GPSTrackingService. {0}", DateTime.UtcNow); GetCurrentLocation(); }, null, 0, TimerWait);
_timer = new Timer(o => { GetCurrentLocation(); }, null, 0, TimerWait);
//_timer = new Timer(o => { GetCurrentLocation(); }, null, 0, TimerWait);
}
catch (Exception ex)
{
var fileService = DependencyService.Get<ISaveException>();
fileService.SaveTextAsync(App.FileName, ex.StackTrace);
}
return StartCommandResult.Sticky;
}
/// <summary>
///
/// </summary>
/// <param name="intent"></param>
/// <returns></returns>
public override IBinder OnBind(Intent intent)
{
// This example isn't of a bound service, so we just return NULL.
return null;
}
/// <summary>
/// On Destroy App
/// </summary>
public override void OnDestroy()
{
base.OnDestroy();
_timer.Dispose();
_timer = null;
//Log.Debug(logTag, "Service has been terminated");
}
/// <summary>
/// Get Current Location and Insert to DB
/// </summary>
public async void GetCurrentLocation()
{
try
{
string statusMessage = string.Empty;
var locator = CrossGeolocator.Current;
locator.DesiredAccuracy = 100;
//Log.Debug(TAG, " getting GPS connection...");
if (App.MyEmployeeId != null)
{
#region Check Internet Connection
ConnectivityManager connectivityManager = (ConnectivityManager)GetSystemService(ConnectivityService);
NetworkInfo activeConnection = connectivityManager.ActiveNetworkInfo;
bool isOnline = (activeConnection != null) && activeConnection.IsConnected;
#endregion
#region Check GPS Location Position and Emp Code
//LocationManager locationManager = (LocationManager)GetSystemService(LocationService);
bool isGPSOn = App.CheckGPSConnection();
Position position = null;
if (isGPSOn)
{
try
{
position = await locator.GetPositionAsync(timeoutMilliseconds: 10000);
}
catch (Exception)
{
position = null;
}
}
#endregion
#region Insert Location to Local DB or Server DB
//Device Has Internet Connection
if (isOnline)
{
#region Online Location Insert Direct to the Server Database
if (position != null)
{
List<GPSTrackingLocationClass> lcListOnline = new List<GPSTrackingLocationClass>();
GPSTrackingLocationClass objGPSTrack = new GPSTrackingLocationClass();
objGPSTrack.Latitude = position.Latitude;
objGPSTrack.Longitude = position.Longitude;
objGPSTrack.EmpCd = App.MyEmployeeId;
objGPSTrack.GPS_Track_DateTime = App.GetDateTime(DateTime.Now);
IDevice device = DependencyService.Get<IDevice>();
deviceId = device.GetIdentifier();
deviceId = deviceId.Replace("+", "%2B");
objGPSTrack.DeviceId = deviceId;
string url = string.Format(googleAPI, position.Latitude, position.Longitude);
try
{
XElement xml = XElement.Load(url);
if (xml.Element("status").Value == "OK")
{
objGPSTrack.PlaceName = xml.Element("result").Element("formatted_address").Value;
}
else
{
objGPSTrack.PlaceName = string.Empty;
}
}
catch (Exception ex)
{
}
lcListOnline.Add(objGPSTrack);
if (lcListOnline.Count > 0)
{
string jsonContentsOnline = JsonConvert.SerializeObject(lcListOnline);
var responseOnline = await HRMS.ServiceLayer.GetResponseFromWebService.GetResponsePostData<HRMS.ServiceLayer.ServiceClasses.RootObject>(HRMS.ServiceLayer.ServiceURL.PostGPSLocation, jsonContentsOnline);
if (responseOnline.Flag == true)
{
statusMessage = responseOnline.Message;
}
}
}
#endregion
#region Local Database Entries Insert to Server Database
List<LocationTracking> lcListOffline = SideMenu.repoLocation.GetAllLocationAsync();
if (lcListOffline != null)
{
if (lcListOffline.Count > 0)
{
ObservableCollection<LocationTracking> lvCollection = new ObservableCollection<LocationTracking>(lcListOffline);
List<GPSTrackingLocationClass> lcListGoingtoOnline = new List<GPSTrackingLocationClass>();
foreach (var item in lvCollection)
{
GPSTrackingLocationClass objGPSTrackOffline = new GPSTrackingLocationClass();
objGPSTrackOffline.Latitude = item.Latitude;
objGPSTrackOffline.Longitude = item.Longitude;
objGPSTrackOffline.EmpCd = item.EmpCd;
objGPSTrackOffline.GPS_Track_DateTime = item.GPS_Track_DateTime;
objGPSTrackOffline.DeviceId = item.DeviceID;
string urlAPI = string.Format(googleAPI, item.Latitude, item.Longitude);
try
{
XElement xmlURL = XElement.Load(urlAPI);
if (xmlURL.Element("status").Value == "OK")
{
objGPSTrackOffline.PlaceName = xmlURL.Element("result").Element("formatted_address").Value;
}
else
{
objGPSTrackOffline.PlaceName = string.Empty;
}
}
catch (Exception exe)
{
}
if (!string.IsNullOrEmpty(objGPSTrackOffline.PlaceName))
{
lcListGoingtoOnline.Add(objGPSTrackOffline);
}
}
if (lcListGoingtoOnline.Count == lvCollection.Count)
{
string jsonContentsOffline = JsonConvert.SerializeObject(lcListGoingtoOnline);
var responseOffline = await HRMS.ServiceLayer.GetResponseFromWebService.GetResponsePostData<HRMS.ServiceLayer.ServiceClasses.RootObject>(HRMS.ServiceLayer.ServiceURL.PostGPSLocation, jsonContentsOffline);
if (responseOffline.Flag == true)
{
statusMessage = responseOffline.Message;
SideMenu.repoLocation.RemoveLocationAsync();
}
}
}
}
#endregion
}
//Device Has No Internet Connection
else
{
try
{
if (position != null)
{
IDevice device = DependencyService.Get<IDevice>();
deviceId = device.GetIdentifier();
deviceId = deviceId.Replace("+", "%2B");
string newDate = App.GetDateTime(DateTime.Now);
SideMenu.repoLocation.AddNewLocationAsync(newDate, position.Latitude, position.Longitude, App.MyEmployeeId, deviceId, "--");
}
}
catch (Exception ex)
{
//Log.Debug(TAG, "Please turn on GPS in your headset");
}
}
#endregion
}
}
catch (Exception ex)
{
var fileService = DependencyService.Get<ISaveException>();
await fileService.SaveTextAsync(App.FileName, ex.StackTrace);
}
}
/// <summary>
/// Start Location Service
/// </summary>
public static void StartLocationService()
{
// Starting a service like this is blocking, so we want to do it on a background thread
//new Task(() =>
//{
// Start our main service
try
{
Intent intent = new Intent(Android.App.Application.Context, typeof(GPSTrackingService));
Android.App.Application.Context.StartService(intent);
}
catch (Exception ex)
{
var fileService = DependencyService.Get<ISaveException>();
fileService.SaveTextAsync(App.FileName, ex.StackTrace);
}
//}).Start();
}
/// <summary>
/// Start Location Service
/// </summary>
public static void StartApplication()
{
try
{
Intent start = new Intent(Android.App.Application.Context, typeof(MainActivity));
// my activity name is MainActivity replace it with yours
start.AddFlags(ActivityFlags.NewTask);
Android.App.Application.Context.ApplicationContext.StartActivity(start);
}
catch (Exception ex)
{
var fileService = DependencyService.Get<ISaveException>();
fileService.SaveTextAsync(App.FileName, ex.StackTrace);
}
}
public static void SendEmail(string text)
{
var email = new Intent(Android.Content.Intent.ActionSend);
email.PutExtra(Android.Content.Intent.ExtraEmail, new string[] { "pankit.patel#silvertouch.com" });
email.PutExtra(Android.Content.Intent.ExtraSubject, "Send Crash Report");
email.PutExtra(Android.Content.Intent.ExtraText, "Hello, " + text);
email.AddFlags(ActivityFlags.NewTask);
email.SetType("message/rfc822");
Android.App.Application.Context.ApplicationContext.StartActivity(email);
}
/// <summary>
/// Stop Location Service
/// </summary>
public static void StopLocationService()
{
// Check for nulls in case StartLocationService task has not yet completed.
//Log.Debug("App", "StopGPSTrackingService");
try
{
Intent intent = new Intent(Android.App.Application.Context, typeof(GPSTrackingService));
Android.App.Application.Context.StopService(intent);
}
catch (Exception ex)
{
var fileService = DependencyService.Get<ISaveException>();
fileService.SaveTextAsync(App.FileName, ex.StackTrace);
}
}
}
}
This service is only running when app is open (even in background) but if I close the app then service is automatically stop working. How to solve this issue? I have so many xamarin blogs regarding this topic but I din't get any workaround yet.
How to make background service that should be running even after app is close?

Unity/C# - Connect to Unity Server on another computer

I'm currently writing the Networking backend for a game I'm creating in Unity. The problem I'm facing is connecting to my server on another computer (on the same local network). I'm extremely new to uNet and what it entails, so the help is very appreciated.
I've followed a few tutorials, but none have fixed the problem that I've been facing. There's currently no connection at all coming through to the server. I've initialised the server, created sockets, used Network.Listen() to listen to packets coming through the port - but nothing quite yet.
This is a test project that I'm doing before implementing into the game, so sorry for the not-so-clean code.
The IP's are specified in Inspector, but used "127.0.0.1" to preset it. Sorry for the confusion!
Here's the client-side code:
using UnityEngine;
using System.Collections.Generic;
using UnityEngine.UI;
using UnityEngine.Networking;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
public class ClientConnector : NetworkManager
{
NetworkClient m_client;
public GameObject screenCanvas;
bool socketConnection = false;
bool serverConnection = false;
bool connectingToSocket = false;
bool connectingToServer = false;
bool connectedToServer = false;
public int socketConnectionPort = 8888;
public int serverConnectionPort = 8899;
public string connectionIP = "127.0.0.1";
public int maxConnectionsNumber = 10;
int reliableChannelID;
int socketID;
int socketConnectionID;
int serverConnectionID;
void Start ()
{
NetworkTransport.Init();
m_client = new NetworkClient();
if (SetupConnection())
{
ConnectSocket();
}
if (socketConnection)
{
ConnectServer();
}
}
void OnApplicationQuit()
{
NetworkTransport.Shutdown();
}
public void ConnectSocket()
{
connectingToSocket = true;
byte error;
socketConnectionID = NetworkTransport.Connect(socketID, connectionIP, socketConnectionPort, 0, out error);
if (socketConnectionID != 0)
{
socketConnection = true;
connectingToSocket = false;
}
}
public void ConnectServer()
{
connectingToServer = true;
byte error;
serverConnectionID = NetworkTransport.Connect(socketID, connectionIP, serverConnectionPort, 0, out error);
if (!ReferenceEquals(socketID, 0))
{
serverConnection = true;
connectedToServer = true;
connectingToServer = false;
SendSocketMessage();
}
}
bool SetupConnection()
{
ConnectionConfig config = new ConnectionConfig();
reliableChannelID = config.AddChannel(QosType.Reliable);
HostTopology topology = new HostTopology(config, maxConnectionsNumber);
socketID = NetworkTransport.AddHost(topology, socketConnectionPort);
if (!ReferenceEquals(client, null))
{
client.RegisterHandler(MsgType.Connect, OnConnected);
}
if (ReferenceEquals(socketID, 0))
{
Debug.Log("Socket open. Socket ID is: " + socketConnectionID);
return true;
}
else
{
return false;
}
}
void Update ()
{
ReceiveAndHandlePacket();
}
void ReceiveAndHandlePacket()
{
int recChannelID;
byte[] recBuffer = new byte[1024];
int bufferSize = 1024;
int dataSize;
byte error;
NetworkEventType networkEvent = NetworkTransport.Receive(out socketID, out socketConnectionID, out recChannelID, recBuffer, bufferSize, out dataSize, out error);
switch (networkEvent)
{
case NetworkEventType.Nothing:
{
Debug.Log("No packets!");
break;
}
case NetworkEventType.ConnectEvent:
{
break;
}
case NetworkEventType.DataEvent:
{
Stream stream = new MemoryStream(recBuffer);
bufferSize = (int)stream.Position;
BinaryFormatter formatter = new BinaryFormatter();
string data = formatter.Deserialize(stream) as string;
Debug.Log("Received Packet from Server saying: " + data);
break;
}
case NetworkEventType.DisconnectEvent:
{
Debug.Log("Disconnected.");
break;
}
}
}
void SendSocketMessage()
{
byte error;
byte[] buffer = new byte[1024];
Stream stream = new MemoryStream(buffer);
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(stream, "Connecting from Client.");
int bufferSize = (int)stream.Position;
NetworkTransport.Send(socketID, socketConnectionID, reliableChannelID, buffer, bufferSize, out error);
}
void OnConnected(NetworkMessage netMsg)
{
Debug.Log("Connected to Socket on " + connectionIP);
}
}
and here is the server-side code:
using UnityEngine;
using System.Collections.Generic;
using UnityEngine.UI;
using UnityEngine.Networking;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
public class ServerControl : NetworkManager
{
public GameObject connectionsScreen;
public int maxConnectables;
public int socketConnectionPort = 8888;
public string socketConnectionIP = "127.0.0.1";
public int serverConnectionPort = 8899;
bool useNat;
List<NetworkClient> connections = new List<NetworkClient>();
bool serverInitialized;
int channelID;
int socketID;
int connectionID;
void Start()
{
useNat = !Network.HavePublicAddress();
NetworkTransport.Init();
if (SetupSocket())
{
InitializePeerServer();
}
}
void Update()
{
ReceiveAndHandlePackets();
}
void OnApplicationQuit()
{
NetworkTransport.Shutdown();
}
public bool SetupSocket()
{
ConnectionConfig config = new ConnectionConfig();
channelID = config.AddChannel(QosType.Reliable);
HostTopology topology = new HostTopology(config, maxConnections);
socketID = NetworkTransport.AddHost(topology);
byte error;
connectionID = NetworkTransport.Connect(socketID, socketConnectionIP, socketConnectionPort, 0, out error);
if (connectionID != 0)
{
Debug.Log("Socket open on: " + socketConnectionPort + " with IP: " + socketConnectionIP);
return true;
}
else
return false;
}
public void InitializePeerServer()
{
NetworkConnectionError serverStatus;
serverStatus = Network.InitializeServer(maxConnectables, serverConnectionPort, useNat);
Network.Listen(serverConnectionPort);
switch (serverStatus)
{
case NetworkConnectionError.NoError:
{
serverInitialized = true;
break;
}
default:
{
serverInitialized = false;
break;
}
}
}
public void ReceiveAndHandlePackets()
{
int recChannelID;
byte[] recBuffer = new byte[1024];
int bufferSize = 1024;
int dataSize;
byte error;
NetworkEventType networkEvent = NetworkTransport.Receive(out socketID, out connectionID, out recChannelID, recBuffer, bufferSize, out dataSize, out error);
switch (networkEvent)
{
case NetworkEventType.Nothing:
connectionsScreen.transform.GetChild(6).GetComponent<Text>().text = "Message: \nNo packets!";
break;
case NetworkEventType.ConnectEvent:
{
Debug.Log("Incoming connection.");
break;
}
case NetworkEventType.DataEvent:
{
Stream stream = new MemoryStream(recBuffer);
bufferSize = (int)stream.Position;
BinaryFormatter formatter = new BinaryFormatter();
string data = formatter.Deserialize(stream) as string;
Debug.Log("Received packet, saying: " + data);
connectionsScreen.transform.GetChild(6).GetComponent<Text>().text = "Message Received: " + data;
break;
}
case NetworkEventType.DisconnectEvent:
{
Debug.Log("Disconnected.");
break;
}
}
}
public override void OnClientConnect(NetworkConnection conn)
{
base.OnClientConnect(conn);
NetworkClient client = new NetworkClient(conn);
connections.Add(client);
Debug.Log("New connection from " + conn.address);
}
}
Any and all help is very greatly appreciated!
Thank you!

Correct way to store encryption key for SqlCipher database

I have a Xamarin application and have managed to download my data from my server to my device. I have also got it set up so that it can take a SqlCipher Encryption key to encrypt the data.
My question is where is the correct location to store my key that I use to encrypt this data? Is it to you KeyStore / KeyChain? Which mono classes should I be looking to use?
Due to the popularity of this question I am going to post my implementation of this:
PCL interface
public interface IAuth
{
void CreateStore();
IEnumerable<string> FindAccountsForService(string serviceId);
void Save(string pin,string serviceId);
void Delete(string serviceId);
}
Android
public class IAuthImplementation : IAuth
{
Context context;
KeyStore ks;
KeyStore.PasswordProtection prot;
static readonly object fileLock = new object();
const string FileName = "MyProg.Accounts";
static readonly char[] Password = null;
public void CreateStore()
{
this.context = Android.App.Application.Context;
ks = KeyStore.GetInstance(KeyStore.DefaultType);
prot = new KeyStore.PasswordProtection(Password);
try
{
lock (fileLock)
{
using (var s = context.OpenFileInput(FileName))
{
ks.Load(s, Password);
}
}
}
catch (Java.IO.FileNotFoundException)
{
//ks.Load (null, Password);
LoadEmptyKeyStore(Password);
}
}
public IEnumerable<string> FindAccountsForService(string serviceId)
{
var r = new List<string>();
var postfix = "-" + serviceId;
var aliases = ks.Aliases();
while (aliases.HasMoreElements)
{
var alias = aliases.NextElement().ToString();
if (alias.EndsWith(postfix))
{
var e = ks.GetEntry(alias, prot) as KeyStore.SecretKeyEntry;
if (e != null)
{
var bytes = e.SecretKey.GetEncoded();
var password = System.Text.Encoding.UTF8.GetString(bytes);
r.Add(password);
}
}
}
return r;
}
public void Delete(string serviceId)
{
var alias = MakeAlias(serviceId);
ks.DeleteEntry(alias);
Save();
}
public void Save(string pin, string serviceId)
{
var alias = MakeAlias(serviceId);
var secretKey = new SecretAccount(pin);
var entry = new KeyStore.SecretKeyEntry(secretKey);
ks.SetEntry(alias, entry, prot);
Save();
}
void Save()
{
lock (fileLock)
{
using (var s = context.OpenFileOutput(FileName, FileCreationMode.Private))
{
ks.Store(s, Password);
}
}
}
static string MakeAlias(string serviceId)
{
return "-" + serviceId;
}
class SecretAccount : Java.Lang.Object, ISecretKey
{
byte[] bytes;
public SecretAccount(string password)
{
bytes = System.Text.Encoding.UTF8.GetBytes(password);
}
public byte[] GetEncoded()
{
return bytes;
}
public string Algorithm
{
get
{
return "RAW";
}
}
public string Format
{
get
{
return "RAW";
}
}
}
static IntPtr id_load_Ljava_io_InputStream_arrayC;
void LoadEmptyKeyStore(char[] password)
{
if (id_load_Ljava_io_InputStream_arrayC == IntPtr.Zero)
{
id_load_Ljava_io_InputStream_arrayC = JNIEnv.GetMethodID(ks.Class.Handle, "load", "(Ljava/io/InputStream;[C)V");
}
IntPtr intPtr = IntPtr.Zero;
IntPtr intPtr2 = JNIEnv.NewArray(password);
JNIEnv.CallVoidMethod(ks.Handle, id_load_Ljava_io_InputStream_arrayC, new JValue[]
{
new JValue (intPtr),
new JValue (intPtr2)
});
JNIEnv.DeleteLocalRef(intPtr);
if (password != null)
{
JNIEnv.CopyArray(intPtr2, password);
JNIEnv.DeleteLocalRef(intPtr2);
}
}
Call Create Store in the main activity of Android app first. - This could possibly be improved and remove CreateStrore() from the interface by checking if ks == null in Save and Delete and calling the method if true
iOS
public class IAuthImplementation : IAuth
{
public IEnumerable<string> FindAccountsForService(string serviceId)
{
var query = new SecRecord(SecKind.GenericPassword);
query.Service = serviceId;
SecStatusCode result;
var records = SecKeyChain.QueryAsRecord(query, 1000, out result);
return records != null ?
records.Select(GetAccountFromRecord).ToList() :
new List<string>();
}
public void Save(string pin, string serviceId)
{
var statusCode = SecStatusCode.Success;
var serializedAccount = pin;
var data = NSData.FromString(serializedAccount, NSStringEncoding.UTF8);
//
// Remove any existing record
//
var existing = FindAccount(serviceId);
if (existing != null)
{
var query = new SecRecord(SecKind.GenericPassword);
query.Service = serviceId;
statusCode = SecKeyChain.Remove(query);
if (statusCode != SecStatusCode.Success)
{
throw new Exception("Could not save account to KeyChain: " + statusCode);
}
}
//
// Add this record
//
var record = new SecRecord(SecKind.GenericPassword);
record.Service = serviceId;
record.Generic = data;
record.Accessible = SecAccessible.WhenUnlocked;
statusCode = SecKeyChain.Add(record);
if (statusCode != SecStatusCode.Success)
{
throw new Exception("Could not save account to KeyChain: " + statusCode);
}
}
public void Delete(string serviceId)
{
var query = new SecRecord(SecKind.GenericPassword);
query.Service = serviceId;
var statusCode = SecKeyChain.Remove(query);
if (statusCode != SecStatusCode.Success)
{
throw new Exception("Could not delete account from KeyChain: " + statusCode);
}
}
string GetAccountFromRecord(SecRecord r)
{
return NSString.FromData(r.Generic, NSStringEncoding.UTF8);
}
string FindAccount(string serviceId)
{
var query = new SecRecord(SecKind.GenericPassword);
query.Service = serviceId;
SecStatusCode result;
var record = SecKeyChain.QueryAsRecord(query, out result);
return record != null ? GetAccountFromRecord(record) : null;
}
public void CreateStore()
{
throw new NotImplementedException();
}
}
WP
public class IAuthImplementation : IAuth
{
public IEnumerable<string> FindAccountsForService(string serviceId)
{
using (var store = IsolatedStorageFile.GetUserStoreForApplication())
{
string[] auths = store.GetFileNames("MyProg");
foreach (string path in auths)
{
using (var stream = new BinaryReader(new IsolatedStorageFileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, store)))
{
int length = stream.ReadInt32();
byte[] data = stream.ReadBytes(length);
byte[] unprot = ProtectedData.Unprotect(data, null);
yield return Encoding.UTF8.GetString(unprot, 0, unprot.Length);
}
}
}
}
public void Save(string pin, string serviceId)
{
byte[] data = Encoding.UTF8.GetBytes(pin);
byte[] prot = ProtectedData.Protect(data, null);
var path = GetAccountPath(serviceId);
using (var store = IsolatedStorageFile.GetUserStoreForApplication())
using (var stream = new IsolatedStorageFileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, store))
{
stream.WriteAsync(BitConverter.GetBytes(prot.Length), 0, sizeof(int)).Wait();
stream.WriteAsync(prot, 0, prot.Length).Wait();
}
}
public void Delete(string serviceId)
{
var path = GetAccountPath(serviceId);
using (var store = IsolatedStorageFile.GetUserStoreForApplication())
{
store.DeleteFile(path);
}
}
private string GetAccountPath(string serviceId)
{
return String.Format("{0}", serviceId);
}
public void CreateStore()
{
throw new NotImplementedException();
}
}
This is an adaptation of the Xamarin.Auth library (Found Here) but removes the dependency from the Xamarin.Auth library to provide cross platform use through the interface in the PCL. For this reason I have simplified it to only save one string. This is probably not the best implementation but it works in my case. Feel free to expand upon this
There is a nuget package called KeyChain.NET that encapsulated this logic for iOs, Android and Windows Phone.
It's open source and you have find sample at its github repository
More info at this blog post

Categories