1 // SPDX-License-Identifier: CDDL-1.0
2 /*
3 * CDDL HEADER START
4 *
5 * This file and its contents are supplied under the terms of the Common
6 * Development and Distribution License ("CDDL"), version 1.0. You may only use
7 * this file in accordance with the terms of version 1.0 of the CDDL.
8 *
9 * A full copy of the text of the CDDL should have accompanied this source. A
10 * copy of the CDDL is also available via the Internet at
11 * http://www.illumos.org/license/CDDL.
12 *
13 * CDDL HEADER END
14 */
15
16 /*
17 * Copyright (c) 2026 by Garth Snyder. All rights reserved.
18 */
19
20 #include <assert.h>
21 #include <stddef.h>
22 #include <stdint.h>
23 #include <sys/byteorder.h>
24 #include <sys/spa_checksum.h>
25 #include <sys/stdtypes.h>
26 #include <sys/types.h>
27 #include <sys/zfs_ioctl.h>
28 #include <zfs_fletcher.h>
29
30 #include "zstream_modules.h"
31 #include "zstream_util.h"
32
33 #define CK_OFFSET offsetof(dmu_replay_record_t, drr_u.drr_checksum.drr_checksum)
34 #define END_CK_OFFSET offsetof(dmu_replay_record_t, drr_u.drr_end.drr_checksum)
35
36 typedef enum { F4_SET, F4_VALIDATE } fletcher4_op_t;
37
38 typedef zio_cksum_t fletcher4_context_t;
39
40 static fletcher4_context_t fletcher4_contexts[MAX_FLETCHER_4];
41 static int next_context = 0;
42
43 static inline int
fletcher_4_incremental(boolean_t swap,void * buff,size_t size,void * cksum)44 fletcher_4_incremental(boolean_t swap, void *buff, size_t size, void *cksum)
45 {
46 if (swap) {
47 return (fletcher_4_incremental_byteswap(buff, size, cksum));
48 } else {
49 return (fletcher_4_incremental_native(buff, size, cksum));
50 }
51 }
52
53 /*
54 * Emit or validate (below) a replay record with proper checksums and with
55 * proper maintenance of the stream checksum. That is:
56 *
57 * 1) Update stream checksum with the record header up to drr_checksum.
58 * 2) Update drr_checksum field in the record header from stream checksum.
59 * 3) Update stream checksum with the checksum field in the record header.
60 * 4) Update stream checksum with the contents of the payload.
61 *
62 * DRR_BEGIN records do not have record checksums. They can't, because the
63 * drr_begin struct overlaps with space that would otherwise be used for the
64 * end-record checksum.
65 */
66 static disposition_t
chain_add_fletcher4(drr_packet_t * item,zio_cksum_t * stream_cksum)67 chain_add_fletcher4(drr_packet_t *item, zio_cksum_t *stream_cksum)
68 {
69 if (item == NULL)
70 return (D_OK);
71
72 dmu_replay_record_t *drr = &item->dp_drr;
73 struct drr_end *drre = &item->dp_drr.drr_u.drr_end;
74 zio_cksum_t *record_cksum = &drr->drr_u.drr_checksum.drr_checksum;
75 zio_cksum_t *end_cksum = &drre->drr_checksum;
76
77 boolean_t swap = OPTION_ENABLED(CA_BYTESWAP_ON_OUTPUT);
78 uint32_t drr_type = swap ? BSWAP_32(drr->drr_type) : drr->drr_type;
79
80 if (drr_type == DRR_BEGIN) {
81 ZIO_SET_CHECKSUM(stream_cksum, 0, 0, 0, 0);
82 } else if (drr_type == DRR_END) {
83 *end_cksum = *stream_cksum;
84 if (swap)
85 ZIO_CHECKSUM_BSWAP(end_cksum);
86 }
87 fletcher_4_incremental(swap, drr, CK_OFFSET, stream_cksum);
88 if (drr_type != DRR_BEGIN && !IS_CONCLUSION(drr, drr_type)) {
89 *record_cksum = *stream_cksum;
90 if (swap)
91 ZIO_CHECKSUM_BSWAP(record_cksum);
92 }
93 if (drr_type == DRR_END) {
94 ZIO_SET_CHECKSUM(stream_cksum, 0, 0, 0, 0);
95 } else {
96 fletcher_4_incremental(swap, record_cksum,
97 sizeof (drr->drr_u.drr_checksum.drr_checksum),
98 stream_cksum);
99 if (item->dp_payload_size > 0) {
100 fletcher_4_incremental(swap, item->dp_payload,
101 item->dp_payload_size, stream_cksum);
102 }
103 }
104 return (D_OK);
105 }
106
107 static disposition_t
chain_validate_fletcher4(drr_packet_t * item,zio_cksum_t * stream_cksum)108 chain_validate_fletcher4(drr_packet_t *item, zio_cksum_t *stream_cksum)
109 {
110 if (item == NULL || OPTION_ENABLED(CA_IGNORE_CKSUMS)) {
111 return (D_OK);
112 }
113
114 dmu_replay_record_t *drr = &item->dp_drr;
115 struct drr_end *drre = &item->dp_drr.drr_u.drr_end;
116 zio_cksum_t *record_cksum = &drr->drr_u.drr_checksum.drr_checksum;
117 zio_cksum_t *end_cksum = &drre->drr_checksum;
118
119 boolean_t swap = ATTR_IS_SET(CA_BYTESWAPPED);
120 uint32_t drr_type = swap ? BSWAP_32(drr->drr_type) : drr->drr_type;
121
122 if (drr_type == DRR_BEGIN) {
123 ZIO_SET_CHECKSUM(stream_cksum, 0, 0, 0, 0);
124 } else if (drr_type == DRR_END) {
125 off_t stream_offset = item->dp_stream_offset + END_CK_OFFSET;
126 validate_or_exit(stream_cksum, end_cksum, swap,
127 "in DRR_END record", stream_offset);
128 }
129 fletcher_4_incremental(swap, drr, CK_OFFSET, stream_cksum);
130 if (drr_type != DRR_BEGIN && !IS_CONCLUSION(drr, drr_type)) {
131 off_t stream_offset = item->dp_stream_offset + CK_OFFSET;
132 validate_or_exit(stream_cksum, record_cksum,
133 swap, "at DRR record end", stream_offset);
134 }
135 if (drr_type == DRR_END) {
136 ZIO_SET_CHECKSUM(stream_cksum, 0, 0, 0, 0);
137 } else {
138 fletcher_4_incremental(swap, record_cksum,
139 sizeof (drr->drr_u.drr_checksum.drr_checksum),
140 stream_cksum);
141 if (item->dp_payload_size > 0) {
142 fletcher_4_incremental(swap, item->dp_payload,
143 item->dp_payload_size, stream_cksum);
144 }
145 }
146 return (D_OK);
147 }
148
149 static chain_step_t
fletcher4_serial_step(fletcher4_op_t operation)150 fletcher4_serial_step(fletcher4_op_t operation)
151 {
152 int context_ix = next_context++ % MAX_FLETCHER_4;
153 fletcher4_context_t *context = &fletcher4_contexts[context_ix];
154
155 ZIO_SET_CHECKSUM(context, 0, 0, 0, 0);
156 chain_step_t step = {
157 .cs_type = CS_SERIAL,
158 .cs_in_size = sizeof (drr_packet_t),
159 .cs_out_size = sizeof (drr_packet_t),
160 .cs_context = context,
161 .cs_serial = {
162 .process = (zc_serial_process_f *)
163 ((operation == F4_VALIDATE) ?
164 chain_validate_fletcher4 : chain_add_fletcher4)
165 }
166 };
167 return (step);
168 }
169
170 chain_step_t
serial_add_fletcher4(void)171 serial_add_fletcher4(void)
172 {
173 return (fletcher4_serial_step(F4_SET));
174 }
175
176 chain_step_t
serial_validate_fletcher4(void)177 serial_validate_fletcher4(void)
178 {
179 return (fletcher4_serial_step(F4_VALIDATE));
180 }
181