using Serilog; using System; using System.IO; using System.Net; using System.Text; using System.Threading.Tasks; using TINK.Model.Repository.Exception; using TINK.Model.Repository.Request; using TINK.Model.Repository.Response; using TINK.Model.Logging; using TINK.Repository.Response; namespace TINK.Model.Repository { /// Object which manages calls to copri. public class CopriCallsHttps : ICopriServer { /// Builds requests. private IRequestBuilder requestBuilder; /// Initializes a instance of the copri calls https object. /// Host to connect to. /// Id of the merchant. /// Holds the name and version of the TINKApp. /// Session cookie if user is logged in, null otherwise. public CopriCallsHttps( Uri p_oCopriHost, string p_strMerchantId, string userAgent, string sessionCookie = null) { m_oCopriHost = p_oCopriHost ?? throw new System.Exception($"Can not construct {GetType().ToString()}- object. Uri of copri host must not be null."); UserAgent = !string.IsNullOrEmpty(userAgent) ? userAgent : throw new System.Exception($"Can not construct {GetType().ToString()}- object. User agent must not be null or empty."); requestBuilder = string.IsNullOrEmpty(sessionCookie) ? new RequestBuilder(p_strMerchantId) as IRequestBuilder : new RequestBuilderLoggedIn(p_strMerchantId, sessionCookie); } /// Holds the URL for rest calls. private Uri m_oCopriHost; /// Spacifies name and version of app. private string UserAgent { get; } /// Returns true because value requested form copri server are returned. public bool IsConnected => true; /// Gets the merchant id. public string MerchantId => requestBuilder.MerchantId; /// Gets the session cookie if user is logged in, an empty string otherwise. public string SessionCookie => requestBuilder.SessionCookie; /// Logs user in. /// Mailaddress of user to log in. /// Password to log in. /// Id specifying user and hardware. /// Response which holds auth cookie public async Task DoAuthorizationAsync( string mailAddress, string password, string deviceId) { return await DoAuthorizationAsync( m_oCopriHost.AbsoluteUri, requestBuilder.DoAuthorization(mailAddress, password, deviceId), () => requestBuilder.DoAuthorization(mailAddress, "********", deviceId), UserAgent); } /// Logs user out. /// Response which holds auth cookie public async Task DoAuthoutAsync() { return await DoAuthoutAsync(m_oCopriHost.AbsoluteUri, requestBuilder.DoAuthout(), UserAgent); } /// Gets bikes available. /// Response holding list of bikes. public async Task GetBikesAvailableAsync() { return await GetBikesAvailableAsync(m_oCopriHost.AbsoluteUri, requestBuilder.GetBikesAvailable(), UserAgent); } /// Gets a list of bikes reserved/ booked by acctive user. /// Response holding list of bikes. public async Task GetBikesOccupiedAsync() { try { return await GetBikesOccupiedAsync(m_oCopriHost.AbsoluteUri, requestBuilder.GetBikesOccupied(), UserAgent); } catch (NotSupportedException) { // No user logged in. await Task.CompletedTask; return ResponseHelper.GetBikesOccupiedNone(); } } /// Get list of stations. /// List of files. public async Task GetStationsAsync() { return await GetStationsAsync(m_oCopriHost.AbsoluteUri, requestBuilder.GetStations(), UserAgent); } /// Get authentication keys. /// Id of the bike to get keys for. /// Response holding authentication keys. public async Task GetAuthKeys(int bikeId) => await GetAuthKeysAsync(m_oCopriHost.AbsoluteUri, requestBuilder.CalculateAuthKeys(bikeId), UserAgent); /// Gets booking request response. /// Id of the bike to book. /// Holds the uri of the operator or null, in case of single operator setup. /// Booking response. public async Task DoReserveAsync( int bikeId, Uri operatorUri) { return await DoReserveAsync( operatorUri?.AbsoluteUri ?? m_oCopriHost.AbsoluteUri, requestBuilder.DoReserve(bikeId), UserAgent); } /// Gets canel booking request response. /// Id of the bike to book. /// Holds the uri of the operator or null, in case of single operator setup. /// Response on cancel booking request. public async Task DoCancelReservationAsync( int bikeId, Uri operatorUri) { return await DoCancelReservationAsync( operatorUri?.AbsoluteUri ?? m_oCopriHost.AbsoluteUri, requestBuilder.DoCancelReservation(bikeId), UserAgent); } /// Get authentication keys. /// Id of the bike to get keys for. /// Holds the uri of the operator or null, in case of single operator setup. /// Response holding authentication keys. public async Task CalculateAuthKeysAsync( int bikeId, Uri operatorUri) => await GetAuthKeysAsync( operatorUri?.AbsoluteUri ?? m_oCopriHost.AbsoluteUri, requestBuilder.CalculateAuthKeys(bikeId), UserAgent); /// Updates lock state for a booked bike. /// Id of the bike to update locking state for. /// Geolocation of lock. /// New locking state. /// Holds the filling level percentage of the battery. /// Holds the uri of the operator or null, in case of single operator setup. /// Response on updating locking state. public async Task UpdateLockingStateAsync( int bikeId, LocationDto location, lock_state state, double batteryLevel, Uri operatorUri) { return await DoUpdateLockingStateAsync( operatorUri?.AbsoluteUri ?? m_oCopriHost.AbsoluteUri, requestBuilder.UpateLockingState(bikeId, location, state, batteryLevel), UserAgent); } /// Gets booking request request. /// Id of the bike to book. /// Used to publish GUID from app to copri. Used for initial setup of bike in copri. /// Holds the filling level percentage of the battery. /// Holds the uri of the operator or null, in case of single operator setup. /// Requst on booking request. public async Task DoBookAsync( int bikeId, Guid guid, double batteryPercentage, Uri operatorUri) { return await DoBookAsync( operatorUri?.AbsoluteUri ?? m_oCopriHost.AbsoluteUri, requestBuilder.DoBook(bikeId, guid, batteryPercentage), UserAgent); } /// Returns a bike. /// Id of the bike to return. /// Geolocation of lock. /// Holds the uri of the operator or null, in case of single operator setup. /// Response on returning request. public async Task DoReturn( int bikeId, LocationDto location, Uri operatorUri) { return await DoReturn( operatorUri?.AbsoluteUri ?? m_oCopriHost.AbsoluteUri, requestBuilder.DoReturn(bikeId, location), UserAgent); } /// Submits feedback to copri server. /// True if bike is broken. /// General purpose message or error description. /// Holds the uri of the operator or null, in case of single operator setup. /// Response on submitting feedback request. public async Task DoSubmitFeedback( string message, bool isBikeBroken, Uri operatorUri) => await DoSubmitFeedback( operatorUri?.AbsoluteUri ?? m_oCopriHost.AbsoluteUri, requestBuilder.DoSubmitFeedback(message, isBikeBroken), UserAgent); /// Logs user in. /// Host to connect to. /// Command to log user in. /// Response which holds auth cookie public static async Task DoAuthorizationAsync( string copriHost, string command, Func displayCommand, string userAgent = null) { #if !WINDOWS_UWP /// Extract session cookie from response. string l_oResponseText = string.Empty; try { l_oResponseText = await PostAsync( copriHost, command, userAgent, displayCommand); // Do not include password into exception output when an error occurres. } catch (System.Exception l_oException) { if (l_oException.GetIsConnectFailureException()) { throw new WebConnectFailureException("Login fehlgeschlagen aufgrund eines Netzwerkfehlers.", l_oException); } if (l_oException.GetIsForbiddenException()) { throw new WebForbiddenException("Login fehlgeschlagen aufgrund eines Netzwerkfehlers.", l_oException); } throw; } return JsonConvert.DeserializeObject>(l_oResponseText)?.tinkjson; #else return null; #endif } /// Logs user out. /// Host to connect to. /// Command to log user out. public static async Task DoAuthoutAsync( string p_strCopriHost, string p_oCommand, string userAgent = null) { #if !WINDOWS_UWP string l_oLogoutResponse; try { l_oLogoutResponse = await PostAsync(p_strCopriHost, p_oCommand, userAgent); } catch (System.Exception l_oException) { if (l_oException.GetIsConnectFailureException()) { throw new WebConnectFailureException("Login fehlgeschlagen wegen Netzwerkfehler.", l_oException); } if (l_oException.GetIsForbiddenException()) { throw new WebForbiddenException("Login fehlgeschlagen wegen Netzwerkfehler.", l_oException); } throw; } /// Extract session cookie from response. return JsonConvert.DeserializeObject>(l_oLogoutResponse)?.tinkjson; #else return null; #endif } /// /// Get list of stations from file. /// /// URL of the copri host to connect to. /// Command to get stations. /// List of files. public static async Task GetStationsAsync( string p_strCopriHost, string p_oCommand, string userAgent = null) { #if !WINDOWS_UWP string l_oStationsAllResponse; try { l_oStationsAllResponse = await PostAsync(p_strCopriHost, p_oCommand, userAgent); } catch (System.Exception l_oException) { if (l_oException.GetIsConnectFailureException()) { throw new WebConnectFailureException("Abfage der verfügbaren Räder fehlgeschlagen wegen Netzwerkfehler.", l_oException); } if (l_oException.GetIsForbiddenException()) { throw new WebForbiddenException("Abfage der verfügbaren Räder fehlgeschlagen wegen Netzwerkfehler.", l_oException); } throw; } // Extract bikes from response. return JsonConvert.DeserializeObject>(l_oStationsAllResponse)?.tinkjson; #else return null; #endif } /// Gets a list of bikes from Copri. /// URL of the copri host to connect to. /// Command to get bikes. /// Response holding list of bikes. public static async Task GetBikesAvailableAsync( string p_strCopriHost, string l_oCommand, string userAgent = null) { #if !WINDOWS_UWP string l_oBikesAvaialbeResponse; try { l_oBikesAvaialbeResponse = await PostAsync(p_strCopriHost, l_oCommand, userAgent); } catch (System.Exception l_oException) { if (l_oException.GetIsConnectFailureException()) { throw new WebConnectFailureException("Abfage der verfügbaren Räder fehlgeschlagen wegen Netzwerkfehler.", l_oException); } if (l_oException.GetIsForbiddenException()) { throw new WebForbiddenException("Abfage der verfügbaren Räder fehlgeschlagen wegen Netzwerkfehler.", l_oException); } throw; } // Extract bikes from response. return CopriCallsStatic.DeserializeBikesAvailableResponse(l_oBikesAvaialbeResponse); #else return null; #endif } /// Gets a list of bikes reserved/ booked by acctive user from Copri. /// URL of the copri host to connect to. /// Command to post. /// Response holding list of bikes. public static async Task GetBikesOccupiedAsync( string p_strCopriHost, string p_oCommand, string userAgent = null) { #if !WINDOWS_UWP string l_oBikesOccupiedResponse; try { l_oBikesOccupiedResponse = await PostAsync(p_strCopriHost, p_oCommand, userAgent); } catch (System.Exception l_oException) { if (l_oException.GetIsConnectFailureException()) { throw new WebConnectFailureException("Abfage der reservierten/ gebuchten Räder fehlgeschlagen wegen Netzwerkfehler.", l_oException); } if (l_oException.GetIsForbiddenException()) { throw new WebForbiddenException("Abfage der reservierten/ gebuchten Räder fehlgeschlagen wegen Netzwerkfehler.", l_oException); } throw; } // Extract bikes from response. return CopriCallsStatic.DeserializeBikesOccupiedResponse(l_oBikesOccupiedResponse); #else return null; #endif } /// Get auth keys from COPRI. /// Host to connect to. /// Command to log user in. /// Response on booking request. public static async Task GetAuthKeysAsync( string p_strCopriHost, string p_oCommand, string userAgent = null) { #if !WINDOWS_UWP string l_oBikesAvaialbeResponse; try { l_oBikesAvaialbeResponse = await PostAsync(p_strCopriHost, p_oCommand, userAgent); } catch (System.Exception l_oException) { if (l_oException.GetIsConnectFailureException()) { throw new WebConnectFailureException("Schlosssuche wegen Netzwerkfehler fehlgeschlagen.", l_oException); } if (l_oException.GetIsForbiddenException()) { throw new WebForbiddenException("Schlosssuche wegen Netzwerkfehler fehlgeschlagen.", l_oException); } throw; } // Extract bikes from response. return JsonConvert.DeserializeObject>(l_oBikesAvaialbeResponse)?.tinkjson; #else return null; #endif } /// Gets booking request response. /// Host to connect to. /// Command to log user in. /// Response on booking request. public static async Task DoReserveAsync( string p_strCopriHost, string p_oCommand, string userAgent = null) { #if !WINDOWS_UWP string l_oBikesAvaialbeResponse; try { l_oBikesAvaialbeResponse = await PostAsync(p_strCopriHost, p_oCommand, userAgent); } catch (System.Exception l_oException) { if (l_oException.GetIsConnectFailureException()) { throw new WebConnectFailureException("Reservierung des Fahrrads wegen Netzwerkfehler fehlgeschlagen.", l_oException); } if (l_oException.GetIsForbiddenException()) { throw new WebForbiddenException("Reservierung des Fahrrads wegen Netzwerkfehler fehlgeschlagen.", l_oException); } throw; } // Extract bikes from response. return JsonConvert.DeserializeObject>(l_oBikesAvaialbeResponse)?.tinkjson; #else return null; #endif } /// Gets canel booking request response. /// Host to connect to. /// Command to log user in. /// Response on cancel booking request. public static async Task DoCancelReservationAsync( string copriHost, string command, string userAgent = null) { #if !WINDOWS_UWP string l_oBikesAvaialbeResponse; try { l_oBikesAvaialbeResponse = await PostAsync(copriHost, command, userAgent); } catch (System.Exception l_oException) { if (l_oException.GetIsConnectFailureException()) { throw new WebConnectFailureException("Reservierung des Fahrrads aufgrund eines Netzwerkfehlers fehlgeschlagen.", l_oException); } if (l_oException.GetIsForbiddenException()) { throw new WebForbiddenException("Reservierung des Fahrrads aufgrund eines Netzwerkfehlers fehlgeschlagen.", l_oException); } throw; } // Extract bikes from response. return JsonConvert.DeserializeObject>(l_oBikesAvaialbeResponse)?.tinkjson; #else return null; #endif } public static async Task DoUpdateLockingStateAsync( string copriHost, string command, string agent = null) { #if !WINDOWS_UWP string l_oBikesAvaialbeResponse; try { l_oBikesAvaialbeResponse = await PostAsync(copriHost, command, agent); } catch (System.Exception l_oException) { if (l_oException.GetIsConnectFailureException()) { throw new WebConnectFailureException("Aktualisierung des Schlossstatuses wegen Netzwerkfehler fehlgeschlagen.", l_oException); } if (l_oException.GetIsForbiddenException()) { throw new WebForbiddenException("Aktualisierung des Schlossstatuses wegen Netzwerkfehler fehlgeschlagen.", l_oException); } throw; } // Extract bikes from response. return JsonConvert.DeserializeObject>(l_oBikesAvaialbeResponse)?.tinkjson; #else return null; #endif } public static async Task DoBookAsync( string copriHost, string command, string agent = null) { #if !WINDOWS_UWP string l_oBikesAvaialbeResponse; try { l_oBikesAvaialbeResponse = await PostAsync(copriHost, command, agent); } catch (System.Exception l_oException) { if (l_oException.GetIsConnectFailureException()) { throw new WebConnectFailureException("Buchung des Fahrrads wegen Netzwerkfehler fehlgeschlagen.", l_oException); } if (l_oException.GetIsForbiddenException()) { throw new WebForbiddenException("Buchung des Fahrrads wegen Netzwerkfehler fehlgeschlagen.", l_oException); } throw; } // Extract bikes from response. return JsonConvert.DeserializeObject>(l_oBikesAvaialbeResponse)?.tinkjson; #else return null; #endif } public static async Task DoReturn( string copriHost, string command, string userAgent = null) { #if !WINDOWS_UWP string l_oBikesAvaialbeResponse; try { l_oBikesAvaialbeResponse = await PostAsync(copriHost, command, userAgent); } catch (System.Exception l_oException) { if (l_oException.GetIsConnectFailureException()) { throw new WebConnectFailureException("Rückgabe des Fahrrads aufgrund eines Netzwerkfehlers fehlgeschlagen.", l_oException); } if (l_oException.GetIsForbiddenException()) { throw new WebForbiddenException("Rückgabe des Fahrrads aufgrund eines Netzwerkfehlers fehlgeschlagen.", l_oException); } throw; } // Extract bikes from response. return JsonConvert.DeserializeObject>(l_oBikesAvaialbeResponse)?.tinkjson; #else return null; #endif } public async Task DoSubmitFeedback( string copriHost, string command, string userAgent = null) { #if !WINDOWS_UWP string userFeedbackResponse; try { userFeedbackResponse = await PostAsync(copriHost, command, userAgent); } catch (System.Exception l_oException) { if (l_oException.GetIsConnectFailureException()) { throw new WebConnectFailureException("Senden der Rückmeldung aufgrund eines Netzwerkfehlers fehlgeschlagen.", l_oException); } if (l_oException.GetIsForbiddenException()) { throw new WebForbiddenException("Senden der Rückmeldung aufgrund eines Netzwerkfehlers fehlgeschlagen.", l_oException); } throw; } // Extract bikes from response. return JsonConvert.DeserializeObject>(userFeedbackResponse)?.tinkjson; #else return null; #endif } /// http get- request. /// Ulr to get info from. /// response from server public static async Task Get(string Url) { string result = string.Empty; HttpWebRequest myRequest = (HttpWebRequest)WebRequest.Create(Url); myRequest.Method = "GET"; using (var myResponse = await myRequest.GetResponseAsync()) { using (var sr = new StreamReader(myResponse.GetResponseStream(), Encoding.UTF8)) { result = sr.ReadToEnd(); } } return result; } /// http- post request. /// Command to send. /// Command to display/ log used for error handling. /// Address of server to communicate with. /// Response as text. /// An unused member PostAsyncHttpClient using HttpClient for posting was removed 2020-04-02. private static async Task PostAsync( string uRL, string p_strCommand, string userAgent = null, Func p_oDisplayCommand = null) { if (string.IsNullOrEmpty(p_strCommand)) { Log.ForContext().Fatal("Can not post command. Command must not be null or empty."); throw new ArgumentException("Can not post command. Command must not be null or empty."); } if (string.IsNullOrEmpty(uRL)) { Log.ForContext().Fatal("Can not post command. Host must not be null or empty."); throw new ArgumentException("Can not post command. Host must not be null or empty."); } // Get display version of command to used for display/ logging (password should never be included in output) Func displayCommandFunc = p_oDisplayCommand ?? delegate () { return p_strCommand; }; try { #if !WINDOWS_UWP var l_strHost = uRL; // Returns a http request. var l_oRequest = WebRequest.CreateHttp(l_strHost); l_oRequest.Method = "POST"; l_oRequest.ContentType = "application/x-www-form-urlencoded"; l_oRequest.UserAgent = userAgent; // Workaround for issue https://bugzilla.xamarin.com/show_bug.cgi?id=57705 // If not KeepAlive is set to true Stream.Write leads arbitrarily to an object disposed exception. l_oRequest.KeepAlive = true; byte[] l_oPostData = Encoding.UTF8.GetBytes(p_strCommand); l_oRequest.ContentLength = l_oPostData.Length; // Get the request stream. using (Stream l_oDataStream = await l_oRequest.GetRequestStreamAsync()) { // Write the data to the request stream. await l_oDataStream.WriteAsync(l_oPostData, 0, l_oPostData.Length); } // Get the response. var l_oResponse = await l_oRequest.GetResponseAsync() as HttpWebResponse; if (l_oResponse == null) { throw new System.Exception(string.Format("Reserve request failed. Response form from server was not of expected type.")); } if (l_oResponse.StatusCode != HttpStatusCode.OK) { throw new CommunicationException(string.Format( "Posting request {0} failed. Expected status code is {1} but was {2}.", displayCommandFunc(), HttpStatusCode.OK, l_oResponse.StatusCode)); } string response = string.Empty; // Get the request stream. using (Stream l_oDataStream = l_oResponse.GetResponseStream()) using (StreamReader l_oReader = new StreamReader(l_oDataStream)) { // Read the content. response = l_oReader.ReadToEnd(); // Display the content. Console.WriteLine(response); // Clean up the streams. l_oResponse.Close(); } Log.ForContext().Verbose("Post command {DisplayCommand} to host {URL} received {ResponseText:j}.", displayCommandFunc(), uRL, response); return response; #else return null; #endif } catch (System.Exception l_oException) { Log.ForContext().InformationOrError("Posting command {DisplayCommand} to host {URL} failed. {Exception}.", displayCommandFunc(), uRL, l_oException); throw; } } } }