001/*
002 * jPOS Project [http://jpos.org]
003 * Copyright (C) 2000-2026 jPOS Software SRL
004 *
005 * This program is free software: you can redistribute it and/or modify
006 * it under the terms of the GNU Affero General Public License as
007 * published by the Free Software Foundation, either version 3 of the
008 * License, or (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013 * GNU Affero General Public License for more details.
014 *
015 * You should have received a copy of the GNU Affero General Public License
016 * along with this program, if not, see <http://www.gnu.org/licenses/>.
017 */
018
019package org.jpos.iso;
020
021import java.io.PrintStream;
022import java.util.Objects;
023
024/**
025 * Structured representation of DE-027 (POS Capability) in the jPOS CMF.
026 *
027 * <p>DE-027 is a 27-byte fixed-length field that describes the capabilities
028 * of the point-of-service terminal. It is the <em>capability</em> counterpart
029 * of {@link PosDataCode} (DE-022), which records what actually happened.
030 *
031 * <h2>Wire layout</h2>
032 * <pre>
033 *  Bytes  1- 4  Sub-field 27-1: card reading capability      (B4, bit-flags)
034 *  Bytes  5- 8  Sub-field 27-2: cardholder verification cap. (B4, bit-flags)
035 *  Byte   9     Sub-field 27-3: approval code length         (N1, ASCII)
036 *  Bytes 10-12  Sub-field 27-4: cardholder receipt length    (N3, ASCII)
037 *  Bytes 13-15  Sub-field 27-5: card acceptor receipt length (N3, ASCII)
038 *  Bytes 16-18  Sub-field 27-6: cardholder display length    (N3, ASCII)
039 *  Bytes 19-21  Sub-field 27-7: card acceptor display length (N3, ASCII)
040 *  Bytes 22-24  Sub-field 27-8: ICC scripts data length      (N3, ASCII)
041 *  Byte  25     Sub-field 27-9: track 3 rewrite capability   (A1, 'Y'/'N')
042 *  Byte  26     Sub-field 27-10: card capture capability     (A1, 'Y'/'N')
043 *  Byte  27     Sub-field 27-11: PIN input length capability (B1, binary)
044 * </pre>
045 *
046 * <p>Sub-fields 27-1 and 27-2 share the same bit-flag tables as DE-022.
047 * The {@link PosDataCode.ReadingMethod} and {@link PosDataCode.VerificationMethod}
048 * enums are therefore reused directly.
049 *
050 * <p>The bit-numbering convention follows ISO 8583: B1 = MSB = {@code 0x80}.
051 * Internally, {@link PosFlags} maps flag {@code intValue()} 1 to the LSB of
052 * each 4-byte word and serialises little-endian within each word—identical to
053 * {@link PosDataCode}. See the DE-022 spec note for the historical rationale.
054 *
055 * <p>Usage example:
056 * <pre>{@code
057 * // Build and pack
058 * PosCapability cap = PosCapability.builder()
059 *     .readingCapability(PosDataCode.ReadingMethod.MAGNETIC_STRIPE)
060 *     .readingCapability(PosDataCode.ReadingMethod.ICC)
061 *     .verificationCapability(PosDataCode.VerificationMethod.ONLINE_PIN)
062 *     .verificationCapability(PosDataCode.VerificationMethod.MANUAL_SIGNATURE)
063 *     .approvalCodeLength(6)
064 *     .cardholderReceiptLength(40)
065 *     .cardAcceptorReceiptLength(40)
066 *     .cardholderDisplayLength(16)
067 *     .cardAcceptorDisplayLength(16)
068 *     .iccScriptDataLength(128)
069 *     .track3RewriteCapable(false)
070 *     .cardCaptureCapable(false)
071 *     .pinInputLength(12)
072 *     .build();
073 *
074 * msg.set(new ISOBinaryField(27, cap.pack()));
075 *
076 * // Unpack
077 * PosCapability received = PosCapability.unpack(msg.getBytes(27));
078 * if (received.canReadICC()) { ... }
079 * }</pre>
080 *
081 * @see PosDataCode
082 * @see PosFlags
083 * @see <a href="https://jpos.org/doc/jPOS-CMF.pdf">jPOS CMF — DE-027</a>
084 */
085public class PosCapability extends PosFlags {
086
087    /** Wire length of DE-027 in bytes. */
088    public static final int WIRE_LENGTH = 27;
089
090    /** Byte offset of sub-field 27-1 (card reading capability) within the wire buffer. */
091    private static final int READING_OFFSET = 0;
092
093    /** Byte offset of sub-field 27-2 (cardholder verification capability). */
094    private static final int VERIFICATION_OFFSET = 4;
095
096    /** Byte offset of sub-field 27-3 (approval code length, 1 ASCII digit). */
097    private static final int APPROVAL_CODE_LENGTH_OFFSET = 8;
098
099    /** Byte offset of sub-field 27-4 (cardholder receipt data length, 3 ASCII digits). */
100    private static final int CARDHOLDER_RECEIPT_OFFSET = 9;
101
102    /** Byte offset of sub-field 27-5 (card acceptor receipt data length, 3 ASCII digits). */
103    private static final int CARD_ACCEPTOR_RECEIPT_OFFSET = 12;
104
105    /** Byte offset of sub-field 27-6 (cardholder display data length, 3 ASCII digits). */
106    private static final int CARDHOLDER_DISPLAY_OFFSET = 15;
107
108    /** Byte offset of sub-field 27-7 (card acceptor display data length, 3 ASCII digits). */
109    private static final int CARD_ACCEPTOR_DISPLAY_OFFSET = 18;
110
111    /** Byte offset of sub-field 27-8 (ICC scripts data length, 3 ASCII digits). */
112    private static final int ICC_SCRIPT_LENGTH_OFFSET = 21;
113
114    /** Byte offset of sub-field 27-9 (track 3 rewrite capability, 'Y'/'N'). */
115    private static final int TRACK3_REWRITE_OFFSET = 24;
116
117    /** Byte offset of sub-field 27-10 (card capture capability, 'Y'/'N'). */
118    private static final int CARD_CAPTURE_OFFSET = 25;
119
120    /** Byte offset of sub-field 27-11 (PIN input length capability, binary). */
121    private static final int PIN_INPUT_LENGTH_OFFSET = 26;
122
123    private final byte[] b;
124
125    private PosCapability(byte[] b) {
126        this.b = b;
127    }
128
129    // -------------------------------------------------------------------------
130    // PosFlags contract
131    // -------------------------------------------------------------------------
132
133    /**
134     * Returns the raw 27-byte wire buffer backing this instance.
135     * The returned array is the live backing store; callers should not modify it.
136     *
137     * @return the 27-byte wire buffer
138     */
139    @Override
140    public byte[] getBytes() {
141        return b;
142    }
143
144    // -------------------------------------------------------------------------
145    // Factory methods
146    // -------------------------------------------------------------------------
147
148    /**
149     * Returns a new {@link Builder} for constructing a {@code PosCapability}.
150     *
151     * @return a new builder
152     */
153    public static Builder builder() {
154        return new Builder();
155    }
156
157    /**
158     * Unpacks a {@code PosCapability} from a 27-byte wire buffer.
159     *
160     * @param data exactly {@value #WIRE_LENGTH} bytes
161     * @return the decoded {@code PosCapability}
162     * @throws IllegalArgumentException if {@code data} is null or not exactly 27 bytes
163     */
164    public static PosCapability unpack(byte[] data) {
165        if (data == null || data.length != WIRE_LENGTH)
166            throw new IllegalArgumentException(
167                "DE-027 wire buffer must be exactly " + WIRE_LENGTH + " bytes, got "
168                + (data == null ? "null" : data.length));
169        byte[] copy = new byte[WIRE_LENGTH];
170        System.arraycopy(data, 0, copy, 0, WIRE_LENGTH);
171        return new PosCapability(copy);
172    }
173
174    /**
175     * Packs this {@code PosCapability} into a {@value #WIRE_LENGTH}-byte array
176     * suitable for placing directly into DE-027 of an {@link ISOMsg}.
177     *
178     * @return a fresh 27-byte copy of the wire buffer
179     */
180    public byte[] pack() {
181        byte[] copy = new byte[WIRE_LENGTH];
182        System.arraycopy(b, 0, copy, 0, WIRE_LENGTH);
183        return copy;
184    }
185
186    /**
187     * Returns a {@link Builder} pre-populated with all values from this instance,
188     * allowing selective modification.
189     *
190     * @return a builder initialised from this instance
191     */
192    public Builder toBuilder() {
193        Builder bld = new Builder();
194        System.arraycopy(b, 0, bld.b, 0, WIRE_LENGTH);
195        return bld;
196    }
197
198    // -------------------------------------------------------------------------
199    // Sub-field 27-1: card reading capability
200    // -------------------------------------------------------------------------
201
202    /**
203     * Returns {@code true} if the terminal supports the given card reading method.
204     *
205     * @param method the reading method to test
206     * @return {@code true} if the bit for {@code method} is set
207     */
208    public boolean canRead(PosDataCode.ReadingMethod method) {
209        Objects.requireNonNull(method);
210        int v = method.intValue();
211        // method.getOffset() is 0 for ReadingMethod (its absolute PosDataCode offset)
212        // READING_OFFSET is also 0, so no correction needed here — kept explicit for clarity
213        int offset = READING_OFFSET;
214        for (; v != 0; v >>>= 8, offset++) {
215            if (offset >= READING_OFFSET + 4) break;
216            if ((b[offset] & (byte) v) != (byte) v) return false;
217        }
218        return true;
219    }
220
221    /**
222     * Returns {@code true} if the terminal can read ICC (chip) cards.
223     *
224     * @return {@code true} if {@link PosDataCode.ReadingMethod#ICC} is set
225     */
226    public boolean canReadICC() {
227        return canRead(PosDataCode.ReadingMethod.ICC);
228    }
229
230    /**
231     * Returns {@code true} if the terminal can read magnetic stripe cards.
232     *
233     * @return {@code true} if {@link PosDataCode.ReadingMethod#MAGNETIC_STRIPE} is set
234     */
235    public boolean canReadMagstripe() {
236        return canRead(PosDataCode.ReadingMethod.MAGNETIC_STRIPE);
237    }
238
239    /**
240     * Returns {@code true} if the terminal supports contactless (RFID) reading.
241     *
242     * @return {@code true} if {@link PosDataCode.ReadingMethod#CONTACTLESS} is set
243     */
244    public boolean canReadContactless() {
245        return canRead(PosDataCode.ReadingMethod.CONTACTLESS);
246    }
247
248    // -------------------------------------------------------------------------
249    // Sub-field 27-2: cardholder verification capability
250    // -------------------------------------------------------------------------
251
252    /**
253     * Returns {@code true} if the terminal supports the given cardholder verification method.
254     *
255     * @param method the verification method to test
256     * @return {@code true} if the bit for {@code method} is set
257     */
258    public boolean canVerify(PosDataCode.VerificationMethod method) {
259        Objects.requireNonNull(method);
260        int v = method.intValue();
261        // method.getOffset() is 4 in PosDataCode's 16-byte buffer; here the
262        // verification sub-field lives at VERIFICATION_OFFSET (byte 4), so we
263        // do NOT add method.getOffset() — we read relative to VERIFICATION_OFFSET only.
264        int offset = VERIFICATION_OFFSET;
265        for (; v != 0; v >>>= 8, offset++) {
266            if (offset >= VERIFICATION_OFFSET + 4) break;
267            if ((b[offset] & (byte) v) != (byte) v) return false;
268        }
269        return true;
270    }
271
272    /**
273     * Returns {@code true} if the terminal supports online PIN entry.
274     *
275     * @return {@code true} if {@link PosDataCode.VerificationMethod#ONLINE_PIN} is set
276     */
277    public boolean canVerifyOnlinePin() {
278        return canVerify(PosDataCode.VerificationMethod.ONLINE_PIN);
279    }
280
281    /**
282     * Returns {@code true} if the terminal supports offline PIN entry in clear.
283     *
284     * @return {@code true} if {@link PosDataCode.VerificationMethod#OFFLINE_PIN_IN_CLEAR} is set
285     */
286    public boolean canVerifyOfflinePinInClear() {
287        return canVerify(PosDataCode.VerificationMethod.OFFLINE_PIN_IN_CLEAR);
288    }
289
290    /**
291     * Returns {@code true} if the terminal supports offline encrypted PIN entry.
292     *
293     * @return {@code true} if {@link PosDataCode.VerificationMethod#OFFLINE_PIN_ENCRYPTED} is set
294     */
295    public boolean canVerifyOfflinePinEncrypted() {
296        return canVerify(PosDataCode.VerificationMethod.OFFLINE_PIN_ENCRYPTED);
297    }
298
299    /**
300     * Returns {@code true} if the terminal supports manual signature verification.
301     *
302     * @return {@code true} if {@link PosDataCode.VerificationMethod#MANUAL_SIGNATURE} is set
303     */
304    public boolean canVerifySignature() {
305        return canVerify(PosDataCode.VerificationMethod.MANUAL_SIGNATURE);
306    }
307
308    // -------------------------------------------------------------------------
309    // Sub-fields 27-3 through 27-11: scalar accessors
310    // -------------------------------------------------------------------------
311
312    /**
313     * Returns the maximum approval code length supported by this terminal (sub-field 27-3).
314     *
315     * @return approval code length, 0–9
316     */
317    public int getApprovalCodeLength() {
318        return b[APPROVAL_CODE_LENGTH_OFFSET] - '0';
319    }
320
321    /**
322     * Returns the maximum cardholder receipt data length in characters (sub-field 27-4).
323     * A value of 0 indicates no receipt capability.
324     *
325     * @return cardholder receipt length, 0–999
326     */
327    public int getCardholderReceiptLength() {
328        return readN3(CARDHOLDER_RECEIPT_OFFSET);
329    }
330
331    /**
332     * Returns the maximum card acceptor (merchant) receipt data length in characters (sub-field 27-5).
333     * A value of 0 indicates no receipt capability.
334     *
335     * @return card acceptor receipt length, 0–999
336     */
337    public int getCardAcceptorReceiptLength() {
338        return readN3(CARD_ACCEPTOR_RECEIPT_OFFSET);
339    }
340
341    /**
342     * Returns the maximum cardholder display data length in characters (sub-field 27-6).
343     * A value of 0 indicates no display capability.
344     *
345     * @return cardholder display length, 0–999
346     */
347    public int getCardholderDisplayLength() {
348        return readN3(CARDHOLDER_DISPLAY_OFFSET);
349    }
350
351    /**
352     * Returns the maximum card acceptor display data length in characters (sub-field 27-7).
353     * A value of 0 indicates no display capability.
354     *
355     * @return card acceptor display length, 0–999
356     */
357    public int getCardAcceptorDisplayLength() {
358        return readN3(CARD_ACCEPTOR_DISPLAY_OFFSET);
359    }
360
361    /**
362     * Returns the maximum ICC scripts data length in bytes (sub-field 27-8).
363     * A value of 0 indicates no ICC script capability.
364     *
365     * @return ICC scripts data length, 0–999
366     */
367    public int getIccScriptDataLength() {
368        return readN3(ICC_SCRIPT_LENGTH_OFFSET);
369    }
370
371    /**
372     * Returns {@code true} if the terminal can rewrite magnetic stripe track 3 (sub-field 27-9).
373     *
374     * @return {@code true} if track 3 rewrite is supported
375     */
376    public boolean canRewriteTrack3() {
377        return b[TRACK3_REWRITE_OFFSET] == 'Y';
378    }
379
380    /**
381     * Returns {@code true} if the terminal can capture (retain) cards (sub-field 27-10).
382     *
383     * @return {@code true} if card capture is supported
384     */
385    public boolean canCaptureCard() {
386        return b[CARD_CAPTURE_OFFSET] == 'Y';
387    }
388
389    /**
390     * Returns the maximum PIN length the terminal's PIN pad can accept (sub-field 27-11).
391     *
392     * @return PIN input length capability, 0–255
393     */
394    public int getPinInputLength() {
395        return b[PIN_INPUT_LENGTH_OFFSET] & 0xFF;
396    }
397
398    // -------------------------------------------------------------------------
399    // Loggeable / toString
400    // -------------------------------------------------------------------------
401
402    /**
403     * Dumps a human-readable representation of this {@code PosCapability} to the given stream.
404     *
405     * @param p      the output stream
406     * @param indent indentation prefix
407     */
408    public void dump(PrintStream p, String indent) {
409        String inner = indent + "  ";
410        p.printf("%s<pos-capability value='%s'>%n", indent, ISOUtil.hexString(b));
411        p.printf("%sreading-cap:     %s%n", inner, ISOUtil.hexString(b, 0, 4));
412        p.printf("%sverification-cap:%s%n", inner, ISOUtil.hexString(b, 4, 4));
413        p.printf("%sapproval-code-len:%d%n", inner, getApprovalCodeLength());
414        p.printf("%scardholder-receipt-len:%d%n", inner, getCardholderReceiptLength());
415        p.printf("%scard-acceptor-receipt-len:%d%n", inner, getCardAcceptorReceiptLength());
416        p.printf("%scardholder-display-len:%d%n", inner, getCardholderDisplayLength());
417        p.printf("%scard-acceptor-display-len:%d%n", inner, getCardAcceptorDisplayLength());
418        p.printf("%sicc-script-len:%d%n", inner, getIccScriptDataLength());
419        p.printf("%strack3-rewrite:%b%n", inner, canRewriteTrack3());
420        p.printf("%scard-capture:%b%n", inner, canCaptureCard());
421        p.printf("%spin-input-len:%d%n", inner, getPinInputLength());
422        p.printf("%s</pos-capability>%n", indent);
423    }
424
425    // -------------------------------------------------------------------------
426    // Internal helpers
427    // -------------------------------------------------------------------------
428
429    private int readN3(int offset) {
430        return (b[offset] - '0') * 100
431             + (b[offset + 1] - '0') * 10
432             + (b[offset + 2] - '0');
433    }
434
435    // -------------------------------------------------------------------------
436    // Builder
437    // -------------------------------------------------------------------------
438
439    /**
440     * Fluent builder for {@link PosCapability}.
441     *
442     * <p>All numeric capacity fields default to 0 (not capable).
443     * Track 3 rewrite and card capture default to {@code false}.
444     * PIN input length defaults to 0.
445     * Reading and verification capability bits default to all-zero (no capabilities declared).
446     */
447    public static final class Builder {
448
449        private final byte[] b = new byte[WIRE_LENGTH];
450
451        private Builder() {
452            // Initialise N1 and N3 ASCII fields to '0'
453            b[APPROVAL_CODE_LENGTH_OFFSET] = '0';
454            for (int i = CARDHOLDER_RECEIPT_OFFSET; i < TRACK3_REWRITE_OFFSET; i++)
455                b[i] = '0';
456            b[TRACK3_REWRITE_OFFSET] = 'N';
457            b[CARD_CAPTURE_OFFSET]   = 'N';
458        }
459
460        /**
461         * Sets a card reading capability bit (sub-field 27-1).
462         * Multiple capabilities may be set by calling this method repeatedly.
463         *
464         * @param method the reading method to declare as supported
465         * @return this builder
466         */
467        public Builder readingCapability(PosDataCode.ReadingMethod method) {
468            Objects.requireNonNull(method);
469            // ReadingMethod.getOffset() == 0; READING_OFFSET == 0 — both start at byte 0
470            setFlag(READING_OFFSET, method.intValue());
471            return this;
472        }
473
474        /**
475         * Sets a cardholder verification capability bit (sub-field 27-2).
476         * Multiple capabilities may be set by calling this method repeatedly.
477         *
478         * @param method the verification method to declare as supported
479         * @return this builder
480         */
481        public Builder verificationCapability(PosDataCode.VerificationMethod method) {
482            Objects.requireNonNull(method);
483            // method.getOffset() == 4 in PosDataCode; here the verification sub-field
484            // is at VERIFICATION_OFFSET (4) — do NOT add method.getOffset()
485            setFlag(VERIFICATION_OFFSET, method.intValue());
486            return this;
487        }
488
489        /**
490         * Sets the maximum approval code length (sub-field 27-3, N1, range 0–9).
491         *
492         * @param length approval code length
493         * @return this builder
494         * @throws IllegalArgumentException if {@code length} is outside 0–9
495         */
496        public Builder approvalCodeLength(int length) {
497            if (length < 0 || length > 9)
498                throw new IllegalArgumentException("approvalCodeLength must be 0-9, got " + length);
499            b[APPROVAL_CODE_LENGTH_OFFSET] = (byte) ('0' + length);
500            return this;
501        }
502
503        /**
504         * Sets the maximum cardholder receipt data length (sub-field 27-4, N3, range 0–999).
505         *
506         * @param length receipt length; 0 = not capable
507         * @return this builder
508         * @throws IllegalArgumentException if {@code length} is outside 0–999
509         */
510        public Builder cardholderReceiptLength(int length) {
511            writeN3(CARDHOLDER_RECEIPT_OFFSET, length, "cardholderReceiptLength");
512            return this;
513        }
514
515        /**
516         * Sets the maximum card acceptor receipt data length (sub-field 27-5, N3, range 0–999).
517         *
518         * @param length receipt length; 0 = not capable
519         * @return this builder
520         * @throws IllegalArgumentException if {@code length} is outside 0–999
521         */
522        public Builder cardAcceptorReceiptLength(int length) {
523            writeN3(CARD_ACCEPTOR_RECEIPT_OFFSET, length, "cardAcceptorReceiptLength");
524            return this;
525        }
526
527        /**
528         * Sets the maximum cardholder display data length (sub-field 27-6, N3, range 0–999).
529         *
530         * @param length display length; 0 = not capable
531         * @return this builder
532         * @throws IllegalArgumentException if {@code length} is outside 0–999
533         */
534        public Builder cardholderDisplayLength(int length) {
535            writeN3(CARDHOLDER_DISPLAY_OFFSET, length, "cardholderDisplayLength");
536            return this;
537        }
538
539        /**
540         * Sets the maximum card acceptor display data length (sub-field 27-7, N3, range 0–999).
541         *
542         * @param length display length; 0 = not capable
543         * @return this builder
544         * @throws IllegalArgumentException if {@code length} is outside 0–999
545         */
546        public Builder cardAcceptorDisplayLength(int length) {
547            writeN3(CARD_ACCEPTOR_DISPLAY_OFFSET, length, "cardAcceptorDisplayLength");
548            return this;
549        }
550
551        /**
552         * Sets the maximum ICC scripts data length (sub-field 27-8, N3, range 0–999).
553         *
554         * @param length ICC script length in bytes; 0 = not capable
555         * @return this builder
556         * @throws IllegalArgumentException if {@code length} is outside 0–999
557         */
558        public Builder iccScriptDataLength(int length) {
559            writeN3(ICC_SCRIPT_LENGTH_OFFSET, length, "iccScriptDataLength");
560            return this;
561        }
562
563        /**
564         * Sets the track 3 magnetic stripe rewrite capability (sub-field 27-9).
565         *
566         * @param capable {@code true} if the terminal can rewrite track 3
567         * @return this builder
568         */
569        public Builder track3RewriteCapable(boolean capable) {
570            b[TRACK3_REWRITE_OFFSET] = capable ? (byte) 'Y' : (byte) 'N';
571            return this;
572        }
573
574        /**
575         * Sets the card capture capability (sub-field 27-10).
576         *
577         * @param capable {@code true} if the terminal can capture (retain) cards
578         * @return this builder
579         */
580        public Builder cardCaptureCapable(boolean capable) {
581            b[CARD_CAPTURE_OFFSET] = capable ? (byte) 'Y' : (byte) 'N';
582            return this;
583        }
584
585        /**
586         * Sets the maximum PIN input length capability (sub-field 27-11, binary, range 0–255).
587         *
588         * @param length maximum PIN length the PIN pad accepts
589         * @return this builder
590         * @throws IllegalArgumentException if {@code length} is outside 0–255
591         */
592        public Builder pinInputLength(int length) {
593            if (length < 0 || length > 255)
594                throw new IllegalArgumentException("pinInputLength must be 0-255, got " + length);
595            b[PIN_INPUT_LENGTH_OFFSET] = (byte) length;
596            return this;
597        }
598
599        /**
600         * Builds the {@link PosCapability}.
601         *
602         * @return a new {@code PosCapability} backed by a copy of the accumulated state
603         */
604        public PosCapability build() {
605            byte[] copy = new byte[WIRE_LENGTH];
606            System.arraycopy(b, 0, copy, 0, WIRE_LENGTH);
607            return new PosCapability(copy);
608        }
609
610        // Internal helpers
611
612        private void setFlag(int byteOffset, int flagValue) {
613            for (int v = flagValue; v != 0; v >>>= 8, byteOffset++) {
614                if (byteOffset < WIRE_LENGTH)
615                    b[byteOffset] |= (byte) v;
616            }
617        }
618
619        private void writeN3(int offset, int value, String name) {
620            if (value < 0 || value > 999)
621                throw new IllegalArgumentException(name + " must be 0-999, got " + value);
622            b[offset]     = (byte) ('0' + value / 100);
623            b[offset + 1] = (byte) ('0' + (value / 10) % 10);
624            b[offset + 2] = (byte) ('0' + value % 10);
625        }
626    }
627}