class Server: def __init__(self, address=""): self.address = address self.max_packet_length = 0xffff self.obex_version = OBEX_Version() self.request_handler = requests.RequestHandler() self.connected = False self.remote_info = None def start_service(self, port, name, uuid, service_classes, service_profiles, provider, description, protocols): socket = BluetoothSocket(RFCOMM) socket.bind((self.address, port)) socket.listen(1) advertise_service(socket, name, uuid, service_classes, service_profiles, provider, description, protocols) print("Starting server for %s on port %i" % socket.getsockname()) # self.serve(socket) return socket @staticmethod def stop_service(socket): stop_advertising(socket) def serve(self, socket): while True: connection, address = socket.accept() if not self.accept_connection(*address): connection.close() continue self.connected = True while self.connected: request = self.request_handler.decode(connection) self.process_request(connection, request, *address) def _max_length(self): if hasattr(self, "remote_info"): return self.remote_info.max_packet_length else: return self.max_packet_length def send_response(self, socket, response, header_list=None): # TODO: This needs to be able to split messages that are longer than # the maximum message length agreed with the other party. if header_list is None: header_list = [] while header_list: if response.add_header(header_list[0], self._max_length()): header_list.pop(0) else: socket.sendall(response.encode()) response.reset_headers() # Always send at least one request. socket.sendall(response.encode()) def _reject(self, socket): self.send_response(socket, responses.Forbidden()) # noinspection PyUnusedLocal @staticmethod def accept_connection(address, port): return True def process_request(self, connection, request, *address): """Processes the request from the connection. This method should be reimplemented in subclasses to add support for more request types. """ if isinstance(request, requests.Connect): self.connect(connection, request) elif isinstance(request, requests.Disconnect): self.disconnect(connection) elif isinstance(request, requests.Put): self.put(connection, request) else: self._reject(connection) def connect(self, socket, request): if request.obex_version > self.obex_version: self._reject(socket) self.remote_info = request max_length = self.remote_info.max_packet_length flags = 0 data = (self.obex_version.to_byte(), flags, max_length) response = responses.ConnectSuccess(data) self.send_response(socket, response) def disconnect(self, socket): response = responses.Success() self.send_response(socket, response) self.connected = False def put(self, socket, request): self._reject(socket)
class Client: """Client client = Client(address, port) Provides common functionality for OBEX clients, including methods for connecting to and disconnecting from a server, sending and receiving headers, and methods for higher level actions such as "get", "put", "set path" and "abort". The address used is a standard six-field bluetooth address, and the port should correspond to the port providing the service you wish to access. """ def __init__(self, address, port): self.address = address self.port = port self.max_packet_length = 0xffff self.obex_version = OBEX_Version() self.response_handler = responses.ResponseHandler() self.socket = None self._external_socket = False self.connection_id = None def _send_headers(self, request, header_list, max_length): """Convenience method to add headers to a request and send one or more requests with those headers.""" # Ensure that any Connection ID information is sent first. if self.connection_id: header_list.insert(0, self.connection_id) while header_list: if request.add_header(header_list[0], max_length): header_list.pop(0) else: self.socket.sendall(request.encode()) if isinstance(request, requests.Connect): response = self.response_handler.decode_connection( self.socket) else: response = self.response_handler.decode(self.socket) if not isinstance(response, responses.Continue): return response request.reset_headers() # Always send at least one request. if isinstance(request, requests.Get): # Turn the last Get request containing the headers into a # Get_Final request. request.code = requests.Get_Final.code self.socket.sendall(request.encode()) if isinstance(request, requests.Connect): response = self.response_handler.decode_connection(self.socket) else: response = self.response_handler.decode(self.socket) return response def _collect_parts(self, header_list): body = [] new_headers = [] for header in header_list: if isinstance(header, headers.Body): body.append(header.data) elif isinstance(header, headers.End_Of_Body): body.append(header.data) else: new_headers.append(header) return new_headers, b"".join(body) def set_socket(self, socket): """set_socket(self, socket) Sets the socket to be used for communication to the socket specified. If socket is None, the client will create a socket for internal use when a connection is made. This is the default behaviour. This method must not be called once a connection has been opened. Only after an existing connection has been disconnected is it safe to set a new socket. """ self.socket = socket if socket is None: self._external_socket = False else: self._external_socket = True def connect(self, header_list=()): """connect(self, header_list = ()) Sends a connection message to the server and returns its response. Typically, the response is either Success or a subclass of FailureResponse. Specific headers can be sent by passing a sequence as the header_list keyword argument. """ if not self._external_socket: self.socket = Socket() self.socket.connect((self.address, self.port)) flags = 0 data = (self.obex_version.to_byte(), flags, self.max_packet_length) max_length = self.max_packet_length request = requests.Connect(data) header_list = list(header_list) response = self._send_headers(request, header_list, max_length) if isinstance(response, responses.ConnectSuccess): self.remote_info = response for header in response.header_data: if isinstance(header, headers.Connection_ID): # Recycle the Connection ID data to create a new header # for future use. self.connection_id = headers.Connection_ID(header.decode()) elif not self._external_socket: self.socket.close() return response def disconnect(self, header_list=()): """disconnect(self, header_list = ()) Sends a disconnection message to the server and returns its response. Typically, the response is either Success or a subclass of FailureResponse. Specific headers can be sent by passing a sequence as the header_list keyword argument. If the socket was not supplied using set_socket(), it will be closed. """ max_length = self.remote_info.max_packet_length request = requests.Disconnect() header_list = list(header_list) response = self._send_headers(request, header_list, max_length) if not self._external_socket: self.socket.close() self.connection_id = None return response def put(self, name, file_data, header_list=(), callback=None): """put(self, name, file_data, header_list = (), callback = None) Sends a file with the given name, containing the file_data specified, to the server for storage in the current directory for the session. If a callback is specified, it will be called with each response obtained during the put operation. If no callback is specified, the final response is returned when the put operation is complete or an error occurs. Additional headers can be sent by passing a sequence as the header_list keyword argument. These will be sent after the name and file length information associated with the name and file_data supplied. """ for response in self._put(name, file_data, header_list): if isinstance(response, responses.Continue) or \ isinstance(response, responses.Success): # Report successful responses if using a callback. if callback: callback(response) elif callback: # Report failure responses using the callback, then return. callback(response) return else: # Return failure responses directly. return response # Finally, return the last response if not using a callback. if not callback: return response def _put(self, name, file_data, header_list=()): header_list = [headers.Name(name), headers.Length(len(file_data))] + list(header_list) max_length = self.remote_info.max_packet_length request = requests.Put() response = self._send_headers(request, header_list, max_length) yield response if not isinstance(response, responses.Continue): return # Send the file data. # The optimum size is the maximum packet length accepted by the # remote device minus three bytes for the header ID and length # minus three bytes for the request. optimum_size = max_length - 3 - 3 i = 0 while i < len(file_data): data = file_data[i:i + optimum_size] i += len(data) if i < len(file_data): request = requests.Put() request.add_header(headers.Body(data, False), max_length) self.socket.sendall(request.encode()) response = self.response_handler.decode(self.socket) yield response if not isinstance(response, responses.Continue): return else: request = requests.Put_Final() request.add_header(headers.End_Of_Body(data, False), max_length) self.socket.sendall(request.encode()) response = self.response_handler.decode(self.socket) yield response if not isinstance(response, responses.Success): return def get(self, name=None, header_list=(), callback=None): """get(self, name = None, header_list = (), callback = None) Requests the specified file from the server's current directory for the session. If a callback is specified, it will be called with each response obtained during the get operation. If no callback is specified, a value is returned which depends on the success of the operation. For an operation without callback, if successful, this method returns a tuple containing a list of responses received during the operation and the file data received; if unsuccessful, a single response object is returned. Additional headers can be sent by passing a sequence as the header_list keyword argument. These will be sent after the name information. """ returned_headers = [] for response in self._get(name, header_list): if isinstance(response, responses.Continue) or \ isinstance(response, responses.Success): # Report successful responses if using a callback or collect # them for later. if callback: callback(response) else: returned_headers += response.header_data elif callback: # Report failure responses using the callback, then return. callback(response) return else: # Return failure responses directly. return response # Finally, return the collected responses if not using a callback. if not callback: return self._collect_parts(returned_headers) def _get(self, name=None, header_list=()): header_list = list(header_list) if name is not None: header_list = [headers.Name(name)] + header_list max_length = self.remote_info.max_packet_length request = requests.Get() response = self._send_headers(request, header_list, max_length) yield response if not isinstance(response, responses.Continue) and \ not isinstance(response, responses.Success): return # Retrieve the file data. file_data = [] request = requests.Get_Final() while isinstance(response, responses.Continue): self.socket.sendall(request.encode()) response = self.response_handler.decode(self.socket) yield response def setpath(self, name="", create_dir=False, to_parent=False, header_list=()): """setpath(self, name = "", create_dir = False, to_parent = False, header_list = ()) Requests a change to the server's current directory for the session to the directory with the specified name, and returns the response. This method is also used to perform other actions, such as navigating to the parent directory (set to_parent to True) and creating a new directory (set create_dir to True). Additional headers can be sent by passing a sequence as the header_list keyword argument. These will be sent after the name information. """ header_list = list(header_list) if name is not None: header_list = [headers.Name(name)] + header_list max_length = self.remote_info.max_packet_length flags = 0 if not create_dir: flags |= requests.Set_Path.DontCreateDir if to_parent: flags |= requests.Set_Path.NavigateToParent request = requests.Set_Path((flags, 0)) response = self._send_headers(request, header_list, max_length) return response def delete(self, name, header_list=()): """delete(self, name, header_list = ()) Requests the deletion of the file with the specified name from the current directory and returns the server's response. """ header_list = [headers.Name(name)] + list(header_list) max_length = self.remote_info.max_packet_length request = requests.Put_Final() return self._send_headers(request, header_list, max_length) def abort(self, header_list=()): """abort(self, header_list = ()) Aborts the current session and returns the server's response. Specific headers can be sent by passing a sequence as the header_list keyword argument. Warning: This method should only be called to terminate a running operation and is therefore only useful for developers who want to reimplementing existing operations. """ header_list = list(header_list) max_length = self.remote_info.max_packet_length request = requests.Abort() response = self._send_headers(request, header_list, max_length) return response
class Client(object): def __init__(self, callback=None, err_callback=None, loop=None): self.max_packet_length = 0xffff self.obex_version = OBEX_Version() self.__callback=callback self.__err_callback=err_callback self.loop = loop # internal state variables self.state = common.IDLE self.state_put = common.PUT_IDLE # internal holders self.pending_headers = [] self.response_handler = responses.ResponseHandler() self.length = 0 self.type = None self.invalid = False # a state flag that allow us to know if we have to send more # headers as soon as we get a reply self.sending_headers = False def cleanup(self): self.socket.remove_callbacks() self.socket.close() def callback(self, *args, **kwargs): if self.invalid: return print "callback", args, kwargs if callable(self.__callback): self.__callback(self, *args, **kwargs) def err_callback(self, *args, **kwargs): print "err_callback", args, kwargs self.invalid = True if callable(self.__err_callback): self.__err_callback(self, *args, **kwargs) def internal_callback(self, *args, **kwargs): if self.state != common.PUT: return self.callback(*args, **kwargs) if self.state_put == common.PUT_HEADER: return self.put_headers_done(*args, **kwargs) if self.state_put == common.PUT_BODY: return self.put_body_done(*args, **kwargs) if self.state_put == common.PUT_FINAL: return self.put_done(*args, **kwargs) raise Exception("Invalid state %s %s" % ( common.STATES[self.state], common.STATES_PUT[self.state_put] )) def data_ready(self, amount): print "data_ready", amount if self.length == 0: if amount < 3: return format = ">BH" data = self.socket.recv(3) amount=amount-3 type, length = struct.unpack(format, data) else: type = self.type length = self.length if length-3>amount: self.type = type self.length = length self.data = data print "pending data", length return data += self.socket.recv(length - 3) self.type = 0 self.length = 0 print type, length, len(data) if isinstance(self.request, requests.Connect): response = self.response_handler.decode_connection(type, length, data) self.remote_info = response else: response = self.response_handler.decode(type, length, data) if self.sending_headers and not isinstance(response, responses.Continue): self.err_callback(error=common.ObexError(response)) return self.internal_callback(response=response) def __connected_rfcomm(self, *args, **kwargs): self.callback(*args[1:], **kwargs) def __failed_rfcomm(self, *args, **kwargs): self.err_callback(error=common.ErrnoError(args[0]), *args[1:], **kwargs) def connect_rfcomm(self, address, port, callback, err_callback, bind=None): ''' Start up the RFcomm level connection. Once this successed you have to call connect_rfcomm ''' print "connecting rfcomm" self.socket = BluetoothAsyncSocket() self.__callback = callback self.__err_callback = err_callback if bind: self.socket._sock.bind((bind, 0)) self.socket.connect_ex((address, port), callback=self.__connected_rfcomm, err_callback=self.__failed_rfcomm) self.state = common.CONNECTING_RFCOMM return common.ReplyPending() def connect_obex(self, header_list=[]): ''' Start the Obex connection. ''' flags = 0 data = (self.obex_version.to_byte(), flags, self.max_packet_length) max_length = self.max_packet_length request = requests.Connect(data) # upgrade our state self.state = common.CONNECTING_OBEX # register to get informed when more data is available self.socket.setReadReady(self.data_ready) self.pending_headers = list(header_list) self._send_headers(request, max_length) def _send_headers(self, request, max_length): """Convenience method to add headers to a request and send one or more requests with those headers.""" while self.pending_headers: if request.add_header(self.pending_headers[0], max_length): self.pending_headers.pop(0) else: print "sending on headers", request.encode() self.socket.sendall(request.encode()) request.reset_headers() self.sending_headers = True self.request = request # now we wait for data return common.ReplyPending self.sending_headers = False # Always send at least one request. if isinstance(request, requests.Get): # Turn the last Get request containing the headers into a # Get_Final request. request.code = requests.Get_Final.code print "sending", len(request.encode()) self.socket.sendall(request.encode()) self.request = request return common.ReplyPending def put_headers_done(self, response): if not isinstance(response, responses.Continue): return self.err_callback(common.ObexError(response)) self.put_body() def put_body_done(self, response): if not isinstance(response, responses.Continue): return self.err_callback(common.ObexError(response)) self.put_body() def put_done(self, response): if not isinstance(response, responses.Success): return self.err_callback(common.ObexError(response)) self.callback(response) self.state = common.CONNECTED self.state_put = common.PUT_IDLE def put_body(self): max_length = self.remote_info.max_packet_length file_data = self.file_data # Send the file data. # The optimum size is the maximum packet length accepted by the # remote device minus three bytes for the header ID and length # minus three bytes for the request. optimum_size = max_length - 3 - 3 data = file_data[:optimum_size] self.file_data=self.file_data[optimum_size:] if len(data) == 0: raise Exception("work done") if len(self.file_data) > 0: self.state_put = common.PUT_BODY request = requests.Put() request.add_header(headers.Body(data, False), max_length) self.socket.sendall(request.encode()) else: self.state_put = common.PUT_FINAL request = requests.Put_Final() request.add_header(headers.End_Of_Body(data, False), max_length) self.socket.sendall(request.encode()) def put(self, name, file_data, header_list = ()): """put(self, name, file_data, header_list = ()) Sends a file with the given name, containing the file_data specified, to the server for storage in the current directory for the session, and returns the response. Additional headers can be sent by passing a sequence as the header_list keyword argument. These will be sent after the name and file length information associated with the name and file_data supplied. """ header_list = [ headers.Name(name), headers.Length(len(file_data)) ] + list(header_list) max_length = self.remote_info.max_packet_length request = requests.Put() self.state = common.PUT self.state_put = common.PUT_HEADER self.file_data = file_data self.pending_headers = header_list self._send_headers(request, max_length)