/*
 * Copyright (c) 2001-2003 Shiman Associates Inc. All Rights Reserved.
 * 
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use, copy,
 * modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#ifdef linux
#include <linux/cdrom.h>
#include <sys/ioctl.h>
#else
#include <sys/cdio.h>
#endif
#include "mas/mas_dpi.h"
#include "mas_cdrom_common.h"
#include "cdrom_int_device.h"
#include "profile.h"


static void _queue_ret_val( int32 reaction, int32 ret_val );

/* The head of the circular list */
struct cdrom_device head;


int32 mas_dev_init_library()
{
	/* Initialize the linked list */
	head.next = &head;
	head.prev = &head;

	return 1;
}


int32 mas_dev_init_instance(int32 device_instance, void* predicate)
{
	struct cdrom_device	*new_cd_dev;
	struct cdrom_device	*current_cd_dev;
	char			*device_location = (char*) predicate;
	int			ret_val;
	
	

	masc_entering_log_level("Instantiating cdrom device: mas_dev_init_instance()");

	/* If the device_location is null set it to point to "auto" */
	if(!device_location) device_location = "auto";

	/* Check that this device is not already open */
	current_cd_dev = head.next;
	while(current_cd_dev != &head)
	{
		if(strcmp(current_cd_dev->device_location, device_location) == 0)
		{
			masc_log_message(MAS_VERBLVL_WARNING, "Device already instantiated: %s", device_location);
			goto failure;
		}
		current_cd_dev = current_cd_dev->next;
	}

	/* Allocate space for the new struct */
	if((new_cd_dev = (struct cdrom_device*)calloc(1, sizeof(struct cdrom_device))) == NULL)
	{
		masc_log_message(MAS_VERBLVL_ERROR, "calloc returned NULL");
		goto failure;
	}

	/* Somewhere over the rainbow this will really autofind a cdrom */
	if(strcmp(device_location, "auto") == 0)
	{
#ifdef linux
		device_location = "/dev/cdrom";
#elif (defined sun)
		device_location = "/vol/dev/aliases/cdrom0";
#endif
	}

	/* Now open the cdrom */
	if((new_cd_dev->dev_handle = open(device_location, O_RDONLY)) == -1)
	{
		free(new_cd_dev);
		masc_log_message(MAS_VERBLVL_ERROR, "failed to open device: %s", strerror(errno));
		goto failure;
	}

	/* Add the device name to the device struct */
	if((new_cd_dev->device_location = (char*)malloc(strlen(device_location) + 1)) == NULL)
	{
		close(new_cd_dev->dev_handle);
		free(new_cd_dev);
		masc_log_message(MAS_VERBLVL_ERROR, "malloc returned NULL");
		goto failure;
	}
	strcpy(new_cd_dev->device_location, device_location);

	/* Set the instance number that created this device */
	new_cd_dev->instance = device_instance;

	/* Add the cdrom device to the linked list of cd devices */
	new_cd_dev->next = head.next;
	new_cd_dev->prev = &head;
	new_cd_dev->next->prev = new_cd_dev;
	head.next = new_cd_dev;

	/* Now update the info about the cd */
	if(!mas_cdrom_update_status(new_cd_dev))
	{
		mas_dev_exit_instance(device_instance, new_cd_dev);
		goto failure;
	}

	/* Get the reaction port */
	if(masd_get_port_by_name(device_instance, "reaction", &new_cd_dev->reaction_port) < 0)
	{
		masc_log_message(MAS_VERBLVL_ERROR, "Could not get MAS reaction port.");
		mas_dev_exit_instance(device_instance, new_cd_dev);
		goto failure;
	}

	ret_val = 1;
	goto success;
failure:
	ret_val = 0;

success:
	/* FIXME return success value when possible */
	masc_exiting_log_level();
	return ret_val;
}

int32 mas_dev_exit_instance(int32 device_instance, void* predicate)
{
	struct cdrom_device	*cd_dev;
	int			i;


	masc_entering_log_level("Exiting device instance: mas_dev_exit_instance()");

	/* If device_instance is -1 then this function is being called
	 * internally with a pointer to a device. */
	if(device_instance == -1)
		cd_dev = (struct cdrom_device*)predicate;
	else
	{
		if((cd_dev = InstancetoCDDev(device_instance)) == NULL)
		{
			masc_exiting_log_level();
			return 0;
		}
	}

	/* Free and close the device resources */
	if(cd_dev->device_location) free(cd_dev->device_location);
	if(cd_dev->cd_genre) free(cd_dev->cd_genre);
	if(cd_dev->cd_title) free(cd_dev->cd_title);
	if(cd_dev->cd_year) free(cd_dev->cd_year);
	if(cd_dev->cd_misc_data) free(cd_dev->cd_misc_data);
	if(cd_dev->tracks)
	{
		for(i = 0; i <= cd_dev->number_of_tracks; i++)
			if(cd_dev->tracks[i].trackname) free(cd_dev->tracks[i].trackname);
		free(cd_dev->tracks);
	}
	close(cd_dev->dev_handle);

	/* Now remove it from the linked list */
	cd_dev->prev->next = cd_dev->next;
	cd_dev->next->prev = cd_dev->prev;
	free(cd_dev);

	masc_exiting_log_level();
	return 1;
}

int32 mas_dev_exit_library()
{
	while(head.next != &head)
		mas_dev_exit_instance(-1, head.next);

	return 1;
}

int32 mas_dev_show_state(int32 device_instance, void* predicate)
{
	return 1;
}

int32 mas_cdrom_play_track(int32 device_instance, void* predicate)
{
	struct mas_package	package;
	struct cdrom_ti		ti;	/* Unix defined struct cdio.h */
	struct cdrom_device	*cd_dev = NULL;
	int			track;
	int			ret_val;



	masc_entering_log_level("Playing cdrom track: mas_cdrom_play_track()");

	/* Get the data from the mas package */
	masc_setup_package( &package, predicate, 0, MASC_PACKAGE_STATIC|MASC_PACKAGE_EXTRACT);
	masc_pull_int32(&package, &track);

	if((cd_dev = InstancetoCDDev(device_instance)) == NULL)
		goto failure;

	/* Make sure the cd info is up to date */
	if(!mas_cdrom_update_status(cd_dev))
		goto failure;

	/* Set the beginning track to play */
	if(track > cd_dev->number_of_tracks)
		ti.cdti_trk0 = 1;
	else
		ti.cdti_trk0 = track;

	/* Set the last track to play to the last one on the cd */
	ti.cdti_trk1 = cd_dev->number_of_tracks;

	/* Set the index to 1 */
	ti.cdti_ind0 = 1;
	ti.cdti_ind1 = 1;

	if(ioctl(cd_dev->dev_handle, CDROMPLAYTRKIND, &ti) == -1)
	{
		masc_log_message(MAS_VERBLVL_ERROR, "ioctl failed: %s", strerror(errno));
		goto failure;
	}

	/* Update the status from the new changes */
	if(!mas_cdrom_update_status(cd_dev))
		goto failure;

	ret_val = 1;
	goto success;
failure:
	ret_val = 0;
success:
	/* stuff the predicate package */
        masc_strike_package( &package );
        _queue_ret_val( cd_dev->reaction_port, ret_val );
	masc_exiting_log_level();
	return ret_val;
}

int32 mas_cdrom_play_msf(int32 device_instance, void* predicate)
{
	struct mas_package	package;
	struct cdrom_msf	msf;	/* Unix cdio.h defined struct */
	struct cdrom_device	*cd_dev;
	int			minute, second, frame;
	int			ret_val;


	masc_entering_log_level("Playing cdrom msf location: mas_cdrom_play_msf()");

	/* Get the data from the mas package */
	masc_setup_package(&package, predicate, 0, MASC_PACKAGE_STATIC|MASC_PACKAGE_EXTRACT);
	masc_pull_int32(&package, &minute);
	masc_pull_int32(&package, &second);
	masc_pull_int32(&package, &frame);
	
	if((cd_dev = InstancetoCDDev(device_instance)) == NULL)
		goto failure;

	/* Make sure the cd info is up to date */
	if(!mas_cdrom_update_status(cd_dev))
		goto failure;

	/* Set the starting time to play at */
	msf.cdmsf_min0 = minute;
	msf.cdmsf_sec0 = second;
	msf.cdmsf_frame0 = frame;

	/* Set the ending time to the end of the cd */
	msf.cdmsf_min1 = cd_dev->tracks[cd_dev->number_of_tracks].start_msf.minute;
	msf.cdmsf_sec1 = cd_dev->tracks[cd_dev->number_of_tracks].start_msf.second;
	msf.cdmsf_frame1 = cd_dev->tracks[cd_dev->number_of_tracks].start_msf.frame;

	if(ioctl(cd_dev->dev_handle, CDROMPLAYMSF, &msf) == -1)
	{
		masc_log_message(MAS_VERBLVL_ERROR, "ioctl failed: %s", strerror(errno));
		goto failure;
	}

	/* Make sure the cd info is up to date */
	if(!mas_cdrom_update_status(cd_dev))
		goto failure;

	ret_val = 1;
	goto success;
failure:
	ret_val = 0;
success:
	/* stuff the predicate package */
        masc_strike_package( &package );
        _queue_ret_val( cd_dev->reaction_port, ret_val );
	masc_exiting_log_level();
	return ret_val;
}

int mas_cdrom_update_status(struct cdrom_device *cd_dev)
{
	struct cdrom_subchnl	subchnl; /* Unix defined struct cdio.h */


	masc_entering_log_level("Updating cdrom status: mas_cdrom_update_status()");

	/* Check if the cdrom was closed and reopen it if neccessary. */
	if(cd_dev->dev_handle == -1)
	{
		if((cd_dev->dev_handle = open(cd_dev->device_location, O_RDONLY)) == -1)
		{
			masc_log_message(MAS_VERBLVL_ERROR, "Failed to reopen cdrom: %s", strerror(errno));
			masc_exiting_log_level();
			return 0;
		}
	}

	/* Get the status of the cdrom */
	subchnl.cdsc_format = CDROM_MSF;
	if(ioctl(cd_dev->dev_handle, CDROMSUBCHNL, &subchnl) == -1)
	{
		masc_log_message(MAS_VERBLVL_ERROR, "ioctl failed: %s", strerror(errno));
		masc_exiting_log_level();
		return 0;
	}

	/* Set the current status of the cdrom */
	switch(subchnl.cdsc_audiostatus)
	{
		case CDROM_AUDIO_PLAY:
			cd_dev->status = CDROM_PLAY;
			break;
		case CDROM_AUDIO_PAUSED:
			cd_dev->status = CDROM_PAUSE;
			break;
		case CDROM_AUDIO_COMPLETED:
			cd_dev->status = CDROM_COMPLETE;
			break;
		case CDROM_AUDIO_INVALID:
		case CDROM_AUDIO_NO_STATUS:
		case CDROM_AUDIO_ERROR:
			cd_dev->status = CDROM_STOP;
			break;
		default:
			cd_dev->status = CDROM_ERROR;
	}

	/* Set the current playing track */
	cd_dev->current_track = subchnl.cdsc_trk;

	/* Set the addresses of the current track */
	cd_dev->absolute_msf.minute = subchnl.cdsc_absaddr.msf.minute;
	cd_dev->absolute_msf.second = subchnl.cdsc_absaddr.msf.second;
	cd_dev->absolute_msf.frame = subchnl.cdsc_absaddr.msf.frame;

	cd_dev->relative_msf.minute = subchnl.cdsc_reladdr.msf.minute;
	cd_dev->relative_msf.second = subchnl.cdsc_reladdr.msf.second;
	cd_dev->relative_msf.frame = subchnl.cdsc_reladdr.msf.frame;

	/* Now update the track information */
	if(!mas_dev_read_track_info(cd_dev))
	{
		masc_exiting_log_level();
		return 0;
	}

	masc_exiting_log_level();
	return 1;
}

int32 mas_cdrom_set_status(int32 device_instance, void* predicate)
{
	struct cdrom_device	*cd_dev;
	struct mas_package	package;
	int			state;
	int			cd_status;
	int			ret_val;


	masc_entering_log_level("Setting cdrom status: mas_cdrom_set_status()");

	/* Get the data from the mas package */
	masc_setup_package(&package, predicate, 0, MASC_PACKAGE_STATIC|MASC_PACKAGE_EXTRACT);
	masc_pull_int32(&package, &cd_status);

	if((cd_dev = InstancetoCDDev(device_instance)) == NULL)
		goto failure;

	/* Find which state to change to */
	switch(cd_status)
	{
		case CDROM_PLAY:
			state = CDROMPAUSE;
			break;
		case CDROM_PAUSE:
			state = CDROMPAUSE;
			break;
		case CDROM_RESUME:
			state = CDROMRESUME;
			break;
		case CDROM_STOP:
			state = CDROMSTOP;
			break;
		case CDROM_EJECT:
			state = CDROMEJECT;
			break;
		default:
			goto failure;
	}

	if(ioctl(cd_dev->dev_handle, state, 0) == -1)
	{
		masc_log_message(MAS_VERBLVL_ERROR, "ioctl failed: %s", strerror(errno));
		goto failure;
	}

	/* If the cdrom was eject it needs to be closed also */
	if(state == CDROMEJECT)
	{
		close(cd_dev->dev_handle);
		cd_dev->dev_handle = -1;
	}

	ret_val = 1;
	goto success;
failure:
	ret_val = 0;
success:
	/* stuff the predicate package */
        masc_strike_package( &package );
        _queue_ret_val( cd_dev->reaction_port, ret_val );
	masc_exiting_log_level();
	return ret_val;
}

int32 mas_cdrom_get_info(int32 device_instance, void* predicate)
{
	struct mas_package	package;
	struct cdrom_device	*cd_dev;
	int			ret_val = 1;
	int			query_cddb;
	int			i;
	char			*username = NULL, *servername = NULL;


	masc_entering_log_level("Getting cdrom information: mas_cdrom_get_info()");

	/* Make sure the cd info is up to date */
	if((cd_dev = InstancetoCDDev(device_instance)) == NULL)
		ret_val = 0;
	else if(!mas_cdrom_update_status(cd_dev))
		ret_val = 0;

	/* Get the data from the mas package */
	masc_setup_package(&package, predicate, 0, MASC_PACKAGE_STATIC|MASC_PACKAGE_EXTRACT);
	masc_pull_int32(&package, &query_cddb);

	/* If query_cddb is true then get the cddb info */
	if(ret_val && query_cddb)
	{
		masc_pull_string(&package, &servername, FALSE);
		masc_pull_string(&package, &username, FALSE);
		/* Check if the cddb update fails */
		if(!update_cddb_info(cd_dev, username, servername))
			/* Set ret_val to 1 indicating only the track info is being sent. */
			ret_val = 1;
		else
			/* Set ret_val to 2 indicating cddb data and track info is being sent */
			ret_val = 2;
	}

	/* stuff the predicate package */
        masc_setup_package(&package, NULL, 0, MASC_PACKAGE_NOFREE );

	masc_push_int32(&package, ret_val);
	if(ret_val)
	{
		/* First push the cd track info */
		masc_push_int32(&package, cd_dev->number_of_tracks);
		for(i=0; i<cd_dev->number_of_tracks; i++)
		{
			masc_push_int32(&package, cd_dev->tracks[i].start_msf.minute);
			masc_push_int32(&package, cd_dev->tracks[i].start_msf.second);
			masc_push_int32(&package, cd_dev->tracks[i].start_msf.frame);
			masc_push_int32(&package, cd_dev->tracks[i].length_msf.minute);
			masc_push_int32(&package, cd_dev->tracks[i].length_msf.second);
			masc_push_int32(&package, cd_dev->tracks[i].length_msf.frame);
		}
		/* Push the cddb info if needed */
		if(ret_val == 2)
		{
			masc_push_int32(&package, cd_dev->cddb_id);
			masc_push_string(&package, cd_dev->cd_title);
			masc_push_string(&package, cd_dev->cd_genre);
			masc_push_string(&package, cd_dev->cd_year);
			masc_push_string(&package, cd_dev->cd_misc_data);
			for(i=0; i<cd_dev->number_of_tracks; i++)
				masc_push_string(&package, cd_dev->tracks[i].trackname);
		}
	}
	
	/* Send the package off */
	masc_finalize_package(&package);
	masd_reaction_queue_response(cd_dev->reaction_port, package.contents, package.size);
        masc_strike_package( &package );

	masc_exiting_log_level();
	return ret_val;

}

int32 mas_cdrom_get_status(int32 device_instance, void* predicate)
{
	struct mas_package	package;
	struct cdrom_device	*cd_dev;
	int			ret_val = 1;


	masc_entering_log_level("Getting cdrom status: mas_cdrom_get_status()");

	/* Make sure the cd info is up to date */
	if((cd_dev = InstancetoCDDev(device_instance)) == NULL)
		ret_val = 0;
	else if(!mas_cdrom_update_status(cd_dev))
		ret_val = 0;

	/* stuff the predicate package */
        masc_setup_package( &package, NULL, 0, MASC_PACKAGE_NOFREE );
	masc_push_int32(&package, ret_val);
	if(ret_val)
	{
		if(cd_dev->device_location) masc_push_string(&package, cd_dev->device_location);
		masc_push_int32(&package, cd_dev->status);
		masc_push_int32(&package, cd_dev->current_track);
		masc_push_int32(&package, cd_dev->absolute_msf.minute);
		masc_push_int32(&package, cd_dev->absolute_msf.second);
		masc_push_int32(&package, cd_dev->absolute_msf.frame);
		masc_push_int32(&package, cd_dev->relative_msf.minute);
		masc_push_int32(&package, cd_dev->relative_msf.second);
		masc_push_int32(&package, cd_dev->relative_msf.frame);
	}
	
	/* Now send the package off */
	masc_finalize_package(&package);
	masd_reaction_queue_response(cd_dev->reaction_port, package.contents, package.size);
	masc_strike_package(&package);

	masc_exiting_log_level();
	return ret_val;
}

int mas_dev_read_track_info(struct cdrom_device* cd_dev)
{
	struct cdrom_tochdr	toc; /* Unix defined struct cdio.h */
	struct cdrom_tocentry	tocentry; /* Unix defined struct cdio.h */
	int			track;



	masc_entering_log_level("Reading track information: mas_dev_read_track_info()");

	/* Get the table of contents for the cd */
	if(ioctl(cd_dev->dev_handle, CDROMREADTOCHDR, &toc) == -1)
	{
		masc_log_message(MAS_VERBLVL_ERROR, "ioctl failed: %s", strerror(errno));
		if(cd_dev->tracks) free(cd_dev->tracks);
		cd_dev->tracks = NULL;
		masc_exiting_log_level();
		return 0;
	}


	/* Get the number of tracks on the cd */
	cd_dev->number_of_tracks = toc.cdth_trk1 - toc.cdth_trk0 + 1;
	if(cd_dev->number_of_tracks <= 0)
	{
		masc_log_message(MAS_VERBLVL_ERROR, "Number of tracks reported was: %d", cd_dev->number_of_tracks);
		if(cd_dev->tracks) free(cd_dev->tracks);
		cd_dev->tracks = NULL;
		masc_exiting_log_level();
		return 0;
	}

	/* Allocate memory to hold all the track info + the lead out track. */
	if(cd_dev->tracks)
	{	
		free(cd_dev->tracks);
		cd_dev->tracks = NULL;
	}
	if((cd_dev->tracks = (struct track_info*)calloc(cd_dev->number_of_tracks + 2, sizeof(struct track_info))) == NULL)
	{
		masc_log_message(MAS_VERBLVL_ERROR, "calloc returned NULL");
		masc_exiting_log_level();
		return 0;
	}
	
	/* Now fill in all the individual track data */
	tocentry.cdte_format = CDROM_MSF;
	for(track = 0; track < cd_dev->number_of_tracks; track++)
	{
		/* Add the track offset to the first track number */
		tocentry.cdte_track = track + toc.cdth_trk0;

		if(ioctl(cd_dev->dev_handle, CDROMREADTOCENTRY, &tocentry) == -1)
		{
			masc_log_message(MAS_VERBLVL_ERROR, "ioctl failed: %s", strerror(errno));
			free(cd_dev->tracks);
			cd_dev->tracks = NULL;
			masc_exiting_log_level();
			return 0;
		}

		/* Fill in the tracks info */
		cd_dev->tracks[track].number = tocentry.cdte_track;
		cd_dev->tracks[track].audio_track = tocentry.cdte_ctrl != CDROM_DATA_TRACK;

		cd_dev->tracks[track].start_msf.minute = tocentry.cdte_addr.msf.minute;
		cd_dev->tracks[track].start_msf.second = tocentry.cdte_addr.msf.second;
		cd_dev->tracks[track].start_msf.frame = tocentry.cdte_addr.msf.frame;
	}

	/* Get the cdroms leadout */
	tocentry.cdte_track = CDROM_LEADOUT;
	tocentry.cdte_format = CDROM_MSF;
	if(ioctl(cd_dev->dev_handle, CDROMREADTOCENTRY, &tocentry) == -1)	
	{
		masc_log_message(MAS_VERBLVL_ERROR, "ioctl failed: %s", strerror(errno));
		free(cd_dev->tracks);
		cd_dev->tracks = NULL;
		masc_exiting_log_level();
		return 0;
	}

	cd_dev->tracks[track].number = track + toc.cdth_trk0;
	cd_dev->tracks[track].audio_track = 0;
	cd_dev->tracks[track].start_msf.minute = tocentry.cdte_addr.msf.minute;
	cd_dev->tracks[track].start_msf.second = tocentry.cdte_addr.msf.second;
	cd_dev->tracks[track].start_msf.frame = tocentry.cdte_addr.msf.frame;

	/* Now calculate all the track lengths using the absolute track values */
	for(track = 0; track < cd_dev->number_of_tracks; track++)
	{
		cd_dev->tracks[track].length_msf.minute = cd_dev->tracks[track+1].start_msf.minute - cd_dev->tracks[track].start_msf.minute;
		cd_dev->tracks[track].length_msf.second = cd_dev->tracks[track+1].start_msf.second - cd_dev->tracks[track].start_msf.second;
		cd_dev->tracks[track].length_msf.frame = cd_dev->tracks[track+1].start_msf.frame - cd_dev->tracks[track].start_msf.frame;

		if(cd_dev->tracks[track].length_msf.frame < 0)
		{
			cd_dev->tracks[track].length_msf.second --;
			cd_dev->tracks[track].length_msf.frame += 75;
		}

		if(cd_dev->tracks[track].length_msf.second < 0)
		{
			cd_dev->tracks[track].length_msf.minute --;
			cd_dev->tracks[track].length_msf.second += 60;
		}

	}

	masc_exiting_log_level();
	return 1;
}

struct cdrom_device *InstancetoCDDev(int instance)
{
	struct cdrom_device	*current = &head;


	masc_entering_log_level("Looking up the cdrom device related to instance number: InstancetoCDDev()");

	while(current->next != &head && current->instance != instance)
		current = current->next;

	if(current != &head)
	{
		masc_exiting_log_level();
		return current;
	}
	else
	{
		masc_log_message(MAS_VERBLVL_ERROR, "No cdrom device for the given instance number: %d", instance);
		masc_exiting_log_level();
		return NULL;
	}
}

void
_queue_ret_val( int32 reaction, int32 ret_val )
{
    struct mas_package package;
    
    /* stuff the predicate package */
    masc_setup_package( &package, NULL, 0, MASC_PACKAGE_NOFREE );
    masc_push_int32( &package, ret_val);
    masc_finalize_package( &package);
    
    masd_reaction_queue_response(reaction, package.contents, package.size);
    masc_strike_package(&package);
}

