Android billing exception for a starting character

advertisements

I get this exception from my crash reports:

java.lang.RuntimeException: Unable to start service [email protected] with Intent { act=com.android.vending.billing.PURCHASE_STATE_CHANGED cmp=com.problemio/.BillingService (has extras) }: java.lang.IllegalArgumentException: utils.Base64DecoderException: single trailing character at offset 19
at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:2376)
at android.app.ActivityThread.access$1900(ActivityThread.java:123)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1210)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4424)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:784)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:551)
at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.IllegalArgumentException: utils.Base64DecoderException: single trailing character at offset 19
at utils.Security.generatePublicKey(Security.java:199)
at utils.Security.verifyPurchase(Security.java:118)
at com.problemio.BillingService.purchaseStateChanged(BillingService.java:545)
at com.problemio.BillingService.handleCommand(BillingService.java:421)
at com.problemio.BillingService.onStart(BillingService.java:398)
at android.app.Service.onStartCommand(Service.java:438)
at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:2359)
... 10 more
Caused by: utils.Base64DecoderException: single trailing character at offset 19
at utils.Base64.decode(Base64.java:529)
at utils.Base64.decode(Base64.java:444)
at utils.Base64.decode(Base64.java:390)
at utils.Security.generatePublicKey(Security.java:189)
... 16 more
java.lang.IllegalArgumentException: utils.Base64DecoderException: single trailing character at offset 19
at utils.Security.generatePublicKey(Security.java:199)
at utils.Security.verifyPurchase(Security.java:118)
at com.problemio.BillingService.purchaseStateChanged(BillingService.java:545)
at com.problemio.BillingService.handleCommand(BillingService.java:421)
at com.problemio.BillingService.onStart(BillingService.java:398)
at android.app.Service.onStartCommand(Service.java:438)
at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:2359)
at android.app.ActivityThread.access$1900(ActivityThread.java:123)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1210)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4424)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:784)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:551)
at dalvik.system.NativeStart.main(Native Method)
Caused by: utils.Base64DecoderException: single trailing character at offset 19
at utils.Base64.decode(Base64.java:529)
at utils.Base64.decode(Base64.java:444)
at utils.Base64.decode(Base64.java:390)
at utils.Security.generatePublicKey(Security.java:189)
... 16 more
utils.Base64DecoderException: single trailing character at offset 19
at utils.Base64.decode(Base64.java:529)
at utils.Base64.decode(Base64.java:444)
at utils.Base64.decode(Base64.java:390)
at utils.Security.generatePublicKey(Security.java:189)
at utils.Security.verifyPurchase(Security.java:118)
at com.problemio.BillingService.purchaseStateChanged(BillingService.java:545)
at com.problemio.BillingService.handleCommand(BillingService.java:421)
at com.problemio.BillingService.onStart(BillingService.java:398)
at android.app.Service.onStartCommand(Service.java:438)
at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:2359)
at android.app.ActivityThread.access$1900(ActivityThread.java:123)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1210)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4424)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:784)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:551)
at dalvik.system.NativeStart.main(Native Method)

but I don't quite understand what the problem was. Any suggestions?

It points to this method:

  /**
   * Decodes Base64 content using the supplied decodabet and returns
   * the decoded byte array.
   *
   * @param source the Base64 encoded data
   * @param off the offset of where to begin decoding
   * @param len the length of characters to decode
   * @param decodabet the decodabet for decoding Base64 content
   * @return decoded data
   */
  public static byte[] decode(byte[] source, int off, int len, byte[] decodabet)
      throws Base64DecoderException {
    int len34 = len * 3 / 4;
    byte[] outBuff = new byte[2 + len34]; // Upper limit on size of output
    int outBuffPosn = 0;

    byte[] b4 = new byte[4];
    int b4Posn = 0;
    int i = 0;
    byte sbiCrop = 0;
    byte sbiDecode = 0;
    for (i = 0; i < len; i++) {
      sbiCrop = (byte) (source[i + off] & 0x7f); // Only the low seven bits
      sbiDecode = decodabet[sbiCrop];

      if (sbiDecode >= WHITE_SPACE_ENC) { // White space Equals sign or better
        if (sbiDecode >= EQUALS_SIGN_ENC) {
          // An equals sign (for padding) must not occur at position 0 or 1
          // and must be the last byte[s] in the encoded value
          if (sbiCrop == EQUALS_SIGN) {
            int bytesLeft = len - i;
            byte lastByte = (byte) (source[len - 1 + off] & 0x7f);
            if (b4Posn == 0 || b4Posn == 1) {
              throw new Base64DecoderException(
                  "invalid padding byte '=' at byte offset " + i);
            } else if ((b4Posn == 3 && bytesLeft > 2)
                || (b4Posn == 4 && bytesLeft > 1)) {
              throw new Base64DecoderException(
                  "padding byte '=' falsely signals end of encoded value "
                      + "at offset " + i);
            } else if (lastByte != EQUALS_SIGN && lastByte != NEW_LINE) {
              throw new Base64DecoderException(
                  "encoded value has invalid trailing byte");
            }
            break;
          }

          b4[b4Posn++] = sbiCrop;
          if (b4Posn == 4) {
            outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, decodabet);
            b4Posn = 0;
          }
        }
      } else {
        throw new Base64DecoderException("Bad Base64 input character at " + i
            + ": " + source[i + off] + "(decimal)");
      }
    }

    // Because web safe encoding allows non padding base64 encodes, we
    // need to pad the rest of the b4 buffer with equal signs when
    // b4Posn != 0.  There can be at most 2 equal signs at the end of
    // four characters, so the b4 buffer must have two or three
    // characters.  This also catches the case where the input is
    // padded with EQUALS_SIGN
    if (b4Posn != 0) {
      if (b4Posn == 1) {
        throw new Base64DecoderException("single trailing character at offset "
            + (len - 1));
      }
      b4[b4Posn++] = EQUALS_SIGN;
      outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, decodabet);
    }

    byte[] out = new byte[outBuffPosn];
    System.arraycopy(outBuff, 0, out, 0, outBuffPosn);
    return out;
  }

The error seems to be pointing to these lines in my BillingService.java

at com.problemio.BillingService.purchaseStateChanged(BillingService.java:585)
at com.problemio.BillingService.handleCommand(BillingService.java:461)
at com.problemio.BillingService.onStart(BillingService.java:438)

The line on 585 is the line

purchases = Security.verifyPurchase(signedData, signature);

in this method:

private void purchaseStateChanged(int startId, String signedData, String signature) {
    ArrayList<Security.VerifiedPurchase> purchases;
    purchases = Security.verifyPurchase(signedData, signature);
    if (purchases == null) {
        return;
    }

and the line 461 is this line:

purchaseStateChanged(startId, signedData, signature);

in this method:

public void handleCommand(Intent intent, int startId) {
    String action = intent.getAction();
    if (Consts.DEBUG) {
        Log.i(TAG, "handleCommand() action: " + action);
    }
    if (Consts.ACTION_CONFIRM_NOTIFICATION.equals(action)) {
        String[] notifyIds = intent.getStringArrayExtra(Consts.NOTIFICATION_ID);
        confirmNotifications(startId, notifyIds);
    } else if (Consts.ACTION_GET_PURCHASE_INFORMATION.equals(action)) {
        String notifyId = intent.getStringExtra(Consts.NOTIFICATION_ID);
        getPurchaseInformation(startId, new String[] { notifyId });
    } else if (Consts.ACTION_PURCHASE_STATE_CHANGED.equals(action)) {
        String signedData = intent.getStringExtra(Consts.INAPP_SIGNED_DATA);
        String signature = intent.getStringExtra(Consts.INAPP_SIGNATURE);
        purchaseStateChanged(startId, signedData, signature);
    } else if (Consts.ACTION_RESPONSE_CODE.equals(action)) {
        long requestId = intent.getLongExtra(Consts.INAPP_REQUEST_ID, -1);
        int responseCodeIndex = intent.getIntExtra(Consts.INAPP_RESPONSE_CODE,
                ResponseCode.RESULT_ERROR.ordinal());
        ResponseCode responseCode = ResponseCode.valueOf(responseCodeIndex);
        checkResponseCode(requestId, responseCode);
    }
}

and line 438 is this line:

handleCommand(intent, startId);

in this method:

@Override
public void onStart(Intent intent, int startId) {
    handleCommand(intent, startId);
}

Thanks!


Does this happen all the time or only one / a few times? The signature you are getting purchaseStateChanged(startId, signedData, signature) is also Base64 encoded, and the error says that the encoding is bad. If it only happened once or twice it might be a glitch in the Google Play IAP servers.