static dc_status_t
reefnet_sensuspro_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value)
{
	reefnet_sensuspro_parser_t *parser = (reefnet_sensuspro_parser_t *) abstract;

	if (abstract->size < 12)
		return DC_STATUS_DATAFORMAT;

	if (!parser->cached) {
		const unsigned char footer[2] = {0xFF, 0xFF};

		const unsigned char *data = abstract->data;
		unsigned int size = abstract->size;

		unsigned int interval = array_uint16_le (data + 4);

		unsigned int maxdepth = 0;
		unsigned int nsamples = 0;
		unsigned int offset = 10;
		while (offset + sizeof (footer) <= size &&
			memcmp (data + offset, footer, sizeof (footer)) != 0)
		{
			unsigned int value = array_uint16_le (data + offset);
			unsigned int depth = (value & 0x01FF);
			if (depth > maxdepth)
				maxdepth = depth;

			nsamples++;

			offset += 2;
		}

		parser->cached = 1;
		parser->divetime = nsamples * interval;
		parser->maxdepth = maxdepth;
	}

	if (value) {
		switch (type) {
		case DC_FIELD_DIVETIME:
			*((unsigned int *) value) = parser->divetime;
			break;
		case DC_FIELD_MAXDEPTH:
			*((double *) value) = (parser->maxdepth * FSW - parser->atmospheric) / parser->hydrostatic;
			break;
		case DC_FIELD_GASMIX_COUNT:
			*((unsigned int *) value) = 0;
			break;
		case DC_FIELD_DIVEMODE:
			*((dc_divemode_t *) value) = DC_DIVEMODE_GAUGE;
			break;
		default:
			return DC_STATUS_UNSUPPORTED;
		}
	}

	return DC_STATUS_SUCCESS;
}
Пример #2
0
static dc_status_t
hw_ostc_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime)
{
	hw_ostc_parser_t *parser = (hw_ostc_parser_t *) abstract;
	const unsigned char *data = abstract->data;
	unsigned int size = abstract->size;

	// Cache the header data.
	dc_status_t rc = hw_ostc_parser_cache (parser);
	if (rc != DC_STATUS_SUCCESS)
		return rc;

	unsigned int version = parser->version;
	const hw_ostc_layout_t *layout = parser->layout;

	unsigned int divetime = 0;
	if (version > 0x20) {
		// Use the dive time stored in the extended header, rounded down towards
		// the nearest minute, to match the value displayed by the ostc.
		divetime = (array_uint16_le (data + layout->duration) / 60) * 60;
	} else {
		// Use the normal dive time (excluding the shallow parts of the dive).
		divetime = array_uint16_le (data + layout->divetime) * 60 + data[layout->divetime + 2];
	}

	const unsigned char *p = data + layout->datetime;

	dc_datetime_t dt;
	if (version == 0x23) {
		dt.year   = p[0] + 2000;
		dt.month  = p[1];
		dt.day    = p[2];
	} else {
		dt.year   = p[2] + 2000;
		dt.month  = p[0];
		dt.day    = p[1];
	}
	dt.hour   = p[3];
	dt.minute = p[4];
	dt.second = 0;

	dc_ticks_t ticks = dc_datetime_mktime (&dt);
	if (ticks == (dc_ticks_t) -1)
		return DC_STATUS_DATAFORMAT;

	ticks -= divetime;

	if (!dc_datetime_localtime (datetime, ticks))
		return DC_STATUS_DATAFORMAT;

	return DC_STATUS_SUCCESS;
}
static dc_status_t
reefnet_sensuspro_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata)
{
	reefnet_sensuspro_parser_t *parser = (reefnet_sensuspro_parser_t*) abstract;

	const unsigned char header[4] = {0x00, 0x00, 0x00, 0x00};
	const unsigned char footer[2] = {0xFF, 0xFF};

	const unsigned char *data = abstract->data;
	unsigned int size = abstract->size;

	unsigned int offset = 0;
	while (offset + sizeof (header) <= size) {
		if (memcmp (data + offset, header, sizeof (header)) == 0) {
			if (offset + 10 > size)
				return DC_STATUS_DATAFORMAT;

			unsigned int time = 0;
			unsigned int interval = array_uint16_le (data + offset + 4);

			offset += 10;
			while (offset + sizeof (footer) <= size &&
				memcmp (data + offset, footer, sizeof (footer)) != 0)
			{
				unsigned int value = array_uint16_le (data + offset);
				unsigned int depth = (value & 0x01FF);
				unsigned int temperature = (value & 0xFE00) >> 9;

				dc_sample_value_t sample = {0};

				// Time (seconds)
				time += interval;
				sample.time = time;
				if (callback) callback (DC_SAMPLE_TIME, sample, userdata);

				// Temperature (°F)
				sample.temperature = (temperature - 32.0) * (5.0 / 9.0);
				if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata);

				// Depth (absolute pressure in fsw)
				sample.depth = (depth * FSW - parser->atmospheric) / parser->hydrostatic;
				if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata);

				offset += 2;
			}
			break;
		} else {
			offset++;
		}
	}
Пример #4
0
static unsigned int
get_profile_first (const unsigned char data[], const oceanic_common_layout_t *layout)
{
    unsigned int value;

    if (layout->pt_mode_logbook == 0) {
        value = array_uint16_le (data + 5);
    } else {
        value = array_uint16_le (data + 4);
    }

    if (layout->memsize > 0x10000)
        return (value & 0x1FFF) * PAGESIZE;
    else
        return (value & 0x0FFF) * PAGESIZE;
}
Пример #5
0
static dc_status_t
hw_ostc_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata)
{
	dc_buffer_t *buffer = dc_buffer_new (0);
	if (buffer == NULL)
		return DC_STATUS_NOMEMORY;

	dc_status_t rc = hw_ostc_device_dump (abstract, buffer);
	if (rc != DC_STATUS_SUCCESS) {
		dc_buffer_free (buffer);
		return rc;
	}

	// Emit a device info event.
	unsigned char *data = dc_buffer_get_data (buffer);
	dc_event_devinfo_t devinfo;
	devinfo.firmware = array_uint16_be (data + 264);
	devinfo.serial = array_uint16_le (data + 6);
	if (devinfo.serial > 7000)
		devinfo.model = 3; // OSTC 2C
	else if (devinfo.serial > 2048)
		devinfo.model = 2; // OSTC 2N
	else if (devinfo.serial > 300)
		devinfo.model = 1; // OSTC Mk2
	else
		devinfo.model = 0; // OSTC
	device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo);

	rc = hw_ostc_extract_dives (abstract, dc_buffer_get_data (buffer),
		dc_buffer_get_size (buffer), callback, userdata);

	dc_buffer_free (buffer);

	return rc;
}
Пример #6
0
dc_status_t
atomics_cobalt_device_version (dc_device_t *abstract, unsigned char data[], unsigned int size)
{
	atomics_cobalt_device_t *device = (atomics_cobalt_device_t *) abstract;

	if (!ISINSTANCE (abstract))
		return DC_STATUS_INVALIDARGS;

	if (size < SZ_VERSION)
		return DC_STATUS_INVALIDARGS;

#ifdef HAVE_LIBUSB
	// Send the command to the dive computer.
	uint8_t bRequest = 0x01;
	int rc = libusb_control_transfer (device->handle,
		LIBUSB_RECIPIENT_DEVICE | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_OUT,
		bRequest, 0, 0, NULL, 0, TIMEOUT);
	if (rc != LIBUSB_SUCCESS) {
		ERROR (abstract->context, "Failed to send the command.");
		return EXITCODE(rc);
	}

	HEXDUMP (abstract->context, DC_LOGLEVEL_INFO, "Write", &bRequest, 1);

	// Receive the answer from the dive computer.
	int length = 0;
	unsigned char packet[SZ_VERSION + 2] = {0};
	rc = libusb_bulk_transfer (device->handle, 0x82,
		packet, sizeof (packet), &length, TIMEOUT);
	if (rc != LIBUSB_SUCCESS || length != sizeof (packet)) {
		ERROR (abstract->context, "Failed to receive the answer.");
		return EXITCODE(rc);
	}

	HEXDUMP (abstract->context, DC_LOGLEVEL_INFO, "Read", packet, length);

	// Verify the checksum of the packet.
	unsigned short crc = array_uint16_le (packet + SZ_VERSION);
	unsigned short ccrc = checksum_add_uint16 (packet, SZ_VERSION, 0x0);
	if (crc != ccrc) {
		ERROR (abstract->context, "Unexpected answer checksum.");
		return DC_STATUS_PROTOCOL;
	}

	memcpy (data, packet, SZ_VERSION);

	return DC_STATUS_SUCCESS;
#else
	return DC_STATUS_UNSUPPORTED;
#endif
}
Пример #7
0
static dc_status_t
hw_ostc_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata)
{
	hw_ostc_parser_t *parser = (hw_ostc_parser_t *) abstract;
	const unsigned char *data = abstract->data;
	unsigned int size = abstract->size;

	// Cache the parser data.
	dc_status_t rc = hw_ostc_parser_cache (parser);
	if (rc != DC_STATUS_SUCCESS)
		return rc;

	unsigned int version = parser->version;
	unsigned int header = parser->header;
	const hw_ostc_layout_t *layout = parser->layout;

	// Get the sample rate.
	unsigned int samplerate = 0;
	if (version == 0x23)
		samplerate = data[header + 3];
	else
		samplerate = data[36];

	// Get the salinity factor.
	unsigned int salinity = data[layout->salinity];
	if (version == 0x23)
		salinity += 100;
	if (salinity < 100 || salinity > 104)
		salinity = 100;
	double hydrostatic = GRAVITY * salinity * 10.0;

	// Get the number of sample descriptors.
	unsigned int nconfig = 0;
	if (version == 0x23)
		nconfig = data[header + 4];
	else
		nconfig = 6;
	if (nconfig > MAXCONFIG) {
		ERROR(abstract->context, "Too many sample descriptors.");
		return DC_STATUS_DATAFORMAT;
	}

	// Get the extended sample configuration.
	hw_ostc_sample_info_t info[MAXCONFIG] = {{0}};
	for (unsigned int i = 0; i < nconfig; ++i) {
		if (version == 0x23) {
			info[i].type    = data[header + 5 + 3 * i + 0];
			info[i].size    = data[header + 5 + 3 * i + 1];
			info[i].divisor = data[header + 5 + 3 * i + 2];
		} else {
			info[i].type    = i;
			info[i].divisor = (data[37 + i] & 0x0F);
			info[i].size    = (data[37 + i] & 0xF0) >> 4;
		}

		if (info[i].divisor) {
			switch (info[i].type) {
			case 0: // Temperature
			case 1: // Deco / NDL
				if (info[i].size != 2) {
					ERROR(abstract->context, "Unexpected sample size.");
					return DC_STATUS_DATAFORMAT;
				}
				break;
			case 5: // CNS
				if (info[i].size != 1 && info[i].size != 2) {
					ERROR(abstract->context, "Unexpected sample size.");
					return DC_STATUS_DATAFORMAT;
				}
				break;
			default: // Not yet used.
				break;
			}
		}
	}

	unsigned int time = 0;
	unsigned int nsamples = 0;

	unsigned int offset = header;
	if (version == 0x23)
		offset += 5 + 3 * nconfig;
	while (offset + 3 <= size) {
		dc_sample_value_t sample = {0};

		nsamples++;

		// Time (seconds).
		time += samplerate;
		sample.time = time;
		if (callback) callback (DC_SAMPLE_TIME, sample, userdata);

		// Initial gas mix.
		if (time == samplerate) {
			unsigned int idx = parser->initial;
			unsigned int o2 = parser->gasmix[idx].oxygen;
			unsigned int he = parser->gasmix[idx].helium;
			sample.event.type = SAMPLE_EVENT_GASCHANGE2;
			sample.event.time = 0;
			sample.event.flags = 0;
			sample.event.value = o2 | (he << 16);
			if (callback) callback (DC_SAMPLE_EVENT, sample, userdata);
		}

		// Depth (mbar).
		unsigned int depth = array_uint16_le (data + offset);
		sample.depth = (depth * BAR / 1000.0) / hydrostatic;
		if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata);
		offset += 2;

		// Extended sample info.
		unsigned int length =  data[offset] & 0x7F;
		offset += 1;

		// Check for buffer overflows.
		if (offset + length > size) {
			ERROR (abstract->context, "Buffer overflow detected!");
			return DC_STATUS_DATAFORMAT;
		}

		// Get the event byte(s).
		unsigned int nbits = 0;
		unsigned int events = 0;
		while (data[offset - 1] & 0x80) {
			if (nbits && version != 0x23)
				break;
			if (length < 1) {
				ERROR (abstract->context, "Buffer overflow detected!");
				return DC_STATUS_DATAFORMAT;
			}
			events |= data[offset] << nbits;
			nbits += 8;
			offset++;
			length--;
		}

		// Alarms
		sample.event.type = 0;
		sample.event.time = 0;
		sample.event.flags = 0;
		sample.event.value = 0;
		switch (events & 0x0F) {
		case 0: // No Alarm
			break;
		case 1: // Slow
			sample.event.type = SAMPLE_EVENT_ASCENT;
			break;
		case 2: // Deco Stop missed
			sample.event.type = SAMPLE_EVENT_CEILING;
			break;
		case 3: // Deep Stop missed
			sample.event.type = SAMPLE_EVENT_CEILING;
			break;
		case 4: // ppO2 Low Warning
			sample.event.type = SAMPLE_EVENT_PO2;
			break;
		case 5: // ppO2 High Warning
			sample.event.type = SAMPLE_EVENT_PO2;
			break;
		case 6: // Manual Marker
			sample.event.type = SAMPLE_EVENT_BOOKMARK;
			break;
		case 7: // Low Battery
			break;
		}
		if (sample.event.type && callback)
			callback (DC_SAMPLE_EVENT, sample, userdata);

		// Manual Gas Set & Change
		if (events & 0x10) {
			if (length < 2) {
				ERROR (abstract->context, "Buffer overflow detected!");
				return DC_STATUS_DATAFORMAT;
			}
			unsigned int o2 = data[offset];
			unsigned int he = data[offset + 1];
			unsigned int idx = hw_ostc_find_gasmix (parser, o2, he, MANUAL);
			if (idx >= parser->ngasmixes) {
				if (idx >= NGASMIXES) {
					ERROR (abstract->context, "Maximum number of gas mixes reached.");
					return DC_STATUS_NOMEMORY;
				}
				parser->gasmix[idx].oxygen = o2;
				parser->gasmix[idx].helium = he;
				parser->ngasmixes = idx + 1;
			}
			sample.event.type = SAMPLE_EVENT_GASCHANGE2;
			sample.event.time = 0;
			sample.event.flags = 0;
			sample.event.value = o2 | (he << 16);
			if (callback) callback (DC_SAMPLE_EVENT, sample, userdata);
			offset += 2;
			length -= 2;
		}

		// Gas Change
		if (events & 0x20) {
			if (length < 1) {
				ERROR (abstract->context, "Buffer overflow detected!");
				return DC_STATUS_DATAFORMAT;
			}
			unsigned int idx = data[offset];
			if (idx < 1 || idx > parser->ngasmixes) {
				ERROR(abstract->context, "Invalid gas mix.");
				return DC_STATUS_DATAFORMAT;
			}
			idx--; /* Convert to a zero based index. */
			unsigned int o2 = parser->gasmix[idx].oxygen;
			unsigned int he = parser->gasmix[idx].helium;
			sample.event.type = SAMPLE_EVENT_GASCHANGE2;
			sample.event.time = 0;
			sample.event.flags = 0;
			sample.event.value = o2 | (he << 16);
			if (callback) callback (DC_SAMPLE_EVENT, sample, userdata);
			offset++;
			length--;
		}

		if (version == 0x23) {
			// SetPoint Change
			if (events & 0x40) {
				if (length < 1) {
					ERROR (abstract->context, "Buffer overflow detected!");
					return DC_STATUS_DATAFORMAT;
				}
				sample.setpoint = data[offset] / 100.0;
				if (callback) callback (DC_SAMPLE_SETPOINT, sample, userdata);
				offset++;
				length--;
			}

			// Bailout Event
			if (events & 0x0100) {
				if (length < 2) {
					ERROR (abstract->context, "Buffer overflow detected!");
					return DC_STATUS_DATAFORMAT;
				}
				sample.event.type = SAMPLE_EVENT_GASCHANGE2;
				sample.event.time = 0;
				sample.event.flags = 0;
				sample.event.value = data[offset] | (data[offset + 1] << 16);
				if (callback) callback (DC_SAMPLE_EVENT, sample, userdata);
				offset += 2;
				length -= 2;
			}
		}

		// Extended sample info.
		for (unsigned int i = 0; i < nconfig; ++i) {
			if (info[i].divisor && (nsamples % info[i].divisor) == 0) {
				if (length < info[i].size) {
					ERROR (abstract->context, "Buffer overflow detected!");
					return DC_STATUS_DATAFORMAT;
				}

				unsigned int value = 0;
				switch (info[i].type) {
				case 0: // Temperature (0.1 °C).
					value = array_uint16_le (data + offset);
					sample.temperature = value / 10.0;
					if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata);
					break;
				case 1: // Deco / NDL
					if (data[offset]) {
						sample.deco.type = DC_DECO_DECOSTOP;
						sample.deco.depth = data[offset];
					} else {
						sample.deco.type = DC_DECO_NDL;
						sample.deco.depth = 0.0;
					}
					sample.deco.time = data[offset + 1] * 60;
					if (callback) callback (DC_SAMPLE_DECO, sample, userdata);
					break;
				case 5: // CNS
					if (info[i].size == 2)
						sample.cns = array_uint16_le (data + offset) / 100.0;
					else
						sample.cns = data[offset] / 100.0;
					if (callback) callback (DC_SAMPLE_CNS, sample, userdata);
					break;
				default: // Not yet used.
					break;
				}

				offset += info[i].size;
				length -= info[i].size;
			}
		}

		if (version != 0x23) {
			// SetPoint Change
			if (events & 0x40) {
				if (length < 1) {
					ERROR (abstract->context, "Buffer overflow detected!");
					return DC_STATUS_DATAFORMAT;
				}
				sample.setpoint = data[offset] / 100.0;
				if (callback) callback (DC_SAMPLE_SETPOINT, sample, userdata);
				offset++;
				length--;
			}

			// Bailout Event
			if (events & 0x80) {
				if (length < 2) {
					ERROR (abstract->context, "Buffer overflow detected!");
					return DC_STATUS_DATAFORMAT;
				}
				sample.event.type = SAMPLE_EVENT_GASCHANGE2;
				sample.event.time = 0;
				sample.event.flags = 0;
				sample.event.value = data[offset] | (data[offset + 1] << 16);
				if (callback) callback (DC_SAMPLE_EVENT, sample, userdata);
				offset += 2;
				length -= 2;
			}
		}

		// Skip remaining sample bytes (if any).
		if (length) {
			WARNING (abstract->context, "Remaining %u bytes skipped.", length);
		}
		offset += length;
	}

	if (offset + 2 > size || data[offset] != 0xFD || data[offset + 1] != 0xFD) {
		ERROR (abstract->context, "Invalid end marker found!");
		return DC_STATUS_DATAFORMAT;
	}

	parser->cached = PROFILE;

	return DC_STATUS_SUCCESS;
}
Пример #8
0
static dc_status_t
hw_ostc3_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata)
{
	hw_ostc3_device_t *device = (hw_ostc3_device_t *) abstract;

	// Enable progress notifications.
	dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER;
	progress.maximum = (RB_LOGBOOK_SIZE * RB_LOGBOOK_COUNT) + SZ_MEMORY;
	device_event_emit (abstract, DC_EVENT_PROGRESS, &progress);

	// Download the version data.
	unsigned char id[SZ_VERSION] = {0};
	dc_status_t rc = hw_ostc3_device_version (abstract, id, sizeof (id));
	if (rc != DC_STATUS_SUCCESS) {
		ERROR (abstract->context, "Failed to read the version.");
		return rc;
	}

	// Emit a device info event.
	dc_event_devinfo_t devinfo;
	devinfo.model = 0;
	devinfo.firmware = array_uint16_be (id + 2);
	devinfo.serial = array_uint16_le (id + 0);
	device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo);

	// Allocate memory.
	unsigned char *header = (unsigned char *) malloc (RB_LOGBOOK_SIZE * RB_LOGBOOK_COUNT);
	if (header == NULL) {
		ERROR (abstract->context, "Failed to allocate memory.");
		return DC_STATUS_NOMEMORY;
	}

	// Download the logbook headers.
	rc = hw_ostc3_transfer (device, &progress, HEADER,
              NULL, 0, header, RB_LOGBOOK_SIZE * RB_LOGBOOK_COUNT);
	if (rc != DC_STATUS_SUCCESS) {
		ERROR (abstract->context, "Failed to read the header.");
		free (header);
		return rc;
	}

	// Locate the most recent dive.
	// The device maintains an internal counter which is incremented for every
	// dive, and the current value at the time of the dive is stored in the
	// dive header. Thus the most recent dive will have the highest value.
	unsigned int count = 0;
	unsigned int latest = 0;
	unsigned int maximum = 0;
	for (unsigned int i = 0; i < RB_LOGBOOK_COUNT; ++i) {
		unsigned int offset = i * RB_LOGBOOK_SIZE;

		// Ignore uninitialized header entries.
		if (array_isequal (header + offset, RB_LOGBOOK_SIZE, 0xFF))
			continue;

		// Get the internal dive number.
		unsigned int current = array_uint16_le (header + offset + 80);
		if (current > maximum) {
			maximum = current;
			latest = i;
		}

		count++;
	}

	// Calculate the total and maximum size.
	unsigned int ndives = 0;
	unsigned int size = 0;
	unsigned int maxsize = 0;
	for (unsigned int i = 0; i < count; ++i) {
		unsigned int idx = (latest + RB_LOGBOOK_COUNT - i) % RB_LOGBOOK_COUNT;
		unsigned int offset = idx * RB_LOGBOOK_SIZE;

		// Uninitialized header entries should no longer be present at this
		// stage, unless the dives are interleaved with empty entries. But
		// that's something we don't support at all.
		if (array_isequal (header + offset, RB_LOGBOOK_SIZE, 0xFF)) {
			WARNING (abstract->context, "Unexpected empty header found.");
			break;
		}

		// Get the firmware version.
		unsigned int firmware = array_uint16_be (header + offset + 0x30);

		// Calculate the profile length.
		unsigned int length = RB_LOGBOOK_SIZE + array_uint24_le (header + offset + 9) - 6;
		if (firmware >= 93)
			length += 3;

		// Check the fingerprint data.
		if (memcmp (header + offset + 12, device->fingerprint, sizeof (device->fingerprint)) == 0)
			break;

		if (length > maxsize)
			maxsize = length;
		size += length;
		ndives++;
	}

	// Update and emit a progress event.
	progress.maximum = (RB_LOGBOOK_SIZE * RB_LOGBOOK_COUNT) + size;
	device_event_emit (abstract, DC_EVENT_PROGRESS, &progress);

	// Finish immediately if there are no dives available.
	if (ndives == 0) {
		free (header);
		return DC_STATUS_SUCCESS;
	}

	// Allocate enough memory for the largest dive.
	unsigned char *profile = (unsigned char *) malloc (maxsize);
	if (profile == NULL) {
		ERROR (abstract->context, "Failed to allocate memory.");
		free (header);
		return DC_STATUS_NOMEMORY;
	}

	// Download the dives.
	for (unsigned int i = 0; i < ndives; ++i) {
		unsigned int idx = (latest + RB_LOGBOOK_COUNT - i) % RB_LOGBOOK_COUNT;
		unsigned int offset = idx * RB_LOGBOOK_SIZE;

		// Get the firmware version.
		unsigned int firmware = array_uint16_be (header + offset + 0x30);

		// Calculate the profile length.
		unsigned int length = RB_LOGBOOK_SIZE + array_uint24_le (header + offset + 9) - 6;
		if (firmware >= 93)
			length += 3;

		// Download the dive.
		unsigned char number[1] = {idx};
		rc = hw_ostc3_transfer (device, &progress, DIVE,
			number, sizeof (number), profile, length);
		if (rc != DC_STATUS_SUCCESS) {
			ERROR (abstract->context, "Failed to read the dive.");
			free (profile);
			free (header);
			return rc;
		}

		// Verify the header in the logbook and profile are identical.
		if (memcmp (profile, header + offset, RB_LOGBOOK_SIZE) != 0) {
			ERROR (abstract->context, "Unexpected profile header.");
			free (profile);
			free (header);
			return rc;

		}

		if (callback && !callback (profile, length, profile + 12, sizeof (device->fingerprint), userdata))
			break;
	}

	free (profile);
	free (header);

	return DC_STATUS_SUCCESS;
}
static dc_status_t
mares_nemo_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value)
{
	mares_nemo_parser_t *parser = (mares_nemo_parser_t *) abstract;

	if (abstract->size == 0)
		return DC_STATUS_DATAFORMAT;

	const unsigned char *data = abstract->data;
	const unsigned char *p = abstract->data + 2 + parser->sample_count * parser->sample_size;

	if (value) {
		if (parser->mode != parser->freedive) {
			dc_gasmix_t *gasmix = (dc_gasmix_t *) value;
			switch (type) {
			case DC_FIELD_DIVETIME:
				*((unsigned int *) value) = parser->sample_count * 20;
				break;
			case DC_FIELD_MAXDEPTH:
				*((double *) value) = array_uint16_le (p + 53 - 10) / 10.0;
				break;
			case DC_FIELD_GASMIX_COUNT:
				if (parser->mode == 0 || parser->mode == 1)
					*((unsigned int *) value) = 1;
				else
					*((unsigned int *) value) = 0;
				break;
			case DC_FIELD_GASMIX:
				switch (parser->mode) {
				case 0: // Air
					gasmix->oxygen = 0.21;
					break;
				case 1: // Nitrox
					gasmix->oxygen = p[53 - 43] / 100.0;
					break;
				default:
					return DC_STATUS_UNSUPPORTED;
				}
				gasmix->helium = 0.0;
				gasmix->nitrogen = 1.0 - gasmix->oxygen - gasmix->helium;
				break;
			default:
				return DC_STATUS_UNSUPPORTED;
			}
		} else {
			unsigned int divetime = 0;
			switch (type) {
			case DC_FIELD_DIVETIME:
				for (unsigned int i = 0; i < parser->sample_count; ++i) {
					unsigned int idx = 2 + parser->sample_size * i;
					divetime += data[idx + 2] + data[idx + 3] * 60;
				}
				*((unsigned int *) value) = divetime;
				break;
			case DC_FIELD_MAXDEPTH:
				*((double *) value) = array_uint16_le (p + 28 - 10) / 10.0;
				break;
			case DC_FIELD_GASMIX_COUNT:
				*((unsigned int *) value) = 0;
				break;
			default:
				return DC_STATUS_UNSUPPORTED;
			}
		}
	}

	return DC_STATUS_SUCCESS;
}
Пример #10
0
static dc_status_t
hw_ostc_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value)
{
	hw_ostc_parser_t *parser = (hw_ostc_parser_t *) abstract;
	const unsigned char *data = abstract->data;
	unsigned int size = abstract->size;

	// Cache the header data.
	dc_status_t rc = hw_ostc_parser_cache (parser);
	if (rc != DC_STATUS_SUCCESS)
		return rc;

	// Cache the profile data.
	if (parser->cached < PROFILE) {
		rc = hw_ostc_parser_samples_foreach (abstract, NULL, NULL);
		if (rc != DC_STATUS_SUCCESS)
			return rc;
	}

	unsigned int version = parser->version;
	const hw_ostc_layout_t *layout = parser->layout;

	dc_gasmix_t *gasmix = (dc_gasmix_t *) value;
	dc_salinity_t *water = (dc_salinity_t *) value;
	dc_field_string_t *string = (dc_field_string_t *) value;

	unsigned int salinity = data[layout->salinity];
	if (version == 0x23)
		salinity += 100;
	char buf[BUFLEN];

	if (value) {
		switch (type) {
		case DC_FIELD_DIVETIME:
			*((unsigned int *) value) = array_uint16_le (data + layout->divetime) * 60 + data[layout->divetime + 2];
			break;
		case DC_FIELD_MAXDEPTH:
			*((double *) value) = array_uint16_le (data + layout->maxdepth) / 100.0;
			break;
		case DC_FIELD_AVGDEPTH:
			*((double *) value) = array_uint16_le (data + layout->avgdepth) / 100.0;
			break;
		case DC_FIELD_GASMIX_COUNT:
			*((unsigned int *) value) = parser->ngasmixes;
			break;
		case DC_FIELD_GASMIX:
			gasmix->oxygen = parser->gasmix[flags].oxygen / 100.0;
			gasmix->helium = parser->gasmix[flags].helium / 100.0;
			gasmix->nitrogen = 1.0 - gasmix->oxygen - gasmix->helium;
			break;
		case DC_FIELD_SALINITY:
			if (salinity < 100 || salinity > 104)
				return DC_STATUS_UNSUPPORTED;

			if (salinity == 100)
				water->type = DC_WATER_FRESH;
			else
				water->type = DC_WATER_SALT;
			water->density = salinity * 10.0;
			break;
		case DC_FIELD_ATMOSPHERIC:
			*((double *) value) = array_uint16_le (data + layout->atmospheric) / 1000.0;
			break;
		case DC_FIELD_TEMPERATURE_MINIMUM:
			*((double *) value) = (signed short) array_uint16_le (data + layout->temperature) / 10.0;
			break;
		case DC_FIELD_DIVEMODE:
			if (version == 0x21) {
				switch (data[51]) {
				case OSTC_APNEA:
					*((dc_divemode_t *) value) = DC_DIVEMODE_FREEDIVE;
					break;
				case OSTC_GAUGE:
					*((dc_divemode_t *) value) = DC_DIVEMODE_GAUGE;
					break;
				case OSTC_ZHL16_OC:
				case OSTC_ZHL16_OC_GF:
					*((dc_divemode_t *) value) = DC_DIVEMODE_OC;
					break;
				case OSTC_ZHL16_CC:
				case OSTC_ZHL16_CC_GF:
				case OSTC_PSCR_GF:
					*((dc_divemode_t *) value) = DC_DIVEMODE_CC;
					break;
				default:
					return DC_STATUS_DATAFORMAT;
				}
			} else if (version == 0x22) {
				switch (data[51]) {
				case FROG_ZHL16:
				case FROG_ZHL16_GF:
					*((dc_divemode_t *) value) = DC_DIVEMODE_OC;
					break;
				case FROG_APNEA:
					*((dc_divemode_t *) value) = DC_DIVEMODE_FREEDIVE;
					break;
				default:
					return DC_STATUS_DATAFORMAT;
				}
			} else if (version == 0x23) {
				switch (data[82]) {
				case OSTC3_OC:
					*((dc_divemode_t *) value) = DC_DIVEMODE_OC;
					break;
				case OSTC3_CC:
					*((dc_divemode_t *) value) = DC_DIVEMODE_CC;
					break;
				case OSTC3_GAUGE:
					*((dc_divemode_t *) value) = DC_DIVEMODE_GAUGE;
					break;
				case OSTC3_APNEA:
					*((dc_divemode_t *) value) = DC_DIVEMODE_FREEDIVE;
					break;
				default:
					return DC_STATUS_DATAFORMAT;
				}
			} else {
				return DC_STATUS_UNSUPPORTED;
			}
			break;
		case DC_FIELD_STRING:
			switch(flags) {
			case 0: /* battery */
				string->desc = "Battery at end";
				snprintf(buf, BUFLEN, "%.2f", array_uint16_le (data + layout->battery) / 1000.0);
				break;
			case 1: /* desat */
				string->desc = "Desat time";
				snprintf(buf, BUFLEN, "%0u:%02u", array_uint16_le (data + layout->desat) / 60,
						 array_uint16_le (data + layout->desat) % 60);
				break;
			case 2: /* fw_version */
				string->desc = "FW Version";
				snprintf(buf, BUFLEN, "%0u.%02u", data[layout->fw_version], data[layout->fw_version + 1]);
				break;
			case 3: /* serial */
				string->desc = "Serial";
				snprintf(buf, BUFLEN, "%u", parser->serial);
				break;
			case 4: /* Deco model */
				string->desc = "Deco model";
				if ((version == 0x23 && data[layout->decomode] == OSTC3_ZHL16) ||
						(version == 0x22 && data[layout->decomode] == FROG_ZHL16) ||
						(version == 0x21 && (data[layout->decomode] == OSTC_ZHL16_OC || data[layout->decomode] == OSTC_ZHL16_CC)))
					strncpy(buf, "ZH-L16", BUFLEN);
				if ((version == 0x23 && data[layout->decomode] == OSTC3_ZHL16_GF) ||
						(version == 0x22 && data[layout->decomode] == FROG_ZHL16_GF) ||
						(version == 0x21 && (data[layout->decomode] == OSTC_ZHL16_OC_GF || data[layout->decomode] == OSTC_ZHL16_CC_GF)))
					strncpy(buf, "ZH-L16-GF", BUFLEN);
				else
					return DC_STATUS_DATAFORMAT;
				break;
			case 5: /* Deco model info */
				string->desc = "Deco model info";
				if ((version == 0x23 && data[layout->decomode] == OSTC3_ZHL16) ||
						(version == 0x22 && data[layout->decomode] == FROG_ZHL16) ||
						(version == 0x21 && (data[layout->decomode] == OSTC_ZHL16_OC || data[layout->decomode] == OSTC_ZHL16_CC)))
					snprintf(buf, BUFLEN, "Saturation %u, Desaturation %u", layout->deco_info1, layout->deco_info2);
				if ((version == 0x23 && data[layout->decomode] == OSTC3_ZHL16_GF) ||
						(version == 0x22 && data[layout->decomode] == FROG_ZHL16_GF) ||
						(version == 0x21 && (data[layout->decomode] == OSTC_ZHL16_OC_GF || data[layout->decomode] == OSTC_ZHL16_CC_GF)))
					snprintf(buf, BUFLEN, "GF %u/%u", data[layout->deco_info1], data[layout->deco_info2]);
				else
					return DC_STATUS_DATAFORMAT;
				break;
			default:
				return DC_STATUS_UNSUPPORTED;
			}
			string->value = strdup(buf);
			break;
		default:
			return DC_STATUS_UNSUPPORTED;
		}
	}

	return DC_STATUS_SUCCESS;
}
Пример #11
0
static dc_status_t
reefnet_sensus_handshake (reefnet_sensus_device_t *device)
{
	dc_device_t *abstract = (dc_device_t *) device;

	// Send the command to the device.
	unsigned char command = 0x0A;
	int n = serial_write (device->port, &command, 1);
	if (n != 1) {
		ERROR (abstract->context, "Failed to send the command.");
		return EXITCODE (n);
	}

	// Receive the answer from the device.
	unsigned char handshake[SZ_HANDSHAKE + 2] = {0};
	n = serial_read (device->port, handshake, sizeof (handshake));
	if (n != sizeof (handshake)) {
		ERROR (abstract->context, "Failed to receive the handshake.");
		return EXITCODE (n);
	}

	// Verify the header of the packet.
	if (handshake[0] != 'O' || handshake[1] != 'K') {
		ERROR (abstract->context, "Unexpected answer header.");
		return DC_STATUS_PROTOCOL;
	}

	// The device is now waiting for a data request.
	device->waiting = 1;

	// Store the clock calibration values.
	device->systime = dc_datetime_now ();
	device->devtime = array_uint32_le (handshake + 8);

	// Store the handshake packet.
	memcpy (device->handshake, handshake + 2, SZ_HANDSHAKE);

	// Emit a clock event.
	dc_event_clock_t clock;
	clock.systime = device->systime;
	clock.devtime = device->devtime;
	device_event_emit (&device->base, DC_EVENT_CLOCK, &clock);

	// Emit a device info event.
	dc_event_devinfo_t devinfo;
	devinfo.model = handshake[2] - '0';
	devinfo.firmware = handshake[3] - '0';
	devinfo.serial = array_uint16_le (handshake + 6);
	device_event_emit (&device->base, DC_EVENT_DEVINFO, &devinfo);

	// Emit a vendor event.
	dc_event_vendor_t vendor;
	vendor.data = device->handshake;
	vendor.size = sizeof (device->handshake);
	device_event_emit (abstract, DC_EVENT_VENDOR, &vendor);

	// Wait at least 10 ms to ensures the data line is
	// clear before transmission from the host begins.

	serial_sleep (device->port, 10);

	return DC_STATUS_SUCCESS;
}
Пример #12
0
static dc_status_t
reefnet_sensus_device_dump (dc_device_t *abstract, dc_buffer_t *buffer)
{
	reefnet_sensus_device_t *device = (reefnet_sensus_device_t*) abstract;

	// Erase the current contents of the buffer and
	// pre-allocate the required amount of memory.
	if (!dc_buffer_clear (buffer) || !dc_buffer_reserve (buffer, SZ_MEMORY)) {
		ERROR (abstract->context, "Insufficient buffer space available.");
		return DC_STATUS_NOMEMORY;
	}

	// Enable progress notifications.
	dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER;
	progress.maximum = 4 + SZ_MEMORY + 2 + 3;
	device_event_emit (abstract, DC_EVENT_PROGRESS, &progress);

	// Wake-up the device.
	dc_status_t rc = reefnet_sensus_handshake (device);
	if (rc != DC_STATUS_SUCCESS)
		return rc;

	// Send the command to the device.
	unsigned char command = 0x40;
	int n = serial_write (device->port, &command, 1);
	if (n != 1) {
		ERROR (abstract->context, "Failed to send the command.");
		return EXITCODE (n);
	}

	// The device leaves the waiting state.
	device->waiting = 0;

	// Receive the answer from the device.
	unsigned int nbytes = 0;
	unsigned char answer[4 + SZ_MEMORY + 2 + 3] = {0};
	while (nbytes < sizeof (answer)) {
		unsigned int len = sizeof (answer) - nbytes;
		if (len > 128)
			len = 128;

		n = serial_read (device->port, answer + nbytes, len);
		if (n != len) {
			ERROR (abstract->context, "Failed to receive the answer.");
			return EXITCODE (n);
		}

		// Update and emit a progress event.
		progress.current += len;
		device_event_emit (abstract, DC_EVENT_PROGRESS, &progress);

		nbytes += len;
	}

	// Verify the headers of the package.
	if (memcmp (answer, "DATA", 4) != 0 ||
		memcmp (answer + sizeof (answer) - 3, "END", 3) != 0) {
		ERROR (abstract->context, "Unexpected answer start or end byte(s).");
		return DC_STATUS_PROTOCOL;
	}

	// Verify the checksum of the package.
	unsigned short crc = array_uint16_le (answer + 4 + SZ_MEMORY);
	unsigned short ccrc = checksum_add_uint16 (answer + 4, SZ_MEMORY, 0x00);
	if (crc != ccrc) {
		ERROR (abstract->context, "Unexpected answer checksum.");
		return DC_STATUS_PROTOCOL;
	}

	dc_buffer_append (buffer, answer + 4, SZ_MEMORY);

	return DC_STATUS_SUCCESS;
}
Пример #13
0
static dc_status_t
atomics_cobalt_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata)
{
	atomics_cobalt_device_t *device = (atomics_cobalt_device_t *) abstract;

	// Enable progress notifications.
	dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER;
	progress.maximum = SZ_MEMORY + 2;
	device_event_emit (abstract, DC_EVENT_PROGRESS, &progress);

	// Emit a vendor event.
	dc_event_vendor_t vendor;
	vendor.data = device->version;
	vendor.size = sizeof (device->version);
	device_event_emit (abstract, DC_EVENT_VENDOR, &vendor);

	// Emit a device info event.
	dc_event_devinfo_t devinfo;
	devinfo.model = array_uint16_le (device->version + 12);
	devinfo.firmware = (array_uint16_le (device->version + 8) << 16)
		+ array_uint16_le (device->version + 10);
	devinfo.serial = 0;
	for (unsigned int i = 0; i < 8; ++i) {
		devinfo.serial *= 10;
		devinfo.serial += device->version[i] - '0';
	}
	device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo);

	// Allocate a memory buffer.
	dc_buffer_t *buffer = dc_buffer_new (0);
	if (buffer == NULL)
		return DC_STATUS_NOMEMORY;

	unsigned int ndives = 0;
	dc_status_t rc = DC_STATUS_SUCCESS;
	while ((rc = atomics_cobalt_read_dive (abstract, buffer, (ndives == 0), &progress)) == DC_STATUS_SUCCESS) {
		unsigned char *data = dc_buffer_get_data (buffer);
		unsigned int size = dc_buffer_get_size (buffer);

		if (size == 0) {
			dc_buffer_free (buffer);
			return DC_STATUS_SUCCESS;
		}

		if (memcmp (data + FP_OFFSET, device->fingerprint, sizeof (device->fingerprint)) == 0) {
			dc_buffer_free (buffer);
			return DC_STATUS_SUCCESS;
		}

		if (callback && !callback (data, size, data + FP_OFFSET, sizeof (device->fingerprint), userdata)) {
			dc_buffer_free (buffer);
			return DC_STATUS_SUCCESS;
		}

		// Adjust the maximum value to take into account the two checksum bytes
		// for the next dive. Since we don't know the total number of dives in
		// advance, we can't calculate the total number of checksum bytes and
		// adjust the maximum on the fly.
		progress.maximum += 2;

		ndives++;
	}

	dc_buffer_free (buffer);

	return rc;
}
Пример #14
0
static dc_status_t
mares_darwin_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata)
{
	mares_darwin_parser_t *parser = (mares_darwin_parser_t *) abstract;

	if (abstract->size < parser->headersize)
		return DC_STATUS_DATAFORMAT;

	unsigned int time = 0;

	unsigned int pressure = array_uint16_be (abstract->data + 0x17);

	unsigned int offset = parser->headersize;
	while (offset + parser->samplesize <= abstract->size) {
			dc_sample_value_t sample = {0};

			unsigned int value = array_uint16_le (abstract->data + offset);
			unsigned int depth = value & 0x07FF;
			unsigned int ascent = (value & 0xE000) >> 13;
			unsigned int violation = (value & 0x1000) >> 12;
			unsigned int deco = (value & 0x0800) >> 11;

			// Surface Time (seconds).
			time += 20;
			sample.time = time;
			if (callback) callback (DC_SAMPLE_TIME, sample, userdata);

			// Depth (1/10 m).
			sample.depth = depth / 10.0;
			if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata);

			// Ascent rate
			if (ascent) {
				sample.event.type = SAMPLE_EVENT_ASCENT;
				sample.event.time = 0;
				sample.event.flags = 0;
				sample.event.value = ascent;
				if (callback) callback (DC_SAMPLE_EVENT, sample, userdata);
			}

			// Deco violation
			if (violation) {
				sample.event.type = SAMPLE_EVENT_CEILING;
				sample.event.time = 0;
				sample.event.flags = 0;
				sample.event.value = 0;
				if (callback) callback (DC_SAMPLE_EVENT, sample, userdata);
			}

			// Deco stop
			if (deco) {
				sample.event.type = SAMPLE_EVENT_DECOSTOP;
				sample.event.time = 0;
				sample.event.flags = 0;
				sample.event.value = 0;
				if (callback) callback (DC_SAMPLE_EVENT, sample, userdata);
			}

			if (parser->samplesize == 3) {
				unsigned int type = (time / 20 + 2) % 3;
				if (type == 0) {
					// Tank Pressure (bar)
					pressure -= abstract->data[offset + 2];
					sample.pressure.tank = 0;
					sample.pressure.value = pressure;
					if (callback) callback (DC_SAMPLE_PRESSURE, sample, userdata);
				}
			}

			offset += parser->samplesize;
	}

	return DC_STATUS_SUCCESS;
}
static dc_status_t
mares_nemo_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size)
{
	mares_nemo_parser_t *parser = (mares_nemo_parser_t *) abstract;

	// Clear the previous state.
	parser->base.data = NULL;
	parser->base.size = 0;
	parser->mode = 0;
	parser->length = 0;
	parser->sample_count = 0;
	parser->sample_size = 0;
	parser->header = 0;
	parser->extra = 0;

	if (size == 0)
		return DC_STATUS_SUCCESS;

	if (size < 2 + 3)
		return DC_STATUS_DATAFORMAT;

	unsigned int length = array_uint16_le (data);
	if (length > size)
		return DC_STATUS_DATAFORMAT;

	unsigned int extra = 0;
	const unsigned char marker[3] = {0xAA, 0xBB, 0xCC};
	if (memcmp (data + length - 3, marker, sizeof (marker)) == 0) {
		if (parser->model == PUCKAIR)
			extra = 7;
		else
			extra = 12;
	}

	if (length < 2 + extra + 3)
		return DC_STATUS_DATAFORMAT;

	unsigned int mode = data[length - extra - 1];

	unsigned int header_size = 53;
	unsigned int sample_size = 2;
	if (extra) {
		if (parser->model == PUCKAIR)
			sample_size = 3;
		else
			sample_size = 5;
	}
	if (mode == parser->freedive) {
		header_size = 28;
		sample_size = 6;
	}

	unsigned int nsamples = array_uint16_le (data + length - extra - 3);

	unsigned int nbytes = 2 + nsamples * sample_size + header_size + extra;
	if (length != nbytes)
		return DC_STATUS_DATAFORMAT;

	// Store the new state.
	parser->base.data = data;
	parser->base.size = size;
	parser->mode = mode;
	parser->length = length;
	parser->sample_count = nsamples;
	parser->sample_size = sample_size;
	parser->header = header_size;
	parser->extra = extra;

	return DC_STATUS_SUCCESS;
}
static dc_status_t
mares_nemo_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata)
{
	mares_nemo_parser_t *parser = (mares_nemo_parser_t *) abstract;

	if (abstract->size == 0)
		return DC_STATUS_DATAFORMAT;

	const unsigned char *data = abstract->data;
	unsigned int size = abstract->size;

	if (parser->mode != parser->freedive) {
		unsigned int time = 0;
		for (unsigned int i = 0; i < parser->sample_count; ++i) {
			dc_sample_value_t sample = {0};

			unsigned int idx = 2 + parser->sample_size * i;
			unsigned int value = array_uint16_le (data + idx);
			unsigned int depth = value & 0x07FF;
			unsigned int ascent = (value & 0xC000) >> 14;
			unsigned int violation = (value & 0x2000) >> 13;
			unsigned int deco = (value & 0x1000) >> 12;

			// Time (seconds).
			time += 20;
			sample.time = time;
			if (callback) callback (DC_SAMPLE_TIME, sample, userdata);

			// Depth (1/10 m).
			sample.depth = depth / 10.0;
			if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata);

			// Ascent rate
			if (ascent) {
				sample.event.type = SAMPLE_EVENT_ASCENT;
				sample.event.time = 0;
				sample.event.flags = 0;
				sample.event.value = ascent;
				if (callback) callback (DC_SAMPLE_EVENT, sample, userdata);
			}

			// Deco violation
			if (violation) {
				sample.event.type = SAMPLE_EVENT_CEILING;
				sample.event.time = 0;
				sample.event.flags = 0;
				sample.event.value = 0;
				if (callback) callback (DC_SAMPLE_EVENT, sample, userdata);
			}

			// Deco stop
			if (deco) {
				sample.event.type = SAMPLE_EVENT_DECOSTOP;
				sample.event.time = 0;
				sample.event.flags = 0;
				sample.event.value = 0;
				if (callback) callback (DC_SAMPLE_EVENT, sample, userdata);
			}

			// Pressure (1 bar).
			if (parser->sample_size == 3) {
				sample.pressure.tank = 0;
				sample.pressure.value = data[idx + 2];
				if (callback) callback (DC_SAMPLE_PRESSURE, sample, userdata);
			}
		}
	} else {
Пример #17
0
device_status_t
mares_common_extract_dives (mares_common_device_t *device, const mares_common_layout_t *layout, const unsigned char data[], dive_callback_t callback, void *userdata)
{
	assert (layout != NULL);

	// Get the freedive mode for this model.
	unsigned int freedive = 2;
	if (data[1] == 1 || data[1] == 7)
		freedive = 3;

	// Get the end of the profile ring buffer.
	unsigned int eop = array_uint16_le (data + 0x6B);
	if (eop < layout->rb_profile_begin || eop >= layout->rb_profile_end) {
		WARNING ("Ringbuffer pointer out of range.");
		return DEVICE_STATUS_ERROR;
	}

	// Make the ringbuffer linear, to avoid having to deal
	// with the wrap point. The buffer has extra space to
	// store the profile data for the freedives.
	unsigned char *buffer = (unsigned char *) malloc (
		layout->rb_profile_end - layout->rb_profile_begin +
		layout->rb_freedives_end - layout->rb_freedives_begin);
	if (buffer == NULL) {
		WARNING ("Out of memory.");
		return DEVICE_STATUS_MEMORY;
	}

	memcpy (buffer + 0, data + eop, layout->rb_profile_end - eop);
	memcpy (buffer + layout->rb_profile_end - eop, data + layout->rb_profile_begin, eop - layout->rb_profile_begin);

	// For a freedive session, the Mares Nemo stores all the freedives of
	// that session in a single logbook entry, and each sample is actually
	// a summary for each individual freedive in the session. The profile
	// data is stored in a separate memory area. Since only the most recent
	// recent freediving session can have profile data, we keep track of the
	// number of freedives.
	unsigned int nfreedives = 0;

	unsigned int offset = layout->rb_profile_end - layout->rb_profile_begin;
	while (offset >= 3) {
		// Check for the presence of extra header bytes, which can be detected
		// by means of a three byte marker sequence.
		unsigned int extra = 0;
		const unsigned char marker[3] = {0xAA, 0xBB, 0xCC};
		if (memcmp (buffer + offset - 3, marker, sizeof (marker)) == 0) {
			extra = 12;
		}

		// Check for overflows due to incomplete dives.
		if (offset < extra + 3)
			break;

		// Check the dive mode of the logbook entry. Valid modes are
		// 0 (air), 1 (EANx), 2 (freedive) or 3 (bottom timer).
		// If the ringbuffer has never reached the wrap point before,
		// there will be "empty" memory (filled with 0xFF) and
		// processing should stop at this point.
		unsigned int mode = buffer[offset - extra - 1];
		if (mode == 0xFF)
			break;

		// The header and sample size are dependant on the dive mode. Only
		// in freedive mode, the sizes are different from the other modes.
		unsigned int header_size = 53;
		unsigned int sample_size = (extra ? 5 : 2);
		if (mode == freedive) {
			header_size = 28;
			sample_size = 6;
			nfreedives++;
		}

		// Get the number of samples in the profile data.
		unsigned int nsamples = array_uint16_le (buffer + offset - extra - 3);

		// Calculate the total number of bytes for this dive.
		// If the buffer does not contain that much bytes, we reached the
		// end of the ringbuffer. The current dive is incomplete (partially
		// overwritten with newer data), and processing should stop.
		unsigned int nbytes = 2 + nsamples * sample_size + header_size + extra;
		if (offset < nbytes)
			break;

		// Move to the start of the dive.
		offset -= nbytes;

		// Verify that the length that is stored in the profile data
		// equals the calculated length. If both values are different,
		// something is wrong and an error is returned.
		unsigned int length = array_uint16_le (buffer + offset);
		if (length != nbytes) {
			WARNING ("Calculated and stored size are not equal.");
			free (buffer);
			return DEVICE_STATUS_ERROR;
		}

		// Process the profile data for the most recent freedive entry.
		// Since we are processing the entries backwards (newest to oldest),
		// this entry will always be the first one.
		if (mode == freedive && nfreedives == 1) {
			// Count the number of freedives in the profile data.
			unsigned int count = 0;
			unsigned int idx = layout->rb_freedives_begin;
			while (idx + 2 <= layout->rb_freedives_end &&
				count != nsamples)
			{
				// Each freedive in the session ends with a zero sample.
				unsigned int sample = array_uint16_le (data + idx);
				if (sample == 0)
					count++;

				// Move to the next sample.
				idx += 2;
			}

			// Verify that the number of freedive entries in the session
			// equals the number of freedives in the profile data. If
			// both values are different, the profile data is incomplete.
			assert (count == nsamples);

			// Append the profile data to the main logbook entry. The
			// buffer is guaranteed to have enough space, and the dives
			// that will be overwritten have already been processed.
			memcpy (buffer + offset + nbytes, data + layout->rb_freedives_begin, idx - layout->rb_freedives_begin);
			nbytes += idx - layout->rb_freedives_begin;
		}

		unsigned int fp_offset = offset + length - extra - FP_OFFSET;
		if (device && memcmp (buffer + fp_offset, device->fingerprint, sizeof (device->fingerprint)) == 0) {
			free (buffer);
			return DEVICE_STATUS_SUCCESS;
		}

		if (callback && !callback (buffer + offset, nbytes, buffer + fp_offset, sizeof (device->fingerprint), userdata)) {
			free (buffer);
			return DEVICE_STATUS_SUCCESS;
		}
	}

	free (buffer);

	return DEVICE_STATUS_SUCCESS;
}
Пример #18
0
dc_status_t
cressi_leonardo_extract_dives (dc_device_t *abstract, const unsigned char data[], unsigned int size, dc_dive_callback_t callback, void *userdata)
{
	cressi_leonardo_device_t *device = (cressi_leonardo_device_t *) abstract;
	dc_context_t *context = (abstract ? abstract->context : NULL);

	if (abstract && !ISINSTANCE (abstract))
		return DC_STATUS_INVALIDARGS;

	if (size < SZ_MEMORY)
		return DC_STATUS_DATAFORMAT;

	// Locate the most recent dive.
	// The device maintains an internal counter which is incremented for every
	// dive, and the current value at the time of the dive is stored in the
	// dive header. Thus the most recent dive will have the highest value.
	unsigned int count = 0;
	unsigned int latest = 0;
	unsigned int maximum = 0;
	for (unsigned int i = 0; i < RB_LOGBOOK_COUNT; ++i) {
		unsigned int offset = RB_LOGBOOK_BEGIN + i * RB_LOGBOOK_SIZE;

		// Ignore uninitialized header entries.
		if (array_isequal (data + offset, RB_LOGBOOK_SIZE, 0xFF))
			break;

		// Get the internal dive number.
		unsigned int current = array_uint16_le (data + offset);
		if (current > maximum) {
			maximum = current;
			latest = i;
		}

		count++;
	}

	unsigned char *buffer = (unsigned char *) malloc (RB_LOGBOOK_SIZE + RB_PROFILE_END - RB_PROFILE_BEGIN);
	if (buffer == NULL) {
		ERROR (context, "Failed to allocate memory.");
		return DC_STATUS_NOMEMORY;
	}

	for (unsigned int i = 0; i < count; ++i) {
		unsigned int idx = (latest + RB_LOGBOOK_COUNT - i) % RB_LOGBOOK_COUNT;
		unsigned int offset = RB_LOGBOOK_BEGIN + idx * RB_LOGBOOK_SIZE;

		// Get the ringbuffer pointers.
		unsigned int header = array_uint16_le (data + offset + 2);
		unsigned int footer = array_uint16_le (data + offset + 4);
		if (header < RB_PROFILE_BEGIN || header + 2 > RB_PROFILE_END ||
			footer < RB_PROFILE_BEGIN || footer + 2 > RB_PROFILE_END)
		{
			ERROR (abstract->context, "Invalid ringbuffer pointer detected.");
			free (buffer);
			return DC_STATUS_DATAFORMAT;
		}

		// Get the same pointers from the profile.
		unsigned int header2 = array_uint16_le (data + footer);
		unsigned int footer2 = array_uint16_le (data + header);
		if (header2 != header || footer2 != footer) {
			ERROR (abstract->context, "Invalid ringbuffer pointer detected.");
			free (buffer);
			return DC_STATUS_DATAFORMAT;
		}

		// Calculate the profile address and length.
		unsigned int address = header + 2;
		unsigned int length = RB_PROFILE_DISTANCE (header, footer) - 2;

		// Check the fingerprint data.
		if (device && memcmp (data + offset + 8, device->fingerprint, sizeof (device->fingerprint)) == 0)
			break;

		// Copy the logbook entry.
		memcpy (buffer, data + offset, RB_LOGBOOK_SIZE);

		// Copy the profile data.
		if (address + length > RB_PROFILE_END) {
			unsigned int len_a = RB_PROFILE_END - address;
			unsigned int len_b = length - len_a;
			memcpy (buffer + RB_LOGBOOK_SIZE, data + address, len_a);
			memcpy (buffer + RB_LOGBOOK_SIZE + len_a, data + RB_PROFILE_BEGIN, len_b);
		} else {
			memcpy (buffer + RB_LOGBOOK_SIZE, data + address, length);
		}

		if (callback && !callback (buffer, RB_LOGBOOK_SIZE + length, buffer + 8, sizeof (device->fingerprint), userdata)) {
			break;
		}
	}

	free (buffer);

	return DC_STATUS_SUCCESS;
}
Пример #19
0
static void cochran_dive_event(struct divecomputer *dc, const unsigned char *s,
			       unsigned int seconds, unsigned int *in_deco,
			       unsigned int *deco_ceiling, unsigned int *deco_time)
{
	switch (s[0]) {
	case 0xC5:	// Deco obligation begins
		*in_deco = 1;
		add_event(dc, seconds, SAMPLE_EVENT_DECOSTOP,
			SAMPLE_FLAGS_BEGIN, 0,
			QT_TRANSLATE_NOOP("gettextFromC", "deco stop"));
		break;
	case 0xDB:	// Deco obligation ends
		*in_deco = 0;
		add_event(dc, seconds, SAMPLE_EVENT_DECOSTOP,
			SAMPLE_FLAGS_END, 0,
			QT_TRANSLATE_NOOP("gettextFromC", "deco stop"));
		break;
	case 0xAD:	// Raise deco ceiling 10 ft
		*deco_ceiling -= 10; // ft
		*deco_time = (array_uint16_le(s + 3) + 1) * 60;
		break;
	case 0xAB:	// Lower deco ceiling 10 ft
		*deco_ceiling += 10;	// ft
		*deco_time = (array_uint16_le(s + 3) + 1) * 60;
		break;
	case 0xA8:	// Entered Post Dive interval mode (surfaced)
		break;
	case 0xA9:	// Exited PDI mode (re-submierged)
		break;
	case 0xBD:	// Switched to normal PO2 setting
		break;
	case 0xC0:	// Switched to FO2 21% mode (generally upon surface)
		break;
	case 0xC1:	// "Ascent rate alarm
		add_event(dc, seconds, SAMPLE_EVENT_ASCENT,
			SAMPLE_FLAGS_BEGIN, 0,
			QT_TRANSLATE_NOOP("gettextFromC", "ascent"));
		break;
	case 0xC2:	// Low battery warning
#ifdef SAMPLE_EVENT_BATTERY
		add_event(dc, seconds, SAMPLE_EVENT_BATTERY,
			SAMPLE_FLAGS_NONE, 0,
			QT_TRANSLATE_NOOP("gettextFromC", "battery"));
#endif
		break;
	case 0xC3:	// CNS warning
		add_event(dc, seconds, SAMPLE_EVENT_OLF,
			SAMPLE_FLAGS_BEGIN, 0,
			QT_TRANSLATE_NOOP("gettextFromC", "OLF"));
		break;
	case 0xC4:	// Depth alarm begin
		add_event(dc, seconds, SAMPLE_EVENT_MAXDEPTH,
			SAMPLE_FLAGS_BEGIN, 0,
			QT_TRANSLATE_NOOP("gettextFromC", "maxdepth"));
		break;
	case 0xC8:	// PPO2 alarm begin
		add_event(dc, seconds, SAMPLE_EVENT_PO2,
			SAMPLE_FLAGS_BEGIN, 0,
			QT_TRANSLATE_NOOP("gettextFromC", "pO₂"));
		break;
	case 0xCC:	// Low cylinder 1 pressure";
		break;
	case 0xCD:	// Switch to deco blend setting
		add_event(dc, seconds, SAMPLE_EVENT_GASCHANGE,
			SAMPLE_FLAGS_NONE, 0,
			QT_TRANSLATE_NOOP("gettextFromC", "gaschange"));
		break;
	case 0xCE:	// NDL alarm begin
		add_event(dc, seconds, SAMPLE_EVENT_RBT,
			SAMPLE_FLAGS_BEGIN, 0,
			QT_TRANSLATE_NOOP("gettextFromC", "rbt"));
		break;
	case 0xD0:	// Breathing rate alarm begin
		break;
	case 0xD3:	// Low gas 1 flow rate alarm begin";
		break;
	case 0xD6:	// Ceiling alarm begin
		add_event(dc, seconds, SAMPLE_EVENT_CEILING,
			SAMPLE_FLAGS_BEGIN, 0,
			QT_TRANSLATE_NOOP("gettextFromC", "ceiling"));
		break;
	case 0xD8:	// End decompression mode
		*in_deco = 0;
		add_event(dc, seconds, SAMPLE_EVENT_DECOSTOP,
			SAMPLE_FLAGS_END, 0,
			QT_TRANSLATE_NOOP("gettextFromC", "deco stop"));
		break;
	case 0xE1:	// Ascent alarm end
		add_event(dc, seconds, SAMPLE_EVENT_ASCENT,
			SAMPLE_FLAGS_END, 0,
			QT_TRANSLATE_NOOP("gettextFromC", "ascent"));
		break;
	case 0xE2:	// Low transmitter battery alarm
		add_event(dc, seconds, SAMPLE_EVENT_TRANSMITTER,
			SAMPLE_FLAGS_BEGIN, 0,
			QT_TRANSLATE_NOOP("gettextFromC", "transmitter"));
		break;
	case 0xE3:	// Switch to FO2 mode
		break;
	case 0xE5:	// Switched to PO2 mode
		break;
	case 0xE8:	// PO2 too low alarm
		add_event(dc, seconds, SAMPLE_EVENT_PO2,
			SAMPLE_FLAGS_BEGIN, 0,
			QT_TRANSLATE_NOOP("gettextFromC", "pO₂"));
		break;
	case 0xEE:	// NDL alarm end
		add_event(dc, seconds, SAMPLE_EVENT_RBT,
			SAMPLE_FLAGS_END, 0,
			QT_TRANSLATE_NOOP("gettextFromC", "rbt"));
		break;
	case 0xEF:	// Switch to blend 2
		add_event(dc, seconds, SAMPLE_EVENT_GASCHANGE,
			SAMPLE_FLAGS_NONE, 0,
			QT_TRANSLATE_NOOP("gettextFromC", "gaschange"));
		break;
	case 0xF0:	// Breathing rate alarm end
		break;
	case 0xF3:	// Switch to blend 1 (often at dive start)
		add_event(dc, seconds, SAMPLE_EVENT_GASCHANGE,
			SAMPLE_FLAGS_NONE, 0,
			QT_TRANSLATE_NOOP("gettextFromC", "gaschange"));
		break;
	case 0xF6:	// Ceiling alarm end
		add_event(dc, seconds, SAMPLE_EVENT_CEILING,
			SAMPLE_FLAGS_END, 0,
			QT_TRANSLATE_NOOP("gettextFromC", "ceiling"));
		break;
	default:
		break;
	}
}
Пример #20
0
device_status_t
suunto_common2_device_foreach (device_t *abstract, dive_callback_t callback, void *userdata)
{
	suunto_common2_device_t *device = (suunto_common2_device_t*) abstract;

	// Enable progress notifications.
	device_progress_t progress = DEVICE_PROGRESS_INITIALIZER;
	progress.maximum = RB_PROFILE_END - RB_PROFILE_BEGIN + 8 + SZ_VERSION + (SZ_MINIMUM > 4 ? SZ_MINIMUM : 4);
	device_event_emit (abstract, DEVICE_EVENT_PROGRESS, &progress);

	// Read the version info.
	unsigned char version[SZ_VERSION] = {0};
	device_status_t rc = suunto_common2_device_version (abstract, version, sizeof (version));
	if (rc != DEVICE_STATUS_SUCCESS) {
		WARNING ("Cannot read memory header.");
		return rc;
	}

	// Update and emit a progress event.
	progress.current += sizeof (version);
	device_event_emit (abstract, DEVICE_EVENT_PROGRESS, &progress);

	// Read the serial number.
	unsigned char serial[SZ_MINIMUM > 4 ? SZ_MINIMUM : 4] = {0};
	rc = suunto_common2_device_read (abstract, 0x0023, serial, sizeof (serial));
	if (rc != DEVICE_STATUS_SUCCESS) {
		WARNING ("Cannot read memory header.");
		return rc;
	}

	// Update and emit a progress event.
	progress.current += sizeof (serial);
	device_event_emit (abstract, DEVICE_EVENT_PROGRESS, &progress);

	// Emit a device info event.
	device_devinfo_t devinfo;
	devinfo.model = version[0];
	devinfo.firmware = array_uint24_be (version + 1);
	devinfo.serial = array_uint32_be (serial);
	device_event_emit (abstract, DEVICE_EVENT_DEVINFO, &devinfo);

	// Read the header bytes.
	unsigned char header[8] = {0};
	rc = suunto_common2_device_read (abstract, 0x0190, header, sizeof (header));
	if (rc != DEVICE_STATUS_SUCCESS) {
		WARNING ("Cannot read memory header.");
		return rc;
	}

	// Obtain the pointers from the header.
	unsigned int last  = array_uint16_le (header + 0);
	unsigned int count = array_uint16_le (header + 2);
	unsigned int end   = array_uint16_le (header + 4);
	unsigned int begin = array_uint16_le (header + 6);

	// Memory buffer to store all the dives.

	unsigned char data[SZ_MINIMUM + RB_PROFILE_END - RB_PROFILE_BEGIN] = {0};

	// Calculate the total amount of bytes.

	unsigned int remaining = RB_PROFILE_DISTANCE (begin, end, count != 0);

	// Update and emit a progress event.

	progress.maximum -= (RB_PROFILE_END - RB_PROFILE_BEGIN) - remaining;
	progress.current += sizeof (header);
	device_event_emit (abstract, DEVICE_EVENT_PROGRESS, &progress);

	// To reduce the number of read operations, we always try to read
	// packages with the largest possible size. As a consequence, the
	// last package of a dive can contain data from more than one dive.
	// Therefore, the remaining data of this package (and its size)
	// needs to be preserved for the next dive.

	unsigned int available = 0;

	// The ring buffer is traversed backwards to retrieve the most recent
	// dives first. This allows us to download only the new dives.

	unsigned int current = last;
	unsigned int previous = end;
	unsigned int address = previous;
	unsigned int offset = remaining + SZ_MINIMUM;
	while (remaining) {
		// Calculate the size of the current dive.
		unsigned int size = RB_PROFILE_DISTANCE (current, previous, 1);
		if (size < 4 || size > remaining) {
			WARNING ("Unexpected profile size.");
			return DEVICE_STATUS_ERROR;
		}

		unsigned int nbytes = available;
		while (nbytes < size) {
			// Handle the ringbuffer wrap point.
			if (address == RB_PROFILE_BEGIN)
				address = RB_PROFILE_END;

			// Calculate the package size. Try with the largest possible
			// size first, and adjust when the end of the ringbuffer or
			// the end of the profile data is reached.
			unsigned int len = SZ_PACKET;
			if (RB_PROFILE_BEGIN + len > address)
				len = address - RB_PROFILE_BEGIN; // End of ringbuffer.
			if (nbytes + len > remaining)
				len = remaining - nbytes; // End of profile.
			/*if (nbytes + len > size)
				len = size - nbytes;*/ // End of dive (for testing only).

			// Move to the begin of the current package.
			offset -= len;
			address -= len;

			// Always read at least the minimum amount of bytes, because
			// reading fewer bytes is unreliable. The memory buffer is
			// large enough to prevent buffer overflows, and the extra
			// bytes are automatically ignored (due to reading backwards).
			unsigned int extra = 0;
			if (len < SZ_MINIMUM)
				extra = SZ_MINIMUM - len;

			// Read the package.
			rc = suunto_common2_device_read (abstract, address - extra, data + offset - extra, len + extra);
			if (rc != DEVICE_STATUS_SUCCESS) {
				WARNING ("Cannot read memory.");
				return rc;
			}

			// Update and emit a progress event.
			progress.current += len;
			device_event_emit (abstract, DEVICE_EVENT_PROGRESS, &progress);

			// Next package.
			nbytes += len;
		}

		// The last package of the current dive contains the previous and
		// next pointers (in a continuous memory area). It can also contain
		// a number of bytes from the next dive.

		remaining -= size;
		available = nbytes - size;

		unsigned char *p = data + offset + available;
		unsigned int prev = array_uint16_le (p + 0);
		unsigned int next = array_uint16_le (p + 2);
		if (next != previous) {
			WARNING ("Profiles are not continuous.");
			return DEVICE_STATUS_ERROR;
		}

		// Next dive.
		previous = current;
		current = prev;

		unsigned int fp_offset = FP_OFFSET;
		if (devinfo.model == 0x15)
			fp_offset += 6; // HelO2

		if (memcmp (p + fp_offset, device->fingerprint, sizeof (device->fingerprint)) == 0)
			return DEVICE_STATUS_SUCCESS;

		if (callback && !callback (p + 4, size - 4, p + fp_offset, sizeof (device->fingerprint), userdata))
			return DEVICE_STATUS_SUCCESS;
	}

	return DEVICE_STATUS_SUCCESS;
}
Пример #21
0
static dc_status_t
atomics_cobalt_read_dive (dc_device_t *abstract, dc_buffer_t *buffer, int init, dc_event_progress_t *progress)
{
#ifdef HAVE_LIBUSB
	atomics_cobalt_device_t *device = (atomics_cobalt_device_t *) abstract;

	if (device_is_cancelled (abstract))
		return DC_STATUS_CANCELLED;

	// Erase the current contents of the buffer.
	if (!dc_buffer_clear (buffer)) {
		ERROR (abstract->context, "Insufficient buffer space available.");
		return DC_STATUS_NOMEMORY;
	}

	// Send the command to the dive computer.
	uint8_t bRequest = 0;
	if (device->simulation)
		bRequest = init ? 0x02 : 0x03;
	else
		bRequest = init ? 0x09 : 0x0A;
	int rc = libusb_control_transfer (device->handle,
		LIBUSB_RECIPIENT_DEVICE | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_OUT,
		bRequest, 0, 0, NULL, 0, TIMEOUT);
	if (rc != LIBUSB_SUCCESS) {
		ERROR (abstract->context, "Failed to send the command.");
		return EXITCODE(rc);
	}

	HEXDUMP (abstract->context, DC_LOGLEVEL_INFO, "Write", &bRequest, 1);

	unsigned int nbytes = 0;
	while (1) {
		// Receive the answer from the dive computer.
		int length = 0;
		unsigned char packet[8 * 1024] = {0};
		rc = libusb_bulk_transfer (device->handle, 0x82,
			packet, sizeof (packet), &length, TIMEOUT);
		if (rc != LIBUSB_SUCCESS) {
			ERROR (abstract->context, "Failed to receive the answer.");
			return EXITCODE(rc);
		}

		HEXDUMP (abstract->context, DC_LOGLEVEL_INFO, "Read", packet, length);

		// Update and emit a progress event.
		if (progress) {
			progress->current += length;
			device_event_emit (abstract, DC_EVENT_PROGRESS, progress);
		}

		// Append the packet to the output buffer.
		dc_buffer_append (buffer, packet, length);
		nbytes += length;

		// If we received fewer bytes than requested, the transfer is finished.
		if (length < sizeof (packet))
			break;
	}

	// Check for a buffer error.
	if (dc_buffer_get_size (buffer) != nbytes) {
		ERROR (abstract->context, "Insufficient buffer space available.");
		return DC_STATUS_NOMEMORY;
	}

	// Check for the minimum length.
	if (nbytes < 2) {
		ERROR (abstract->context, "Data packet is too short.");
		return DC_STATUS_PROTOCOL;
	}

	// When only two 0xFF bytes are received, there are no more dives.
	unsigned char *data = dc_buffer_get_data (buffer);
	if (nbytes == 2 && data[0] == 0xFF && data[1] == 0xFF) {
		dc_buffer_clear (buffer);
		return DC_STATUS_SUCCESS;
	}

	// Verify the checksum of the packet.
	unsigned short crc = array_uint16_le (data + nbytes - 2);
	unsigned short ccrc = checksum_add_uint16 (data, nbytes - 2, 0x0);
	if (crc != ccrc) {
		ERROR (abstract->context, "Unexpected answer checksum.");
		return DC_STATUS_PROTOCOL;
	}

	// Remove the checksum bytes.
	dc_buffer_slice (buffer, 0, nbytes - 2);

	return DC_STATUS_SUCCESS;
#else
	return DC_STATUS_UNSUPPORTED;
#endif
}