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}