private void publishNums(ByteString topic, int start, int num, int M) throws Exception {
   for (int i = 1; i <= num; i++) {
     PubSubProtocol.Map.Builder propsBuilder =
         PubSubProtocol.Map.newBuilder()
             .addEntries(
                 PubSubProtocol.Map.Entry.newBuilder()
                     .setKey(OPT_MOD)
                     .setValue(ByteString.copyFromUtf8(String.valueOf((start + i) % M))));
     MessageHeader.Builder headerBuilder = MessageHeader.newBuilder().setProperties(propsBuilder);
     Message msg =
         Message.newBuilder()
             .setBody(ByteString.copyFromUtf8(String.valueOf((start + i))))
             .setHeader(headerBuilder)
             .build();
     publisher.publish(topic, msg);
   }
 }
  private void receiveNumModM(
      final ByteString topic,
      final ByteString subid,
      final String filterClassName,
      final ClientMessageFilter filter,
      final int start,
      final int num,
      final int M,
      final boolean consume)
      throws Exception {
    PubSubProtocol.Map userOptions =
        PubSubProtocol.Map.newBuilder()
            .addEntries(
                PubSubProtocol.Map.Entry.newBuilder()
                    .setKey(OPT_MOD)
                    .setValue(ByteString.copyFromUtf8(String.valueOf(M))))
            .build();
    SubscriptionOptions.Builder optionsBuilder =
        SubscriptionOptions.newBuilder()
            .setCreateOrAttach(CreateOrAttach.ATTACH)
            .setOptions(userOptions);
    if (null != filterClassName) {
      optionsBuilder.setMessageFilter(filterClassName);
    }
    subscriber.subscribe(topic, subid, optionsBuilder.build());

    final int base = start + M - start % M;

    final AtomicInteger expected = new AtomicInteger(base);
    final CountDownLatch latch = new CountDownLatch(1);
    MessageHandler msgHandler =
        new MessageHandler() {
          public synchronized void deliver(
              ByteString topic,
              ByteString subscriberId,
              Message msg,
              Callback<Void> callback,
              Object context) {
            try {
              int value = Integer.valueOf(msg.getBody().toStringUtf8());
              // duplicated messages received, ignore them
              if (value > start) {
                if (value == expected.get()) {
                  expected.addAndGet(M);
                } else {
                  logger.error(
                      "Did not receive expected value, expected {}, got {}", expected.get(), value);
                  expected.set(0);
                  latch.countDown();
                }
                if (expected.get() == (base + num * M)) {
                  latch.countDown();
                }
              }
              callback.operationFinished(context, null);
              if (consume) {
                subscriber.consume(topic, subid, msg.getMsgId());
              }
            } catch (Exception e) {
              logger.error("Received bad message", e);
              latch.countDown();
            }
          }
        };
    if (null != filter) {
      subscriber.startDeliveryWithFilter(topic, subid, msgHandler, filter);
    } else {
      subscriber.startDelivery(topic, subid, msgHandler);
    }
    assertTrue(
        "Timed out waiting for messages mod " + M + " expected is " + expected.get(),
        latch.await(10, TimeUnit.SECONDS));
    assertEquals(
        "Should be expected message with " + (base + num * M), (base + num * M), expected.get());
    subscriber.stopDelivery(topic, subid);
    subscriber.closeSubscription(topic, subid);
  }