Ejemplo n.º 1
0
 public EventQueue Run(CapsRequest eqReq)
 {
     Running = true;
     byte[] response = Encoding.UTF8.GetBytes("HTTP/1.0 200 OK\r\n"); ;
     return EventQueueHandler(eqReq);
     //byte[] wr = Encoding.UTF8.GetBytes("HTTP/1.0 200 OK\r\n");
     //Console.WriteLine("[EventQueue]: Writing " + response.Length + " back into socket");
     //_sock.Write(response, 0, response.Length);
 }
Ejemplo n.º 2
0
        //private Dictionary<string, bool> SubHack = new Dictionary<string, bool>();
        private void ProxyCaps(NetworkStream netStream, string meth, string uri, Dictionary<string, string> headers, byte[] content, int reqNo)
        {
            Match match = new Regex(@"^(https?)://([^:/]+)(:\d+)?(/.*)$").Match(uri);
            if (!match.Success)
            {
                Console.WriteLine("[" + reqNo + "] Malformed proxy URI: " + uri);
                byte[] wr = Encoding.ASCII.GetBytes("HTTP/1.0 404 Not Found\r\nContent-Length: 0\r\n\r\n");
                netStream.Write(wr, 0, wr.Length);
                return;
            }

            //Console.WriteLine("[" + reqNo + "]: " + meth + "; " + uri + "; ");
            CapInfo cap = null;
            lock (this)
            {
                if (KnownCaps.ContainsKey(uri))
                {
                    cap = KnownCaps[uri];
                }
            }

            CapsRequest capReq = null; bool shortCircuit = false; bool requestFailed = false;
            if (cap != null)
            {
                Console.WriteLine("[ProxyCaps]: " + cap.CapType + "; format: " + cap.ReqFmt + "; content-type: " + headers["content-type"]);
                capReq = new CapsRequest(cap);

                if (cap.ReqFmt == CapsDataFormat.SD)
                {
                    capReq.Request = OSDParser.DeserializeLLSDXml(content);
                }
                else
                {
                    capReq.Request = OSDParser.DeserializeLLSDBinary(content);
                }

                foreach (CapsDelegate d in cap.GetDelegates())
                {
                    if (d(capReq, CapsStage.Request)) { shortCircuit = true; break; }
                }

                if (cap.ReqFmt == CapsDataFormat.SD)
                {
                    content = OSDParser.SerializeLLSDXmlBytes((OSD)capReq.Request);
                }
                else
                {
                    content = OSDParser.SerializeLLSDXmlBytes(capReq.Request);
                }
            }
            else
            {
                Console.WriteLine("[GRID PROXY]: Unknown Cap " + uri);
                return;
            }

            byte[] respBuf = null;
            string consoleMsg = String.Empty;

            if (shortCircuit)
            {
                //    //if (eq == null)
                //    //{
                //    //    Console.WriteLine("[GRID SURFER]: Creating new EventQueue");
                //    //    eq = new EventQueue(netStream);
                //    //}
                //    //else
                //    //{
                //    //    Console.WriteLine("[GRID SURFER]: Event Queue already exists???");
                //    //    eq.SetSock(netStream);
                //    //}

                //    //eq = eq.Run(capReq);
                //    ////eq = null;

                //    return;

                //byte[] wr = Encoding.UTF8.GetBytes("HTTP/1.0 200 OK\r\n");
                //netStream.Write(wr, 0, wr.Length);

            }
            else
            {
                HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(uri);
                req.KeepAlive = false;
                foreach (string header in headers.Keys)
                {
                    if (header == "accept" || header == "connection" ||
                       header == "content-length" || header == "date" || header == "expect" ||
                       header == "host" || header == "if-modified-since" || header == "referer" ||
                       header == "transfer-encoding" || header == "user-agent" ||
                       header == "proxy-connection")
                    {
                        // can't touch these!
                    }
                    else if (header == "content-type")
                    {
                        req.ContentType = headers["content-type"];
                    }
                    else
                    {
                        req.Headers[header] = headers[header];
                    }
                }

                req.Method = meth;
                req.ContentLength = content.Length;

                HttpWebResponse resp;
                try
                {
                    Stream reqStream = req.GetRequestStream();
                    reqStream.Write(content, 0, content.Length);
                    reqStream.Close();
                    resp = (HttpWebResponse)req.GetResponse();
                }
                catch (WebException e)
                {
                    if (e.Status == WebExceptionStatus.Timeout || e.Status == WebExceptionStatus.SendFailure)
                    {
                        Console.WriteLine("Request timeout");
                        byte[] wr = Encoding.ASCII.GetBytes("HTTP/1.0 504 Proxy Request Timeout\r\nContent-Length: 0\r\n\r\n");
                        netStream.Write(wr, 0, wr.Length);
                        return;
                    }
                    else if (e.Status == WebExceptionStatus.ProtocolError && e.Response != null)
                    {
                        resp = (HttpWebResponse)e.Response; requestFailed = true;
                    }
                    else
                    {
                        Console.WriteLine("Request error: " + e.ToString());
                        byte[] wr = Encoding.ASCII.GetBytes("HTTP/1.0 502 Gateway Error\r\nContent-Length: 0\r\n\r\n"); // FIXME
                        netStream.Write(wr, 0, wr.Length);
                        return;
                    }
                }

                try
                {
                    Stream respStream = resp.GetResponseStream();
                    int read;
                    int length = 0;
                    respBuf = new byte[256];

                    do
                    {
                        read = respStream.Read(respBuf, length, 256);
                        if (read > 0)
                        {
                            length += read;
                            Array.Resize(ref respBuf, length + 256);
                        }
                    } while (read > 0);

                    Array.Resize(ref respBuf, length);

                    if (capReq != null && !requestFailed)
                    {
                        if (cap.RespFmt == CapsDataFormat.SD)
                        {
                            capReq.Response = OSDParser.DeserializeLLSDXml(respBuf);
                        }
                        else
                        {
                            capReq.Response = OSDParser.DeserializeLLSDXml(respBuf);
                        }

                    }

                    consoleMsg += "[" + reqNo + "] Response from " + uri + "\nStatus: " + (int)resp.StatusCode + " " + resp.StatusDescription + "\n";

                    {
                        byte[] wr = Encoding.UTF8.GetBytes("HTTP/1.0 " + (int)resp.StatusCode + " " + resp.StatusDescription + "\r\n");
                        netStream.Write(wr, 0, wr.Length);
                    }

                    for (int i = 0; i < resp.Headers.Count; i++)
                    {
                        string key = resp.Headers.Keys[i];
                        string val = resp.Headers[i];
                        string lkey = key.ToLower();
                        if (lkey != "content-length" && lkey != "transfer-encoding" && lkey != "connection")
                        {
                            consoleMsg += key + ": " + val + "\n";
                            byte[] wr = Encoding.UTF8.GetBytes(key + ": " + val + "\r\n");
                            netStream.Write(wr, 0, wr.Length);
                        }
                    }
                }
                catch (Exception)
                {
                    // TODO: Should we handle this somehow?
                }
            }

            if (cap != null && !requestFailed)
            {
                foreach (CapsDelegate d in cap.GetDelegates())
                {
                    try
                    {
                        if (d(capReq, CapsStage.Response)) { break; }
                    }
                    catch (Exception e)
                    {
                        Console.WriteLine(e.ToString()); break;
                    }
                }

                if (capReq.Response != null)
                {
                    if (cap.RespFmt == CapsDataFormat.SD)
                    {
                        respBuf = OSDParser.SerializeLLSDXmlBytes((OSD)capReq.Response);
                    }
                    else
                    {
                        respBuf = OSDParser.SerializeLLSDXmlBytes(capReq.Response);
                    }
                }
            }

            if (respBuf != null)
                consoleMsg += "\n" + Encoding.UTF8.GetString(respBuf) + "\n--------";
            #if DEBUG_CAPS
            Console.WriteLine(consoleMsg);
            #endif

            /* try {
                if(resp.StatusCode == HttpStatusCode.OK) respBuf = CapsFixup(uri,respBuf);
            } catch(Exception e) {
                Console.WriteLine("["+reqNo+"] Couldn't fixup response: "+e.ToString());
            } */

            #if DEBUG_CAPS
            Console.WriteLine("[" + reqNo + "] Fixed-up response:\n" + Encoding.UTF8.GetString(respBuf) + "\n--------");
            #endif

            try
            {
                if (capReq.Response != null)
                {
                    if (shortCircuit)
                    {
                        byte[] wr = Encoding.UTF8.GetBytes("HTTP/1.0 200 OK\r\n");
                        netStream.Write(wr, 0, wr.Length);
                    }
                    byte[] wr2 = Encoding.UTF8.GetBytes("Content-Length: " + respBuf.Length + "\r\n\r\n");
                    netStream.Write(wr2, 0, wr2.Length);

                    netStream.Write(respBuf, 0, respBuf.Length);
                }
                else
                {
                    byte[] wr = Encoding.UTF8.GetBytes("HTTP/1.0 404 Not Found\r\n");
                    netStream.Write(wr, 0, wr.Length);
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }

            return;
        }
Ejemplo n.º 3
0
        private bool KnownCapDelegate(CapsRequest capReq, CapsStage stage)
        {
            lock (this)
            {
                if (!KnownCapsDelegates.ContainsKey(capReq.Info.CapType))
                    return false;

                List<CapsDelegate> delegates = KnownCapsDelegates[capReq.Info.CapType];

                foreach (CapsDelegate d in delegates)
                {
                    if (d(capReq, stage)) { return true; }
                }
            }

            return false;
        }
Ejemplo n.º 4
0
        private bool FixupEventQueueGet(CapsRequest capReq, CapsStage stage)
        {
            //if (stage == CapsStage.Request)
            //    return ReifyEventQueueGetRequest(capReq);

            if (stage != CapsStage.Response) return false;

            Console.WriteLine("HERE : " + stage.ToString());

            OSDMap map = (OSDMap)capReq.Response;
            OSDArray array = (OSDArray)map["events"];

            for (int i = 0; i < array.Count; i++)
            {
                OSDMap evt = (OSDMap)array[i];

                string message = evt["message"].AsString();
                OSDMap body = (OSDMap)evt["body"];

                if (message == "TeleportFinish" || message == "CrossedRegion")
                {
                    OSDMap info = null;
                    if (message == "TeleportFinish")
                        info = (OSDMap)(((OSDArray)body["Info"])[0]);
                    else
                        info = (OSDMap)(((OSDArray)body["RegionData"])[0]);
                    byte[] bytes = info["SimIP"].AsBinary();
                    uint simIP = Utils.BytesToUInt(bytes);
                    ushort simPort = (ushort)info["SimPort"].AsInteger();
                    string capsURL = info["SeedCapability"].AsString();

                    GenericCheck(ref simIP, ref simPort, ref capsURL, capReq.Info.Sim == activeCircuit);

                    info["SeedCapability"] = OSD.FromString(capsURL);
                    bytes[0] = (byte)(simIP % 256);
                    bytes[1] = (byte)((simIP >> 8) % 256);
                    bytes[2] = (byte)((simIP >> 16) % 256);
                    bytes[3] = (byte)((simIP >> 24) % 256);
                    info["SimIP"] = OSD.FromBinary(bytes);
                    info["SimPort"] = OSD.FromInteger(simPort);
                }
                else if (message == "EnableSimulator")
                {
                    OSDMap info = null;
                    info = (OSDMap)(((OSDArray)body["SimulatorInfo"])[0]);
                    byte[] bytes = info["IP"].AsBinary();
                    uint IP = Utils.BytesToUInt(bytes);
                    ushort Port = (ushort)info["Port"].AsInteger();
                    string capsURL = null;

                    GenericCheck(ref IP, ref Port, ref capsURL, capReq.Info.Sim == activeCircuit);

                    bytes[0] = (byte)(IP % 256);
                    bytes[1] = (byte)((IP >> 8) % 256);
                    bytes[2] = (byte)((IP >> 16) % 256);
                    bytes[3] = (byte)((IP >> 24) % 256);
                    info["IP"] = OSD.FromBinary(bytes);
                    info["Port"] = OSD.FromInteger(Port);
                }
                else if (message == "EstablishAgentCommunication")
                {
                    string ipAndPort = body["sim-ip-and-port"].AsString();
                    string[] pieces = ipAndPort.Split(':');
                    byte[] bytes = IPAddress.Parse(pieces[0]).GetAddressBytes();
                    uint simIP = Utils.BytesToUInt(bytes);
                    ushort simPort = (ushort)Convert.ToInt32(pieces[1]);

                    string capsURL = body["seed-capability"].AsString();

            #if DEBUG_CAPS
                Console.WriteLine("DEBUG: Got EstablishAgentCommunication for " + ipAndPort + " with seed cap " + capsURL);
            #endif

                    GenericCheck(ref simIP, ref simPort, ref capsURL, false);
                    body["seed-capability"] = OSD.FromString(capsURL);
                    string ipport = String.Format("{0}:{1}", new IPAddress(simIP), simPort);
                    body["sim-ip-and-port"] = OSD.FromString(ipport);

            #if DEBUG_CAPS
                Console.WriteLine("DEBUG: Modified EstablishAgentCommunication to " + body["sim-ip-and-port"].AsString() + " with seed cap " + capsURL);
            #endif
                }
            }

            return false;
        }
Ejemplo n.º 5
0
        public void ForwardCaps(string uri, CapsRequest capReq)
        {
            bool requestFailed = false;
            HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(uri);
            req.KeepAlive = false;
            req.ContentType = "application/llsd+xml";

            req.Method = "POST";
            byte[] content = OSDParser.SerializeLLSDXmlBytes(capReq.Request);
            req.ContentLength = content.Length;
            Console.WriteLine("    >> content-length: " + req.ContentLength);
            HttpWebResponse resp = null;
            try
            {
                Stream reqStream = req.GetRequestStream();
                reqStream.Write(content, 0, content.Length);
                reqStream.Close();
            }
            catch (WebException e)
            {
                if (e.Status == WebExceptionStatus.Timeout || e.Status == WebExceptionStatus.SendFailure)
                {
                    Console.WriteLine("Request timeout");
                    //byte[] wr = Encoding.ASCII.GetBytes("HTTP/1.0 504 Proxy Request Timeout\r\nContent-Length: 0\r\n\r\n");
                    //netStream.Write(wr, 0, wr.Length);
                    return;
                }
                else if (e.Status == WebExceptionStatus.ProtocolError && e.Response != null)
                {
                    //resp = (HttpWebResponse)e.Response;
                    requestFailed = true;
                }
                else
                {
                    Console.WriteLine("Request error: " + e.ToString());
                    //byte[] wr = Encoding.ASCII.GetBytes("HTTP/1.0 502 Gateway Error\r\nContent-Length: 0\r\n\r\n"); // FIXME
                    //netStream.Write(wr, 0, wr.Length);
                    return;
                }
            }

            try
            {
                resp = (HttpWebResponse)req.GetResponse();
            }
            catch (WebException e)
            {
                if (e.Status == WebExceptionStatus.Timeout || e.Status == WebExceptionStatus.SendFailure)
                {
                    Console.WriteLine("Request timeout");
                    //byte[] wr = Encoding.ASCII.GetBytes("HTTP/1.0 504 Proxy Request Timeout\r\nContent-Length: 0\r\n\r\n");
                    //netStream.Write(wr, 0, wr.Length);
                    return;
                }
                else if (e.Status == WebExceptionStatus.ProtocolError && e.Response != null)
                {
                    //resp = (HttpWebResponse)e.Response;
                    requestFailed = true;
                }
                else
                {
                    Console.WriteLine("Request error: " + e.ToString());
                    //byte[] wr = Encoding.ASCII.GetBytes("HTTP/1.0 502 Gateway Error\r\nContent-Length: 0\r\n\r\n"); // FIXME
                    //netStream.Write(wr, 0, wr.Length);
                    return;
                }
            }

            byte[] respBuf = null;
            try
            {
                Stream respStream = resp.GetResponseStream();
                int read;
                int length = 0;
                respBuf = new byte[256];

                do
                {
                    read = respStream.Read(respBuf, length, 256);
                    if (read > 0)
                    {
                        length += read;
                        Array.Resize(ref respBuf, length + 256);
                    }
                } while (read > 0);

                Array.Resize(ref respBuf, length);

                if (capReq != null && !requestFailed)
                {
                    if (capReq.Info.RespFmt == CapsDataFormat.SD)
                    {
                        capReq.Response = OSDParser.DeserializeLLSDXml(respBuf);
                    }
                    else
                    {
                        capReq.Response = OSDParser.DeserializeLLSDXml(respBuf);
                    }

                }

                //consoleMsg += "[" + reqNo + "] Response from " + uri + "\nStatus: " + (int)resp.StatusCode + " " + resp.StatusDescription + "\n";

                //{
                //    byte[] wr = Encoding.UTF8.GetBytes("HTTP/1.0 " + (int)resp.StatusCode + " " + resp.StatusDescription + "\r\n");
                //    netStream.Write(wr, 0, wr.Length);
                //}

                //for (int i = 0; i < resp.Headers.Count; i++)
                //{
                //    string key = resp.Headers.Keys[i];
                //    string val = resp.Headers[i];
                //    string lkey = key.ToLower();
                //    if (lkey != "content-length" && lkey != "transfer-encoding" && lkey != "connection")
                //    {
                //        consoleMsg += key + ": " + val + "\n";
                //        byte[] wr = Encoding.UTF8.GetBytes(key + ": " + val + "\r\n");
                //        netStream.Write(wr, 0, wr.Length);
                //    }
                //}
            }
            catch (Exception)
            {
                // TODO: Should we handle this somehow?
            }
        }
Ejemplo n.º 6
0
        public bool FixupSeedCapsResponse(CapsRequest capReq, CapsStage stage)
        {
            if (stage != CapsStage.Response) return false;

            OSDMap nm = new OSDMap();

            if (capReq.Response.Type == OSDType.Map)
            {
                OSDMap m = (OSDMap)capReq.Response;

                foreach (string key in m.Keys)
                {
                    string val = m[key].AsString();

                    if (!String.IsNullOrEmpty(val))
                    {
                        if (!KnownCaps.ContainsKey(val))
                        {
                            CapInfo newCap = new CapInfo(val, capReq.Info.Sim, key);
                            newCap.AddDelegate(new CapsDelegate(KnownCapDelegate));
                            lock (this) { KnownCaps[val] = newCap; }
                            //Console.WriteLine("    >> Adding KnownCap " + val + " = " + newCap);
                        }
                        nm[key] = OSD.FromString(loginURI + val);
                        //Console.WriteLine("    >> Sending transformed Cap " + loginURI + val);
                    }
                    else
                    {
                        nm[key] = OSD.FromString(val);
                    }
                }
            }

            //Console.WriteLine("---------------");
            //lock (this)
            //{
            //    foreach (KeyValuePair<string, CapInfo> kvp in KnownCaps)
            //    {
            //        Console.WriteLine(" >> Key: " + kvp.Key + "; Value: " + kvp.Value.CapType);
            //    }
            //}
            //Console.WriteLine("---------------");

            capReq.Response = nm;
            return false;
        }
Ejemplo n.º 7
0
 private bool ReifyEventQueueGetRequest(CapsRequest capReq)
 {
     return false;
 }
Ejemplo n.º 8
0
        void StartEventQueue(CapsRequest eqRequest)
        {
            EventQueueEvent eventQueueEvent = null;
            int totalMsPassed = 0;

            Console.WriteLine("[EventQueue] Starting..." + id);
            while (Running)
            {
                if (eventQueue.Dequeue(BATCH_WAIT_INTERVAL, ref eventQueueEvent))
                {
                    Console.WriteLine("[EventQueue]: Dequeued event " + eventQueueEvent.Name);
                    // An event was dequeued
                    totalMsPassed = 0;

                    List<EventQueueEvent> eventsToSend = new List<EventQueueEvent>();
                    eventsToSend.Add(eventQueueEvent);

                    DateTime start = DateTime.Now;
                    int batchMsPassed = 0;

                    // Wait BATCH_WAIT_INTERVAL milliseconds looking for more events,
                    // or until the size of the current batch equals MAX_EVENTS_PER_RESPONSE
                    while (batchMsPassed < BATCH_WAIT_INTERVAL && eventsToSend.Count < MAX_EVENTS_PER_RESPONSE)
                    {
                        if (eventQueue.Dequeue(BATCH_WAIT_INTERVAL - batchMsPassed, ref eventQueueEvent))
                        {
                            Console.WriteLine("[EventQueue]: Dequeued event " + eventQueueEvent.Name);
                            eventsToSend.Add(eventQueueEvent);
                        }
                        batchMsPassed = (int)(DateTime.Now - start).TotalMilliseconds;
                    }

                    SendResponse(eqRequest, eventsToSend);
                    return;
                }
                else
                {
                    // BATCH_WAIT_INTERVAL milliseconds passed with no event. Check if the connection
                    // has timed out yet.
                    totalMsPassed += BATCH_WAIT_INTERVAL;

                    if (totalMsPassed >= CONNECTION_TIMEOUT)
                    {
                        Console.WriteLine("[EventQueue] Timeout without an event, " + totalMsPassed);
                        List<EventQueueEvent> evl = new List<EventQueueEvent>();
                        evl.Add(FakeEvent);
                        SendResponse(eqRequest, evl);
                        return;
                    }
                }
            }

            Console.WriteLine("[EventQueue] Handler thread is no longer Running");
        }
Ejemplo n.º 9
0
        void SendResponse(CapsRequest eqRequest, List<EventQueueEvent> eventsToSend)
        {
            if (eventsToSend != null)
            {
                OSDArray responseArray = new OSDArray(eventsToSend.Count);

                // Put all of the events in an array
                for (int i = 0; i < eventsToSend.Count; i++)
                {
                    EventQueueEvent currentEvent = eventsToSend[i];

                    OSDMap eventMap = new OSDMap(2);
                    eventMap.Add("message", OSD.FromString(currentEvent.Name));
                    eventMap.Add("body", currentEvent.Body);
                    responseArray.Add(eventMap);
                }

                // Create a map containing the events array and the id of this response
                OSDMap responseMap = new OSDMap(2);
                responseMap.Add("events", responseArray);
                responseMap.Add("id", OSD.FromInteger(currentID++));

                eqRequest.Response = responseMap;

                //string respstr = "HTTP/1.0 200 OK\r\n";
                //respstr += "Content-Type: application/llsd+xml\r\n";

                //// Serialize the events and send the response
                //byte[] buffer = OSDParser.SerializeLLSDXmlBytes(responseMap);
                //respstr += "Content-Length: " + buffer.Length + "\r\n\r\n";

                //Console.WriteLine("----- Sending ----");
                //Console.WriteLine(respstr);

                //byte[] top = Encoding.UTF8.GetBytes(respstr);
                //_sock.Write(top, 0, top.Length);
                //_sock.Write(buffer, 0, buffer.Length);

                ////response = new byte[top.Length + buffer.Length];
                ////top.CopyTo(response, 0);
                ////buffer.CopyTo(response, top.Length);
                ////response.Body.Flush();
            }
            else
            {
                //// The 502 response started as a bug in the LL event queue server implementation,
                //// but is now hardcoded into the protocol as the code to use for a timeout
                //response = Encoding.UTF8.GetBytes("HTTP/1.0 502 Bad Gateway\r\n");
                //response = Encoding.UTF8.GetBytes("HTTP/1.0 200 OK\r\n");

                //_sock.Write(response, 0, response.Length);

            }
        }
Ejemplo n.º 10
0
        private EventQueue EventQueueHandler(CapsRequest eqRequest)
        {
            // Decode the request
            OSD osdRequest = eqRequest.Request;

            if (eqRequest != null && osdRequest.Type == OSDType.Map)
            {
                OSDMap requestMap = (OSDMap)osdRequest;
                int ack = requestMap["ack"].AsInteger();
                bool done = requestMap["done"].AsBoolean();

                if (ack != currentID - 1 && ack != 0)
                {
                    Console.WriteLine("[EventQueue] Received an ack for id " + ack + ", last id sent was " + (currentID - 1));
                }

                if (!done)
                {
                    StartEventQueue(eqRequest);
                    eventQueue.Close();
                    eventQueue = new BlockingQueue<EventQueueEvent>();
                    return this;
                }
                else
                {
                    Console.WriteLine("[EventQueue] Shutting down the event queue at the client's request " + id);
                    Stop();

                    //response = Encoding.UTF8.GetBytes("HTTP/1.0 404 Not Found\r\n");
                    return null;
                }
            }
            else
            {
                Console.WriteLine("[EventQueue] Received a request with invalid or missing LLSD at, closing the connection");

                //response = Encoding.UTF8.GetBytes("HTTP/1.0 400 Bad Request\r\n");
                return null;
            }
        }