/// <summary>
        ///     Initialize a new MatchmakingRequest object
        /// </summary>
        /// <param name="client">
        ///     An already existing MatchmakingClient to use as the client for the request
        /// </param>
        /// <param name="request">
        ///     The ticket data to send with the matchmaking request.
        ///     Data will be copied internally on construction and treated as immutable.
        /// </param>
        /// <param name="timeoutMs">
        ///     The amount of time to wait (in ms) before aborting an incomplete MatchmakingRequest after it has been sent.
        ///     Match requests that time out on the client side will immediately be set to completed and stop listening for a match
        ///     assignment.
        /// </param>
        public MatchmakingRequest(MatchmakingClient client, CreateTicketRequest request, uint timeoutMs = 0)
        {
            m_Client = client
                       ?? throw new ArgumentNullException(nameof(client), logPre + $"Matchmaking {nameof(client)} must not be null");

            if (request == null)
            {
                throw new ArgumentNullException(nameof(request), logPre + $"{nameof(request)} must be a non-null, valid {nameof(CreateTicketRequest)} object");
            }

            // Try to immediately create and store the protobuf version of the CreateTicketRequest
            //  This allows us to fail fast, and also copies the data to prevent it from being mutable
            //  This may cause exceptions inside the protobuf code, which is fine since we're in the constructor
            var createTicketRequest = new Protobuf.CreateTicketRequest();


            string key = nameof(QosTicketInfo).ToLower();

            if (request.Properties != null && !request.Properties.ContainsKey(key))
            {
                QosTicketInfo results = QosConnector.Instance.Execute();
                if (results?.QosResults?.Count > 0)
                {
                    request.Properties = request.Properties ?? new Dictionary <string, string>();
                    request.Properties.Add(key, JsonUtility.ToJson(results));
                }
            }

            // Only set properties if not null
            // Request properties have to be massaged to be protobuf ByteString compatible
            if (request.Properties != null)
            {
                foreach (var kvp in request.Properties)
                {
                    var keyToLower = kvp.Key.ToLower();

#if UNITY_EDITOR || DEVELOPMENT_BUILD
                    if (!kvp.Key.Equals(keyToLower))
                    {
                        Debug.LogWarning(logPre + $"Ticket property with key {kvp.Key} must be all lowercase; changing in-place.");
                    }
#endif
                    createTicketRequest.Properties.Add(keyToLower, ByteString.CopyFrom(Encoding.UTF8.GetBytes(kvp.Value)));
                }
            }

            // Only add attributes if they exist
            if (request?.Attributes?.Count > 0)
            {
                createTicketRequest.Attributes.Add(request.Attributes);
            }

            m_CreateTicketRequest = createTicketRequest;

            State = MatchmakingRequestState.NotStarted;

            m_MatchRequestTimeoutMs = timeoutMs;
        }
        private void CreateTicket()
        {
            CreateTicketRequest request = new CreateTicketRequest();

            request.Ticket = ticket;
            State          = MatchmakingState.Requesting;
            CreateTicketResponse ticketResponse = m_client.CreateTicket(request);

            ticket = ticketResponse.Ticket;
            if (ticket == null)
            {
                return;
            }
        }
 /// <summary>
 ///     Initialize a new MatchmakingRequest object
 /// </summary>
 /// <param name="endpoint">
 ///     The base URL of the matchmaking service, in the form of 'cloud.connected.unity3d.com/[UPID]'
 /// </param>
 /// <param name="request">
 ///     The ticket data to send with the matchmaking request.
 ///     Data will be copied internally on construction and treated as immutable.
 /// </param>
 /// <param name="timeoutMs">
 ///     The amount of time to wait (in ms) before aborting an incomplete MatchmakingRequest after it has been sent.
 ///     Match requests that time out on the client side will immediately be set to completed and stop listening for a match
 ///     assignment.
 /// </param>
 public MatchmakingRequest(string endpoint, CreateTicketRequest request, uint timeoutMs = 0)
     : this(new MatchmakingClient(endpoint), request, timeoutMs)
 {
 }