docs: public int readDirect ( byte[] b, int off, int len ) throws IOException {
          if ( len <= 0 ) {
              return 0;
          }
          long start = this.fp;
  
          if ( this.tmp == null ) {
              throw new IOException("Bad file descriptor");
          }
          // ensure file is open
          try ( SmbFileHandleImpl fd = ensureOpen();
                SmbTreeHandleImpl th = fd.getTree() ) {
  
              /*
               * Read AndX Request / Response
               */
  
              if ( log.isTraceEnabled() ) {
                  log.trace("read: fid=" + fd + ",off=" + off + ",len=" + len);
              }
  
              SmbComReadAndXResponse response = new SmbComReadAndXResponse(th.getConfig(), b, off);
  
              int type = this.file.getType();
              int r, n;
              int blockSize = ( type == SmbConstants.TYPE_FILESYSTEM ) ? this.readSizeFile : this.readSize;
              do {
                  r = len > blockSize ? blockSize : len;
  
                  if ( log.isTraceEnabled() ) {
                      log.trace("read: len=" + len + ",r=" + r + ",fp=" + this.fp + ",b.length=" + b.length);
                  }
  
                  try {
  
                      if ( th.isSMB2() ) {
                          Smb2ReadRequest request = new Smb2ReadRequest(th.getConfig(), fd.getFileId(), b, off);
                          request.setOffset(type == SmbConstants.TYPE_NAMED_PIPE ? 0 : this.fp);
                          request.setReadLength(r);
                          request.setRemainingBytes(len - r);
  
                          try {
                              Smb2ReadResponse resp = th.send(request, RequestParam.NO_RETRY);
                              n = resp.getDataLength();
                          }
                          catch ( SmbException e ) {
                              if ( e.getNtStatus() == 0xC0000011 ) {
                                  log.debug("Reached end of file", e);
                                  n = -1;
                              }
                              else {
                                  throw e;
                              }
                          }
                          if ( n <= 0 ) {
                              return (int) ( ( this.fp - start ) > 0L ? this.fp - start : -1 );
                          }
                          this.fp += n;
                          off += n;
                          len -= n;
                          continue;
                      }
  
                      SmbComReadAndX request = new SmbComReadAndX(th.getConfig(), fd.getFid(), this.fp, r, null);
                      if ( type == SmbConstants.TYPE_NAMED_PIPE ) {
                          request.setMinCount(1024);
                          request.setMaxCount(1024);
                          request.setRemaining(1024);
                      }
                      else if ( this.largeReadX ) {
                          request.setMaxCount(r & 0xFFFF);
                          request.setOpenTimeout( ( r >> 16 ) & 0xFFFF);
                      }
                      th.send(request, response, RequestParam.NO_RETRY);
                      n = response.getDataLength();
                  }
                  catch ( SmbException se ) {
                      if ( type == SmbConstants.TYPE_NAMED_PIPE && se.getNtStatus() == NtStatus.NT_STATUS_PIPE_BROKEN ) {
                          return -1;
                      }
                      throw seToIoe(se);
                  }
                  if ( n <= 0 ) {
                      return (int) ( ( this.fp - start ) > 0L ? this.fp - start : -1 );
                  }
                  this.fp += n;
                  len -= n;
                  response.adjustOffset(n);
              }
              while ( len > blockSize && n == r );
              // this used to be len > 0, but this is BS:
              // - InputStream.read gives no such guarantee
              // - otherwise the caller would need to figure out the block size, or otherwise might end up with very small
              // reads
              return (int) ( this.fp - start );
          }
      }