######################################################################
#     XTalk - A BSD talk client written in Python.
#     (C) Adam P. Jenkins <adampjenkins@yahoo.com>
#     
#     This program is free software; you can redistribute it and/or
#     modify it under the terms of the GNU General Public License
#     as published by the Free Software Foundation; either version 2
#     of the License, or (at your option) any later version.
#     
#     This program is distributed in the hope that it will be useful,
#     but WITHOUT ANY WARRANTY; without even the implied warranty of
#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#     GNU General Public License for more details.
#     
#     You should have received a copy of the GNU General Public License
#     along with this program; if not, write to the Free Software
#     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
######################################################################

# Implements the C structures for talking to a talk daemon

import struct, regex, regsub, socket, string

# This modules defines classes necessary to communicate with a talk
# daemon.  The talk daemon requires communication via binary data
# structures, so this module provides an interface.  There are three
# classes defined, which correspond to C structures.  Each of them has
# a method, toCStruct(), which returns a string containing the proper
# binary representation of the structure, and a method,
# fromCStruct(str), which takes such a binary string, and initializes
# itself from the binary string.  Each class also has a "size"
# attribute which returns its size in bytes.
# The classes are:
# 
# sockaddr_in:
#   family   # one of the family constants from socket module
#   port     # a port number
#   address  # an address in cannonical or number-dot format
#
# CTL_MSG:
#
# CTL_RESPONSE




# The declaration for sockaddr is
# struct sockaddr {
#   unsigned short family;  /* AF_* */
#   char sa_data[14];       /* the resto of the data */
# };
#
# sizeof(sockaddr) == 16
# 
# What's the rest of the data.  Normally it's a sockaddr_in.
# struct in_addr {
#   __u32 s_addr;
# };
#
# struct sockaddr_in {
#   short family;
#   short port;
#   struct in_addr;
#   /* plus padding to fill out to the size of a sockaddr. */
# };
#

TALK_VERSION = 1

# message types
LEAVE_INVITE = 0
LOOK_UP = 1
DELETE = 2
ANNOUNCE = 3

# response types
SUCCESS = 0
NOT_HERE = 1
FAILED = 2
MACHINE_UNKNOWN = 3
PERMISSION_DENIED = 4
UNKNOWN_REQUEST = 5
BADVERSION = 6
BADADDR	= 7
BADCTLADDR = 8

def htons(n):
    return struct.pack('bb', n >> 8, n & 0xff)

def htonl(n):
    return struct.pack('4b', n >> 24, n >> 16, n >> 8, n & 0xff)

def htonb(n):
    return struct.pack('b', n)

def ntohs(n):
    hi, low = struct.unpack('bb', n)
    return ((hi & 0xff) << 8) + (low & 0xff)

def ntohl(n):
    tmp = struct.unpack('4b', n)
    return (((tmp[0] & 0xff) << 24) +
	    ((tmp[1] & 0xff) << 16) +
	    ((tmp[2] & 0xff) << 8) +
	    (tmp[3] & 0xff))

def ntohb(n):
    tmp = struct.unpack('b', n)
    return (tmp[0] & 0xff)


class sockaddr_in:
    size = 16
    def __init__(self, family=0, port=0, addr='0.0.0.0'):
	self.family = family
	self.port = port
	self.address = addr

    # returns the equivalent of a sockaddr_in, already in network byte
    # order.
    def toCStruct(self):
	# make sure address is in numbers-dots notation
	addr = socket.gethostbyname(self.address)
	# break it up into components
	addr = regsub.split(addr, '\.')
	# and convert them to numbers
	addr = map(string.atoi, addr)

	return (htons(self.family) + htons(self.port) +
		struct.pack('4b8x', addr[0], addr[1], addr[2], addr[3]))

    def fromCStruct(self, str):
	self.family = ntohs(str[0:2])
	self.port = ntohs(str[2:4])

	addr = struct.unpack('bbbb', str[4:8])
	addr = map(lambda x: x & 0xff, addr)
	self.address = '%d.%d.%d.%d' % (addr[0], addr[1], addr[2], addr[3])

class CTL_MSG:
    size = 84
    version = TALK_VERSION
    type = 0
    idNum = 0
    addr = sockaddr_in()
    ctlAddr = sockaddr_in()
    pid = 0
    localName = ''
    remoteName = ''
    remoteTTY = ''

    def toCStruct(self):
	return (htonb(self.version) + htonb(self.type) + htons(0) +
		htonl(self.idNum) + self.addr.toCStruct() +
		self.ctlAddr.toCStruct() + htonl(self.pid) +
		self.localName + ('\0' * (12 - len(self.localName))) +
		self.remoteName + ('\0' * (12 - len(self.remoteName))) +
		self.remoteTTY + ('\0' * (16 - len(self.remoteTTY))))
			    
    def fromCStruct(self, str):
	self.version = ntohb(str[0])
	self.type = ntohb(str[1])
	self.idNum = ntohl(str[4:8])
	self.addr.fromCStruct(str[8:24])
	self.ctlAddr.fromCStruct(str[24:40])
	self.pid = ntohl(str[40:44])
	self.localName = regsub.sub('\0*$', '', str[44:56])
	self.remoteName = regsub.sub('\0*$', '', str[56:68])
	self.remoteTTY = regsub.sub('\0*$', '', str[68:84])
	

class CTL_RESPONSE:
    size = 24
    version = TALK_VERSION
    type = 0
    answer = 0
    idNum = 0
    addr = sockaddr_in()

    def toCStruct(self):
	return (struct.pack('bbbx', self.version, self.type,
			    self.answer) +
		htonl(self.idNum) + self.addr.toCStruct())

    def fromCStruct(self, str):
	self.version = ntohb(str[0])
	self.type = ntohb(str[1])
	self.answer = ntohb(str[2])
	self.idNum = ntohl(str[4:8])
	self.addr.fromCStruct(str[8:24])

