public void Run() { BS = new BlindSocket(); BP = new BlindPacket(); BS.ConnectWithECDH(BlindNetConst.ServerIP, BlindNetConst.WebDevicePort); while (true) { try { BP = BS.CryptoReceive();// 아이디에 따른 장치제어 결과 받아옴 } catch { break; } BP.data = BlindNetUtil.ByteTrimEndNull(BP.data); string ReceiveByteToStringGender = Encoding.Default.GetString(BP.data);// 변환 바이트 -> string = default,GetString | string -> 바이트 = utf8,GetBytes //11 : USB,CAM 차단 | 10: USB만 차단 | 01: 웹캠만 차단 | 00 : 모두허용 DeviceToggle(ReceiveByteToStringGender); Thread.Sleep(1000); } }
static bool GetAES(ref byte[] data, BlindSocket socket, out Cryptography.AES256 aes256) { aes256 = null; uint encryptDate = BitConverter.ToUInt32(data, 4); byte[] realData = new byte[data.Length - 8]; Array.Copy(data, 8, realData, 0, realData.Length); data = realData; Console.WriteLine("Encrypted date : " + encryptDate); socket.CryptoSend(BitConverter.GetBytes(encryptDate), PacketType.Info); byte[] key = socket.CryptoReceiveMsg(); if (key == null) { MessageBox.Show("파일 복호화에 실패했습니다.", "파일 열기"); return(false); } Console.WriteLine("Received key {0} bytes", key.Length); byte[] iv = socket.CryptoReceiveMsg(); if (iv == null) { MessageBox.Show("파일 복호화에 실패했습니다.", "파일 열기"); return(false); } Console.WriteLine("Received iv {0} bytes", iv.Length); aes256 = new Cryptography.AES256(key, iv); return(true); }
static _Main() { MainSocket = new BlindSocket(); MainPacket = new BlindPacket(); ReceiveByteToStringGenderText = ""; Result = ""; }
public void connect() { if (!isInner) { lockSock = new BlindSocket(); lockSock.ConnectWithECDH(BlindNetConst.ServerIP, BlindNetConst.LOCKPORT); MessageBox.Show("락 포트 연결!"); } }
public void Run() { this.hDB = new MySqlConnection("Server=" + BlindNetConst.DatabaseIP + ";Database=BlindChat;Uid=root;Pwd=kit2020;"); this.hDB.Open(); recvSock = GetChatRecvSocket(); sendSock = GetChatSendSocket(); IPEndPoint iep = (IPEndPoint)(recvSock.socket.RemoteEndPoint); logger = new Logger(UserID, iep.Address.ToString(), LogService.Chat); SetOnline((int)UserStat.Online); byte[] data; while (true) { data = recvSock.CryptoReceiveMsg(); if (data == null) { recvSock.Close(); sendSock.Close(); SetOnline((int)UserStat.Offline); global.ListBlindChat.Remove(this); logger.Log(LogRank.INFO, "BlindChat Disconnected"); return; } ChatPacket chatPacket = BlindNetUtil.ByteToStruct <ChatPacket>(data); if (chatPacket.Type == ChatType.Time) { ClientUpdateData(chatPacket); logger.Log(LogRank.INFO, "Chat Data Synchronized"); } else if (chatPacket.Type == ChatType.NewRoom) { ExecuteNewRoom(chatPacket); logger.Log(LogRank.INFO, "Created New Chat Room"); } else if (chatPacket.Type == ChatType.Message) { MessageToParticipants(chatPacket); } else if (chatPacket.Type == ChatType.RoomJoined) { ExecuteInvitation(chatPacket); } else if (chatPacket.Type == ChatType.Exit) { ExecuteExit(chatPacket); } } }
public void Run() { connection = new MySqlConnection("Server = " + BlindNetConst.DatabaseIP + "; Port = 3306; Database = document_center; Uid = root; Pwd = kit2020"); mainSocket = new BlindServerScoket(BlindNetConst.ServerIP, BlindNetConst.OPENNERPORT); mainSocket.BindListen(); while (true) { BlindSocket client = mainSocket.AcceptWithECDH(); IPEndPoint iep = (IPEndPoint)(client.socket.RemoteEndPoint); Console.WriteLine("Accepted {0} : {1}", iep.Address, iep.Port); if (client == null) { continue; } byte[] data = BlindNetUtil.ByteTrimEndNull(client.CryptoReceiveMsg()); byte[] tmp = new byte[4]; Array.Copy(data, 0, tmp, 0, data.Length); string ext = GetExt(BitConverter.ToUInt32(tmp, 0)); if (ext == null) { client.CryptoSend(null, PacketType.Disconnect); continue; } client.CryptoSend(Encoding.UTF8.GetBytes(ext), PacketType.Info); data = BlindNetUtil.ByteTrimEndNull(client.CryptoReceiveMsg()); tmp = new byte[4]; Array.Copy(data, 0, tmp, 0, data.Length); int encryptDate = BitConverter.ToInt32(tmp, 0); byte[] key, iv; if (!GetSpecifyKeyPair(out key, out iv, encryptDate)) { client.CryptoSend(null, PacketType.Disconnect); continue; } client.CryptoSend(key, PacketType.Info); client.CryptoSend(iv, PacketType.Info); byte[] latestKey, latestIv; if (!GetLatestKeyPair(out latestKey, out latestIv)) { client.CryptoSend(null, PacketType.Disconnect); continue; } client.CryptoSend(latestKey, PacketType.Info); client.CryptoSend(latestIv, PacketType.Info); client.Close(); } }
static string GetSpecifyExt(uint id, BlindSocket socket) { socket.CryptoSend(BitConverter.GetBytes(id), PacketType.Info); byte[] bExt = socket.CryptoReceiveMsg(); if (bExt == null) { MessageBox.Show("파일 복호화에 실패했습니다.", "파일 열기"); return(null); } string ext = "." + Encoding.UTF8.GetString(bExt); Console.WriteLine("Ext : " + ext); return(ext); }
static void Main(string[] args) { Console.WriteLine("Well Come to Console \r\n"); Console.WriteLine("All Press KeyDown -> Program Exit"); MainWebSocket.BindListen(); Console.WriteLine("VpnServer Connection Listening"); BS = MainWebSocket.AcceptWithECDH(); //따로 받는이유 : 브로드캐스트로 인해서 여러개의 클라를 받으려면 따로 받아줘야함. IPEndPoint iep = (IPEndPoint)(BS.socket.RemoteEndPoint); Console.WriteLine("VpnServer Connection Start. (Connection IP : " + iep.Address.ToString() + ")\r\n"); Console.WriteLine("----------------------------------------------------"); WS = new WebSocket(BlindNetConst.ServerIP, BlindNetConst.WebTcpPort, BS, BP); Console.ReadLine(); }
public WebSocket(string ServerIP, int ServerPort, BlindSocket BS, BlindPacket BP) { this.ServerIP = ServerIP; this.ServerPort = ServerPort; SUBBS = BS; SUBBP = BP; TListener = null; TClient = null; ClientStream = null; ReadBuffer = new byte[10240]; TListener = new TcpListener(IPAddress.Parse(this.ServerIP), this.ServerPort); TListener.Start(); Console.WriteLine("WebServer Open\r\n"); TListener.BeginAcceptTcpClient(OnServerConnect, null); // 클라이언트 접속 대기 }
public MainForm(bool isInner, string ClientID) { InitializeComponent(); this.isInner = isInner; this.ClientID = ClientID; mainSocket = new BlindSocket(); _uiSyncContext = SynchronizationContext.Current; //UI panel_Fore.BackColor = BlindColor.Primary; Button_DocCenter.ForeColor = BlindColor.Light; panel3.BackColor = BlindColor.Primary; panel4.BackColor = panel1.BackColor = BlindColor.DarkGreen; btn_ActivateUser.ForeColor = btn_ActivateChat.ForeColor = BlindColor.Light; this.isMove = false; this.panel_Fore.BackColor = Color.LightGray; this.DoubleBuffered = true; this.SetStyle(ControlStyles.ResizeRedraw, true); }
static bool GetLatestAES(BlindSocket socket, out Cryptography.AES256 aes256) { aes256 = null; byte[] key = socket.CryptoReceiveMsg(); if (key == null) { MessageBox.Show("파일 복호화에 실패했습니다.", "파일 열기"); return(false); } Console.WriteLine("Received key {0} bytes", key.Length); byte[] iv = socket.CryptoReceiveMsg(); if (iv == null) { MessageBox.Show("파일 복호화에 실패했습니다.", "파일 열기"); return(false); } Console.WriteLine("Received iv {0} bytes", iv.Length); aes256 = new Cryptography.AES256(key, iv); return(true); }
public async void Run() { socket = new BlindSocket(); await socket.ConnectWithECDHAsync(BlindNetConst.ServerIP, BlindNetConst.DocCenterPort); socket.socket.NoDelay = true; socket.CryptoSend(BitConverter.GetBytes(isInner), PacketType.Info); BlindPacket packet = socket.CryptoReceive(); if (packet.header == PacketType.Fail) { MessageBox.Show("데이터베이스 연결에 실패했습니다."); return; } UpdateRoot(); if (form.treeview_Dir.Nodes.Count == 0) { return; } form.treeview_Dir.SelectedNode = form.treeview_Dir.Nodes[0]; }
static void Main(string[] args) { var handl = GetConsoleWindow(); BlindOpenner openner = new BlindOpenner(); Task.Run(() => openner.Run()); connection = DataBaseConnection(); //ShowWindow(handl, SW_HIDE); //Console 창 숨기기 socket_docCenter = new BlindServerScoket(BlindNetConst.ServerIP, BlindNetConst.DocCenterPort); socket_docCenter.BindListen(); chatRecvSock = new BlindServerScoket(BlindNetConst.ServerIP, BlindNetConst.CHATPORT); chatRecvSock.BindListen(); chatSendSock = new BlindServerScoket(BlindNetConst.ServerIP, BlindNetConst.CHATPORT + 1); chatSendSock.BindListen(); lockPortSock = new BlindServerScoket(BlindNetConst.ServerIP, BlindNetConst.LOCKPORT); lockPortSock.BindListen(); mainSocket = new BlindServerScoket(); mainSocket.BindListen(); WebDeviceSocket = new BlindServerScoket(BlindNetConst.ServerIP, BlindNetConst.WebDevicePort); WebDeviceSocket.BindListen(); while (true) { BlindSocket client = mainSocket.AcceptWithECDH(); AddConnectedUser(client); } }
public void Run() { ChatPacket packet; ChatTimeStamp syncTime; User user; ChatRoom room; ChatRoomJoined roomJoined; ChatMessage message; DB.Open(); sendSock = new BlindSocket(); sendSock.ConnectWithECDH(BlindNetConst.ServerIP, BlindNetConst.CHATPORT); recvSock = new BlindSocket(); recvSock.ConnectWithECDH(BlindNetConst.ServerIP, BlindNetConst.CHATPORT + 1); syncTime = DB.GetAllTime(); packet = BlindChatUtil.StructToChatPacket(syncTime); ChatPacketSend(packet); //string sql; while (true) { packet = ChatPacketReceive(); if (packet.Type == ChatType.User) { //사용자 UI에 표시 user = BlindChatUtil.ChatPacketToStruct <User>(packet); AddUser(user); } else if (packet.Type == ChatType.Room) { //방추가 UI에 표시 room = BlindChatUtil.ChatPacketToStruct <ChatRoom>(packet); AddRoom(room); } else if (packet.Type == ChatType.RoomJoined) { //방인원 UI에 표시 roomJoined = BlindChatUtil.ChatPacketToStruct <ChatRoomJoined>(packet); AddMember(roomJoined); } else if (packet.Type == ChatType.Message) { //메시지 UI에 표시 message = BlindChatUtil.ChatPacketToStruct <ChatMessage>(packet); AddMessage(message); } else if (packet.Type == ChatType.Reset) { if (!Start) { LoadList(); LoadUI(); #if DEBUG MessageBox.Show("데이터 로드 완료"); #endif } Start = true; } else if (packet.Type == ChatType.Exit) { ExecuteExit(packet); } else { } } }
public void Run() { socket = _Main.socket_docCenter.AcceptWithECDH(); socket.socket.NoDelay = true; logger = new Logger(uid, ((IPEndPoint)(socket.socket.RemoteEndPoint)).Address.ToString(), LogService.DocumentCenter); logger.Log(LogRank.INFO, "Connected to document center."); isInner = BitConverter.ToBoolean(socket.CryptoReceiveMsg(), 0); connection = new MySqlConnection("Server = " + BlindNetConst.DatabaseIP + "; Port = 3306; Database = document_center; Uid = root; Pwd = kit2020"); try { connection.Open(); } catch (Exception ex) { Console.WriteLine("ERROR : [UID : " + uid + "] " + ex.Message); socket.CryptoSend(null, PacketType.Fail); return; } socket.CryptoSend(null, PacketType.OK); while (true) { //try //{ BlindPacket packet = socket.CryptoReceive(); if (packet.header != PacketType.Disconnect) { packet.data = BlindNetUtil.ByteTrimEndNull(packet.data); } switch (packet.header) { case PacketType.DocRefresh: UpdateRoot(); break; case PacketType.DocDirInfo: { byte[] data = BlindNetUtil.ByteTrimEndNull(packet.data); byte[] tmp = new byte[4]; Array.Copy(data, 0, tmp, 0, data.Length); UpdateDir(BitConverter.ToUInt32(tmp, 0)); break; } case PacketType.DocAddDir: AddDir(BlindNetUtil.ByteToStruct <Directory_Info>(packet.data)); break; case PacketType.DocRemoveDir: { byte[] data = BlindNetUtil.ByteTrimEndNull(packet.data); byte[] tmp = new byte[4]; Array.Copy(data, 0, tmp, 0, data.Length); RemoveDir(BitConverter.ToUInt32(tmp, 0)); break; } case PacketType.DocRemoveFile: { byte[] data = BlindNetUtil.ByteTrimEndNull(packet.data); byte[] tmp = new byte[4]; Array.Copy(data, 0, tmp, 0, data.Length); RemoveFile(BitConverter.ToUInt32(tmp, 0)); break; } case PacketType.DocChngNameDir: ChangeNameDir(BlindNetUtil.ByteToStruct <Directory_Info>(packet.data)); break; case PacketType.DocFileUpload: FileUpload(BlindNetUtil.ByteToStruct <Directory_Info>(packet.data)); break; case PacketType.DocFileDownload: { byte[] data = BlindNetUtil.ByteTrimEndNull(packet.data); byte[] tmp = new byte[4]; Array.Copy(data, 0, tmp, 0, data.Length); FileDownload(BitConverter.ToUInt32(tmp, 0)); break; } case PacketType.DocDirDownload: { byte[] data = BlindNetUtil.ByteTrimEndNull(packet.data); byte[] tmp = new byte[4]; Array.Copy(data, 0, tmp, 0, data.Length); DirDownload(BitConverter.ToUInt32(tmp, 0)); break; } case PacketType.DocGetFileSize: { byte[] data = BlindNetUtil.ByteTrimEndNull(packet.data); byte[] tmp = new byte[4]; Array.Copy(data, 0, tmp, 0, data.Length); GetFileSize(BitConverter.ToUInt32(tmp, 0)); break; } case PacketType.DocGetDirSize: { byte[] data = BlindNetUtil.ByteTrimEndNull(packet.data); byte[] tmp = new byte[4]; Array.Copy(data, 0, tmp, 0, data.Length); GetDirSize(BitConverter.ToUInt32(tmp, 0)); break; } case PacketType.DocRenameFile: { byte[] data = BlindNetUtil.ByteTrimEndNull(packet.data); byte[] tmp = new byte[4]; Array.Copy(data, 0, tmp, 0, data.Length); RenameFile(BitConverter.ToUInt32(tmp, 0)); break; } case PacketType.DocMoveFile: MoveFile(BlindNetUtil.ByteToStruct <SrcDstInfo>(packet.data)); break; case PacketType.DocMoveDir: MoveDir(BlindNetUtil.ByteToStruct <SrcDstInfo>(packet.data)); break; case PacketType.DocCopyFile: CopyFile(BlindNetUtil.ByteToStruct <SrcDstInfo>(packet.data)); break; case PacketType.DocCopyDir: CopyDir(BlindNetUtil.ByteToStruct <SrcDstInfo>(packet.data)); break; case PacketType.Disconnect: logger.Log(LogRank.INFO, "Disconnected from document center"); return; } //} //catch (Exception ex) //{ // Console.WriteLine("ERROR : [UID : " + uid + "] " + ex.Message); // return; //} } }
static void Main(string[] args) { #if !DEBUG var handl = GetConsoleWindow(); ShowWindow(handl, SW_HIDE); #endif if (args.Length != 1) { MessageBox.Show("잘못된 접근입니다.", "오류"); //return; } string test = @"C:\Users\rgh48\Downloads\부서원 인적사항.blind"; FileInfo file = new FileInfo(test); if (!file.Exists) { MessageBox.Show("파일이 존재하지 않습니다.", "파일 열기"); return; } FileStream fs = file.Open(FileMode.Open); byte[] buffer = new byte[fs.Length]; Task <int> taskRead = fs.ReadAsync(buffer, 0, (int)file.Length); BlindSocket socket = new BlindSocket(); Console.WriteLine("Start connecting."); //Task<bool> taskCon = socket.ConnectWithECDHAsync(BlindNetConst.ServerIP, BlindNetConst.OPENNERPORT); //taskCon.Wait(); //if (!taskCon.Result) socket.ConnectWithECDH(BlindNetConst.ServerIP, BlindNetConst.OPENNERPORT); if (false) { MessageBox.Show("서버와 연결에 실패했습니다.", "파일 열기"); return; } Console.WriteLine("Connected with server."); taskRead.Wait(); if (taskRead.Result == 0) { MessageBox.Show("파일 읽기에 실패했습니다.", "파일 열기"); return; } fs.Close(); uint id = BitConverter.ToUInt32(buffer, 0); string ext = GetSpecifyExt(id, socket); if (ext == null) { return; } Cryptography.AES256 aes256; if (!GetAES(ref buffer, socket, out aes256)) { return; } Console.WriteLine("Start decrypted"); byte[] decrypted = aes256.Decryption(buffer); Console.WriteLine("Decrypted"); string tempFilePath = Path.GetTempPath() + Path.GetFileNameWithoutExtension(args[0]) + ext; MakeTempFile(tempFilePath, decrypted); Console.WriteLine("File maked"); Process procFile = Process.Start(tempFilePath); Cryptography.AES256 latestAes; if (!GetLatestAES(socket, out latestAes)) { MessageBox.Show("최신 키 받아오기를 실패했습니다."); return; } socket.Close(); procFile.WaitForExit(); Console.WriteLine("Canceled"); FileInfo decFile = new FileInfo(tempFilePath); FileStream decFs = decFile.OpenRead(); buffer = new byte[decFile.Length]; decFs.Read(buffer, 0, (int)decFile.Length); decFs.Close(); buffer = AddDataToFile(id, latestAes.Encryption(buffer)); FileStream encFs = file.Open(FileMode.Open); encFs.Write(buffer, 0, buffer.Length); encFs.Close(); decFile.Delete(); }
static async void AddConnectedUser(BlindSocket socket) { if (socket == null) { return; } IPEndPoint iep = (IPEndPoint)(socket.socket.RemoteEndPoint); //로그인 인증 uint cid; byte[] ClientReceiveMsg = socket.CryptoReceiveMsg(); // 아이디,isinner 받음. (bool형. 디버그했을때 실질적인 값 : true -> "True" | false -> "False") string ClientGenderMsg = Encoding.UTF8.GetString(ClientReceiveMsg); // 바이트 -> 스트링 if (Encoding.UTF8.GetString(ClientReceiveMsg) != "\0") { cid = GetClientID(ClientGenderMsg.Split(',')[0].ToString()); //[0] -> dkdlel } else { cid = 0; } logger = new Logger(cid, iep.Address.ToString(), LogService.Login); if (cid != 0) { logger.Log(LogRank.INFO, "[Login Success] " + "Login ID : \"" + ClientGenderMsg.Split(',')[0].ToString() + "\" " + "VPN Whether: \"" + (ClientGenderMsg.Split(',')[1].ToString() == "True" ? "True" : "False") + "\""); } else { logger.Log(LogRank.WARN, "[Login Fail] " + "Login ID : \"" + ClientGenderMsg.Split(',')[0].ToString() + "\" " + "VPN Whether: \"" + (ClientGenderMsg.Split(',')[1].ToString() == "True" ? "True" : "False") + "\""); } socket.CryptoSend(BitConverter.GetBytes(cid), PacketType.Response);//cid 보냄 if (cid == 0) { socket.Close(); return; } uint[] gids = GetGids(cid); Console.WriteLine("Accepted {0} : {1}" + $"({cid})", iep.Address, iep.Port); //Client 구조체 초기화 및 추가 TaskScheduler scheduler = TaskScheduler.Default; BlindClient client = new BlindClient(); client.socket = socket; client.token = new CancellationTokenSource(); client.documentCenter = new Doc_Center(cid, gids); //기능 객체 생성 client.tDocumentCenter = Task.Factory.StartNew(() => client.documentCenter.Run(), client.token.Token, TaskCreationOptions.LongRunning, scheduler); //기능 객체의 최초 함수 실행 client.chat = new BlindChat(cid); client.tChat = Task.Factory.StartNew(() => client.chat.Run(), client.token.Token, TaskCreationOptions.LongRunning, scheduler); client.blindLock = new BlindLock(cid); client.tBlindLock = Task.Factory.StartNew(() => client.blindLock.Run(), client.token.Token, TaskCreationOptions.LongRunning, scheduler); client.blindWebDevice = new BlindWebDevice(cid); client.tBlindWebDevice = Task.Factory.StartNew(() => client.blindWebDevice.Run(), client.token.Token, TaskCreationOptions.LongRunning, scheduler); Clients.Add(client); }
public void Run() { BS = _Main.WebDeviceSocket.AcceptWithECDH(); connection = new MySqlConnection(@"server=54.84.228.2; database=BlindWeb; user=root; password=kit2020"); connection.Open(); IPEndPoint iep = (IPEndPoint)(BS.socket.RemoteEndPoint); logger = new Logger(cid, iep.Address.ToString(), LogService.DeviceControl); string ClientSendResultValue = "NULL"; string QueryResultBackupValue = ""; string UsbValue = ""; string CamValue = ""; while (true) { string DeviceUsbQuery = "SELECT cusb from blindDevice where cid=" + cid + ";"; //USB Command = new MySqlCommand(DeviceUsbQuery, connection); //검색할 쿼리, 연결된 쿼리 UsbValue = Command.ExecuteScalar().ToString(); string DeviceCamQuery = "SELECT ccamera from blindDevice where cid=" + cid + ";"; //CAM Command = new MySqlCommand(DeviceCamQuery, connection); //검색할 쿼리, 연결된 쿼리 CamValue = Command.ExecuteScalar().ToString(); /* USB가 1번째 CAM이 2번째라고 했을때 10진수로 값을 정해서 보냄 (0은 차단 1은 허용) * USB 1 CAM 1 = 11 (USB는 허용 CAM도 허용인 경우) -> 값 11 클라 전송 * USB 1 CAM 0 = 10 (USB는 허용 CAM은 차단인 경우) -> 값 10 클라 전송 * USB 0 CAM 1 = 01 (USB는 차단 CAM은 허용인 경우) -> 값 01 클라 전송 * USB 0 CAM 0 = 00 (USB는 허용 CAM도 허용인 경우) -> 값 00 클라 전송 */ ClientSendResultValue = ""; if (UsbValue == "True" && CamValue == "True") { ClientSendResultValue = "11"; } else if (UsbValue == "True" && CamValue == "False") { ClientSendResultValue = "10"; } else if (UsbValue == "False" && CamValue == "True") { ClientSendResultValue = "01"; } else if (UsbValue == "False" && CamValue == "False") { ClientSendResultValue = "00"; } if (QueryResultBackupValue != ClientSendResultValue) { QueryResultBackupValue = ClientSendResultValue; if (ClientSendResultValue == "11") { logger.Log(LogRank.INFO, "DeviceControl(USB:Allow | CAM:Allow) cid: " + cid); } else if (ClientSendResultValue == "10") { logger.Log(LogRank.INFO, "DeviceControl(USB:Allow | CAM:Deny) cid: " + cid); } else if (ClientSendResultValue == "01") { logger.Log(LogRank.INFO, "DeviceControl(USB:Deny | CAM:Allow) cid: " + cid); } else if (ClientSendResultValue == "00") { logger.Log(LogRank.INFO, "DeviceControl(USB:Deny | CAM:Deny) cid: " + cid); } } byte[] SendStringToByteGender = Encoding.UTF8.GetBytes(ClientSendResultValue);// 변환 바이트 -> string = default,GetString | string -> 바이트 = utf8,GetBytes if (BS.CryptoSend(SendStringToByteGender, PacketType.Response) == 0) { break; } } }