Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 74 additions & 63 deletions client.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,38 +8,31 @@
import argparse

class VoiceClient:
# Audio parameters
CHUNK = 1024
FORMAT = pyaudio.paInt16
CHANNELS = 1
RATE = 44100

def __init__(self, host, port):
self.host = host
self.port = port
self.running = True
self.name = ''
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.audio = pyaudio.PyAudio()

# Set up audio streams
self.call_active = False

self.input_stream = self.audio.open(
format=self.FORMAT,
channels=self.CHANNELS,
rate=self.RATE,
input=True,
format=self.FORMAT, channels=self.CHANNELS,
rate=self.RATE, input=True,
frames_per_buffer=self.CHUNK
)

self.output_stream = self.audio.open(
format=self.FORMAT,
channels=self.CHANNELS,
rate=self.RATE,
output=True,
format=self.FORMAT, channels=self.CHANNELS,
rate=self.RATE, output=True,
frames_per_buffer=self.CHUNK
)

# Connect to server

print(colored(f"Connecting to voice server at {host}:{port}...", "yellow"))
try:
self.sock.connect((host, port))
Expand All @@ -48,40 +41,30 @@ def __init__(self, host, port):
self.cleanup()
sys.exit(1)
print(colored("Connected to voice server.", "green"))

def start(self):
"""Start the voice client"""
# Set up signal handler
signal.signal(signal.SIGINT, self.handle_signal)

# Get user's name
while not self.name:
self.name = input(colored("Enter your name: ", "blue")).strip()

# Send name to server
self.sock.send(f"NAME:{self.name}".encode())

# Start audio threads
receive_thread = threading.Thread(target=self.receive_audio)
send_thread = threading.Thread(target=self.send_audio)

receive_thread.daemon = True
send_thread.daemon = True


receive_thread = threading.Thread(target=self.receive_audio, daemon=True)
send_thread = threading.Thread(target=self.send_audio, daemon=True)
text_thread = threading.Thread(target=self.user_input_loop, daemon=True)

receive_thread.start()
send_thread.start()

text_thread.start()

print(colored("Voice chat started! Press Ctrl+C to exit.", "yellow"))

# Keep main thread alive (cross-platform)

try:
while self.running:
time.sleep(0.1) # Sleep briefly to prevent high CPU usage
time.sleep(0.1)
except KeyboardInterrupt:
self.handle_signal(None, None)

def send_audio(self):
"""Capture and send audio to server"""
while self.running:
try:
data = self.input_stream.read(self.CHUNK, exception_on_overflow=False)
Expand All @@ -91,64 +74,92 @@ def send_audio(self):
if self.running:
print(colored(f"Error sending audio: {e}", "red"))
break

def receive_audio(self):
"""Receive and play audio from server"""
while self.running:
try:
data = self.sock.recv(self.CHUNK * 2)
if not data:
break
# Check for control messages

# Handle control / text messages
if data.startswith(b"CONTROL:"):
message = data[8:].decode()
print(colored(message, "cyan"))

if message.startswith("INCOMING_CALL:"):
caller = message[len("INCOMING_CALL:"):]
print(colored(f"[!] Incoming call from {caller}. Type /accept or /reject", "cyan"))

elif message == "CALL_ACCEPTED":
self.call_active = True
print(colored("[✓] Call accepted!", "green"))

elif "CALL_ENDED" in message or "CALL_REJECTED" in message:
self.call_active = False
print(colored("[!] Call ended or rejected.", "red"))

else:
print(colored(message, "cyan"))
continue

# Check for server full message
if data == b"SERVER_FULL":
print(colored("Server is full. Try again later.", "red"))
self.running = False
break

# Play audio data
self.output_stream.write(data)


# If actual audio
if self.call_active or True:
self.output_stream.write(data)

except Exception as e:
if self.running:
print(colored(f"Error receiving audio: {e}", "red"))
break


def user_input_loop(self):
"""Handle slash commands from user (text)"""
while self.running:
cmd = input().strip()
if cmd:
if cmd == "/quit":
self.handle_signal(None, None)
else:
self.sock.send(cmd.encode())

def handle_signal(self, signum, frame):
"""Handle Ctrl+C"""
print(colored("\nExiting voice chat...", "yellow"))
self.running = False
self.cleanup()
sys.exit(0)

def cleanup(self):
"""Cleanup resources"""
self.running = False
if hasattr(self, 'input_stream'):
self.input_stream.stop_stream()
self.input_stream.close()
try:
self.input_stream.stop_stream()
self.input_stream.close()
except:
pass
if hasattr(self, 'output_stream'):
self.output_stream.stop_stream()
self.output_stream.close()
try:
self.output_stream.stop_stream()
self.output_stream.close()
except:
pass
if hasattr(self, 'audio'):
self.audio.terminate()
try:
self.audio.terminate()
except:
pass
if hasattr(self, 'sock'):
self.sock.close()

try:
self.sock.close()
except:
pass

def main():
parser = argparse.ArgumentParser(description='Voice Chat Client')
parser.add_argument('host', help='server address')
parser.add_argument('port', type=int, help='server port')
args = parser.parse_args()

client = VoiceClient(args.host, args.port)
client.start()

if __name__ == "__main__":
main()
28 changes: 28 additions & 0 deletions identity.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# identity.py

import hashlib
from ecdsa import SigningKey, Ed25519
import os

KEY_PATH = "device_key.pem"

def get_device_id_and_key():
# Load or generate a new private key
if os.path.exists(KEY_PATH):
with open(KEY_PATH, 'rb') as f:
sk = SigningKey.from_pem(f.read())
else:
sk = SigningKey.generate(curve=Ed25519)
with open(KEY_PATH, 'wb') as f:
f.write(sk.to_pem(format="pkcs8"))

vk = sk.verifying_key
public_key_bytes = vk.to_string()

# Create a device ID by hashing the public key
device_id = hashlib.sha256(public_key_bytes).hexdigest()[:20]

# Export public key as PEM
public_key_pem = vk.to_pem().decode()

return device_id, public_key_pem
31 changes: 31 additions & 0 deletions register_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# register_client.py

import json
import socket
from identity import get_device_id_and_key

def register(server_ip='127.0.0.1', port=9000, local_ip='127.0.0.1', voice_port=8081):
device_id, public_key = get_device_id_and_key()

request = {
'device_id': device_id,
'public_key': public_key,
'ip': local_ip,
'port': voice_port
}

try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((server_ip, port))
s.send(json.dumps(request).encode())
response = s.recv(1024).decode()
s.close()

print("Registry Server Response:")
print(response)

except Exception as e:
print(f"Failed to connect to registry server: {e}")

if __name__ == "__main__":
register()
74 changes: 74 additions & 0 deletions registry_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# registry_server.py

import socket
import threading
import json
import os

REGISTRY_FILE = 'db.json'
HOST = '0.0.0.0'
PORT = 9000

# Load or initialize the registry
if os.path.exists(REGISTRY_FILE):
with open(REGISTRY_FILE, 'r') as f:
registry = json.load(f)
else:
registry = {}

def save_registry():
with open(REGISTRY_FILE, 'w') as f:
json.dump(registry, f, indent=2)

def handle_client(conn, addr):
try:
data = conn.recv(4096).decode()
request = json.loads(data)

device_id = request['device_id']
public_key = request['public_key']
ip = request['ip']
port = request['port']

# Check if device is already registered
existing = next((num for num, v in registry.items() if v['device_id'] == device_id), None)

if existing:
number = existing
else:
# Assign next available 3-digit number
for i in range(100, 1000):
number = str(i)
if number not in registry:
registry[number] = {
'device_id': device_id,
'public_key': public_key,
'ip': ip,
'port': port
}
save_registry()
break

response = {
'status': 'ok',
'number': number
}
conn.send(json.dumps(response).encode())

except Exception as e:
conn.send(json.dumps({'status': 'error', 'error': str(e)}).encode())
finally:
conn.close()

def main():
print(f"Starting Registry Server on port {PORT}...")
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind((HOST, PORT))
server.listen(5)

while True:
conn, addr = server.accept()
threading.Thread(target=handle_client, args=(conn, addr)).start()

if __name__ == "__main__":
main()
Loading