feat(v4.0.0): storage speaks messages -- a device over v3's block chains, common and private

What is on a storage port (v4/system/storage.c): it takes Block read,
Block write and Block data and answers Block data or Block done.  It
holds a view of a common chain, a private chain, or both; private block k
is number 2^32 - 1 - k.  Behind it is v3's block subsystem, reached
through v4/system/store_v3.c, the one v4 file that includes v3's headers.

v3's block code links here with its two device back ends, its log and
its clock, and nothing else of v3.

v4/tests/test_store.c: 36 checks at 64 bits, 35 at 32, and under the
sanitizers.  docs/v4.0.0/MESH.md 8.3, 8.4.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rajames
2026-10-06 20:41:07 -04:00
co-authored by Claude Opus 5.5
parent 0e761cb117
commit 4a505a154d
6 changed files with 525 additions and 6 deletions
+39 -6
View File
@@ -49,6 +49,39 @@ HOST_DATA_RING := 30
HOST_RET_RING := 31
node_size = $(if $(findstring /test_host_,$(1)),-DV4_NODE_WORDS=$(HOST_WORDS) -DV4_DATA_RING=$(HOST_DATA_RING) -DV4_RET_RING=$(HOST_RET_RING))
# STORAGE (docs/v4.0.0/MESH.md section 8). What is on a storage port is
# system/storage.c, held to this model's warnings; behind it is the kernel's
# block subsystem, which is v3's, built as v3 builds it and reached through
# system/store_v3.c. Tests named test_store*.c and test_host_*.c link them.
# V3_BLOCK_SRCS is all of v3 that the block subsystem needs: its two device
# back ends, the log and the clock.
ROOT_DIR := $(abspath $(HERE)/..)
V3_BLOCK_SRCS := $(addprefix $(ROOT_DIR)/v3/src/,block_subsystem.c blkio_ram.c blkio_file.c log.c platform/platform_init.c platform/linux/time.c)
V3_CFLAGS := -std=gnu99 -Wall $(OPT) -I$(ROOT_DIR)/v3/include -I$(ROOT_DIR)/kernel/include
STORE_SRC := $(HERE)/system/storage.c
STORE_V3_SRC := $(HERE)/system/store_v3.c
V3_INC := -isystem $(ROOT_DIR)/v3/include -isystem $(ROOT_DIR)/kernel/include
uses_store = $(or $(findstring /test_store,$(1)),$(findstring /test_host_,$(1)))
store_src = $(if $(call uses_store,$(1)),$(STORE_SRC))
store_inc = $(if $(call uses_store,$(1)),$(V3_INC))
$(BINDIR)/v3blocks.o: $(V3_BLOCK_SRCS) $(wildcard $(ROOT_DIR)/v3/include/*.h) $(HERE)/Makefile
@mkdir -p $(BINDIR)/v3o
cd $(BINDIR)/v3o && $(CC) $(V3_CFLAGS) -c $(V3_BLOCK_SRCS)
$(LD) -r $(BINDIR)/v3o/*.o -o $@
$(BINDIR)/san-v3blocks.o: $(V3_BLOCK_SRCS) $(wildcard $(ROOT_DIR)/v3/include/*.h) $(HERE)/Makefile
@mkdir -p $(BINDIR)/san-v3o
cd $(BINDIR)/san-v3o && $(CC) $(V3_CFLAGS) -O1 -g -fno-omit-frame-pointer -fsanitize=address,undefined -fno-sanitize-recover=all -c $(V3_BLOCK_SRCS)
$(LD) -r $(BINDIR)/san-v3o/*.o -o $@
define STORE_V3_RULE
$(BINDIR)/store_v3-$(1).o: $(STORE_V3_SRC) $$(wildcard $(HERE)/include/v4/*.h) $(HERE)/Makefile
@mkdir -p $(BINDIR)
$$(CC) $(V3_CFLAGS) -I$(HERE)/include -DV4_CELL_BITS=$(1) -c $(STORE_V3_SRC) -o $$@
endef
$(foreach w,32 64,$(eval $(call STORE_V3_RULE,$(w))))
store_objs = $(if $(call uses_store,$(2)),$(BINDIR)/store_v3-$(1).o $(BINDIR)/v3blocks.o)
san_store_objs = $(if $(call uses_store,$(2)),$(BINDIR)/store_v3-$(1).o $(BINDIR)/san-v3blocks.o)
# The capsule sources: definitions as text (include/v4/text.h), which tests
# assemble onto a node. The directory is passed to the tests so that they
# find the files whatever directory make was run from.
@@ -191,16 +224,16 @@ endif
#
# Both binaries depend on this Makefile, so a flag change rebuilds them.
define TEST_RULE
$(BINDIR)/$(1)-$(notdir $(2)): $(2) $$(SRCS) $$(wildcard $(HERE)/include/v4/*.h) $$(wildcard $(HERE)/tests/*.h) $(HERE)/Makefile
$(BINDIR)/$(1)-$(notdir $(2)): $(2) $$(SRCS) $(call store_src,$(2)) $(call store_objs,$(1),$(2)) $$(wildcard $(HERE)/include/v4/*.h) $$(wildcard $(HERE)/tests/*.h) $(HERE)/Makefile
@mkdir -p $(BINDIR)
$$(CC) $$(CFLAGS) -I$(HERE)/include -DV4_CELL_BITS=$(1) $(call node_size,$(2)) $(capsule_dir) \
$(2) $$(SRCS) -o $$@
$$(CC) $$(CFLAGS) -I$(HERE)/include $(call store_inc,$(2)) -DV4_CELL_BITS=$(1) $(call node_size,$(2)) $(capsule_dir) \
$(2) $$(SRCS) $(call store_src,$(2)) $(call store_objs,$(1),$(2)) -o $$@
$(BINDIR)/san-$(1)-$(notdir $(2)): $(2) $$(SRCS) $$(wildcard $(HERE)/include/v4/*.h) $$(wildcard $(HERE)/tests/*.h) $(HERE)/Makefile
$(BINDIR)/san-$(1)-$(notdir $(2)): $(2) $$(SRCS) $(call store_src,$(2)) $(call san_store_objs,$(1),$(2)) $$(wildcard $(HERE)/include/v4/*.h) $$(wildcard $(HERE)/tests/*.h) $(HERE)/Makefile
@mkdir -p $(BINDIR)
$$(CC) $$(CSTD) $$(WARN) -O1 -g -fno-omit-frame-pointer \
-fsanitize=address,undefined -fno-sanitize-recover=all -I$(HERE)/include -DV4_CELL_BITS=$(1) $(call node_size,$(2)) $(capsule_dir) \
$(2) $$(SRCS) -o $$@
-fsanitize=address,undefined -fno-sanitize-recover=all -I$(HERE)/include $(call store_inc,$(2)) -DV4_CELL_BITS=$(1) $(call node_size,$(2)) $(capsule_dir) \
$(2) $$(SRCS) $(call store_src,$(2)) $(call san_store_objs,$(1),$(2)) -o $$@
endef
# One run target per (width, test) and one prerequisite line per width, so
+12
View File
@@ -34,6 +34,18 @@
#define V4_MSG_OUTPUT 2 /* what a node printed */
#define V4_MSG_DONE 3 /* how the text ended: one word, V4_TEXT_* */
/* Storage (docs/v4.0.0/MESH.md 8.3). A block's number is one word of
* payload, written as it is; a block is 1024 characters. */
#define V4_MSG_BLOCK_READ 4 /* the block's number; answered by BLOCK_DATA, or BLOCK_DONE with an error */
#define V4_MSG_BLOCK_WRITE 5 /* the block's number; a BLOCK_DATA follows; answered by BLOCK_DONE */
#define V4_MSG_BLOCK_DATA 6 /* 1024 characters */
#define V4_MSG_BLOCK_DONE 7 /* one word, V4_STORE_* */
#define V4_STORE_OK 0
#define V4_STORE_NONE 1 /* there is no storage for that number */
#define V4_STORE_REFUSED 2
#define V4_STORE_RANGE 3 /* there is no such block */
/* how text ended */
#define V4_TEXT_QUIT 0 /* QUIT: nothing is to be said */
#define V4_TEXT_COMPLETED 1 /* a console says " ok" */
+83
View File
@@ -0,0 +1,83 @@
/* storage.h -- what is on a storage port. docs/v4.0.0/MESH.md section 8.
*
* A device (fabric.h) that speaks the block messages of message.h and
* answers them from one or two chains of blocks: a view. The common chain
* is the store its nodes share; the private chain is one node's own. What
* a chain is, is its owner's affair: the device reaches it only through
* v4_store_ops, and the kernel's block subsystem stands behind those
* (system/store_v3.c).
*
* WHICH CHAIN A NUMBER MEANS. Block numbers are 1 to V4_STORE_TOP.
* - Private block k is number V4_STORE_TOP - k, and is the private
* chain's block V4_STORE_FIRST + k, its first on a device. A number
* is the private chain's when k is less than the blocks that chain has
* on devices.
* - Any other number is the common chain's, as it is.
* - With no common chain, a number below V4_STORE_FIRST is the private
* chain's, as it is: its fast RAM.
* - A number that belongs to no chain the view has: V4_STORE_NONE.
* - 0, a number above V4_STORE_TOP, or one past the end of its chain:
* V4_STORE_RANGE.
*
* It has a number, as a node has: messages for it are addressed to that
* number and its answers come from it. A message for any other number is
* let go.
*
* A write is two messages, BLOCK_WRITE with the number and BLOCK_DATA with
* the block; they are paired by who sent them. BLOCK_DATA from a sender
* with no BLOCK_WRITE before it is answered V4_STORE_REFUSED.
*
* The device always takes what it is sent, so that a node writing to it is
* never left waiting on a device that is waiting to be read: answers queue,
* V4_STORE_PENDING of them. Only when that many wait is the first word of
* another message not taken yet.
*
* Nothing here uses the C library.
*/
#ifndef V4_STORAGE_H
#define V4_STORAGE_H
#include "v4/message.h"
#define V4_STORE_TOP 0xffffffffUL
#define V4_STORE_FIRST 2048UL
#define V4_STORE_PENDING 64u
#define V4_STORE_BYTES 1024u
/* A chain of blocks. read and write return V4_STORE_*; `blocks` is how
* many block numbers the chain answers to, counting from 0, its fast RAM
* included. */
typedef struct {
int (*read)(void *chain, uint32_t block, unsigned char *bytes);
int (*write)(void *chain, uint32_t block, const unsigned char *bytes);
uint32_t (*blocks)(void *chain);
} v4_store_ops;
typedef struct {
const v4_store_ops *ops;
void *common, *private_chain; /* 0: the view has none */
v4_cell number;
v4_message in; /* the message arriving */
struct { int used; v4_cell from; uint64_t block; } writing[V4_STORE_PENDING]; /* BLOCK_WRITEs whose data has not come */
struct { v4_cell to; int read; uint64_t block; int status; } answer[V4_STORE_PENDING];
unsigned head, count; /* the answers waiting: the oldest, and how many */
v4_message out; /* the answer being given */
unsigned out_at;
int out_ready;
unsigned char bytes[V4_STORE_BYTES];
unsigned long taken; /* whole messages taken, for whoever counts */
} v4_store;
void v4_store_init(v4_store *s, const v4_store_ops *ops, void *common, void *private_chain, v4_cell number);
/* The two functions of a v4_device; `self` is the v4_store. */
int v4_store_take(void *self, v4_cell value);
int v4_store_give(void *self, v4_cell *value);
/* The kernel's block subsystem as chains: a chain is a blk_chain_t *
* (v3/include/block_subsystem.h). system/store_v3.c. */
extern const v4_store_ops v4_store_v3;
#endif /* V4_STORAGE_H */
+161
View File
@@ -0,0 +1,161 @@
/* storage.c -- what is on a storage port. See storage.h.
* docs/v4.0.0/MESH.md section 8.
*
* Nothing here uses the C library: the bare-metal kernel links this file.
*/
#include "v4/storage.h"
void v4_store_init(v4_store *s, const v4_store_ops *ops, void *common, void *private_chain, v4_cell number)
{
unsigned i;
s->ops = ops;
s->common = common;
s->private_chain = private_chain;
s->number = number;
s->in.count = 0;
for (i = 0; i < V4_STORE_PENDING; i++) s->writing[i].used = 0;
s->head = 0;
s->count = 0;
s->out.count = 0;
s->out_at = 0;
s->out_ready = 0;
s->taken = 0;
}
/* The chain a number means and the block in it; or 0, with why in *status. */
static void *chain_for(const v4_store *s, uint64_t n, uint32_t *block, int *status)
{
*status = V4_STORE_RANGE;
if (n == 0 || n > V4_STORE_TOP) return 0;
if (s->private_chain) {
uint64_t k = V4_STORE_TOP - n, have = s->ops->blocks(s->private_chain);
if (have > V4_STORE_FIRST && k < have - V4_STORE_FIRST) { *block = (uint32_t)(V4_STORE_FIRST + k); return s->private_chain; }
}
*block = (uint32_t)n;
if (s->common) return s->common;
if (s->private_chain && n < V4_STORE_FIRST) return s->private_chain;
*status = V4_STORE_NONE;
return 0;
}
static void queue(v4_store *s, v4_cell to, int read, uint64_t block, int status)
{
unsigned at = (s->head + s->count) % V4_STORE_PENDING;
if (s->count == V4_STORE_PENDING) return; /* take does not let it come to this */
s->answer[at].to = to;
s->answer[at].read = read;
s->answer[at].block = block;
s->answer[at].status = status;
s->count++;
}
/* A block of data has come from `from`: it is written where that sender's
* BLOCK_WRITE said, if there was one. */
static int write_block(v4_store *s, v4_cell from)
{
unsigned i, k;
uint32_t block;
int status;
void *chain;
for (i = 0; i < V4_STORE_PENDING && !(s->writing[i].used && s->writing[i].from == from); i++) { }
if (i == V4_STORE_PENDING) return V4_STORE_REFUSED;
s->writing[i].used = 0;
if (v4_message_length(&s->in) != V4_STORE_BYTES) return V4_STORE_REFUSED;
chain = chain_for(s, s->writing[i].block, &block, &status);
if (!chain) return status;
for (k = 0; k < V4_STORE_BYTES; k++) s->bytes[k] = (unsigned char)v4_message_char(&s->in, k);
return s->ops->write(chain, block, s->bytes);
}
/* The message in s->in is whole. */
static void served(v4_store *s)
{
v4_cell from = v4_message_from(&s->in), type = v4_message_type(&s->in);
uint64_t block = (uint64_t)(v4_ucell)s->in.word[V4_MSG_HEADER];
unsigned i, free_at = V4_STORE_PENDING;
s->taken++;
if (v4_message_to(&s->in) != s->number) return;
if (type == V4_MSG_BLOCK_DATA) { queue(s, from, 0, 0, write_block(s, from)); return; }
if (type != V4_MSG_BLOCK_READ && type != V4_MSG_BLOCK_WRITE) return;
if (v4_message_length(&s->in) != 4) { queue(s, from, 0, 0, V4_STORE_REFUSED); return; }
if (type == V4_MSG_BLOCK_READ) { queue(s, from, 1, block, 0); return; }
/* a write: its number is kept until its block comes. A sender's second
* write before then takes the place of its first. */
for (i = 0; i < V4_STORE_PENDING; i++) {
if (s->writing[i].used && s->writing[i].from == from) { free_at = i; break; }
if (!s->writing[i].used && free_at == V4_STORE_PENDING) free_at = i;
}
if (free_at == V4_STORE_PENDING) { queue(s, from, 0, 0, V4_STORE_REFUSED); return; }
s->writing[free_at].used = 1;
s->writing[free_at].from = from;
s->writing[free_at].block = block;
}
int v4_store_take(void *self, v4_cell value)
{
v4_store *s = (v4_store *)self;
if (s->in.count == 0 && s->count == V4_STORE_PENDING) return 0;
if (v4_message_word(&s->in, value)) {
served(s);
s->in.count = 0;
}
return 1;
}
/* The oldest answer waiting becomes the message in s->out. */
static void answer(v4_store *s)
{
v4_cell to = s->answer[s->head].to;
int status = s->answer[s->head].status;
unsigned i;
if (s->answer[s->head].read) {
uint32_t block;
void *chain = chain_for(s, s->answer[s->head].block, &block, &status);
if (chain) status = s->ops->read(chain, block, s->bytes);
} else if (status == V4_STORE_OK) {
status = -1; /* a write that worked: said below */
}
s->head = (s->head + 1) % V4_STORE_PENDING;
s->count--;
s->out.word[0] = to;
s->out.word[1] = s->number;
s->out.word[3] = 0;
s->out.word[4] = 0;
s->out.word[5] = 0;
if (status == V4_STORE_OK) { /* a read that worked: the block */
s->out.word[2] = V4_MSG_BLOCK_DATA;
s->out.word[6] = (v4_cell)V4_STORE_BYTES;
for (i = 0; i < V4_STORE_BYTES / 4u; i++)
s->out.word[V4_MSG_HEADER + i] = (v4_cell)((v4_ucell)s->bytes[4u * i] | (v4_ucell)s->bytes[4u * i + 1u] << 8
| (v4_ucell)s->bytes[4u * i + 2u] << 16 | (v4_ucell)s->bytes[4u * i + 3u] << 24);
s->out.count = V4_MSG_HEADER + V4_STORE_BYTES / 4u;
} else {
s->out.word[2] = V4_MSG_BLOCK_DONE;
s->out.word[6] = 4;
s->out.word[V4_MSG_HEADER] = status < 0 ? V4_STORE_OK : status;
s->out.count = V4_MSG_HEADER + 1u;
}
s->out_at = 0;
s->out_ready = 1;
}
int v4_store_give(void *self, v4_cell *value)
{
v4_store *s = (v4_store *)self;
if (!s->out_ready) {
if (s->count == 0) return 0;
answer(s);
}
*value = s->out.word[s->out_at++];
if (s->out_at == s->out.count) s->out_ready = 0;
return 1;
}
+55
View File
@@ -0,0 +1,55 @@
/* store_v3.c -- the kernel's block subsystem as the chains behind a storage
* port. docs/v4.0.0/MESH.md 8.4, docs/v4.0.0/V3-PARITY.md 1d.
*
* A chain is a blk_chain_t (v3/include/block_subsystem.h). Every block goes
* through v3's own calls, so the header, the allocation map, the block
* cards and relocation are v3's, as they were. Who may have a block is not
* checked here yet (MESH.md 8.5).
*
* This is the one file of v4's that includes v3's headers.
*/
#include "v4/storage.h"
#include "block_subsystem.h"
static int v3_read(void *chain, uint32_t block, unsigned char *bytes)
{
blk_chain_t *was = blk_chain_select((blk_chain_t *)chain);
int status = V4_STORE_RANGE;
unsigned i;
if (blk_is_valid(block)) {
const uint8_t *b = blk_get_buffer(block, 0);
status = b ? V4_STORE_OK : V4_STORE_REFUSED;
if (b) for (i = 0; i < V4_STORE_BYTES; i++) bytes[i] = b[i];
}
blk_chain_select(was);
return status;
}
static int v3_write(void *chain, uint32_t block, const unsigned char *bytes)
{
blk_chain_t *was = blk_chain_select((blk_chain_t *)chain);
int status = V4_STORE_RANGE;
unsigned i;
if (blk_is_valid(block)) {
uint8_t *b = blk_get_buffer(block, 1);
status = V4_STORE_REFUSED;
if (b) {
for (i = 0; i < V4_STORE_BYTES; i++) b[i] = bytes[i];
if (blk_update(block) == BLK_OK && blk_flush(block) == BLK_OK) status = V4_STORE_OK;
}
}
blk_chain_select(was);
return status;
}
static uint32_t v3_blocks(void *chain)
{
blk_chain_t *was = blk_chain_select((blk_chain_t *)chain);
uint32_t n = blk_get_total_blocks();
blk_chain_select(was);
return n;
}
const v4_store_ops v4_store_v3 = { v3_read, v3_write, v3_blocks };
+175
View File
@@ -0,0 +1,175 @@
/* test_store.c -- what is on a storage port, spoken to in messages.
* docs/v4.0.0/MESH.md sections 8.3 and 8.4.
*
* No node: the test is the neighbour. The chains are v3's block subsystem
* (v3/src/block_subsystem.c), two of them, each with its fast RAM and a
* device of 64 blocks.
*/
#include "v4/storage.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "block_subsystem.h"
extern void sf_time_init(void);
extern void log_set_level(int level);
static int failures = 0, checks = 0;
#define CHECK(c,...) do{checks++; if(!(c)){failures++; printf("FAIL %s:%d: ",__FILE__,__LINE__); printf(__VA_ARGS__); printf("\n");}}while(0)
#define STORE 90
#define DEVICE_BLOCKS 64u
#define RAM_BYTES ((size_t)BLK_RAM_BLOCKS * BLK_FORTH_SIZE)
static blk_chain_t *make_chain(blk_chain_t *c)
{
blk_chain_t *was = blk_chain_select(c);
int ok = blk_subsys_init((uint8_t *)calloc(1, RAM_BYTES), RAM_BYTES) == BLK_OK
&& blk_subsys_add_raw_device((uint8_t *)calloc(DEVICE_BLOCKS, BLK_FORTH_SIZE), DEVICE_BLOCKS) == BLK_OK;
blk_chain_select(was);
return ok ? c : 0;
}
/* give the device a whole message; 1 if every word was taken */
static int send(v4_store *s, const v4_message *m)
{
unsigned i;
for (i = 0; i < m->count; i++) if (!v4_store_take(s, m->word[i])) return 0;
return 1;
}
static int ask(v4_store *s, v4_cell from, v4_cell type, v4_cell block)
{
v4_message m;
v4_message_text(&m, STORE, from, type, "\0\0\0\0", 4);
m.word[V4_MSG_HEADER] = block;
return send(s, &m);
}
static int data(v4_store *s, v4_cell from, char fill)
{
static char block[V4_STORE_BYTES];
v4_message m;
memset(block, fill, sizeof block);
v4_message_text(&m, STORE, from, V4_MSG_BLOCK_DATA, block, sizeof block);
return send(s, &m);
}
/* the device's next answer, whole; 0 if it has none */
static int answer(v4_store *s, v4_message *m)
{
v4_cell w;
m->count = 0;
if (!v4_store_give(s, &w)) return 0;
while (!v4_message_word(m, w)) if (!v4_store_give(s, &w)) return 0;
return 1;
}
static int is_done(const v4_message *m, v4_cell to, v4_cell status)
{
return v4_message_to(m) == to && v4_message_from(m) == STORE && v4_message_type(m) == V4_MSG_BLOCK_DONE
&& v4_message_length(m) == 4 && m->word[V4_MSG_HEADER] == status;
}
static int is_data(const v4_message *m, v4_cell to, char fill)
{
unsigned i;
if (v4_message_to(m) != to || v4_message_from(m) != STORE || v4_message_type(m) != V4_MSG_BLOCK_DATA
|| v4_message_length(m) != V4_STORE_BYTES) return 0;
for (i = 0; i < V4_STORE_BYTES; i++) if (v4_message_char(m, i) != fill) return 0;
return 1;
}
int main(void)
{
static v4_store both, common_only, private_only;
static v4_message m;
blk_chain_t *common, *own;
v4_cell top = (v4_cell)(v4_ucell)V4_STORE_TOP;
printf("v4 storage tests: V4_CELL_BITS=%d\n", V4_CELL_BITS);
sf_time_init();
log_set_level(-1);
common = make_chain(blk_chain_default());
own = make_chain(blk_chain_new());
CHECK(common && own && common != own, "two chains are made");
if (!common || !own) return 1;
v4_store_init(&both, &v4_store_v3, common, own, STORE);
v4_store_init(&common_only, &v4_store_v3, common, 0, STORE);
v4_store_init(&private_only, &v4_store_v3, 0, own, STORE);
/* ---- a block is written and read ---- */
CHECK(!answer(&both, &m), "with nothing asked there is nothing to read");
CHECK(ask(&both, 11, V4_MSG_BLOCK_WRITE, 2050) && !answer(&both, &m), "a write is not answered until its block has come");
CHECK(data(&both, 11, 'A') && answer(&both, &m) && is_done(&m, 11, V4_STORE_OK), "then it is answered done, to who sent it, from storage");
CHECK(ask(&both, 12, V4_MSG_BLOCK_READ, 2050) && answer(&both, &m) && is_data(&m, 12, 'A'), "another reads what was written");
CHECK(ask(&common_only, 12, V4_MSG_BLOCK_READ, 2050) && answer(&common_only, &m) && is_data(&m, 12, 'A'), "and so does one on another port of the same chain");
CHECK(ask(&both, 12, V4_MSG_BLOCK_WRITE, 20) && data(&both, 12, 'R') && answer(&both, &m) && is_done(&m, 12, V4_STORE_OK)
&& ask(&common_only, 13, V4_MSG_BLOCK_READ, 20) && answer(&common_only, &m) && is_data(&m, 13, 'R'), "a block below 2048 is the common chain's fast RAM");
/* ---- private blocks, from the top down ---- */
CHECK(ask(&both, 13, V4_MSG_BLOCK_WRITE, top) && data(&both, 13, 'P') && answer(&both, &m) && is_done(&m, 13, V4_STORE_OK), "the top number is written");
CHECK(ask(&both, 13, V4_MSG_BLOCK_READ, top) && answer(&both, &m) && is_data(&m, 13, 'P'), "and read back");
CHECK(ask(&private_only, 13, V4_MSG_BLOCK_READ, top) && answer(&private_only, &m) && is_data(&m, 13, 'P'), "it is the private chain's");
{
blk_chain_t *was = blk_chain_select(own);
uint8_t *b = blk_get_buffer((uint32_t)V4_STORE_FIRST, 0);
CHECK(b && b[0] == 'P' && b[1023] == 'P', "private block 0 is that chain's block 2048");
blk_chain_select(common);
b = blk_get_buffer((uint32_t)V4_STORE_FIRST, 0);
CHECK(b && b[0] != 'P', "and is not in the common chain");
blk_chain_select(was);
}
CHECK(ask(&both, 13, V4_MSG_BLOCK_WRITE, top - (v4_cell)(DEVICE_BLOCKS - 1)) && data(&both, 13, 'L') && answer(&both, &m) && is_done(&m, 13, V4_STORE_OK), "the last private block is written");
CHECK(ask(&both, 13, V4_MSG_BLOCK_READ, top - (v4_cell)DEVICE_BLOCKS) && answer(&both, &m) && is_done(&m, 13, V4_STORE_RANGE), "one below the private blocks is no block of the common chain");
CHECK(ask(&common_only, 12, V4_MSG_BLOCK_READ, top) && answer(&common_only, &m) && is_done(&m, 12, V4_STORE_RANGE), "with no private chain the top number is no such block");
CHECK(ask(&private_only, 13, V4_MSG_BLOCK_READ, 2050) && answer(&private_only, &m) && is_done(&m, 13, V4_STORE_NONE), "with no common chain a common number has no storage");
CHECK(ask(&private_only, 13, V4_MSG_BLOCK_WRITE, 20) && data(&private_only, 13, 'r') && answer(&private_only, &m) && is_done(&m, 13, V4_STORE_OK)
&& ask(&private_only, 13, V4_MSG_BLOCK_READ, 20) && answer(&private_only, &m) && is_data(&m, 13, 'r'), "and a number below 2048 is the private chain's fast RAM");
CHECK(ask(&common_only, 12, V4_MSG_BLOCK_READ, 20) && answer(&common_only, &m) && is_data(&m, 12, 'R'), "which is not the common chain's");
/* ---- numbers that are no block ---- */
CHECK(ask(&both, 11, V4_MSG_BLOCK_READ, 0) && answer(&both, &m) && is_done(&m, 11, V4_STORE_RANGE), "0 is no block");
CHECK(ask(&both, 11, V4_MSG_BLOCK_READ, (v4_cell)(V4_STORE_FIRST + DEVICE_BLOCKS)) && answer(&both, &m) && is_done(&m, 11, V4_STORE_RANGE), "one past the end of the common chain is no block");
CHECK(ask(&both, 11, V4_MSG_BLOCK_WRITE, (v4_cell)(V4_STORE_FIRST + DEVICE_BLOCKS)) && data(&both, 11, 'X') && answer(&both, &m) && is_done(&m, 11, V4_STORE_RANGE), "nor can it be written");
#if V4_CELL_BITS > 32
CHECK(ask(&both, 11, V4_MSG_BLOCK_READ, (v4_cell)0x100000000LL) && answer(&both, &m) && is_done(&m, 11, V4_STORE_RANGE), "a number 32 bits do not hold is no block");
CHECK(ask(&both, 11, V4_MSG_BLOCK_READ, -1) && answer(&both, &m) && is_done(&m, 11, V4_STORE_RANGE), "nor is a negative one, on cells wider than 32 bits");
#else
CHECK(top == -1, "on 32-bit cells the top number is -1");
#endif
/* ---- data nobody announced ---- */
CHECK(data(&both, 14, 'Z') && answer(&both, &m) && is_done(&m, 14, V4_STORE_REFUSED), "a block of data with no write before it is refused");
CHECK(ask(&both, 12, V4_MSG_BLOCK_READ, 2050) && answer(&both, &m) && is_data(&m, 12, 'A'), "and nothing was written");
CHECK(ask(&both, 11, V4_MSG_BLOCK_WRITE, 2051) && data(&both, 11, 'B') && answer(&both, &m) && is_done(&m, 11, V4_STORE_OK)
&& data(&both, 11, 'C') && answer(&both, &m) && is_done(&m, 11, V4_STORE_REFUSED)
&& ask(&both, 11, V4_MSG_BLOCK_READ, 2051) && answer(&both, &m) && is_data(&m, 11, 'B'), "a write is for one block of data and no more");
/* ---- two senders at once ---- */
CHECK(ask(&both, 11, V4_MSG_BLOCK_WRITE, 2060) && ask(&both, 12, V4_MSG_BLOCK_WRITE, 2061)
&& data(&both, 12, 'b') && data(&both, 11, 'a'), "two writes are begun before either's block comes, and the blocks come the other way round");
CHECK(answer(&both, &m) && is_done(&m, 12, V4_STORE_OK) && answer(&both, &m) && is_done(&m, 11, V4_STORE_OK), "each is answered, in the order the blocks came");
CHECK(ask(&both, 11, V4_MSG_BLOCK_READ, 2060) && ask(&both, 12, V4_MSG_BLOCK_READ, 2061), "a second request is taken while the answer to the first has not been read");
CHECK(answer(&both, &m) && is_data(&m, 11, 'a') && answer(&both, &m) && is_data(&m, 12, 'b'), "and each block is its own sender's");
/* ---- what is not for storage ---- */
{
v4_message other;
unsigned long before = both.taken;
v4_message_text(&other, 55, 11, V4_MSG_BLOCK_READ, "\0\0\0\0", 4);
other.word[V4_MSG_HEADER] = 2050;
CHECK(send(&both, &other) && both.taken == before + 1 && !answer(&both, &m), "a message for another number is taken and let go");
v4_message_text(&other, STORE, 11, V4_MSG_TEXT, "1 2 +", 5);
CHECK(send(&both, &other) && !answer(&both, &m), "and so is text");
}
/* ---- what v3 wrote reads through the port ---- */
{
blk_chain_t *was = blk_chain_select(common);
uint8_t *b = blk_get_buffer(2070, 1);
CHECK(b != 0, "v3 gives a buffer for block 2070");
if (b) { memset(b, 'V', BLK_FORTH_SIZE); CHECK(blk_update(2070) == BLK_OK && blk_flush(2070) == BLK_OK, "and v3 writes it"); }
blk_chain_select(was);
CHECK(ask(&both, 11, V4_MSG_BLOCK_READ, 2070) && answer(&both, &m) && is_data(&m, 11, 'V'), "what v3's own calls wrote is read through the port");
CHECK(blk_chain_select(0) == was, "the chain selected is the one that was, after the device has been used");
}
printf(" %d checks, %d failures\n", checks, failures);
return failures ? 1 : 0;
}