
/*
 * INPUT FROM DISK
 *
 * Copyright (C) 1999-2000  Thomas Mirlacher
 *
 * 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.
 *
 * The author may be reached as <dent@linuxvideo.org>
 *
 *------------------------------------------------------------
 *
 * 15 Apr 2000
 *	Mirko Streckenbach <strecken@infosun.fmi.uni-passau.de>
 *	patch for RH6.X raw device support
 */

#define DVD_VIDEO_LB_LEN 2048

#if HAVE_CONFIG_H
# include <config.h>
#endif

#include <stdio.h>
#include <stdlib.h>		// exit ()
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <dlfcn.h>

#ifdef HAVE_DVD_AUTHINFO
#define CSS_LIB_NAME "libcss.so.0"
# if defined (__NetBSD__) || defined (__OpenBSD__)
#  include <sys/dvdio.h>
# elif defined (__linux__)
#  include <linux/cdrom.h>
//# elif defined (__sun__)
//#  include <sys/cdio.h>
# else
#  error "Need the DVD ioctls header file"
# endif
#endif

#include <oms/log.h>
#include <oms/plugin/input.h>

#ifdef HAVE_DVD_AUTHINFO
extern int CSSisEncrypted (int fd);
extern int CSSAuthDisc (int fd, char *key_disc);
extern int CSSAuthTitle (int fd, char *key_title, int lba);
extern int CSSGetASF (int fd);
extern int CSSDecryptTitleKey (char *key_title, char *key_disc);
extern void CSSDescramble (u_char *sec, u_char *key);
#endif

#include "linux_raw.h"

static int _dvd_open		(void *self, void *name);
static int _dvd_close		(void *self);
static ssize_t _dvd_read	(void *self, void *buf, size_t count);
static int _dvd_seek		(void *self, off_t pos);
static int _dvd_ctrl		(void *plugin, uint ctrl_id, ...);

#ifdef HAVE_DVD_AUTHINFO
static int _dvd_open_css	(void);
static ssize_t _dvd_read_css	(void *self, void *buf, size_t count);
#endif

static struct {
	off_t pos;
	int fd_raw;
	int fd;
#ifdef HAVE_DVD_AUTHINFO
	uint8_t key_disc[DVD_VIDEO_LB_LEN];
	uint8_t key_title[5];
	void *css_handle;
#endif
} priv;

static plugin_input_t input = {
	priv:		&priv,
	open:		_dvd_open,
	close:		_dvd_close,
	read:		_dvd_read,
	seek:		_dvd_seek,
	ctrl:		_dvd_ctrl,
	blocksize:	DVD_VIDEO_LB_LEN,
        config:         NULL,
};


#ifdef HAVE_DVD_AUTHINFO
/**
 * CSSisEncrypted
 * check if disk is encrypted at all
 * fd   device
 **/

int _CSSisEncrypted (int fd)
{
	dvd_struct dvd;

	dvd.copyright.type = DVD_STRUCT_COPYRIGHT;
	dvd.copyright.layer_num = 0;

	if (ioctl (fd, DVD_READ_STRUCT, &dvd) < 0) {
		LOG (LOG_ERROR, "error using ioctl DVD_READ_STRUCT");
                return -1;
	}

	return dvd.copyright.cpst;      // 0 ... not encrypted
}


static int _dvd_open_css (void)
{
	if (CSSAuthTitle (priv.fd, priv.key_title, priv.pos) < 0)
		LOG (LOG_ERROR, "Authenticate title (%s)", strerror (errno));

	if (CSSDecryptTitleKey (priv.key_title, priv.key_disc) < 0)
		LOG (LOG_ERROR, "Decrypting title (%s)", strerror (errno));

	return 0;
}
#endif


/**
 * this function is responsible for setting up the device to read data from,
 * as well as authorizing the area of the disc ...
 * well, this sounds wired, we have to set the filepointer BEFORE we open
 *  	the device - probaly add a function to request access to a specific 
 *	area, instead of having this hack, have to chekck what's more overhead
 *	1) have a somewhat strange open call, and beeing able to only open
 *		one device a time,
 *	2) add a call for requesting access, probably unused for most of the 
 *		functions
 **/

static int _dvd_open (void *self, void *name)
{
	if ((priv.fd = open ((char *) name, O_RDONLY)) < 0) {
		LOG (LOG_ERROR, "error opening %s (%s)",
			(char *) name, strerror (errno));
		return -1;
	}

	if ((priv.fd_raw = raw_open (name)) < 0) {
		priv.fd_raw = priv.fd;
	} else {
		LOG (LOG_INFO, "Using RAW disk access");
	}

// set filepointer to the right position, to make sure we request
//	authorization for the right area ...

	input.seek (self, priv.pos);

#ifdef HAVE_DVD_AUTHINFO
	if (_CSSisEncrypted (priv.fd)) {
		if (priv.css_handle) {
			input.read = _dvd_read_css;

			if (CSSAuthDisc (priv.fd, priv.key_disc) < 0) {
				LOG (LOG_ERROR, "CSSAuthDisc (%s)", strerror (errno));
				return -1;
			}	

			_dvd_open_css ();	
		} else {
			LOG (LOG_ERROR, "This medium is CSS encrypted");
			LOG (LOG_INFO, "To play this CSS-encrypted DVD you need to get and install libcss");
			exit (-1);
		}
	}
#endif

	return 0;
}


static int _dvd_close (void *self)
{
#ifdef HAVE_DVD_AUTHINFO
	dvd_authinfo ai;
	int i;
#endif

	if (priv.fd != priv.fd_raw)
		close (priv.fd_raw);

#ifdef HAVE_DVD_AUTHINFO
// reset all AGIDs on the DVD drive
	for (i=0; i<4; i++) {
		memset (&ai,  0, sizeof (dvd_authinfo));
		ai.type = DVD_INVALIDATE_AGID;
		ai.lsa.agid = i;
		ioctl (priv.fd, DVD_AUTH, &ai);
	}
#endif

	close (priv.fd);
	priv.fd = -1;

// if disc access has been authorized, reset again ...

	return 0;
}


#ifdef HAVE_DVD_AUTHINFO
static ssize_t _dvd_read_css (void *self, void *buf, size_t count)
{
	int ret;

	priv.pos++;

        ret = read (priv.fd_raw, buf, count);

#ifdef DEBUG
	{
		int i;
		uint8_t *ptr = (uint8_t *) buf;

		for (i=0; i<100; i++) fprintf (stderr, "%02x ", ptr[i]);
	}
#endif

// do the CSS stuff in SW, just for now
	CSSDescramble (buf, priv.key_title);

	return ret;
}
#endif


static ssize_t _dvd_read (void *self, void *buf, size_t count)
{
	int ret;

	priv.pos++;

        if ((ret = read (priv.fd_raw, buf, count)) < 0) {
#ifdef HAVE_DVD_AUTHINFO
		LOG (LOG_ERROR, "read (%s)", strerror (errno));
#else
		LOG (LOG_ERROR, "read (%s)", strerror (errno));
		LOG (LOG_INFO, "most probably the disc is encrypted");
		LOG (LOG_INFO, "please install DVD ioctls");
#endif
	}

	return ret;
}


static int _dvd_seek (void *self, off_t pos)
{
	priv.pos = pos;

	if (priv.fd >= 0) {
        	if (lseek (priv.fd_raw, pos*(off_t)input.blocksize, SEEK_SET) == -1) {
                	LOG (LOG_ERROR, "error in lseek at pos 0x%Lx", priv.pos);
                	return -1;
		} else {
/* Reauthenticate in case new location has different key
 * Horrible kludge by swhite@ox.compsoc.net - and I haven't checked
 * if it creates issues such as memory leaks.  It's probably an unecessary
 * overhead .. there's probably a quicker way of telling if the key negociation
 * is actually required after the seek. */
#ifdef HAVE_DVD_AUTHINFO
	if (_CSSisEncrypted (priv.fd)) {
		if (priv.css_handle) {
			input.read = _dvd_read_css;

			if (CSSAuthDisc (priv.fd, priv.key_disc) < 0) {
				LOG (LOG_ERROR, "CSSAuthDisc (%s)", strerror (errno));
				return -1;
			}	

			_dvd_open_css ();	
		} else {
			LOG (LOG_ERROR, "This medium is CSS encrypted");
			LOG (LOG_INFO, "To play this CSS-encrypted DVD you need to get and install libcss");
			exit (-1);
		}
	}
#endif
			return 0;
		}
        }

	return -1;
}


static int _dvd_ctrl (void *plugin, uint ctrl_id, ...)
{
#if 0	// does not work with linux 2.2.x
//FIXME
	char name[]="/dev/dvd";

	switch (ctrl_id) {
		case CTRL_INPUT_TRAYOPEN: {
			int status, fd;

			if ((fd = open (name, O_RDONLY|O_NONBLOCK)) < 0) {
				LOG (LOG_ERROR, "Cant open device to close tray");
				return 0 ;
			}
			if ((status = ioctl(fd, CDROMEJECT)) < 0) {
				LOG (LOG_ERROR, "Couldnt close tray");
				close (fd);
				return -1;
			}
			return 0;
		}
		case CTRL_INPUT_TRAYCLOSE: {
			int status, fd;
 
			if ((fd = open (name, O_RDONLY|O_NONBLOCK)) < 0) {
				LOG (LOG_ERROR, "Cant open device to close tray");
				return 0;
			}
			if ((status = ioctl(fd, CDROMCLOSETRAY)) < 0) {
				LOG (LOG_ERROR, "Couldnt close tray, perhaps the drive is mounted");
				close(fd);
				return -1;
			}
			return 0;
		}
	}
#endif
	return 0;
}


int PLUGIN_INIT (input_dvd) (char *whoami)
{
	pluginRegister (whoami,
		PLUGIN_ID_INPUT,
		"dvd",
		NULL,
		NULL,
		&input);

#ifdef HAVE_DVD_AUTHINFO
	priv.css_handle = dlopen (CSS_LIB_NAME, RTLD_LAZY|RTLD_GLOBAL); 
#endif
	return 0;
}


void PLUGIN_EXIT (input_dvd) (void)
{
#ifdef HAVE_DVD_AUTHINFO
	if (priv.css_handle)
		dlclose (priv.css_handle);
#endif
}
