RASCSI/python/common/src/piscsi/socket_cmds.py
Daniel Markstedt 52c2aa474f
Rebrand project to PiSCSI (#1016)
* Rebrand project to PiSCSI
- rascsi ->piscsi
- rasctl -> scsictl
- rasdump -> scsidump
- ras* -> piscsi* (rasutil -> piscsi_util, etc.)

* Refined the formatting and wording of the app startup banner
* Kept some references to rascsi and rasctl where backwards compatibility is concerned
* Point to the new github repo URL

Co-authored-by: nucleogenic <nr@nucleogenic.com>
Co-authored-by: Uwe Seimet <Uwe.Seimet@seimet.de>
2022-12-05 09:58:23 -08:00

103 lines
3.5 KiB
Python

"""
Module for sending and receiving data over a socket connection with the PiSCSI backend
"""
import logging
import socket
from time import sleep
from struct import pack, unpack
from piscsi.exceptions import (
EmptySocketChunkException,
InvalidProtobufResponse,
FailedSocketConnectionException,
)
class SocketCmds:
"""
Class for sending and receiving data over a socket connection with the PiSCSI backend
"""
def __init__(self, host="localhost", port=6868):
self.host = host
self.port = port
def send_pb_command(self, payload):
"""
Takes a (str) containing a serialized protobuf as argument.
Establishes a socket connection with PiSCSI.
"""
counter = 0
tries = 20
error_msg = ""
while counter < tries:
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.connect((self.host, self.port))
response = self.send_over_socket(sock, payload)
return response
except socket.error as error:
counter += 1
logging.warning(
"The PiSCSI service is not responding - attempt %s/%s",
str(counter),
str(tries),
)
error_msg = str(error)
sleep(0.2)
except EmptySocketChunkException as ex:
raise ex
except InvalidProtobufResponse as ex:
raise ex
logging.error(error_msg)
raise FailedSocketConnectionException(error_msg)
# pylint: disable=no-self-use
def send_over_socket(self, sock, payload):
"""
Takes a socket object and (str) payload with serialized protobuf.
Sends payload to PiSCSI over socket and captures the response.
Tries to extract and interpret the protobuf header to get response size.
Reads data from socket in 2048 bytes chunks until all data is received.
"""
# Sending the magic word "RASCSI" to authenticate with the server
sock.send(b"RASCSI")
# Prepending a little endian 32bit header with the message size
sock.send(pack("<i", len(payload)))
sock.send(payload)
# Receive the first 4 bytes to get the response header
response = sock.recv(4)
if len(response) >= 4:
# Extracting the response header to get the length of the response message
response_length = unpack("<i", response)[0]
# Reading in chunks, to handle a case where the response message is very large
chunks = []
bytes_recvd = 0
while bytes_recvd < response_length:
chunk = sock.recv(min(response_length - bytes_recvd, 2048))
if chunk == b"":
error_message = (
"Read an empty chunk from the socket. Socket connection has "
"dropped unexpectedly. PiSCSI may have crashed."
)
logging.error(error_message)
raise EmptySocketChunkException(error_message)
chunks.append(chunk)
bytes_recvd = bytes_recvd + len(chunk)
response_message = b"".join(chunks)
return response_message
error_message = (
"The response from PiSCSI did not contain a protobuf header. "
"PiSCSI may have crashed."
)
logging.error(error_message)
raise InvalidProtobufResponse(error_message)