<commit_before>﻿using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Security.Cryptography.X509Certificates;
using System.Linq;
using System.Net.Sockets;
using System.Net.Security;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Net;
using PushSharp.Common;

namespace PushSharp.Apple
{
	public class ApplePushChannel : PushChannelBase
	{
		#region Constants
		private const string hostSandbox = "gateway.sandbox.push.apple.com";
		private const string hostProduction = "gateway.push.apple.com";
		private const int initialReconnectDelay = 3000;
		#endregion
				
		public delegate void ConnectingDelegate(string host, int port);
		public event ConnectingDelegate OnConnecting;

		public delegate void ConnectedDelegate(string host, int port);
		public event ConnectedDelegate OnConnected;

		public delegate void ConnectionFailureDelegate(ConnectionFailureException exception);
		public event ConnectionFailureDelegate OnConnectionFailure;

		public delegate void WaitBeforeReconnectDelegate(int millisecondsToWait);
		public event WaitBeforeReconnectDelegate OnWaitBeforeReconnect;


		ApplePushChannelSettings appleSettings = null;
		List<SentNotification> sentNotifications = new List<SentNotification>();

		public ApplePushChannel(ApplePushChannelSettings settings) : base(settings)
		{
			this.appleSettings = settings as ApplePushChannelSettings;

			//Need to load the private key seperately from apple
			// Fixed by <EMAIL> :
			//      The default is UserKeySet, which has caused internal encryption errors,
			//      Because of lack of permissions on most hosting services.
			//      So MachineKeySet should be used instead.
			certificate = new X509Certificate2(settings.CertificateData, settings.CertificateFilePassword, 
				X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);

			certificates = new X509CertificateCollection();
			certificates.Add(certificate);

			//Start our cleanup task
			taskCleanup = new Task(() => Cleanup(), TaskCreationOptions.LongRunning);
			taskCleanup.ContinueWith((t) => { var ex = t.Exception; }, TaskContinuationOptions.OnlyOnFaulted);
			taskCleanup.Start();
		}

		object sentLock = new object();
		object streamWriteLock = new object();
		int reconnectDelay = 3000;
		float reconnectBackoffMultiplier = 1.5f;
		
		byte[] readBuffer = new byte[6];
		bool connected = false;
		
		X509Certificate certificate;
		X509CertificateCollection certificates;
		TcpClient client;
		SslStream stream;		
			
		Task taskCleanup;
		
		protected override void SendNotification(Common.Notification notification)
		{
			var appleNotification = notification as AppleNotification;

			bool isOkToSend = true;
			byte[] notificationData = new byte[] {};

			try
			{
				notificationData = appleNotification.ToBytes();
			}
			catch (NotificationFailureException nfex)
			{
				//Bad notification format already
				isOkToSend = false;

				this.Events.RaiseNotificationSendFailure(notification, nfex);
			}

			if (isOkToSend)
			{
				Connect();
				
				try 
				{
					lock (streamWriteLock)
					{
						lock (sentLock)
						{
							stream.Write(notificationData);
						
							sentNotifications.Add(new SentNotification(appleNotification));
						}
					}
				}
				catch (Exception ex)
				{ 
					this.QueueNotification(notification); 
				} //If this failed, we probably had a networking error, so let's requeue the notification
			}
		}

		public override void Stop(bool waitForQueueToDrain)
		{
			stopping = true;

			//See if we want to wait for the queue to drain before stopping
			if (waitForQueueToDrain)
			{
				while (QueuedNotificationCount > 0 || sentNotifications.Count > 0)
					Thread.Sleep(50);
			}

			//Sleep a bit to prevent any race conditions
			//especially since our cleanup method may need 3 seconds
			Thread.Sleep(5000);

			if (!CancelTokenSource.IsCancellationRequested)
				CancelTokenSource.Cancel();

			//Wait on our tasks for a maximum of 30 seconds
			Task.WaitAll(new Task[] { base.taskSender, taskCleanup }, 30000);
		}
		
		void Reader()
		{
			try
			{
				var result = stream.BeginRead(readBuffer, 0, 6, new AsyncCallback((asyncResult) =>
				{
					lock (sentLock)
					{
						try
						{
							var bytesRead = stream.EndRead(asyncResult);

							if (bytesRead > 0)
							{
								//Get the enhanced format response
								// byte 0 is always '1', byte 1 is the status, bytes 2,3,4,5 are the identifier of the notification
								var status = readBuffer[1];
								var identifier = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(readBuffer, 2));

								int failedNotificationIndex = -1;
								SentNotification failedNotification = null;

								//Try and find the failed notification in our sent list
								for (int i = 0; i < sentNotifications.Count; i++)
								{
									var n = sentNotifications[i];

									if (n.Identifier.Equals(identifier))
									{
										failedNotificationIndex = i;
										failedNotification = n;
										break;
									}
								}

								//Don't bother doing anything unless we know what failed
								if (failedNotification != null && failedNotificationIndex > -1)
								{
									//Anything before the failed message must have sent OK
									// so let's expedite the success status Success for all those before the failed one
									if (failedNotificationIndex > 0)
									{<commit_msg>Close apns stream on error<commit_after>﻿using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Security.Cryptography.X509Certificates;
using System.Linq;
using System.Net.Sockets;
using System.Net.Security;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Net;
using PushSharp.Common;

namespace PushSharp.Apple
{
	public class ApplePushChannel : PushChannelBase
	{
		#region Constants
		private const string hostSandbox = "gateway.sandbox.push.apple.com";
		private const string hostProduction = "gateway.push.apple.com";
		private const int initialReconnectDelay = 3000;
		#endregion
				
		public delegate void ConnectingDelegate(string host, int port);
		public event ConnectingDelegate OnConnecting;

		public delegate void ConnectedDelegate(string host, int port);
		public event ConnectedDelegate OnConnected;

		public delegate void ConnectionFailureDelegate(ConnectionFailureException exception);
		public event ConnectionFailureDelegate OnConnectionFailure;

		public delegate void WaitBeforeReconnectDelegate(int millisecondsToWait);
		public event WaitBeforeReconnectDelegate OnWaitBeforeReconnect;


		ApplePushChannelSettings appleSettings = null;
		List<SentNotification> sentNotifications = new List<SentNotification>();

		public ApplePushChannel(ApplePushChannelSettings settings) : base(settings)
		{
			this.appleSettings = settings as ApplePushChannelSettings;

			//Need to load the private key seperately from apple
			// Fixed by <EMAIL> :
			//      The default is UserKeySet, which has caused internal encryption errors,
			//      Because of lack of permissions on most hosting services.
			//      So MachineKeySet should be used instead.
			certificate = new X509Certificate2(settings.CertificateData, settings.CertificateFilePassword, 
				X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);

			certificates = new X509CertificateCollection();
			certificates.Add(certificate);

			//Start our cleanup task
			taskCleanup = new Task(() => Cleanup(), TaskCreationOptions.LongRunning);
			taskCleanup.ContinueWith((t) => { var ex = t.Exception; }, TaskContinuationOptions.OnlyOnFaulted);
			taskCleanup.Start();
		}

		object sentLock = new object();
		object streamWriteLock = new object();
		int reconnectDelay = 3000;
		float reconnectBackoffMultiplier = 1.5f;
		
		byte[] readBuffer = new byte[6];
		bool connected = false;
		
		X509Certificate certificate;
		X509CertificateCollection certificates;
		TcpClient client;
		SslStream stream;		
			
		Task taskCleanup;
		
		protected override void SendNotification(Common.Notification notification)
		{
			var appleNotification = notification as AppleNotification;

			bool isOkToSend = true;
			byte[] notificationData = new byte[] {};

			try
			{
				notificationData = appleNotification.ToBytes();
			}
			catch (NotificationFailureException nfex)
			{
				//Bad notification format already
				isOkToSend = false;

				this.Events.RaiseNotificationSendFailure(notification, nfex);
			}

			if (isOkToSend)
			{
				Connect();
				
				try 
				{
					lock (streamWriteLock)
					{
						lock (sentLock)
						{
							stream.Write(notificationData);
						
							sentNotifications.Add(new SentNotification(appleNotification));
						}
					}
				}
				catch (Exception ex)
				{ 
					this.QueueNotification(notification); 
				} //If this failed, we probably had a networking error, so let's requeue the notification
			}
		}

		public override void Stop(bool waitForQueueToDrain)
		{
			stopping = true;

			//See if we want to wait for the queue to drain before stopping
			if (waitForQueueToDrain)
			{
				while (QueuedNotificationCount > 0 || sentNotifications.Count > 0)
					Thread.Sleep(50);
			}

			//Sleep a bit to prevent any race conditions
			//especially since our cleanup method may need 3 seconds
			Thread.Sleep(5000);

			if (!CancelTokenSource.IsCancellationRequested)
				CancelTokenSource.Cancel();

			//Wait on our tasks for a maximum of 30 seconds
			Task.WaitAll(new Task[] { base.taskSender, taskCleanup }, 30000);
		}
		
		void Reader()
		{
			try
			{
				var result = stream.BeginRead(readBuffer, 0, 6, new AsyncCallback((asyncResult) =>
				{
					lock (sentLock)
					{
						try
						{
							var bytesRead = stream.EndRead(asyncResult);

							if (bytesRead > 0)
							{
								//We now expect apple to close the connection on us anyway, so let's try and close things
								// up here as well to get a head start
								//Hopefully this way we have less messages written to the stream that we have to requeue
								try { stream.Close(); stream.Dispose(); }
								catch { }

								try { client.Close(); stream.Dispose(); }
								catch { }

								//Get the enhanced format response
								// byte 0 is always '1', byte 1 is the status, bytes 2,3,4,5 are the identifier of the notification
								var status = readBuffer[1];
								var identifier = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(readBuffer, 2));

								int failedNotificationIndex = -1;
								SentNotification failedNotification = null;

								//Try and find the failed notification in our sent list
								for (int i = 0; i < sentNotifications.Count; i++)
								{
									var n = sentNotifications[i];

									if (n.Identifier.Equals(identifier))
									{
										failedNotificationIndex = i;
										failedNotification = n;
										break;
									}
								}

								//Don't bother doing anything unless we know what failed
								if (failedNotification != null && failedNotificationIndex > -1)
								{
									//Anything before the failed message must have sent OK
									// so let's expedite the success status Success for all those before the failed one
									if (failedNotificationIndex > 0)
									{
{'fraction_non_alphanumeric': 0.07864871997888626, 'fraction_numerical': 0.012316354359109704, 'mean_word_length': 3.2323157110945644, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 25, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2958215', 'n_tokens_mistral': 4126, 'n_tokens_neox': 2993, 'n_words': 940}