From d6aa9f35fb53f410d44e3b88c27ce167e85054a2 Mon Sep 17 00:00:00 2001 From: Philippe PITTOLI Date: Fri, 20 Dec 2019 17:20:08 +0100 Subject: [PATCH] record users, handling user status, new message format --- src/client.cr | 36 ++++++++++++++------ src/common.cr | 55 +++++++++++++++++++++++++++--- src/json_tests.cr | 5 ++- src/main.cr | 87 +++++++++++++++++++++++++++++++++++------------ 4 files changed, 145 insertions(+), 38 deletions(-) diff --git a/src/client.cr b/src/client.cr index b9e3321..7e0052d 100644 --- a/src/client.cr +++ b/src/client.cr @@ -4,13 +4,22 @@ require "json" require "./common.cr" +# TODO +# For now, this example only upload files. +# In a near future, we should be able to download files, too. + service_name = "filestorage" files_and_directories_to_transfer = Array(String).new +# This is the requests we will send to the server +requets = Array(Requets).new + OptionParser.parse do |parser| - parser.on "-s service-name", "--service-name service-name", "Service name." do |name| + parser.on "-s service-name", + "--service-name service-name", + "Service name." do |name| service_name = name end @@ -24,19 +33,16 @@ OptionParser.parse do |parser| end end -client = IPC::Client.new service_name - # # Get informations about files to transfer +# For now, we only want to upload files, so we create an UploadRequest # files_info = Array(FileInfo).new puts "files and directories to transfer" files_and_directories_to_transfer.each do |f| - puts "- #{f}" - if File.directory? f # TODO puts "Directories not supported, for now" @@ -45,11 +51,11 @@ files_and_directories_to_transfer.each do |f| files_info << FileInfo.new file end else - if File.exists? f + if ! File.exists? f puts "#{f} does not exist" - elsif File.file? f + elsif ! File.file? f puts "#{f} is neither a directory or a file" - elsif File.readable? f + elsif ! File.readable? f puts "#{f} is not readable" end end @@ -57,10 +63,16 @@ end pp! files_info -exit 0 +requests << UploadRequest.new files_info # -# Create the authentication message, including files info +# Connection to the service +# + +client = IPC::Client.new service_name + +# +# Sending the authentication message, including files info # token = Token.new 1002, "karchnu" @@ -69,6 +81,10 @@ authentication_message = AuthenticationMessage.new token, files_info client.send(1.to_u8, authentication_message.to_json) +# +# Receiving a response +# + m = client.read # puts "message received: #{m.to_s}" # puts "message received payload: #{String.new m.payload}" diff --git a/src/common.cr b/src/common.cr index 92a76b7..2e810c9 100644 --- a/src/common.cr +++ b/src/common.cr @@ -1,5 +1,16 @@ require "uuid" +enum MessageType + Error + AuthenticationMessage + Response + Transfer +end + +# For now, upload and download are sequentials. +# In a future version, we will be able to send +# arbitrary parts of each file. + class Token JSON.mapping({ uid: Int32, @@ -10,28 +21,62 @@ class Token end end +# Who knows, maybe someday we will be on UDP, too. +#class SHA256 +# JSON.mapping({ +# chunk: Slice(UInt8) +# }) +#end + + +# A file has a name, a size and tags. class FileInfo JSON.mapping({ name: String, - size: UInt32, + size: UInt64, + # list of SHA256, if we are on UDP + # chunks: Array(SHA256), tags: Array(String)? }) + # debugging constructor def initialize(@name, @size, @tags = nil) + # If on UDP + # @chunks = Array(SHA256).new + # arbitrary values here end def initialize(file : File, @tags = nil) - @name = file.basename + @name = File.basename file.path @size = file.size end end +class Request +end + +class UploadRequest < Request + property files_to_upload : Array(FileInfo) + + def initialize(@files_to_upload) + end +end + + +# WIP +class DownloadRequest < Request + property names : Array(String)?, + property tags : Array(String)? + + def initialize(@names = nil, @tags = nil) + end +end + class AuthenticationMessage JSON.mapping({ mid: String, token: Token, - files: Array(FileInfo), - tags: Array(String)? + requests: Array(Requests) }) def initialize(@token, @files, @tags = nil) @@ -50,7 +95,7 @@ class Response end end -class Transfer +class TransferMessage JSON.mapping({ mid: String, chunk: String, diff --git a/src/json_tests.cr b/src/json_tests.cr index 0056f15..9060746 100644 --- a/src/json_tests.cr +++ b/src/json_tests.cr @@ -2,13 +2,16 @@ require "json" require "./common.cr" +files_info = Array(FileInfo).new +files_info << FileInfo.new "file.txt", 4123.to_u64, %w(important truc machin) token = Token.new 1002, "karchnu" -authentication_message = AuthenticationMessage.new token +authentication_message = AuthenticationMessage.new token, files_info # TODO, TEST, DEBUG, XXX, FIXME pp! authentication_message.to_json + am_from_json = AuthenticationMessage.from_json authentication_message.to_json pp! am_from_json diff --git a/src/main.cr b/src/main.cr index 12c4f99..d6f24a6 100644 --- a/src/main.cr +++ b/src/main.cr @@ -30,33 +30,50 @@ OptionParser.parse do |parser| end -# keep track of connected users +# keep track of connected users and their requests +# TODO: requests should be handled concurrently class User + property uid : Int32 property token : Token + property requests : Array(Request) + def initialize(@token) + @uid = token.uid end end # list of connected users -# fd => User -connected_users = Hash(Int32, User).new - +# fd => uid +connected_users = Hash(Int32, Int32).new +users_status = Hash(Int32, User).new + service = IPC::SwitchingService.new service_name +def receiving_files(user : User, event : IPC::Event::Message) +end + +# Could be the reception of a file or a file request +def request_handling(user : User, event : IPC::Event::Message) + puts "request handling" + + # + # Here we get requests from the message received + # + +end + service.loop do |event| case event when IPC::Event::Timer puts "#{CORANGE}IPC::Event::Timer#{CRESET}" - # puts "Disconnected client is: #{client_name}" - when IPC::Event::Connection puts "#{CBLUE}IPC::Event::Connection: #{event.connection.fd}#{CRESET}" when IPC::Event::Disconnection puts "#{CBLUE}IPC::Event::Disconnection: #{event.connection.fd}#{CRESET}" - connected_users.select! do |fd, user| + connected_users.select! do |fd, uid| fd != event.connection.fd end @@ -72,31 +89,57 @@ service.loop do |event| puts "#{CBLUE}IPC::Event::Message#{CRESET}: #{event.connection.fd}" # 1. test if the client is already authenticated - if user = connected_users[event.connection.fd]? + if userid = connected_users[event.connection.fd]? puts "User is connected: #{user.token.login}" + request_handling users_status[userid], event else puts "User is not currently connected" - authentication_message = AuthenticationMessage.from_json(String.new event.message.payload) + # The first message sent to the server has to be the AuthenticationMessage. + # Users sent their token (JWT) to authenticate themselves. + # The token contains the user id, its login and a few other parameters. + # (see the authd documentation). + authentication_message = + AuthenticationMessage.from_json( + String.new event.message.payload + ) - authentication_message.files.each do |file| - puts "uploading #{file.name} - #{file.size} bytes" + # Is the user already recorded in users_status? + if users_status[authentication_message.token.uid]? + puts "We already knew the user #{authentication_message.token.uid}" + pp! users_status[authentication_message.token.uid] + else + # AuthenticationMessage includes requests. + new_user = + User.new authentication_message.token, + authentication_message.requests + + connected_users[event.connection.fd] = new_user.uid + + # record the new user in users_status + users_status[new_user.uid] = new_user + + puts "New user is: #{new_user.token.login}" end - new_user = User.new authentication_message.token - connected_users[event.connection.fd] = new_user - puts "New user is: #{new_user.token.login}" + # The user is now connected. + user = users_status[authentication_message.token.uid] + # We verify the user's rights to upload files. + # TODO RIGHTS + # if user wants to upload but not allowed to: Response + # if user wants to get a file but not allowed to: Response + + # The user is authorized to upload files. + + # TODO: quotas + # Quotas are not defined yet. + + # Sending a response. + # The response is "Ok" when the message is well received and authorized. response = Response.new authentication_message.mid, "Ok" - event.connection.send 2.to_u8, response.to_json + event.connection.send MessageType::Response.to_u8, response.to_json end - - - # puts "New connected client is: #{client_name}" - - # The first message is the connection. - # Users sent their token (JWT) to authenticate. - # From the token, we get the user id, its login and a few other parameters (see the authd documentation). else raise "Event type not supported." end