001package jmri.jmrix.dcc4pc; 002 003import java.util.ArrayDeque; 004import java.util.ArrayList; 005import java.util.Arrays; 006import java.util.Deque; 007import java.util.List; 008 009/** 010 * Decodes the Raw RailCom encoding of a DCC4PC RailCom reader (Omnibus 011 * specification section 9.3). Each packet gives one DCC packet the reader saw, 012 * the state of every enabled input and the RailCom bytes each input with 013 * RailCom data received in the cutout that followed. 014 * <p> 015 * The encoding is stateful: type 1 packets carry no input states and inherit 016 * them from the packet before, and the number of duplicate-flag and length 017 * bytes that follow depends on those states. RailCom data may also be sent as 018 * "duplicate of the 1st, 2nd or 3rd previous" for an input. The decoder 019 * therefore only works on an unbroken stream that starts right after a Reset 020 * Encoding State, which sets every input to unoccupied. A Get Data reply may 021 * end part way through a packet; the leftover bytes are kept and joined to the 022 * next reply. 023 * 024 * @author swalton00 Copyright (C) 2026 025 */ 026public class Dcc4PcRawDecoder { 027 028 public static final int STATE_UNOCCUPIED = 0; 029 public static final int STATE_OCCUPIED = 1; 030 public static final int STATE_ORIENTATION_A = 2; 031 public static final int STATE_ORIENTATION_B = 3; 032 033 /** The reader's output buffer overflowed and packets were lost here. */ 034 public static final int OVERFLOW = 0x81; 035 036 private static final int HISTORY = 3; 037 038 public enum Kind { 039 DCC, OVERFLOW, UNKNOWN_COMMAND 040 } 041 042 /** 043 * RailCom bytes for one input. {@code duplicateOf} is 0 for new data, else 044 * 1-3 for a repeat of that earlier packet; {@code data} is null if the 045 * repeated packet is older than the history kept. {@code sourceDcc} is the 046 * DCC packet the bytes followed. 047 */ 048 public static class InputData { 049 050 public final int input; 051 public final int duplicateOf; 052 public final int[] data; 053 public final int[] sourceDcc; 054 055 InputData(int input, int duplicateOf, int[] data, int[] sourceDcc) { 056 this.input = input; 057 this.duplicateOf = duplicateOf; 058 this.data = data; 059 this.sourceDcc = sourceDcc; 060 } 061 } 062 063 /** 064 * One decoded packet. For DCC packets {@code states[i]} is the state of 065 * {@code getInputs()[i]}; {@code explicitStates} says whether this packet 066 * carried them (type 2) or inherited them (type 1). 067 */ 068 public static class Packet { 069 070 public final Kind kind; 071 public final int[] dcc; 072 public final boolean explicitStates; 073 public final int[] states; 074 public final List<InputData> railCom; 075 076 Packet(Kind kind, int[] dcc, boolean explicitStates, int[] states, List<InputData> railCom) { 077 this.kind = kind; 078 this.dcc = dcc; 079 this.explicitStates = explicitStates; 080 this.states = states; 081 this.railCom = railCom; 082 } 083 } 084 085 private static class Entry { 086 087 final int[] data; 088 final int[] dcc; 089 090 Entry(int[] data, int[] dcc) { 091 this.data = data; 092 this.dcc = dcc; 093 } 094 } 095 096 private final int[] inputs; 097 private final int[] states; 098 private final List<Deque<Entry>> history = new ArrayList<>(); 099 private int[] pending = new int[0]; 100 101 /** 102 * @param enabledInputs the inputs enabled on the reader, numbered from 1, 103 * in ascending order 104 */ 105 public Dcc4PcRawDecoder(int[] enabledInputs) { 106 inputs = enabledInputs.clone(); 107 Arrays.sort(inputs); 108 states = new int[inputs.length]; 109 for (int i = 0; i < inputs.length; i++) { 110 history.add(new ArrayDeque<>()); 111 } 112 } 113 114 /** 115 * @return the enabled inputs, numbered from 1, in the order their states 116 * are reported 117 */ 118 public int[] getInputs() { 119 return inputs.clone(); 120 } 121 122 /** 123 * Start again as the reader does after a Reset Encoding State: every input 124 * unoccupied, no partial packet and no RailCom history. 125 */ 126 public void reset() { 127 pending = new int[0]; 128 Arrays.fill(states, STATE_UNOCCUPIED); 129 for (Deque<Entry> h : history) { 130 h.clear(); 131 } 132 } 133 134 /** 135 * Decode a chunk of Get Data reply bytes. 136 * 137 * @param chunk the reply bytes, each 0-255 138 * @return every packet completed by this chunk 139 */ 140 public List<Packet> decode(int[] chunk) { 141 int[] buf = new int[pending.length + chunk.length]; 142 System.arraycopy(pending, 0, buf, 0, pending.length); 143 for (int i = 0; i < chunk.length; i++) { 144 buf[pending.length + i] = chunk[i] & 0xFF; 145 } 146 List<Packet> packets = new ArrayList<>(); 147 int pos = 0; 148 while (pos < buf.length) { 149 int first = buf[pos]; 150 if ((first & 0x80) != 0) { 151 if (first == OVERFLOW) { 152 // packets were lost, but the reader counts duplicates and states from 153 // what it delivered (spec 9.3 example, packet 4), so both still hold 154 packets.add(new Packet(Kind.OVERFLOW, new int[0], false, states.clone(), new ArrayList<>())); 155 pos++; 156 continue; 157 } 158 // an unknown command's length is unknown too, so the rest of the buffer goes with it 159 packets.add(new Packet(Kind.UNKNOWN_COMMAND, new int[0], false, states.clone(), new ArrayList<>())); 160 pos = buf.length; 161 break; 162 } 163 int[] length = new int[1]; 164 Packet p = parseDcc(buf, pos, length); 165 if (p == null) { 166 break; 167 } 168 packets.add(p); 169 pos += length[0]; 170 } 171 pending = Arrays.copyOfRange(buf, pos, buf.length); 172 return packets; 173 } 174 175 /** 176 * Parse a type 1 or 2 packet at {@code start}. The decoder's state changes 177 * only once the whole packet is present. 178 * 179 * @return the packet, or null if it is not complete yet 180 */ 181 private Packet parseDcc(int[] buf, int start, int[] length) { 182 int pos = start; 183 int first = buf[pos++]; 184 boolean explicit = (first & 0x40) != 0; 185 int dccLength = (first & 0x3F) + 1; 186 if (pos + dccLength > buf.length) { 187 return null; 188 } 189 int[] dcc = Arrays.copyOfRange(buf, pos, pos + dccLength); 190 pos += dccLength; 191 int[] newStates = states.clone(); 192 if (explicit) { 193 int stateBytes = (inputs.length * 2 + 7) / 8; 194 if (pos + stateBytes > buf.length) { 195 return null; 196 } 197 for (int i = 0; i < inputs.length; i++) { 198 newStates[i] = field(buf, pos, i, 2); 199 } 200 pos += stateBytes; 201 } 202 List<Integer> withData = new ArrayList<>(); 203 for (int i = 0; i < inputs.length; i++) { 204 if (newStates[i] >= STATE_ORIENTATION_A) { 205 withData.add(i); 206 } 207 } 208 int dupBytes = (withData.size() * 2 + 7) / 8; 209 if (pos + dupBytes > buf.length) { 210 return null; 211 } 212 int[] dup = new int[withData.size()]; 213 int fresh = 0; 214 for (int k = 0; k < dup.length; k++) { 215 dup[k] = field(buf, pos, k, 2); 216 if (dup[k] == 0) { 217 fresh++; 218 } 219 } 220 pos += dupBytes; 221 int lengthBytes = (fresh * 4 + 7) / 8; 222 if (pos + lengthBytes > buf.length) { 223 return null; 224 } 225 int[] lengths = new int[fresh]; 226 int total = 0; 227 for (int f = 0; f < fresh; f++) { 228 lengths[f] = field(buf, pos, f, 4); 229 total += lengths[f]; 230 } 231 pos += lengthBytes; 232 if (pos + total > buf.length) { 233 return null; 234 } 235 // complete: resolve the data, then commit states and history 236 List<InputData> railCom = new ArrayList<>(); 237 int[][] resolved = new int[dup.length][]; 238 int f = 0; 239 for (int k = 0; k < dup.length; k++) { 240 Deque<Entry> h = history.get(withData.get(k)); 241 int[] source; 242 if (dup[k] == 0) { 243 resolved[k] = Arrays.copyOfRange(buf, pos, pos + lengths[f]); 244 source = dcc; 245 pos += lengths[f]; 246 f++; 247 } else { 248 Entry e = dup[k] <= h.size() ? new ArrayList<>(h).get(dup[k] - 1) : null; 249 resolved[k] = e == null ? null : e.data; 250 source = e == null ? null : e.dcc; 251 } 252 railCom.add(new InputData(inputs[withData.get(k)], dup[k], resolved[k], source)); 253 } 254 for (int k = 0; k < dup.length; k++) { 255 Deque<Entry> h = history.get(withData.get(k)); 256 h.addFirst(new Entry(resolved[k], dcc)); 257 while (h.size() > HISTORY) { 258 h.removeLast(); 259 } 260 } 261 System.arraycopy(newStates, 0, states, 0, states.length); 262 length[0] = pos - start; 263 return new Packet(Kind.DCC, dcc, explicit, newStates, railCom); 264 } 265 266 /** 267 * @return the {@code index}-th {@code width}-bit field (2 or 4) starting at 268 * {@code offset}, first field in the low bits 269 */ 270 private static int field(int[] buf, int offset, int index, int width) { 271 int perByte = 8 / width; 272 int b = buf[offset + index / perByte]; 273 return (b >> ((index % perByte) * width)) & ((1 << width) - 1); 274 } 275}