Compare commits

...

16 Commits

Author SHA1 Message Date
Bastien Nocera
e2f06e8f1e lib: Remove num_stripes from fpi_assemble_frames() 2019-08-05 19:04:25 +02:00
Bastien Nocera
22a252c57e lib: Remove num_stripes from fpi_do_movement_estimation() 2019-08-05 18:57:55 +02:00
Bastien Nocera
3953436f0a lib: Remove num_stripes from do_movement_estimation()
We never used a number of stripes that didn't match the length of the
list anyway.

Closes: #132
2019-08-05 18:55:41 +02:00
Igor Filatov
ae1b10dba8 elan: Fix frame leak in elan_submit_image 2019-08-05 18:43:04 +02:00
Bastien Nocera
860a256f4b HACKING: Clarify the intent of the license 2019-08-05 13:37:28 +02:00
Bastien Nocera
cb2f46ed08 HACKING: Fix a typo 2019-08-05 13:18:18 +02:00
Bastien Nocera
13deaa66fd lib: Fix a typo 2019-08-05 13:18:15 +02:00
Bastien Nocera
3597a5b0ed img: Fix a typo 2019-08-05 13:17:55 +02:00
Bastien Nocera
0352995cb3 data: Fix a number of typos 2019-08-05 13:17:33 +02:00
Bastien Nocera
e9041da7f4 uru4000: Fix a typo 2019-08-05 13:17:30 +02:00
Bastien Nocera
252180e088 upektc: Fix a typo 2019-08-05 13:17:08 +02:00
Bastien Nocera
6361c208bd upeksonly: Fix a number of typos 2019-08-05 13:16:18 +02:00
Bastien Nocera
2ef8ace543 etes603: Fix a typo 2019-08-05 13:16:14 +02:00
Bastien Nocera
0400bcc85e vfs*: Fix a number of typos 2019-08-05 13:13:10 +02:00
Bastien Nocera
76db6a5a16 aes*: Fix a number of typos 2019-08-05 13:12:45 +02:00
Bastien Nocera
5b171f9577 Add code of conduct document 2019-07-25 12:04:39 +02:00
22 changed files with 95 additions and 99 deletions

View File

@@ -3,8 +3,29 @@
## GLib
Although the library uses GLib internally, libfprint is designed to provide
a completely neutral interface to it's application users. So, the public
APIs should never return GLib data types or anything like that.
a completely neutral interface to its application users. So, the public
APIs should never return GLib data types.
## License clarification
Although this library's license could allow for shims that hook up into
proprietary blobs to add driver support for some unsupported devices, the
intent of the original authors, and of current maintainers of the library,
was for this license to allow _integration into_ proprietary stacks, not
_integration of_ proprietary code in the library.
As such, no code to integrate proprietary drivers will be accepted in libfprint
upstream. Proprietary drivers would make it impossible to debug problems in
libfprint, as we wouldn't know what the proprietary driver does behind the
library's back. The closed source nature of drivers is usually used to hide
parts of the hardware setup, such as encryption keys, or protocols, in order
to protect the hardware's integrity. Unfortunately, this is only [security through
obscurity](https://en.wikipedia.org/wiki/Security_through_obscurity).
We however encourage potential contributors to take advantage of libfprint's
source availability to create such shims to make it easier to reverse-engineer
proprietary drivers in order to create new free software drivers, to the extent
permitted by local laws.
## Two-faced-ness

3
code-of-conduct.md Normal file
View File

@@ -0,0 +1,3 @@
This project and its community follow the [Freedesktop.org code of conduct]
[Freedesktop.org code of conduct]: https://www.freedesktop.org/wiki/CodeOfConduct/

View File

@@ -404,7 +404,7 @@ static unsigned char list_BD_values[10] = {
/*
* Adjust the gain according to the histogram data
* 0xbd, 0xbe, 0x29 and 0x2A registers are affected
* Returns 0 if no problem occured
* Returns 0 if no problem occurred
* TODO: This is a basic support for gain. It needs testing/tweaking. */
static int adjust_gain(unsigned char *buffer, int status)
{
@@ -412,7 +412,7 @@ static int adjust_gain(unsigned char *buffer, int status)
static int pos_list_BE = 0;
static int pos_list_BD = 0;
// This is the first adjustement (we begin acquisition)
// This is the first adjustment (we begin acquisition)
// We adjust strip_scan_reqs for future strips and capture_reqs that is sent just after this step
if (status == GAIN_STATUS_FIRST) {
if (buffer[1] > 0x78) { // maximum gain needed
@@ -534,7 +534,7 @@ static void restore_gain(void)
/* capture SM movement:
* request and read strip,
* jump back to request UNLESS theres no finger, in which case exit SM,
* jump back to request UNLESS there's no finger, in which case exit SM,
* report lack of finger presence, and move to finger detection */
enum capture_states {
@@ -611,8 +611,8 @@ static void capture_read_strip_cb(struct libusb_transfer *transfer)
/* send stop capture bits */
aes_write_regv(dev, capture_stop, G_N_ELEMENTS(capture_stop), stub_capture_stop_cb, NULL);
aesdev->strips = g_slist_reverse(aesdev->strips);
fpi_do_movement_estimation(&assembling_ctx, aesdev->strips, aesdev->strips_len);
img = fpi_assemble_frames(&assembling_ctx, aesdev->strips, aesdev->strips_len);
fpi_do_movement_estimation(&assembling_ctx, aesdev->strips);
img = fpi_assemble_frames(&assembling_ctx, aesdev->strips);
img->flags |= FP_IMG_PARTIAL;
g_slist_free_full(aesdev->strips, g_free);
aesdev->strips = NULL;
@@ -728,7 +728,7 @@ static void activate_run_state(fpi_ssm *ssm, struct fp_dev *_dev, void *user_dat
struct fp_img_dev *dev = user_data;
/* activation on aes1610 seems much more straightforward compared to aes2501 */
/* verify theres anything missing here */
/* verify there's anything missing here */
switch (fpi_ssm_get_cur_state(ssm)) {
case WRITE_INIT:
fp_dbg("write init");

View File

@@ -415,7 +415,7 @@ static struct aes_regwrite strip_scan_reqs[] = {
/* capture SM movement:
* write reqs and read data 1 + 2,
* request and read strip,
* jump back to request UNLESS theres no finger, in which case exit SM,
* jump back to request UNLESS there's no finger, in which case exit SM,
* report lack of finger presence, and move to finger detection */
enum capture_states {
@@ -482,10 +482,8 @@ static void capture_read_strip_cb(struct libusb_transfer *transfer,
struct fp_img *img;
aesdev->strips = g_slist_reverse(aesdev->strips);
fpi_do_movement_estimation(&assembling_ctx,
aesdev->strips, aesdev->strips_len);
img = fpi_assemble_frames(&assembling_ctx,
aesdev->strips, aesdev->strips_len);
fpi_do_movement_estimation(&assembling_ctx, aesdev->strips);
img = fpi_assemble_frames(&assembling_ctx, aesdev->strips);
img->flags |= FP_IMG_PARTIAL;
g_slist_free_full(aesdev->strips, g_free);
aesdev->strips = NULL;

View File

@@ -109,7 +109,7 @@ enum aes2501_mesure_drive {
/* Select (1=square | 0=sine) wave drive during measure */
#define AES2501_MEASDRV_SQUARE 0x20
/* 0 = use mesure drive setting, 1 = when sine wave is selected */
/* 0 = use measure drive setting, 1 = when sine wave is selected */
#define AES2501_MEASDRV_MEASURE_SQUARE 0x10
enum aes2501_measure_freq {

View File

@@ -244,8 +244,7 @@ static void capture_set_idle_reqs_cb(struct libusb_transfer *transfer)
struct fp_img *img;
aesdev->strips = g_slist_reverse(aesdev->strips);
img = fpi_assemble_frames(&assembling_ctx,
aesdev->strips, aesdev->strips_len);
img = fpi_assemble_frames(&assembling_ctx, aesdev->strips);
img->flags |= FP_IMG_PARTIAL;
g_slist_free_full(aesdev->strips, g_free);
aesdev->strips = NULL;
@@ -447,7 +446,7 @@ static void init_read_data_cb(struct libusb_transfer *transfer)
}
/* TODO: use calibration table, datasheet is rather terse on that
* need more info for implementaion */
* need more info for implementation */
static void calibrate_read_data_cb(struct libusb_transfer *transfer)
{
fpi_ssm *ssm = transfer->user_data;

View File

@@ -305,7 +305,7 @@ static void capture_set_idle_cmd_cb(struct libusb_transfer *transfer)
struct fp_img *img;
aesdev->strips = g_slist_reverse(aesdev->strips);
img = fpi_assemble_frames(aesdev->assembling_ctx, aesdev->strips, aesdev->strips_len);
img = fpi_assemble_frames(aesdev->assembling_ctx, aesdev->strips);
img->flags |= aesdev->extra_img_flags;
g_slist_foreach(aesdev->strips, (GFunc) g_free, NULL);
g_slist_free(aesdev->strips);

View File

@@ -293,23 +293,22 @@ static void elan_process_frame_thirds(unsigned short *raw_frame,
static void elan_submit_image(struct fp_img_dev *dev)
{
struct elan_dev *elandev = FP_INSTANCE_DATA(FP_DEV(dev));
int num_frames;
GSList *raw_frames;
GSList *frames = NULL;
struct fp_img *img;
G_DEBUG_HERE();
for (int i = 0; i < ELAN_SKIP_LAST_FRAMES; i++)
elandev->frames = g_slist_next(elandev->frames);
elandev->num_frames -= ELAN_SKIP_LAST_FRAMES;
num_frames = elandev->num_frames - ELAN_SKIP_LAST_FRAMES;
raw_frames = g_slist_nth(elandev->frames, ELAN_SKIP_LAST_FRAMES);
assembling_ctx.frame_width = elandev->frame_width;
assembling_ctx.frame_height = elandev->frame_height;
assembling_ctx.image_width = elandev->frame_width * 3 / 2;
g_slist_foreach(elandev->frames, (GFunc) elandev->process_frame,
&frames);
fpi_do_movement_estimation(&assembling_ctx, frames,
elandev->num_frames);
img = fpi_assemble_frames(&assembling_ctx, frames, elandev->num_frames);
g_slist_foreach(raw_frames, (GFunc) elandev->process_frame, &frames);
fpi_do_movement_estimation(&assembling_ctx, frames);
img = fpi_assemble_frames(&assembling_ctx, frames);
img->flags |= FP_IMG_PARTIAL;
fpi_imgdev_image_captured(dev, img);

View File

@@ -1316,7 +1316,7 @@ static void m_init_state(fpi_ssm *ssm, struct fp_dev *_dev, void *user_data)
fpi_ssm_next_state(ssm);
break;
case INIT_SENSOR_REQ:
/* In captured traffic, those are splitted. */
/* In captured traffic, those are split. */
msg_set_regs(dev, 18, REG_MODE_CONTROL, REG_MODE_SLEEP,
REG_50, 0x0F, REG_GAIN, 0x04, REG_VRT, 0x08,
REG_VRB, 0x0D, REG_VCO_CONTROL, REG_VCO_RT,

View File

@@ -136,7 +136,7 @@ static unsigned char upeksonly_get_pixel(struct fpi_line_asmbl_ctx *ctx,
unsigned char *buf;
unsigned offset;
/* The scans from this device are rolled right by two colums */
/* The scans from this device are rolled right by two columns */
if (x < ctx->line_width - 2)
offset = x + 2;
else if ((x > ctx->line_width - 2) && (x < ctx->line_width))
@@ -296,12 +296,12 @@ static void row_complete(struct fp_img_dev *dev)
sdev->num_blank = 0;
} else {
sdev->num_blank++;
/* Don't consider the scan complete unless theres at least
/* Don't consider the scan complete unless there's at least
* MIN_ROWS recorded or very long blank read occurred.
*
* Typical problem spot: one brief touch before starting the
* actual scan. Happens most commonly if scan is started
* from before the first joint resulting in a gap after the inital touch.
* from before the first joint resulting in a gap after the initial touch.
*/
if (sdev->num_blank > FINGER_REMOVED_THRESHOLD) {
sdev->finger_state = FINGER_REMOVED;

View File

@@ -287,7 +287,7 @@ static void capture_read_data_cb(struct libusb_transfer *transfer)
fpi_ssm_mark_completed(ssm);
break;
default:
fp_err("Uknown response!\n");
fp_err("Unknown response!\n");
fpi_ssm_mark_failed(ssm, -EIO);
break;
}

View File

@@ -668,7 +668,7 @@ static uint32_t do_decode(uint8_t *data, int num_bytes, uint32_t key)
data[i] = data[i+1] ^ xorbyte;
}
/* the final byte is implictly zero */
/* the final byte is implicitly zero */
data[i] = 0;
return update_key(key);
}

View File

@@ -577,7 +577,7 @@ static void activate_ssm(fpi_ssm *ssm, struct fp_dev *_dev, void *user_data)
vdev->need_report = 0;
}
/* Asyncronously enquire an interrupt */
/* Asynchronously enquire an interrupt */
vdev->transfer = fpi_usb_alloc();
vdev->transfer->flags |= LIBUSB_TRANSFER_FREE_TRANSFER;
libusb_fill_interrupt_transfer(vdev->transfer, usb_dev, 0x83,

View File

@@ -47,7 +47,7 @@
/* Minimum image height */
#define VFS_IMG_MIN_HEIGHT 200
/* Scan level thresold */
/* Scan level threshold */
#define VFS_IMG_SLT_BEGIN 768
#define VFS_IMG_SLT_END 64
#define VFS_IMG_SLT_LINES 4
@@ -641,7 +641,7 @@ static int action_completed(struct fp_img_dev *dev)
#define offset(x, y) ((x) + ((y) * VFS_FRAME_SIZE))
/* Screen image to remove noise and find bottom line and height od image */
/* Screen image to remove noise and find bottom line and height of image */
static void img_screen(struct vfs101_dev *vdev)
{
int y, x, count, top;
@@ -654,7 +654,7 @@ static void img_screen(struct vfs101_dev *vdev)
/* Image returned from sensor can contain many empty lines,
* for remove these lines compare byte 282-283 (scan level information)
* with two differents threshold, one for the begin of finger image and
* with two different thresholds, one for the begin of finger image and
* one for the end. To increase stability of the code use a counter
* of lines that satisfy the threshold.
*/
@@ -700,7 +700,7 @@ static void img_screen(struct vfs101_dev *vdev)
vdev->height = top - vdev->bottom + 1;
/* Checkk max height */
/* Check max height */
if (vdev->height > VFS_IMG_MAX_HEIGHT)
vdev->height = VFS_IMG_MAX_HEIGHT;
@@ -1178,7 +1178,7 @@ static void m_init_state(fpi_ssm *ssm, struct fp_dev *_dev, void *user_data)
switch (fpi_ssm_get_cur_state(ssm))
{
case M_INIT_0_RECV_DIRTY:
/* Recv eventualy dirty data */
/* Recv eventually dirty data */
vdev->ignore_error = TRUE;
async_recv(ssm, dev);
break;
@@ -1463,7 +1463,7 @@ static void dev_deactivate(struct fp_img_dev *dev)
/* Reset active state */
vdev->active = FALSE;
/* Handle eventualy existing events */
/* Handle eventually existing events */
while (vdev->transfer)
fp_handle_events();

View File

@@ -109,7 +109,7 @@ typedef struct {
unsigned char scan[VFS301_FP_WIDTH];
/* A offseted, stretched, inverted copy of scan... probably could
/* A offsetted, stretched, inverted copy of scan... probably could
* serve finger motion speed detection?
* Seems to be subdivided to some 10B + 53B + 1B blocks */
unsigned char mirror[64];

View File

@@ -1147,7 +1147,7 @@ static const unsigned char vfs301_24[] = { /* 119 B */
*
* The contents of PACKET() inside this blob seems to be some kind
* of a micro-program, which specifies which columns contain what. LE seems
* to be used also here. Not neccessarily is 1 output column described
* to be used also here. Not necessarily is 1 output column described
* by 1 operation. For example the vfs301_line_t::sum section seems
* to perform 2 operations for each column - probably some kind of diff between
* input lines?

View File

@@ -405,7 +405,7 @@ submit_image(fpi_ssm *ssm,
g_slist_free_full(data->rows, g_free);
data->rows = NULL;
fp_dbg("Image captured, commiting");
fp_dbg("Image captured, committing");
fpi_imgdev_image_captured(dev, img);
}

View File

@@ -121,13 +121,12 @@ static void find_overlap(struct fpi_frame_asmbl_ctx *ctx,
}
static unsigned int do_movement_estimation(struct fpi_frame_asmbl_ctx *ctx,
GSList *stripes, size_t num_stripes,
gboolean reverse)
GSList *stripes, gboolean reverse)
{
GSList *list_entry = stripes;
GSList *l;
GTimer *timer;
int frame = 1;
struct fpi_frame *prev_stripe = list_entry->data;
guint num_frames = 0;
struct fpi_frame *prev_stripe;
unsigned int min_error;
/* Max error is width * height * 255, for AES2501 which has the largest
* sensor its 192*16*255 = 783360. So for 32bit value it's ~5482 frame before
@@ -135,39 +134,34 @@ static unsigned int do_movement_estimation(struct fpi_frame_asmbl_ctx *ctx,
*/
unsigned long long total_error = 0;
list_entry = g_slist_next(list_entry);
timer = g_timer_new();
do {
struct fpi_frame *cur_stripe = list_entry->data;
prev_stripe = stripes->data;
for (l = stripes; l != NULL; l = l->next, num_frames++) {
struct fpi_frame *cur_stripe = l->data;
if (reverse) {
find_overlap(ctx, prev_stripe, cur_stripe, &min_error);
cur_stripe->delta_y = -cur_stripe->delta_y;
cur_stripe->delta_x = -cur_stripe->delta_x;
}
else
} else {
find_overlap(ctx, cur_stripe, prev_stripe, &min_error);
}
total_error += min_error;
frame++;
prev_stripe = cur_stripe;
list_entry = g_slist_next(list_entry);
} while (frame < num_stripes);
}
g_timer_stop(timer);
fp_dbg("calc delta completed in %f secs", g_timer_elapsed(timer, NULL));
g_timer_destroy(timer);
return total_error / num_stripes;
return total_error / num_frames;
}
/**
* fpi_do_movement_estimation:
* @ctx: #fpi_frame_asmbl_ctx - frame assembling context
* @stripes: a singly-linked list of #fpi_frame
* @num_stripes: number of items in @stripes to process
*
* fpi_do_movement_estimation() estimates the movement between adjacent
* frames, populating @delta_x and @delta_y values for each #fpi_frame.
@@ -175,19 +169,17 @@ static unsigned int do_movement_estimation(struct fpi_frame_asmbl_ctx *ctx,
* This function is used for devices that don't do movement estimation
* in hardware. If hardware movement estimation is supported, the driver
* should populate @delta_x and @delta_y instead.
*
* Note that @num_stripes might be shorter than the length of the list,
* if some stripes should be skipped.
*/
void fpi_do_movement_estimation(struct fpi_frame_asmbl_ctx *ctx,
GSList *stripes, size_t num_stripes)
GSList *stripes)
{
int err, rev_err;
err = do_movement_estimation(ctx, stripes, num_stripes, FALSE);
rev_err = do_movement_estimation(ctx, stripes, num_stripes, TRUE);
err = do_movement_estimation(ctx, stripes, FALSE);
rev_err = do_movement_estimation(ctx, stripes, TRUE);
fp_dbg("errors: %d rev: %d", err, rev_err);
if (err < rev_err) {
do_movement_estimation(ctx, stripes, num_stripes, FALSE);
do_movement_estimation(ctx, stripes, FALSE);
}
}
@@ -256,45 +248,34 @@ static inline void aes_blit_stripe(struct fpi_frame_asmbl_ctx *ctx,
* fpi_assemble_frames:
* @ctx: #fpi_frame_asmbl_ctx - frame assembling context
* @stripes: linked list of #fpi_frame
* @num_stripes: number of items in @stripes to process
*
* fpi_assemble_frames() assembles individual frames into a single image.
* It expects @delta_x and @delta_y of #fpi_frame to be populated.
*
* Note that @num_stripes might be shorter than the length of the list,
* if some stripes should be skipped.
*
* Returns: a newly allocated #fp_img.
*/
struct fp_img *fpi_assemble_frames(struct fpi_frame_asmbl_ctx *ctx,
GSList *stripes, size_t num_stripes)
GSList *stripes)
{
GSList *stripe;
GSList *l;
struct fp_img *img;
int height = 0;
int i, y, x;
int y, x;
gboolean reverse = FALSE;
struct fpi_frame *fpi_frame;
//FIXME g_return_if_fail
BUG_ON(num_stripes == 0);
BUG_ON(ctx->image_width < ctx->frame_width);
/* Calculate height */
i = 0;
stripe = stripes;
/* No offset for 1st image */
fpi_frame = stripe->data;
fpi_frame = stripes->data;
fpi_frame->delta_x = 0;
fpi_frame->delta_y = 0;
do {
fpi_frame = stripe->data;
for (l = stripes; l != NULL; l = l->next) {
fpi_frame = l->data;
height += fpi_frame->delta_y;
i++;
stripe = g_slist_next(stripe);
} while (i < num_stripes);
}
fp_dbg("height is %d", height);
@@ -314,13 +295,11 @@ struct fp_img *fpi_assemble_frames(struct fpi_frame_asmbl_ctx *ctx,
img->height = height;
/* Assemble stripes */
i = 0;
stripe = stripes;
y = reverse ? (height - ctx->frame_height) : 0;
x = (ctx->image_width - ctx->frame_width) / 2;
do {
fpi_frame = stripe->data;
for (l = stripes; l != NULL; l = l->next) {
fpi_frame = l->data;
if(reverse) {
y += fpi_frame->delta_y;
@@ -333,10 +312,7 @@ struct fp_img *fpi_assemble_frames(struct fpi_frame_asmbl_ctx *ctx,
y += fpi_frame->delta_y;
x += fpi_frame->delta_x;
}
stripe = g_slist_next(stripe);
i++;
} while (i < num_stripes);
}
return img;
}

View File

@@ -63,10 +63,10 @@ struct fpi_frame_asmbl_ctx {
};
void fpi_do_movement_estimation(struct fpi_frame_asmbl_ctx *ctx,
GSList *stripes, size_t num_stripes);
GSList *stripes);
struct fp_img *fpi_assemble_frames(struct fpi_frame_asmbl_ctx *ctx,
GSList *stripes, size_t num_stripes);
GSList *stripes);
/**
* fpi_line_asmbl_ctx:

View File

@@ -214,7 +214,7 @@ API_EXPORTED size_t fp_print_data_get_data(struct fp_print_data *data,
item = list_item->data;
out_item = (struct fpi_print_data_item_fp2 *)buf;
out_item->length = GUINT32_TO_LE(item->length);
/* FIXME: fp_print_data_item->data content is not endianess agnostic */
/* FIXME: fp_print_data_item->data content is not endianness agnostic */
memcpy(out_item->data, item->data, item->length);
buf += sizeof(*out_item);
buf += item->length;
@@ -236,7 +236,7 @@ static struct fp_print_data *fpi_print_data_from_fp1_data(unsigned char *buf,
data = print_data_new(GUINT16_FROM_LE(raw->driver_id),
GUINT32_FROM_LE(raw->devtype), raw->data_type);
item = fpi_print_data_item_new(print_data_len);
/* FIXME: fp_print_data->data content is not endianess agnostic */
/* FIXME: fp_print_data->data content is not endianness agnostic */
memcpy(item->data, raw->data, print_data_len);
data->prints = g_slist_prepend(data->prints, item);
@@ -272,7 +272,7 @@ static struct fp_print_data *fpi_print_data_from_fp2_data(unsigned char *buf,
total_data_len -= item_len;
item = fpi_print_data_item_new(item_len);
/* FIXME: fp_print_data->data content is not endianess agnostic */
/* FIXME: fp_print_data->data content is not endianness agnostic */
memcpy(item->data, raw_item->data, item_len);
data->prints = g_slist_prepend(data->prints, item);

View File

@@ -405,7 +405,7 @@ int fpi_img_to_print_data(struct fp_img_dev *imgdev, struct fp_img *img,
}
}
/* FIXME: space is wasted if we dont hit the max minutiae count. would
/* FIXME: space is wasted if we don't hit the max minutiae count. would
* be good to make this dynamic. */
print = fpi_print_data_new(FP_DEV(imgdev));
item = fpi_print_data_item_new(sizeof(struct xyt_struct));

View File

@@ -149,7 +149,7 @@ static void enroll_stop_cb(struct fp_dev *dev, void *user_data)
* or I/O problems.
*
* The RETRY codes from #fp_enroll_result may be returned from any enroll
* stage. These codes indicate that the scan was not succesful in that the
* stage. These codes indicate that the scan was not successful in that the
* user did not position their finger correctly or similar. When a RETRY code
* is returned, the enrollment stage is <emphasis role="strong">not</emphasis> advanced, so the next call
* into this function will retry the current stage again. The current stage may