static int
callback_minimal(struct lws *wsi, enum lws_callback_reasons reason,
			void *user, void *in, size_t len)
{
	struct per_session_data__minimal *pss =
			(struct per_session_data__minimal *)user;
	struct per_vhost_data__minimal *vhd =
			(struct per_vhost_data__minimal *)
			lws_protocol_vh_priv_get(lws_get_vhost(wsi),
					lws_get_protocol(wsi));
	const struct lws_protocol_vhost_options *pvo;
	const struct msg *pmsg;
	void *retval;
	int n, m, r = 0;

	switch (reason) {
	case LWS_CALLBACK_PROTOCOL_INIT:
		/* create our per-vhost struct */
		vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
				lws_get_protocol(wsi),
				sizeof(struct per_vhost_data__minimal));
		if (!vhd)
			return 1;

		pthread_mutex_init(&vhd->lock_ring, NULL);

		/* recover the pointer to the globals struct */
		pvo = lws_pvo_search(
			(const struct lws_protocol_vhost_options *)in,
			"config");
		if (!pvo || !pvo->value) {
			lwsl_err("%s: Can't find \"config\" pvo\n", __func__);
			return 1;
		}
		vhd->config = pvo->value;

		vhd->context = lws_get_context(wsi);
		vhd->protocol = lws_get_protocol(wsi);
		vhd->vhost = lws_get_vhost(wsi);

		vhd->ring = lws_ring_create(sizeof(struct msg), 8,
					    __minimal_destroy_message);
		if (!vhd->ring) {
			lwsl_err("%s: failed to create ring\n", __func__);
			return 1;
		}

		/* start the content-creating threads */

		for (n = 0; n < (int)LWS_ARRAY_SIZE(vhd->pthread_spam); n++)
			if (pthread_create(&vhd->pthread_spam[n], NULL,
					   thread_spam, vhd)) {
				lwsl_err("thread creation failed\n");
				r = 1;
				goto init_fail;
			}
		break;

	case LWS_CALLBACK_PROTOCOL_DESTROY:
init_fail:
		vhd->finished = 1;
		for (n = 0; n < (int)LWS_ARRAY_SIZE(vhd->pthread_spam); n++)
			if (vhd->pthread_spam[n])
				pthread_join(vhd->pthread_spam[n], &retval);

		if (vhd->ring)
			lws_ring_destroy(vhd->ring);

		pthread_mutex_destroy(&vhd->lock_ring);
		break;

	case LWS_CALLBACK_ESTABLISHED:
		/* add ourselves to the list of live pss held in the vhd */
		lws_ll_fwd_insert(pss, pss_list, vhd->pss_list);
		pss->tail = lws_ring_get_oldest_tail(vhd->ring);
		pss->wsi = wsi;
		break;

	case LWS_CALLBACK_CLOSED:
		/* remove our closing pss from the list of live pss */
		lws_ll_fwd_remove(struct per_session_data__minimal, pss_list,
				  pss, vhd->pss_list);
		break;

	case LWS_CALLBACK_SERVER_WRITEABLE:
		pthread_mutex_lock(&vhd->lock_ring); /* --------- ring lock { */

		pmsg = lws_ring_get_element(vhd->ring, &pss->tail);
		if (!pmsg) {
			pthread_mutex_unlock(&vhd->lock_ring); /* } ring lock ------- */
			break;
		}

		/* notice we allowed for LWS_PRE in the payload already */
		m = lws_write(wsi, ((unsigned char *)pmsg->payload) + LWS_PRE,
			      pmsg->len, LWS_WRITE_TEXT);
		if (m < (int)pmsg->len) {
			pthread_mutex_unlock(&vhd->lock_ring); /* } ring lock ------- */
			lwsl_err("ERROR %d writing to ws socket\n", m);
			return -1;
		}

		lws_ring_consume_and_update_oldest_tail(
			vhd->ring,	/* lws_ring object */
			struct per_session_data__minimal, /* type of objects with tails */
			&pss->tail,	/* tail of guy doing the consuming */
			1,		/* number of payload objects being consumed */
			vhd->pss_list,	/* head of list of objects with tails */
			tail,		/* member name of tail in objects with tails */
			pss_list	/* member name of next object in objects with tails */
		);

		/* more to do? */
		if (lws_ring_get_element(vhd->ring, &pss->tail))
			/* come back as soon as we can write more */
			lws_callback_on_writable(pss->wsi);

		pthread_mutex_unlock(&vhd->lock_ring); /* } ring lock ------- */
		break;

	case LWS_CALLBACK_RECEIVE:
		break;

	case LWS_CALLBACK_EVENT_WAIT_CANCELLED:
		/*
		 * When the "spam" threads add a message to the ringbuffer,
		 * they create this event in the lws service thread context
		 * using lws_cancel_service().
		 *
		 * We respond by scheduling a writable callback for all
		 * connected clients.
		 */
		lws_start_foreach_llp(struct per_session_data__minimal **,
				      ppss, vhd->pss_list) {
			lws_callback_on_writable((*ppss)->wsi);
		} lws_end_foreach_llp(ppss, pss_list);
		break;

	default:
		break;
	}

	return r;
}
static int
callback_lws_mirror(struct lws *wsi, enum lws_callback_reasons reason,
		    void *user, void *in, size_t len)
{
	struct per_session_data__lws_mirror *pss =
			(struct per_session_data__lws_mirror *)user;
	struct per_vhost_data__lws_mirror *v =
			(struct per_vhost_data__lws_mirror *)
			lws_protocol_vh_priv_get(lws_get_vhost(wsi),
					lws_get_protocol(wsi));
	int n, m;

	switch (reason) {

	case LWS_CALLBACK_ESTABLISHED:
		lwsl_info("%s: LWS_CALLBACK_ESTABLISHED\n", __func__);
		pss->ringbuffer_tail = v->ringbuffer_head;
		pss->wsi = wsi;
		break;

	case LWS_CALLBACK_PROTOCOL_INIT: /* per vhost */
		lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
				lws_get_protocol(wsi),
				sizeof(struct per_vhost_data__lws_mirror));
		break;

	case LWS_CALLBACK_PROTOCOL_DESTROY: /* per vhost */
		if (!v)
			break;
		lwsl_info("%s: mirror protocol cleaning up %p\n", __func__, v);
		for (n = 0; n < ARRAY_SIZE(v->ringbuffer); n++)
			if (v->ringbuffer[n].payload) {
				free(v->ringbuffer[n].payload);
				v->ringbuffer[n].payload = NULL;
			}
		break;

	case LWS_CALLBACK_SERVER_WRITEABLE:
		while (pss->ringbuffer_tail != v->ringbuffer_head) {
			m = v->ringbuffer[pss->ringbuffer_tail].len;
			n = lws_write(wsi, (unsigned char *)
				   v->ringbuffer[pss->ringbuffer_tail].payload +
				   LWS_PRE, m, LWS_WRITE_TEXT);
			if (n < 0) {
				lwsl_err("ERROR %d writing to mirror socket\n", n);
				return -1;
			}
			if (n < m)
				lwsl_err("mirror partial write %d vs %d\n", n, m);

			if (pss->ringbuffer_tail == (MAX_MESSAGE_QUEUE - 1))
				pss->ringbuffer_tail = 0;
			else
				pss->ringbuffer_tail++;

			if (((v->ringbuffer_head - pss->ringbuffer_tail) &
			    (MAX_MESSAGE_QUEUE - 1)) == (MAX_MESSAGE_QUEUE - 15))
				lws_rx_flow_allow_all_protocol(lws_get_context(wsi),
					       lws_get_protocol(wsi));

			if (lws_send_pipe_choked(wsi)) {
				lws_callback_on_writable(wsi);
				break;
			}
		}
		break;

	case LWS_CALLBACK_RECEIVE:
		if (((v->ringbuffer_head - pss->ringbuffer_tail) &
		    (MAX_MESSAGE_QUEUE - 1)) == (MAX_MESSAGE_QUEUE - 1)) {
			lwsl_err("dropping!\n");
			goto choke;
		}

		if (v->ringbuffer[v->ringbuffer_head].payload)
			free(v->ringbuffer[v->ringbuffer_head].payload);

		v->ringbuffer[v->ringbuffer_head].payload = malloc(LWS_PRE + len);
		v->ringbuffer[v->ringbuffer_head].len = len;
		memcpy((char *)v->ringbuffer[v->ringbuffer_head].payload +
		       LWS_PRE, in, len);
		if (v->ringbuffer_head == (MAX_MESSAGE_QUEUE - 1))
			v->ringbuffer_head = 0;
		else
			v->ringbuffer_head++;

		if (((v->ringbuffer_head - pss->ringbuffer_tail) &
		    (MAX_MESSAGE_QUEUE - 1)) != (MAX_MESSAGE_QUEUE - 2))
			goto done;

choke:
		lwsl_debug("LWS_CALLBACK_RECEIVE: throttling %p\n", wsi);
		lws_rx_flow_control(wsi, 0);

done:
		lws_callback_on_writable_all_protocol(lws_get_context(wsi),
						      lws_get_protocol(wsi));
		break;

	default:
		break;
	}

	return 0;
}
static int
callback_acme_client(struct lws *wsi, enum lws_callback_reasons reason,
		     void *user, void *in, size_t len)
{
	struct per_vhost_data__lws_acme_client *vhd =
			(struct per_vhost_data__lws_acme_client *)
			lws_protocol_vh_priv_get(lws_get_vhost(wsi),
					lws_get_protocol(wsi));
	char buf[LWS_PRE + 2536], *start = buf + LWS_PRE, *p = start,
	     *end = buf + sizeof(buf) - 1, digest[32], *failreason = NULL;
	unsigned char **pp, *pend;
	const char *content_type;
	const struct lws_protocol_vhost_options *pvo;
	struct lws_acme_cert_aging_args *caa;
	struct acme_connection *ac = NULL;
	struct lws_genhash_ctx hctx;
	struct lws *cwsi;
	int n, m;

	if (vhd)
		ac = vhd->ac;

	switch ((int)reason) {
	case LWS_CALLBACK_PROTOCOL_INIT:
		vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
				lws_get_protocol(wsi),
				sizeof(struct per_vhost_data__lws_acme_client));
		vhd->context = lws_get_context(wsi);
		vhd->protocol = lws_get_protocol(wsi);
		vhd->vhost = lws_get_vhost(wsi);

		/* compute how much we need to hold all the pvo payloads */
		m = 0;
		pvo = (const struct lws_protocol_vhost_options *)in;
		while (pvo) {
			m += strlen(pvo->value) + 1;
			pvo = pvo->next;
		}
		p = vhd->pvo_data = malloc(m);
		if (!p)
			return -1;

		pvo = (const struct lws_protocol_vhost_options *)in;
		while (pvo) {
			start = p;
			n = strlen(pvo->value) + 1;
			memcpy(start, pvo->value, n);
			p += n;

			for (m = 0; m < (int)LWS_ARRAY_SIZE(pvo_names); m++)
				if (!strcmp(pvo->name, pvo_names[m]))
					vhd->pvop[m] = start;

			pvo = pvo->next;
		}

		n = 0;
		for (m = 0; m < (int)LWS_ARRAY_SIZE(pvo_names); m++)
			if (!vhd->pvop[m] && m >= LWS_TLS_REQ_ELEMENT_COMMON_NAME) {
				lwsl_notice("%s: require pvo '%s'\n", __func__,
						pvo_names[m]);
				n |= 1;
			} else
				if (vhd->pvop[m])
					lwsl_info("  %s: %s\n", pvo_names[m],
							vhd->pvop[m]);
		if (n) {
			free(vhd->pvo_data);
			vhd->pvo_data = NULL;

			return -1;
		}

#if !defined(LWS_WITH_ESP32)
		/*
		 * load (or create) the registration keypair while we
		 * still have root
		 */
		if (lws_acme_load_create_auth_keys(vhd, 4096))
			return 1;

		/*
		 * in case we do an update, open the update files while we
		 * still have root
		 */
		lws_snprintf(buf, sizeof(buf) - 1, "%s.upd",
			     vhd->pvop[LWS_TLS_SET_CERT_PATH]);
		vhd->fd_updated_cert = lws_open(buf, LWS_O_WRONLY | LWS_O_CREAT |
						 LWS_O_TRUNC, 0600);
		if (vhd->fd_updated_cert < 0) {
			lwsl_err("unable to create update cert file %s\n", buf);
			return -1;
		}
		lws_snprintf(buf, sizeof(buf) - 1, "%s.upd",
			     vhd->pvop[LWS_TLS_SET_KEY_PATH]);
		vhd->fd_updated_key = lws_open(buf, LWS_O_WRONLY | LWS_O_CREAT |
						LWS_O_TRUNC, 0600);
		if (vhd->fd_updated_key < 0) {
			lwsl_err("unable to create update key file %s\n", buf);
			return -1;
		}
#endif
		break;

	case LWS_CALLBACK_PROTOCOL_DESTROY:
		if (vhd && vhd->pvo_data) {
			free(vhd->pvo_data);
			vhd->pvo_data = NULL;
		}
		if (vhd)
			lws_acme_finished(vhd);
		break;

	case LWS_CALLBACK_VHOST_CERT_AGING:
		if (!vhd)
			break;

		caa = (struct lws_acme_cert_aging_args *)in;
		/*
		 * Somebody is telling us about a cert some vhost is using.
		 *
		 * First see if the cert is getting close enough to expiry that
		 * we *want* to do something about it.
		 */
		if ((int)(ssize_t)len > 14)
			break;

		/*
		 * ...is this a vhost we were configured on?
		 */
		if (vhd->vhost != caa->vh)
			return 1;

		for (n = 0; n < (int)LWS_ARRAY_SIZE(vhd->pvop);n++)
			if (caa->element_overrides[n])
				vhd->pvop_active[n] = caa->element_overrides[n];
			else
				vhd->pvop_active[n] = vhd->pvop[n];

		lwsl_notice("starting acme acquisition on %s: %s\n",
				lws_get_vhost_name(caa->vh), vhd->pvop_active[LWS_TLS_SET_DIR_URL]);

		lws_acme_start_acquisition(vhd, caa->vh);
		break;

	/*
	 * Client
	 */

	case LWS_CALLBACK_CLIENT_ESTABLISHED:
		lwsl_notice("%s: CLIENT_ESTABLISHED\n", __func__);
		break;

	case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
		lwsl_notice("%s: CLIENT_CONNECTION_ERROR: %p\n", __func__, wsi);
		break;

	case LWS_CALLBACK_CLOSED_CLIENT_HTTP:
		lwsl_notice("%s: CLOSED_CLIENT_HTTP: %p\n", __func__, wsi);
		break;

	case LWS_CALLBACK_CLOSED:
		lwsl_notice("%s: CLOSED: %p\n", __func__, wsi);
		break;

	case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP:
		lwsl_notice("lws_http_client_http_response %d\n",
			    lws_http_client_http_response(wsi));
		if (!ac)
			break;
		ac->resp = lws_http_client_http_response(wsi);
		/* we get a new nonce each time */
		if (lws_hdr_total_length(wsi, WSI_TOKEN_REPLAY_NONCE) &&
		    lws_hdr_copy(wsi, ac->replay_nonce,
				 sizeof(ac->replay_nonce),
				 WSI_TOKEN_REPLAY_NONCE) < 0) {
			lwsl_notice("%s: nonce too large\n", __func__);

			goto failed;
		}

		switch (ac->state) {
		case ACME_STATE_DIRECTORY:
			lejp_construct(&ac->jctx, cb_dir, vhd, jdir_tok,
				       LWS_ARRAY_SIZE(jdir_tok));
			break;
		case ACME_STATE_NEW_REG:
			break;
		case ACME_STATE_NEW_AUTH:
			lejp_construct(&ac->jctx, cb_authz, ac, jauthz_tok,
					LWS_ARRAY_SIZE(jauthz_tok));
			break;

		case ACME_STATE_POLLING:
		case ACME_STATE_ACCEPT_CHALL:
			lejp_construct(&ac->jctx, cb_chac, ac, jchac_tok,
					LWS_ARRAY_SIZE(jchac_tok));
			break;

		case ACME_STATE_POLLING_CSR:
			ac->cpos = 0;
			if (ac->resp != 201)
				break;
			/*
			 * He acknowledges he will create the cert...
			 * get the URL to GET it from in the Location
			 * header.
			 */
			if (lws_hdr_copy(wsi, ac->challenge_uri,
					 sizeof(ac->challenge_uri),
					 WSI_TOKEN_HTTP_LOCATION) < 0) {
				lwsl_notice("%s: missing cert location:\n",
					    __func__);

				goto failed;
			}

			lwsl_notice("told to fetch cert from %s\n",
					ac->challenge_uri);
			break;

		default:
			break;
		}
		break;

	case LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER:
		if (!ac)
			break;
		switch (ac->state) {

		case ACME_STATE_DIRECTORY:
			break;
		case ACME_STATE_NEW_REG:
			p += lws_snprintf(p, end - p, "{"
					  "\"resource\":\"new-reg\","
					  "\"contact\":["
					  "\"mailto:%s\""
					  "],\"agreement\":\"%s\""
					  "}",
					  vhd->pvop_active[LWS_TLS_REQ_ELEMENT_EMAIL],
					  ac->urls[JAD_TOS_URL]);

			puts(start);
pkt_add_hdrs:
			ac->len = lws_jws_create_packet(&vhd->jwk,
							start, p - start,
							ac->replay_nonce,
							&ac->buf[LWS_PRE],
							sizeof(ac->buf) -
								 LWS_PRE);
			if (ac->len < 0) {
				ac->len = 0;
				lwsl_notice("lws_jws_create_packet failed\n");
				goto failed;
			}

			pp = (unsigned char **)in;
			pend = (*pp) + len;

			ac->pos = 0;
			content_type =         "application/jose+json";
			if (ac->state == ACME_STATE_POLLING_CSR)
				content_type = "application/pkix-cert";

			if (lws_add_http_header_by_token(wsi,
				    WSI_TOKEN_HTTP_CONTENT_TYPE,
					(uint8_t *)content_type, 21, pp, pend)) {
				lwsl_notice("could not add content type\n");
				goto failed;
			}

			n = sprintf(buf, "%d", ac->len);
			if (lws_add_http_header_by_token(wsi,
					WSI_TOKEN_HTTP_CONTENT_LENGTH,
					(uint8_t *)buf, n, pp, pend)) {
				lwsl_notice("could not add content length\n");
				goto failed;
			}

			lws_client_http_body_pending(wsi, 1);
			lws_callback_on_writable(wsi);
			lwsl_notice("prepare to send ACME_STATE_NEW_REG\n");
			break;
		case ACME_STATE_NEW_AUTH:
			p += lws_snprintf(p, end - p,
					"{"
					 "\"resource\":\"new-authz\","
					 "\"identifier\":{"
					  "\"type\":\"http-01\","
					  "\"value\":\"%s\""
					 "}"
					"}", vhd->pvop_active[LWS_TLS_REQ_ELEMENT_COMMON_NAME]);
			goto pkt_add_hdrs;

		case ACME_STATE_ACCEPT_CHALL:
			/*
			 * Several of the challenges in this document makes use
			 * of a key authorization string.  A key authorization
			 * expresses a domain holder's authorization for a
			 * specified key to satisfy a specified challenge, by
			 * concatenating the token for the challenge with a key
			 * fingerprint, separated by a "." character:
			 *
			 * key-authz = token || '.' ||
			 * 	       base64(JWK_Thumbprint(accountKey))
			 *
			 * The "JWK_Thumbprint" step indicates the computation
			 * specified in [RFC7638], using the SHA-256 digest.  As
			 * specified in the individual challenges below, the
			 * token for a challenge is a JSON string comprised
			 * entirely of characters in the base64 alphabet.
			 * The "||" operator indicates concatenation of strings.
			 *
			 *    keyAuthorization (required, string):  The key
			 *  authorization for this challenge.  This value MUST
			 *  match the token from the challenge and the client's
			 *  account key.
			 *
			 * draft acme-01 tls-sni-01:
			 *
			 *    {
			 *         "keyAuthorization": "evaGxfADs...62jcerQ",
			 *    }   (Signed as JWS)
			 *
			 * draft acme-07 tls-sni-02:
			 *
			 * POST /acme/authz/1234/1
			 * Host: example.com
			 * Content-Type: application/jose+json
			 *
			 * {
			 *  "protected": base64url({
			 *    "alg": "ES256",
			 *    "kid": "https://example.com/acme/acct/1",
			 *    "nonce": "JHb54aT_KTXBWQOzGYkt9A",
			 *    "url": "https://example.com/acme/authz/1234/1"
			 *  }),
			 *  "payload": base64url({
			 *     "keyAuthorization": "evaGxfADs...62jcerQ"
			 *  }),
			 * "signature": "Q1bURgJoEslbD1c5...3pYdSMLio57mQNN4"
			 * }
			 *
			 * On receiving a response, the server MUST verify that
			 * the key authorization in the response matches the
			 * "token" value in the challenge and the client's
			 * account key.  If they do not match, then the server
			 * MUST return an HTTP error in response to the POST
			 * request in which the client sent the challenge.
			 */

			lws_jwk_rfc7638_fingerprint(&vhd->jwk, digest);
			p = start;
			end = &buf[sizeof(buf) - 1];

			p += lws_snprintf(p, end - p,
					  "{\"resource\":\"challenge\","
					  "\"type\":\"tls-sni-0%d\","
					  "\"keyAuthorization\":\"%s.",
					  1 + ac->is_sni_02,
					  ac->chall_token);
			n = lws_jws_base64_enc(digest, 32, p, end - p);
			if (n < 0)
				goto failed;
			p += n;
			p += lws_snprintf(p, end - p, "\"}");
			puts(start);
			goto pkt_add_hdrs;

		case ACME_STATE_POLLING:
			break;

		case ACME_STATE_POLLING_CSR:
			/*
			 * "To obtain a certificate for the domain, the agent
			 * constructs a PKCS#10 Certificate Signing Request that
			 * asks the Let’s Encrypt CA to issue a certificate for
			 * example.com with a specified public key. As usual,
			 * the CSR includes a signature by the private key
			 * corresponding to the public key in the CSR. The agent
			 * also signs the whole CSR with the authorized
			 * key for example.com so that the Let’s Encrypt CA
			 * knows it’s authorized."
			 *
			 * IOW we must create a new RSA keypair which will be
			 * the cert public + private key, and put the public
			 * key in the CSR.  The CSR, just for transport, is also
			 * signed with our JWK, showing that as the owner of the
			 * authorized JWK, the request should be allowed.
			 *
			 * The cert comes back with our public key in it showing
			 * that the owner of the matching private key (we
			 * created that keypair) is the owner of the cert.
			 *
			 * We feed the CSR the elements we want in the cert,
			 * like the CN etc, and it gives us the b64URL-encoded
			 * CSR and the PEM-encoded (public +)private key in
			 * memory buffers.
			 */
			if (ac->goes_around)
				break;

			p += lws_snprintf(p, end - p,
					  "{\"resource\":\"new-cert\","
					  "\"csr\":\"");
			n = lws_tls_acme_sni_csr_create(vhd->context,
							&vhd->pvop_active[0],
							(uint8_t *)p, end - p,
							&ac->alloc_privkey_pem,
							&ac->len_privkey_pem);
			if (n < 0) {
				lwsl_notice("CSR generation failed\n");
				goto failed;
			}
			p += n;
			p += lws_snprintf(p, end - p, "\"}");
			puts(start);
			goto pkt_add_hdrs;

		default:
			break;
		}
		break;

	case LWS_CALLBACK_CLIENT_HTTP_WRITEABLE:
		lwsl_notice("LWS_CALLBACK_CLIENT_HTTP_WRITEABLE\n");
		if (!ac)
			break;
		if (ac->pos == ac->len)
			break;

		ac->buf[LWS_PRE + ac->len] = '\0';
		if (lws_write(wsi, (uint8_t *)ac->buf + LWS_PRE,
			      ac->len, LWS_WRITE_HTTP_FINAL) < 0)
			return -1;
		lwsl_notice("wrote %d\n", ac->len);
		ac->pos = ac->len;
		lws_client_http_body_pending(wsi, 0);
		break;

	/* chunked content */
	case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ:
		if (!ac)
			return -1;
		switch (ac->state) {
		case ACME_STATE_POLLING:
		case ACME_STATE_ACCEPT_CHALL:
		case ACME_STATE_NEW_AUTH:
		case ACME_STATE_DIRECTORY:
			((char *)in)[len] = '\0';
			puts(in);
			m = (int)(signed char)lejp_parse(&ac->jctx,
							 (uint8_t *)in, len);
			if (m < 0 && m != LEJP_CONTINUE) {
				lwsl_notice("lejp parse failed %d\n", m);
				goto failed;
			}
			break;
		case ACME_STATE_NEW_REG:
			((char *)in)[len] = '\0';
			puts(in);
			break;
		case ACME_STATE_POLLING_CSR:
			/* it should be the DER cert! */
			if (ac->cpos + len > sizeof(ac->buf)) {
				lwsl_notice("Incoming cert is too large!\n");
				goto failed;
			}
			memcpy(&ac->buf[ac->cpos], in, len);
			ac->cpos += len;
			break;
		default:
			break;
		}
		break;

	/* unchunked content */
	case LWS_CALLBACK_RECEIVE_CLIENT_HTTP:
		lwsl_notice("%s: LWS_CALLBACK_RECEIVE_CLIENT_HTTP\n", __func__);
		{
			char buffer[2048 + LWS_PRE];
			char *px = buffer + LWS_PRE;
			int lenx = sizeof(buffer) - LWS_PRE;

			if (lws_http_client_read(wsi, &px, &lenx) < 0)
				return -1;
		}
		break;

	case LWS_CALLBACK_COMPLETED_CLIENT_HTTP:
		lwsl_notice("%s: COMPLETED_CLIENT_HTTP\n", __func__);

		if (!ac)
			return -1;
		switch (ac->state) {
		case ACME_STATE_DIRECTORY:
			lejp_destruct(&ac->jctx);

			/* check dir validity */

			for (n = 0; n < 6; n++)
				lwsl_notice("   %d: %s\n", n, ac->urls[n]);

			/*
			 * So... having the directory now... we try to
			 * register our keys next.  It's OK if it ends up
			 * they're already registered... this eliminates any
			 * gaps where we stored the key but registration did
			 * not complete for some reason...
			 */
			ac->state = ACME_STATE_NEW_REG;
			lws_acme_report_status(vhd->vhost, LWS_CUS_REG, NULL);

			strcpy(buf, ac->urls[JAD_NEW_REG_URL]);
			cwsi = lws_acme_client_connect(vhd->context, vhd->vhost,
						       &ac->cwsi, &ac->i, buf,
						       "POST");
			if (!cwsi) {
				lwsl_notice("%s: failed to connect to acme\n",
					    __func__);
				goto failed;
			}
			return -1; /* close the completed client connection */

		case ACME_STATE_NEW_REG:
			if ((ac->resp >= 200 && ac->resp < 299) ||
			     ac->resp == 409) {
				/*
				 * Our account already existed, or exists now.
				 *
				 * Move on to requesting a cert auth.
				 */
				ac->state = ACME_STATE_NEW_AUTH;
				lws_acme_report_status(vhd->vhost, LWS_CUS_AUTH,
							NULL);

				strcpy(buf, ac->urls[JAD_NEW_AUTHZ_URL]);
				cwsi = lws_acme_client_connect(vhd->context,
							vhd->vhost, &ac->cwsi,
							&ac->i, buf, "POST");
				if (!cwsi)
					lwsl_notice("%s: failed to connect\n",
						    __func__);
				return -1; /* close the completed client connection */
			} else {
				lwsl_notice("new-reg replied %d\n", ac->resp);
				goto failed;
			}
			return -1; /* close the completed client connection */

		case ACME_STATE_NEW_AUTH:
			lejp_destruct(&ac->jctx);
			if (ac->resp / 100 == 4) {
				lws_snprintf(buf, sizeof(buf),
					     "Auth failed: %s", ac->detail);
				failreason = buf;
				lwsl_notice("auth failed\n");
				goto failed;
			}
			lwsl_notice("chall: %s (%d)\n", ac->chall_token, ac->resp);
			if (!ac->chall_token[0]) {
				lwsl_notice("no challenge\n");
				goto failed;
			}


			ac->state = ACME_STATE_ACCEPT_CHALL;
			lws_acme_report_status(vhd->vhost, LWS_CUS_CHALLENGE,
						NULL);

			/* tls-sni-01 ... what a mess.
			 * The stuff in
			 * https://tools.ietf.org/html/
			 * 		draft-ietf-acme-acme-01#section-7.3
			 * "requires" n but it's missing from let's encrypt
			 * tls-sni-01 challenge.  The go docs say that they just
			 * implement one hashing round regardless
			 * https://godoc.org/golang.org/x/crypto/acme
			 *
			 * The go way is what is actually implemented today by
			 * letsencrypt
			 *
			 * "A client responds to this challenge by constructing
			 * a key authorization from the "token" value provided
			 * in the challenge and the client's account key.  The
			 * client first computes the SHA-256 digest Z0 of the
			 * UTF8-encoded key authorization, and encodes Z0 in
			 * UTF-8 lower-case hexadecimal form."
			 */

			/* tls-sni-02
			 *
			 * SAN A MUST be constructed as follows: compute the
			 * SHA-256 digest of the UTF-8-encoded challenge token
			 * and encode it in lowercase hexadecimal form.  The
			 * dNSName is "x.y.token.acme.invalid", where x
			 * is the first half of the hexadecimal representation
			 * and y is the second half.
			 */

			memset(&ac->ci, 0, sizeof(ac->ci));

			/* first compute the key authorization */

			lws_jwk_rfc7638_fingerprint(&vhd->jwk, digest);
			p = start;
			end = &buf[sizeof(buf) - 1];

			p += lws_snprintf(p, end - p, "%s.", ac->chall_token);
			n = lws_jws_base64_enc(digest, 32, p, end - p);
			if (n < 0)
				goto failed;
			p += n;

			if (lws_genhash_init(&hctx, LWS_GENHASH_TYPE_SHA256))
				return -1;

			if (lws_genhash_update(&hctx, (uint8_t *)start,
						lws_ptr_diff(p, start))) {
				lws_genhash_destroy(&hctx, NULL);

				return -1;
			}
			if (lws_genhash_destroy(&hctx, digest))
				return -1;

			p = buf;
			for (n = 0; n < 32; n++) {
				p += lws_snprintf(p, end - p, "%02x",
						  digest[n] & 0xff);
				if (n == (32 / 2) - 1)
					p = buf + 64;
			}

			p = ac->san_a;
			if (ac->is_sni_02) {
				lws_snprintf(p, sizeof(ac->san_a),
					     "%s.%s.token.acme.invalid",
					     buf, buf + 64);

				/*
				 * SAN B MUST be constructed as follows: compute
				 * the SHA-256 digest of the UTF-8 encoded key
				 * authorization and encode it in lowercase
				 * hexadecimal form.  The dNSName is
				 * "x.y.ka.acme.invalid" where x is the first
				 * half of the hexadecimal representation and y
				 * is the second half.
				 */
				lws_jwk_rfc7638_fingerprint(&vhd->jwk,
							    (char *)digest);

				p = buf;
				for (n = 0; n < 32; n++) {
					p += lws_snprintf(p, end - p, "%02x",
							  digest[n] & 0xff);
					if (n == (32 / 2) - 1)
						p = buf + 64;
				}

				p = ac->san_b;
				lws_snprintf(p, sizeof(ac->san_b),
					     "%s.%s.ka.acme.invalid",
					     buf, buf + 64);
			} else {
				lws_snprintf(p, sizeof(ac->san_a),
				     "%s.%s.acme.invalid", buf, buf + 64);
				ac->san_b[0] = '\0';
			}

			lwsl_notice("san_a: '%s'\n", ac->san_a);
			lwsl_notice("san_b: '%s'\n", ac->san_b);

			/*
			 * tls-sni-01:
			 *
			 * The client then configures the TLS server at the
			 * domain such that when a handshake is initiated with
			 * the Server Name Indication extension set to
			 * "<Zi[0:32]>.<Zi[32:64]>.acme.invalid", the
			 * corresponding generated certificate is presented.
			 *
			 * tls-sni-02:
			 *
			 *  The client MUST ensure that the certificate is
			 *  served to TLS connections specifying a Server Name
			 *  Indication (SNI) value of SAN A.
			 */
			ac->ci.vhost_name = ac->san_a;

			/*
			 * we bind to exact iface of real vhost, so we can
			 * share the listen socket by SNI
			 */
			ac->ci.iface = ac->real_vh_iface;

			/* listen on the same port as the vhost that triggered
			 * us */
			ac->ci.port = ac->real_vh_port;
			/* Skip filling in any x509 info into the ssl_ctx.
			 * It will be done at the callback
			 * LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS
			 * in this callback handler (below)
			 */
			ac->ci.options = LWS_SERVER_OPTION_CREATE_VHOST_SSL_CTX |
					 LWS_SERVER_OPTION_SKIP_PROTOCOL_INIT |
					 LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
			/* make ourselves protocols[0] for the new vhost */
			ac->ci.protocols = acme_protocols;
			/*
			 * vhost .user points to the ac associated with the
			 * temporary vhost
			 */
			ac->ci.user = ac;

			ac->vhost = lws_create_vhost(lws_get_context(wsi),
						     &ac->ci);
			if (!ac->vhost)
				goto failed;

			/*
			 * The challenge-specific vhost is up... let the ACME
			 * server know we are ready to roll...
			 */

			ac->goes_around = 0;
			cwsi = lws_acme_client_connect(vhd->context, vhd->vhost,
						       &ac->cwsi, &ac->i,
						       ac->challenge_uri,
						       "POST");
			if (!cwsi) {
				lwsl_notice("%s: failed to connect\n",
					    __func__);
				goto failed;
			}
			return -1; /* close the completed client connection */

		case ACME_STATE_ACCEPT_CHALL:
			/*
			 * he returned something like this (which we parsed)
			 *
			 * {
			 *   "type": "tls-sni-01",
			 *   "status": "pending",
			 *   "uri": "https://acme-staging.api.letsencrypt.org/
			 *   		acme/challenge/xCt7bT3FaxoIQU3Qry87t5h
			 *   		uKDcC-L-0ERcD5DLAZts/71100507",
			 *   "token": "j2Vs-vLI_dsza4A35SFHIU03aIe2PzFRijbqCY
			 *   		dIVeE",
			 *   "keyAuthorization": "j2Vs-vLI_dsza4A35SFHIU03aIe2
			 *   		PzFRijbqCYdIVeE.nmOtdFd8Jikn6K8NnYYmT5
			 *   		vCM_PwSDT8nLdOYoFXhRU"
			 * }
			 *
			 */
			lwsl_notice("%s: COMPLETED accept chall: %s\n",
					__func__, ac->challenge_uri);
poll_again:
			ac->state = ACME_STATE_POLLING;
			lws_acme_report_status(vhd->vhost, LWS_CUS_CHALLENGE, NULL);

			if (ac->goes_around++ == 20) {
				lwsl_notice("%s: too many chall retries\n",
					    __func__);

				goto failed;
			}

			lws_timed_callback_vh_protocol(vhd->vhost, vhd->protocol,
					LWS_CALLBACK_USER + 0xac33, ac->goes_around == 1 ? 10 : 2);
			return -1; /* close the completed client connection */

		case ACME_STATE_POLLING:

			if (ac->resp == 202 &&
			    strcmp(ac->status, "invalid") &&
			    strcmp(ac->status, "valid")) {
				lwsl_notice("status: %s\n", ac->status);
				goto poll_again;
			}

			if (!strcmp(ac->status, "invalid")) {
				lwsl_notice("%s: polling failed\n", __func__);
				lws_snprintf(buf, sizeof(buf),
					     "Challenge Invalid: %s", ac->detail);
				failreason = buf;
				goto failed;
			}

			lwsl_notice("Challenge passed\n");

			/*
			 * The challenge was validated... so delete the
			 * temp SNI vhost now its job is done
			 */
			if (ac->vhost)
				lws_vhost_destroy(ac->vhost);
			ac->vhost = NULL;

			/*
			 * now our JWK is accepted as authorized to make
			 * requests for the domain, next move is create the
			 * CSR signed with the JWK, and send it to the ACME
			 * server to request the actual certs.
			 */
			ac->state = ACME_STATE_POLLING_CSR;
			lws_acme_report_status(vhd->vhost, LWS_CUS_REQ, NULL);
			ac->goes_around = 0;

			strcpy(buf, ac->urls[JAD_NEW_CERT_URL]);
			cwsi = lws_acme_client_connect(vhd->context, vhd->vhost,
						       &ac->cwsi, &ac->i, buf,
						       "POST");
			if (!cwsi) {
				lwsl_notice("%s: failed to connect to acme\n",
					    __func__);

				goto failed;
			}
			return -1; /* close the completed client connection */

		case ACME_STATE_POLLING_CSR:
			/*
			 * (after POSTing the CSR)...
			 *
			 * If the CA decides to issue a certificate, then the
			 * server creates a new certificate resource and
			 * returns a URI for it in the Location header field
			 * of a 201 (Created) response.
			 *
			 * HTTP/1.1 201 Created
			 * Location: https://example.com/acme/cert/asdf
			 *
			 * If the certificate is available at the time of the
			 * response, it is provided in the body of the response.
			 * If the CA has not yet issued the certificate, the
			 * body of this response will be empty.  The client
			 * should then send a GET request to the certificate URI
			 * to poll for the certificate.  As long as the
			 * certificate is unavailable, the server MUST provide a
			 * 202 (Accepted) response and include a Retry-After
			 * header to indicate when the server believes the
			 * certificate will be issued.
			 */
			if (ac->resp < 200 || ac->resp > 202) {
				lwsl_notice("CSR poll failed on resp %d\n",
					    ac->resp);
				goto failed;
			}

			if (ac->resp == 200) {
				char *pp;
				int max;

				lwsl_notice("The cert was sent..\n");

				lws_acme_report_status(vhd->vhost,
						LWS_CUS_ISSUE, NULL);

				/*
				 * That means we have the issued cert DER in
				 * ac->buf, length in ac->cpos; and the key in
				 * ac->alloc_privkey_pem, length in
				 * ac->len_privkey_pem.
				 *
				 * We write out a PEM copy of the cert, and a
				 * PEM copy of the private key, using the
				 * write-only fds we opened while we still
				 * had root.
				 *
				 * Estimate the size of the PEM version of the
				 * cert and allocate a temp buffer for it.
				 *
				 * This is a bit complicated because first we
				 * drop the b64url version into the buffer at
				 * +384, then we add the header at 0 and move
				 * lines of it back + '\n' to make PEM.
				 *
				 * This avoids the need for two fullsize
				 * allocations.
				 */

				max = (ac->cpos * 4) / 3 + 16 + 384;

				start = p = malloc(max);
				if (!p)
					goto failed;

				n = lws_b64_encode_string(ac->buf, ac->cpos,
							  start + 384, max - 384);
				if (n < 0) {
					free(start);
					goto failed;
				}

				pp = start + 384;
				p += lws_snprintf(start, 64, "%s",
						"-----BEGIN CERTIFICATE-----\n");

				while (n) {
					m = 65;
					if (n < m)
						m = n;
					memcpy(p, pp, m);
					n -= m;
					p += m;
					pp += m;
					if (n)
						*p++ = '\n';
				}
				p += lws_snprintf(p,
						  max - lws_ptr_diff(p, start),
						  "%s",
						  "\n-----END CERTIFICATE-----\n");

				n = lws_plat_write_cert(vhd->vhost, 0,
						vhd->fd_updated_cert, start,
						lws_ptr_diff(p, start));
				free(start);
				if (n) {
					lwsl_err("unable to write ACME cert! %d\n", n);
					goto failed;
				}
				/*
				 * don't close it... we may update the certs
				 * again
				 */

				if (lws_plat_write_cert(vhd->vhost, 1,
							vhd->fd_updated_key,
							ac->alloc_privkey_pem,
							ac->len_privkey_pem)) {
					lwsl_err("unable to write ACME key!\n");
					goto failed;
				}

				/*
				 * we have written the persistent copies
				 */

				lwsl_notice("%s: Updated certs written for %s "
					    "to %s.upd and %s.upd\n", __func__,
					    vhd->pvop_active[LWS_TLS_REQ_ELEMENT_COMMON_NAME],
					    vhd->pvop_active[LWS_TLS_SET_CERT_PATH],
					    vhd->pvop_active[LWS_TLS_SET_KEY_PATH]);

				/* notify lws there was a cert update */

				if (lws_tls_cert_updated(vhd->context,
					vhd->pvop_active[LWS_TLS_SET_CERT_PATH],
					vhd->pvop_active[LWS_TLS_SET_KEY_PATH],
					ac->buf, ac->cpos,
					ac->alloc_privkey_pem,
					ac->len_privkey_pem)) {
					lwsl_notice("problem setting certs\n");
				}

				lws_acme_finished(vhd);
				lws_acme_report_status(vhd->vhost,
							LWS_CUS_SUCCESS, NULL);

				return 0;
			}

			lws_acme_report_status(vhd->vhost, LWS_CUS_CONFIRM, NULL);

			/* he is preparing the cert, go again with a GET */

			if (ac->goes_around++ == 30) {
				lwsl_notice("%s: too many retries\n",
					    __func__);

				goto failed;
			}

			strcpy(buf, ac->challenge_uri);
			cwsi = lws_acme_client_connect(vhd->context, vhd->vhost,
						       &ac->cwsi, &ac->i, buf,
						       "GET");
			if (!cwsi) {
				lwsl_notice("%s: failed to connect to acme\n",
					    __func__);

				goto failed;
			}
			return -1; /* close the completed client connection */

		default:
			break;
		}
		break;

		case LWS_CALLBACK_USER + 0xac33:
			if (!vhd)
				break;
			cwsi = lws_acme_client_connect(vhd->context, vhd->vhost,
						       &ac->cwsi, &ac->i,
						       ac->challenge_uri,
						       "GET");
			if (!cwsi) {
				lwsl_notice("%s: failed to connect\n", __func__);
				goto failed;
			}
			break;

	case LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS:
		/*
		 * This goes to vhost->protocols[0], but for our temp certs
		 * vhost we created, we have arranged that to be our protocol,
		 * so the callback will come here.
		 *
		 * When we created the temp vhost, we set its pvo to point
		 * to the ac associated with the temp vhost.
		 */
		lwsl_debug("LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS\n");
		ac = (struct acme_connection *)lws_get_vhost_user(
							(struct lws_vhost *)in);

		lws_acme_report_status((struct lws_vhost *)in,
				        LWS_CUS_CREATE_REQ,
				        "creating challenge cert");

		if (lws_tls_acme_sni_cert_create((struct lws_vhost *)in,
						 ac->san_a, ac->san_b)) {
			lwsl_err("%s: creating the sni test cert failed\n", __func__);

			return -1;
		}
		break;

	default:
		break;
	}

	return 0;

failed:
	lwsl_err("%s: failed out\n", __func__);
	lws_acme_report_status(vhd->vhost, LWS_CUS_FAILED, failreason);
	lws_acme_finished(vhd);

	return -1;
}
static int
callback_raw_test(struct lws *wsi, enum lws_callback_reasons reason,
			void *user, void *in, size_t len)
{
	struct per_session_data__raw_test *pss =
			(struct per_session_data__raw_test *)user;
	struct per_vhost_data__raw_test *vhd =
			(struct per_vhost_data__raw_test *)
			lws_protocol_vh_priv_get(lws_get_vhost(wsi),
					lws_get_protocol(wsi));
	lws_sock_file_fd_type u;

	(void)pss;

	switch (reason) {
	case LWS_CALLBACK_PROTOCOL_INIT:
		vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
				lws_get_protocol(wsi),
				sizeof(struct per_vhost_data__raw_test));
		vhd->context = lws_get_context(wsi);
		vhd->protocol = lws_get_protocol(wsi);
		vhd->vhost = lws_get_vhost(wsi);
		{
			const struct lws_protocol_vhost_options *pvo =
					(const struct lws_protocol_vhost_options *)in;
			while (pvo) {
				if (!strcmp(pvo->name, "fifo-path"))
					lws_strncpy(vhd->fifo_path, pvo->value,
							sizeof(vhd->fifo_path));
				pvo = pvo->next;
			}
			if (vhd->fifo_path[0] == '\0') {
				lwsl_err("%s: Missing pvo \"fifo-path\", raw file fd testing disabled\n", __func__);
				break;
			}
		}
		unlink(vhd->fifo_path);
		if (mkfifo(vhd->fifo_path, 0666)) {
			lwsl_err("mkfifo failed\n");
			return 1;
		}
		vhd->fifo = lws_open(vhd->fifo_path, O_NONBLOCK | O_RDONLY);
		if (vhd->fifo == -1) {
			lwsl_err("opening fifo failed\n");
			unlink(vhd->fifo_path);
			return 1;
		}
		lwsl_notice("FIFO %s created\n", vhd->fifo_path);
		u.filefd = vhd->fifo;
		if (!lws_adopt_descriptor_vhost(vhd->vhost,
						LWS_ADOPT_RAW_FILE_DESC, u,
						"protocol-lws-raw-test",
						NULL)) {
			lwsl_err("Failed to adopt fifo descriptor\n");
			close(vhd->fifo);
			unlink(vhd->fifo_path);
			return 1;
		}
		break;

	case LWS_CALLBACK_PROTOCOL_DESTROY:
		if (!vhd)
			break;
		if (vhd->fifo >= 0) {
			close(vhd->fifo);
			unlink(vhd->fifo_path);
		}
		break;


	/*
	 * Callbacks related to Raw file descriptor testing
	 */

	case LWS_CALLBACK_RAW_ADOPT_FILE:
		lwsl_notice("LWS_CALLBACK_RAW_ADOPT_FILE\n");
		break;


	case LWS_CALLBACK_RAW_RX_FILE:
		lwsl_notice("LWS_CALLBACK_RAW_RX_FILE\n");
		{
			char buf[256];
			int n;
			
			n = read(vhd->fifo, buf, sizeof(buf) - 1);
			if (n < 0) {
				lwsl_err("FIFO read failed\n");
				return 1;
			}
			/*
			 * When nobody opened the other side of the FIFO, the FIFO fd acts well and
			 * only signals POLLIN when somebody opened and wrote to it.
			 *
			 * But if the other side of the FIFO closed it, we will see an endless
			 * POLLIN and 0 available to read.
			 *
			 * The only way to handle it is to reopen the FIFO our side and wait for a
			 * new peer.  This is a quirk of FIFOs not of LWS.
			 */
			if (n == 0) { /* peer closed - do reopen in close processing */
				vhd->zero_length_read = 1;
				return 1;
			}
			buf[n] = '\0';
			lwsl_info("read %d\n", n);
			puts(buf);
		}
		break;

	case LWS_CALLBACK_RAW_CLOSE_FILE:
		lwsl_notice("LWS_CALLBACK_RAW_CLOSE_FILE\n");
		if (vhd->zero_length_read) {
			vhd->zero_length_read = 0;
			close(vhd->fifo);
			/* the wsi that adopted the fifo file is closing... reopen the fifo and readopt */
			vhd->fifo = lws_open(vhd->fifo_path, O_NONBLOCK | O_RDONLY);
			if (vhd->fifo == -1) {
				lwsl_err("opening fifo failed\n");
				return 1;
			}
			lwsl_notice("FIFO %s reopened\n", vhd->fifo_path);
			u.filefd = vhd->fifo;
			if (!lws_adopt_descriptor_vhost(vhd->vhost, 0, u, "protocol-lws-raw-test", NULL)) {
				lwsl_err("Failed to adopt fifo descriptor\n");
				close(vhd->fifo);
				return 1;
			}
		}
		break;

	case LWS_CALLBACK_RAW_WRITEABLE_FILE:
		lwsl_notice("LWS_CALLBACK_RAW_WRITEABLE_FILE\n");
		break;

	/*
	 * Callbacks related to Raw socket descriptor testing
	 */

	case LWS_CALLBACK_RAW_ADOPT:
		lwsl_notice("LWS_CALLBACK_RAW_ADOPT\n");
		break;

	case LWS_CALLBACK_RAW_RX:
		lwsl_notice("LWS_CALLBACK_RAW_RX %ld\n", (long)len);
		if (len > sizeof(pss->buf))
			len = sizeof(pss->buf);
		memcpy(pss->buf, in, len);
		pss->len = len;
		lws_callback_on_writable(wsi);
		break;

	case LWS_CALLBACK_RAW_CLOSE:
		lwsl_notice("LWS_CALLBACK_RAW_CLOSE\n");
		break;

	case LWS_CALLBACK_RAW_WRITEABLE:
		lwsl_notice("LWS_CALLBACK_RAW_WRITEABLE\n");
		lws_write(wsi, pss->buf, pss->len, LWS_WRITE_HTTP);
		break;

	default:
		break;
	}

	return 0;
}
Esempio n. 5
0
static int
lws_callback_raw_telnet(struct lws *wsi, enum lws_callback_reasons reason,
			void *user, void *in, size_t len)
{
	struct per_session_data__telnet *pss =
			(struct per_session_data__telnet *)user, **p;
	struct per_vhost_data__telnet *vhd =
			(struct per_vhost_data__telnet *)
			lws_protocol_vh_priv_get(lws_get_vhost(wsi),
					lws_get_protocol(wsi));
	const struct lws_protocol_vhost_options *pvo =
			(const struct lws_protocol_vhost_options *)in;
	int n, m;
	uint8_t buf[LWS_PRE + 800], *pu = in;

	switch ((int)reason) {
	case LWS_CALLBACK_PROTOCOL_INIT:
		vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
				lws_get_protocol(wsi),
				sizeof(struct per_vhost_data__telnet));
		vhd->context = lws_get_context(wsi);
		vhd->protocol = lws_get_protocol(wsi);
		vhd->vhost = lws_get_vhost(wsi);

		while (pvo) {
			if (!strcmp(pvo->name, "ops"))
				vhd->ops = (const struct lws_ssh_ops *)pvo->value;

			pvo = pvo->next;
		}

		if (!vhd->ops) {
			lwsl_err("telnet pvo \"ops\" is mandatory\n");
			return -1;
		}
		break;

        case LWS_CALLBACK_RAW_ADOPT:
		pss->next = vhd->live_pss_list;
		vhd->live_pss_list = pss;
		pss->vhd = vhd;
		pss->state = LTST_WAIT_IAC;
		pss->initial = 0;
		if (vhd->ops->channel_create)
			vhd->ops->channel_create(wsi, &pss->priv);
		lws_callback_on_writable(wsi);
                break;

	case LWS_CALLBACK_RAW_CLOSE:
		p = &vhd->live_pss_list;

		while (*p) {
			if ((*p) == pss) {
				if (vhd->ops->channel_destroy)
					vhd->ops->channel_destroy(pss->priv);
				*p = pss->next;
				continue;
			}
			p = &((*p)->next);
		}
		break;

	case LWS_CALLBACK_RAW_RX:
		n = 0;

		/* this stuff is coming in telnet line discipline, we
		 * have to strip IACs and process IAC repeats */

		while (len--) {
			if (telnet_ld(pss, *pu))
				buf[n++] = *pu++;
			else
				pu++;

			if (n > 100 || !len)
				pss->vhd->ops->rx(pss->priv, wsi, buf, n);
		}
		break;

        case LWS_CALLBACK_RAW_WRITEABLE:
		n = 0;
		if (!pss->initial) {
			memcpy(buf + LWS_PRE, init, sizeof(init));

			n = sizeof(init);
			pss->initial = 1;
		} else {
			/* bring any waiting tx into second half of buffer
			 * restrict how much we can send to 1/4 of the buffer,
			 * because we have to apply telnet line discipline...
			 * in the worst case of all 0xff, doubling the size
			 */
			pu = buf + LWS_PRE + 400;
			m = (int)pss->vhd->ops->tx(pss->priv, LWS_STDOUT, pu,
					((int)sizeof(buf) - LWS_PRE - n - 401) / 2);

			/*
			 * apply telnet line discipline and copy into place
			 * in output buffer
			 */
			while (m--) {
				if (*pu == 0xff)
					buf[LWS_PRE + n++] = 0xff;
				buf[LWS_PRE + n++] = *pu++;
			}
		}
		if (n > 0) {
			m = lws_write(wsi, (unsigned char *)buf + LWS_PRE, n,
				      LWS_WRITE_HTTP);
	                if (m < 0) {
	                        lwsl_err("ERROR %d writing to di socket\n", m);
	                        return -1;
	                }
		}

		if (vhd->ops->tx_waiting(&pss->priv))
		       lws_callback_on_writable(wsi);
		break;

        case LWS_CALLBACK_SSH_UART_SET_RXFLOW:
        	/*
        	 * this is sent to set rxflow state on any connections that
        	 * sink on a particular uart.  The uart index affected is in len
        	 *
        	 * More than one protocol may sink to the same uart, and the
        	 * protocol may select the uart itself, eg, in the URL used
        	 * to set up the connection.
        	 */
        	lws_rx_flow_control(wsi, len & 1);
        	break;

	default:
		break;
	}

	return 0;
}
Esempio n. 6
0
static int
callback_raw_test(struct lws *wsi, enum lws_callback_reasons reason,
			void *user, void *in, size_t len)
{
	struct raw_vhd *vhd = (struct raw_vhd *)lws_protocol_vh_priv_get(
				     lws_get_vhost(wsi), lws_get_protocol(wsi));
	lws_sock_file_fd_type u;
	uint8_t buf[1024];
	int n;

	switch (reason) {
	case LWS_CALLBACK_PROTOCOL_INIT:
		vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
				lws_get_protocol(wsi), sizeof(struct raw_vhd));
		vhd->filefd = lws_open(filepath, O_RDWR);
		if (vhd->filefd == -1) {
			lwsl_err("Unable to open %s\n", filepath);

			return 1;
		}
		u.filefd = (lws_filefd_type)(long long)vhd->filefd;
		if (!lws_adopt_descriptor_vhost(lws_get_vhost(wsi),
						LWS_ADOPT_RAW_FILE_DESC, u,
						"raw-test", NULL)) {
			lwsl_err("Failed to adopt fifo descriptor\n");
			close(vhd->filefd);
			vhd->filefd = -1;

			return 1;
		}
		break;

	case LWS_CALLBACK_PROTOCOL_DESTROY:
		if (vhd && vhd->filefd != -1)
			close(vhd->filefd);
		break;

	/* callbacks related to raw file descriptor */

	case LWS_CALLBACK_RAW_ADOPT_FILE:
		lwsl_notice("LWS_CALLBACK_RAW_ADOPT_FILE\n");
		break;

	case LWS_CALLBACK_RAW_RX_FILE:
		lwsl_notice("LWS_CALLBACK_RAW_RX_FILE\n");
		n = read(vhd->filefd, buf, sizeof(buf));
		if (n < 0) {
			lwsl_err("Reading from %s failed\n", filepath);

			return 1;
		}
		lwsl_hexdump_level(LLL_NOTICE, buf, n);
		break;

	case LWS_CALLBACK_RAW_CLOSE_FILE:
		lwsl_notice("LWS_CALLBACK_RAW_CLOSE_FILE\n");
		break;

	case LWS_CALLBACK_RAW_WRITEABLE_FILE:
		lwsl_notice("LWS_CALLBACK_RAW_WRITEABLE_FILE\n");
		/*
		 * you can call lws_callback_on_writable() on a raw file wsi as
		 * usual, and then write directly into the raw filefd here.
		 */
		break;

	default:
		break;
	}

	return 0;
}
static int
callback_messageboard(struct lws *wsi, enum lws_callback_reasons reason,
		      void *user, void *in, size_t len)
{
	struct per_session_data__gs_mb *pss = (struct per_session_data__gs_mb *)user;
	const struct lws_protocol_vhost_options *pvo;
	struct per_vhost_data__gs_mb *vhd = (struct per_vhost_data__gs_mb *)
		lws_protocol_vh_priv_get(lws_get_vhost(wsi), lws_get_protocol(wsi));
	unsigned char *p, *start, *end, buffer[LWS_PRE + 256];
	char s[512];
	int n;

	switch (reason) {
	case LWS_CALLBACK_PROTOCOL_INIT: /* per vhost */
		vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
			lws_get_protocol(wsi), sizeof(struct per_vhost_data__gs_mb));
		if (!vhd)
			return 1;
		vhd->vh = lws_get_vhost(wsi);
		vhd->gsp = lws_vhost_name_to_protocol(vhd->vh,
						"protocol-generic-sessions");
		if (!vhd->gsp) {
			lwsl_err("messageboard: requires generic-sessions\n");
			return 1;
		}

		pvo = (const struct lws_protocol_vhost_options *)in;
		while (pvo) {
			if (!strcmp(pvo->name, "message-db"))
				strncpy(vhd->message_db, pvo->value,
					sizeof(vhd->message_db) - 1);
			pvo = pvo->next;
		}
		if (!vhd->message_db[0]) {
			lwsl_err("messageboard: \"message-db\" pvo missing\n");
			return 1;
		}

		if (sqlite3_open_v2(vhd->message_db, &vhd->pdb,
				    SQLITE_OPEN_READWRITE |
				    SQLITE_OPEN_CREATE, NULL) != SQLITE_OK) {
			lwsl_err("Unable to open message db %s: %s\n",
				 vhd->message_db, sqlite3_errmsg(vhd->pdb));

			return 1;
		}
		if (sqlite3_exec(vhd->pdb, "create table if not exists msg ("
				 " idx integer primary key, time integer,"
				 " username varchar(32), email varchar(100),"
				 " ip varchar(80), content blob);",
				 NULL, NULL, NULL) != SQLITE_OK) {
			lwsl_err("Unable to create msg table: %s\n",
				 sqlite3_errmsg(vhd->pdb));

			return 1;
		}

		vhd->last_idx = get_last_idx(vhd);
		break;

	case LWS_CALLBACK_PROTOCOL_DESTROY:
		if (vhd->pdb)
			sqlite3_close(vhd->pdb);
		goto passthru;

	case LWS_CALLBACK_ESTABLISHED:
		vhd->gsp->callback(wsi, LWS_CALLBACK_SESSION_INFO,
				   pss->pss_gs, &pss->sinfo, 0);
		if (!pss->sinfo.username[0]) {
			lwsl_notice("messageboard ws attempt with no session\n");

			return -1;
		}

		lws_callback_on_writable(wsi);
		break;

	case LWS_CALLBACK_SERVER_WRITEABLE:
		{
			struct message m;
			char j[MAX_MSG_LEN + 512], e[MAX_MSG_LEN + 512],
				*p = j + LWS_PRE, *start = p,
				*end = j + sizeof(j) - LWS_PRE;

			if (pss->last_idx == vhd->last_idx)
				break;

			/* restrict to last 10 */
			if (!pss->last_idx)
				if (vhd->last_idx >= 10)
					pss->last_idx = vhd->last_idx - 10;

			sprintf(s, "select idx, time, username, email, ip, content "
				   "from msg where idx > %lu order by idx limit 1;",
				   pss->last_idx);
			if (sqlite3_exec(vhd->pdb, s, lookup_cb, &m, NULL) != SQLITE_OK) {
				lwsl_err("Unable to lookup msg: %s\n",
					 sqlite3_errmsg(vhd->pdb));
				return 0;
			}

			/* format in JSON */
			p += snprintf(p, end - p,
					"{\"idx\":\"%lu\",\"time\":\"%lu\",",
					m.idx, m.time);
			p += snprintf(p, end - p, " \"username\":\"%s\",",
				lws_json_purify(e, m.username, sizeof(e)));
			p += snprintf(p, end - p, " \"email\":\"%s\",",
				lws_json_purify(e, m.email, sizeof(e)));
			p += snprintf(p, end - p, " \"ip\":\"%s\",",
				lws_json_purify(e, m.ip, sizeof(e)));
			p += snprintf(p, end - p, " \"content\":\"%s\"}",
				lws_json_purify(e, m.content, sizeof(e)));

			if (lws_write(wsi, (unsigned char *)start, p - start,
				      LWS_WRITE_TEXT) < 0)
				return -1;

			pss->last_idx = m.idx;
			if (pss->last_idx == vhd->last_idx)
				break;

			lws_callback_on_writable(wsi); /* more to do */
		}
		break;

	case LWS_CALLBACK_HTTP:
		pss->our_form = 0;

		/* ie, it's our messageboard new message form */
		if (!strcmp((const char *)in, "/msg")) {
			pss->our_form = 1;
			break;
		}

		goto passthru;

	case LWS_CALLBACK_HTTP_BODY:
		if (!pss->our_form)
			goto passthru;

		if (len < 2)
			break;
		if (!pss->spa) {
			pss->spa = lws_spa_create(wsi, param_names,
						ARRAY_SIZE(param_names),
						MAX_MSG_LEN + 1024, NULL, NULL);
			if (!pss->spa)
				return -1;
		}

		if (lws_spa_process(pss->spa, in, len)) {
			lwsl_notice("spa process blew\n");
			return -1;
		}
		break;

	case LWS_CALLBACK_HTTP_BODY_COMPLETION:
		if (!pss->our_form)
			goto passthru;

		if (post_message(wsi, vhd, pss))
			return -1;

		p = buffer + LWS_PRE;
		start = p;
		end = p + sizeof(buffer) - LWS_PRE;

		if (lws_add_http_header_status(wsi, 200, &p, end))
			return -1;
		if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE,
				(unsigned char *)"text/plain", 10, &p, end))
			return -1;
		if (lws_add_http_header_content_length(wsi, 1, &p, end))
			return -1;
		if (lws_finalize_http_header(wsi, &p, end))
			return -1;
		n = lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS);
		if (n != (p - start)) {
			lwsl_err("_write returned %d from %d\n", n, (p - start));
			return -1;
		}
		s[0] = '0';
		n = lws_write(wsi, (unsigned char *)s, 1, LWS_WRITE_HTTP);
		if (n != 1)
			return -1;

		goto try_to_reuse;

	case LWS_CALLBACK_HTTP_BIND_PROTOCOL:
		if (!pss || pss->pss_gs)
			break;

		pss->pss_gs = malloc(vhd->gsp->per_session_data_size);
		if (!pss->pss_gs)
			return -1;

		memset(pss->pss_gs, 0, vhd->gsp->per_session_data_size);
		break;

	case LWS_CALLBACK_HTTP_DROP_PROTOCOL:
		if (vhd->gsp->callback(wsi, reason, pss ? pss->pss_gs : NULL, in, len))
			return -1;

		if (pss && pss->spa) {
			lws_spa_destroy(pss->spa);
			pss->spa = NULL;
		}
		if (pss && pss->pss_gs) {
			free(pss->pss_gs);
			pss->pss_gs = NULL;
		}
		break;

	default:
passthru:
		return vhd->gsp->callback(wsi, reason, pss ? pss->pss_gs : NULL, in, len);
	}

	return 0;


try_to_reuse:
	if (lws_http_transaction_completed(wsi))
		return -1;

	return 0;
}
static int
callback_esplws_ota(struct lws *wsi, enum lws_callback_reasons reason,
		    void *user, void *in, size_t len)
{
	struct per_session_data__esplws_ota *pss =
			(struct per_session_data__esplws_ota *)user;
	struct per_vhost_data__esplws_ota *vhd =
			(struct per_vhost_data__esplws_ota *)
			lws_protocol_vh_priv_get(lws_get_vhost(wsi),
					lws_get_protocol(wsi));
	unsigned char buf[LWS_PRE + 384], *start = buf + LWS_PRE - 1, *p = start,
	     *end = buf + sizeof(buf) - 1;
	int n;

	switch (reason) {

	case LWS_CALLBACK_PROTOCOL_INIT:
		vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
				lws_get_protocol(wsi),
				sizeof(struct per_vhost_data__esplws_ota));
		vhd->context = lws_get_context(wsi);
		vhd->protocol = lws_get_protocol(wsi);
		vhd->vhost = lws_get_vhost(wsi);
		break;

	case LWS_CALLBACK_PROTOCOL_DESTROY:
		if (!vhd)
			break;
		break;

	/* OTA POST handling */

	case LWS_CALLBACK_HTTP_BODY:
		/* create the POST argument parser if not already existing */
		// lwsl_notice("LWS_CALLBACK_HTTP_BODY (ota) %d %d %p\n", (int)pss->file_length, (int)len, pss->spa);
		lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_CONTENT, 30);
		if (!pss->spa) {
			pss->spa = lws_spa_create(wsi, ota_param_names,
					ARRAY_SIZE(ota_param_names), 4096,
					ota_file_upload_cb, pss);
			if (!pss->spa)
				return -1;

			pss->filename[0] = '\0';
			pss->file_length = 0;
		}

		/* let it parse the POST data */
		if (lws_spa_process(pss->spa, in, len))
			return -1;
		break;

	case LWS_CALLBACK_HTTP_BODY_COMPLETION:
		lwsl_notice("LWS_CALLBACK_HTTP_BODY_COMPLETION (ota)\n");
		/* call to inform no more payload data coming */
		lws_spa_finalize(pss->spa);

		pss->result_len = snprintf(pss->result + LWS_PRE, sizeof(pss->result) - LWS_PRE - 1,
			"Rebooting after OTA update");

		if (lws_add_http_header_status(wsi, HTTP_STATUS_OK, &p, end))
			goto bail;

		if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE,
				(unsigned char *)"text/html", 9, &p, end))
			goto bail;
		if (lws_add_http_header_content_length(wsi, pss->result_len, &p, end))
			goto bail;
		if (lws_finalize_http_header(wsi, &p, end))
			goto bail;

		n = lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS | LWS_WRITE_H2_STREAM_END);
		if (n < 0)
			goto bail;

		lws_callback_on_writable(wsi);
		break;

	case LWS_CALLBACK_HTTP_WRITEABLE:
		if (!pss->result_len)
			break;
		lwsl_debug("LWS_CALLBACK_HTTP_WRITEABLE: sending %d\n",
			   pss->result_len);
		n = lws_write(wsi, (unsigned char *)pss->result + LWS_PRE,
			      pss->result_len, LWS_WRITE_HTTP);
		if (n < 0)
			return 1;

		if (lws_http_transaction_completed(wsi))
			return 1;

		/* stop further service so we don't serve the probe GET to see if we rebooted */
		while (1);

		break;

	case LWS_CALLBACK_HTTP_DROP_PROTOCOL:
		/* called when our wsi user_space is going to be destroyed */
		if (pss->spa) {
			lws_spa_destroy(pss->spa);
			pss->spa = NULL;
		}
		break;

	default:
		break;
	}

	return 0;

bail:
	return 1;
}
Esempio n. 9
0
static int
callback_minimal_broker(struct lws *wsi, enum lws_callback_reasons reason,
			void *user, void *in, size_t len)
{
	struct per_vhost_data__minimal *vhd =
			(struct per_vhost_data__minimal *)
			lws_protocol_vh_priv_get(lws_get_vhost(wsi),
					lws_get_protocol(wsi));
	const struct msg *pmsg;
	void *retval;
	int n, m, r = 0;

	switch (reason) {

	/* --- protocol lifecycle callbacks --- */

	case LWS_CALLBACK_PROTOCOL_INIT:
		vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
				lws_get_protocol(wsi),
				sizeof(struct per_vhost_data__minimal));
		vhd->context = lws_get_context(wsi);
		vhd->protocol = lws_get_protocol(wsi);
		vhd->vhost = lws_get_vhost(wsi);

		vhd->ring = lws_ring_create(sizeof(struct msg), 8,
					    __minimal_destroy_message);
		if (!vhd->ring)
			return 1;

		pthread_mutex_init(&vhd->lock_ring, NULL);

		/* start the content-creating threads */

		for (n = 0; n < (int)LWS_ARRAY_SIZE(vhd->pthread_spam); n++)
			if (pthread_create(&vhd->pthread_spam[n], NULL,
					   thread_spam, vhd)) {
				lwsl_err("thread creation failed\n");
				r = 1;
				goto init_fail;
			}

		if (connect_client(vhd))
			lws_timed_callback_vh_protocol(vhd->vhost,
					vhd->protocol, LWS_CALLBACK_USER, 1);
		break;

	case LWS_CALLBACK_PROTOCOL_DESTROY:
init_fail:
		vhd->finished = 1;
		for (n = 0; n < (int)LWS_ARRAY_SIZE(vhd->pthread_spam); n++)
			if (vhd->pthread_spam[n])
				pthread_join(vhd->pthread_spam[n], &retval);

		if (vhd->ring)
			lws_ring_destroy(vhd->ring);

		pthread_mutex_destroy(&vhd->lock_ring);

		return r;

	case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
		lwsl_err("CLIENT_CONNECTION_ERROR: %s\n",
			 in ? (char *)in : "(null)");
		vhd->client_wsi = NULL;
		lws_timed_callback_vh_protocol(vhd->vhost,
				vhd->protocol, LWS_CALLBACK_USER, 1);
		break;

	/* --- client callbacks --- */

	case LWS_CALLBACK_CLIENT_ESTABLISHED:
		lwsl_user("%s: established\n", __func__);
		vhd->established = 1;
		break;

	case LWS_CALLBACK_CLIENT_WRITEABLE:
		pthread_mutex_lock(&vhd->lock_ring); /* --------- ring lock { */
		pmsg = lws_ring_get_element(vhd->ring, &vhd->tail);
		if (!pmsg)
			goto skip;

		/* notice we allowed for LWS_PRE in the payload already */
		m = lws_write(wsi, ((unsigned char *)pmsg->payload) + LWS_PRE,
			      pmsg->len, LWS_WRITE_TEXT);
		if (m < (int)pmsg->len) {
			pthread_mutex_unlock(&vhd->lock_ring); /* } ring lock */
			lwsl_err("ERROR %d writing to ws socket\n", m);
			return -1;
		}

		lws_ring_consume_single_tail(vhd->ring, &vhd->tail, 1);

		/* more to do for us? */
		if (lws_ring_get_element(vhd->ring, &vhd->tail))
			/* come back as soon as we can write more */
			lws_callback_on_writable(wsi);

skip:
		pthread_mutex_unlock(&vhd->lock_ring); /* } ring lock ------- */
		break;

	case LWS_CALLBACK_CLIENT_CLOSED:
		vhd->client_wsi = NULL;
		vhd->established = 0;
		lws_timed_callback_vh_protocol(vhd->vhost, vhd->protocol,
					       LWS_CALLBACK_USER, 1);
		break;

	case LWS_CALLBACK_EVENT_WAIT_CANCELLED:
		/*
		 * When the "spam" threads add a message to the ringbuffer,
		 * they create this event in the lws service thread context
		 * using lws_cancel_service().
		 *
		 * We respond by scheduling a writable callback for the
		 * connected client, if any.
		 */
		if (vhd && vhd->client_wsi && vhd->established)
			lws_callback_on_writable(vhd->client_wsi);
		break;

	/* rate-limited client connect retries */

	case LWS_CALLBACK_USER:
		lwsl_notice("%s: LWS_CALLBACK_USER\n", __func__);
		if (connect_client(vhd))
			lws_timed_callback_vh_protocol(vhd->vhost,
						vhd->protocol,
						LWS_CALLBACK_USER, 1);
		break;

	default:
		break;
	}

	return lws_callback_http_dummy(wsi, reason, user, in, len);
}
Esempio n. 10
0
static int
callback_minimal(struct lws *wsi, enum lws_callback_reasons reason,
			void *user, void *in, size_t len)
{
	struct per_session_data__minimal *pss =
			(struct per_session_data__minimal *)user;
	struct per_vhost_data__minimal *vhd =
			(struct per_vhost_data__minimal *)
			lws_protocol_vh_priv_get(lws_get_vhost(wsi),
					lws_get_protocol(wsi));
	const struct msg *pmsg;
	struct msg amsg;
	char buf[32];
	int n, m;

	switch (reason) {
	case LWS_CALLBACK_PROTOCOL_INIT:
		vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
				lws_get_protocol(wsi),
				sizeof(struct per_vhost_data__minimal));
		vhd->context = lws_get_context(wsi);
		vhd->protocol = lws_get_protocol(wsi);
		vhd->vhost = lws_get_vhost(wsi);

		vhd->ring = lws_ring_create(sizeof(struct msg), 8,
					    __minimal_destroy_message);
		if (!vhd->ring)
			return 1;
		break;

	case LWS_CALLBACK_PROTOCOL_DESTROY:
		lws_ring_destroy(vhd->ring);
		break;

	case LWS_CALLBACK_ESTABLISHED:
		pss->tail = lws_ring_get_oldest_tail(vhd->ring);
		pss->wsi = wsi;
		if (lws_hdr_copy(wsi, buf, sizeof(buf), WSI_TOKEN_GET_URI) > 0)
			pss->publishing = !strcmp(buf, "/publisher");
		if (!pss->publishing)
			/* add subscribers to the list of live pss held in the vhd */
			lws_ll_fwd_insert(pss, pss_list, vhd->pss_list);
		break;

	case LWS_CALLBACK_CLOSED:
		/* remove our closing pss from the list of live pss */
		lws_ll_fwd_remove(struct per_session_data__minimal, pss_list,
				  pss, vhd->pss_list);
		break;

	case LWS_CALLBACK_SERVER_WRITEABLE:

		if (pss->publishing)
			break;

		pmsg = lws_ring_get_element(vhd->ring, &pss->tail);
		if (!pmsg)
			break;

		/* notice we allowed for LWS_PRE in the payload already */
		m = lws_write(wsi, ((unsigned char *)pmsg->payload) + LWS_PRE,
			      pmsg->len, LWS_WRITE_TEXT);
		if (m < (int)pmsg->len) {
			lwsl_err("ERROR %d writing to ws socket\n", m);
			return -1;
		}

		lws_ring_consume_and_update_oldest_tail(
			vhd->ring,	/* lws_ring object */
			struct per_session_data__minimal, /* type of objects with tails */
			&pss->tail,	/* tail of guy doing the consuming */
			1,		/* number of payload objects being consumed */
			vhd->pss_list,	/* head of list of objects with tails */
			tail,		/* member name of tail in objects with tails */
			pss_list	/* member name of next object in objects with tails */
		);

		/* more to do? */
		if (lws_ring_get_element(vhd->ring, &pss->tail))
			/* come back as soon as we can write more */
			lws_callback_on_writable(pss->wsi);
		break;

	case LWS_CALLBACK_RECEIVE:

		if (!pss->publishing)
			break;

		/*
		 * For test, our policy is ignore publishing when there are
		 * no subscribers connected.
		 */
		if (!vhd->pss_list)
			break;

		n = (int)lws_ring_get_count_free_elements(vhd->ring);
		if (!n) {
			lwsl_user("dropping!\n");
			break;
		}

		amsg.len = len;
		/* notice we over-allocate by LWS_PRE */
		amsg.payload = malloc(LWS_PRE + len);
		if (!amsg.payload) {
			lwsl_user("OOM: dropping\n");
			break;
		}

		memcpy((char *)amsg.payload + LWS_PRE, in, len);
		if (!lws_ring_insert(vhd->ring, &amsg, 1)) {
			__minimal_destroy_message(&amsg);
			lwsl_user("dropping 2!\n");
			break;
		}

		/*
		 * let every subscriber know we want to write something
		 * on them as soon as they are ready
		 */
		lws_start_foreach_llp(struct per_session_data__minimal **,
				      ppss, vhd->pss_list) {
			if (!(*ppss)->publishing)
				lws_callback_on_writable((*ppss)->wsi);
		} lws_end_foreach_llp(ppss, pss_list);
		break;

	default:
		break;
	}

	return 0;
}