aboutsummaryrefslogtreecommitdiffstats
path: root/doc-src/tutorials/gamecenter.html
blob: bf398cd16ee379abc525234cf8524c599109094b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
## The setup

In this tutorial, I'm going to show you how simple it is to creatively
interfere with Apple Game Center traffic using mitmproxy. To set things up, I
registered my mitmproxy CA certificate with my iPhone - there's a [step by step
set of instructions](@!urlTo("certinstall/ios.html")!@) elsewhere in this manual. I then
started mitmproxy on my desktop, and configured the iPhone to use it as a
proxy. 


## Taking a look at the Game Center traffic

Lets take a first look at the Game Center traffic. The game I'll use in this
tutorial is [Super Mega
Worm](http://itunes.apple.com/us/app/super-mega-worm/id388541990?mt=8) - a
great little retro-apocalyptic sidescroller for the iPhone: 

<center>
    <img src="@!urlTo("tutorials/supermega.png")!@"/>
</center>

After finishing a game (take your time), watch the traffic flowing through
mitmproxy:

<center>
    <img src="@!urlTo("tutorials/one.png")!@"/>
</center>

We see a bunch of things we might expect - initialisation, the retrieval of
leaderboards and so forth. Then, right at the end, there's a POST to this
tantalising URL:

<pre>
https://service.gc.apple.com/WebObjects/GKGameStatsService.woa/wa/submitScore
</pre>

The contents of the submission are particularly interesting:

<!--(block|syntax("xml"))-->
<plist version="1.0">
<dict>
    <key>category</key>
    <string>SMW_Adv_USA1</string>
    <key>score-value</key>
    <integer>55</integer>
    <key>timestamp</key>
    <integer>1301553284461</integer>
</dict>
</plist>
<!--(end)-->

This is a [property list](http://en.wikipedia.org/wiki/Property_list),
containing an identifier for the game, a score (55, in this case), and a
timestamp. Looks pretty simple to mess with.


## Modifying and replaying the score submission

Lets edit the score submission. First, select it in mitmproxy, then press
__enter__ to view it. Make sure you're viewing the request, not the response -
you can use __tab__ to flick between the two. Now press __e__ for edit. You'll
be prompted for the part of the request you want to change - press __b__ for
body.  Your preferred editor (taken from the EDITOR environment variable) will
now fire up. Lets bump the score up to something a bit more ambitious:

<!--(block|syntax("xml"))-->
<plist version="1.0">
<dict>
    <key>category</key>
    <string>SMW_Adv_USA1</string>
    <key>score-value</key>
    <integer>2200272667</integer>
    <key>timestamp</key>
    <integer>1301553284461</integer>
</dict>
</plist>
<!--(end)-->

Save the file and exit your editor. 

The final step is to replay this modified request. Simply press __r__ for
replay.

## The glorious result and some intrigue

<center>
    <img src="@!urlTo("tutorials/leaderboard.png")!@"/>
</center>

And that's it - according to the records, I am the greatest Super Mega Worm
player of all time. 

Curiously, the top competitors' scores are all the same: 2,147,483,647. If you
think that number seems familiar, you're right: it's 2^31-1, the maximum value
you can fit into a signed 32-bit int. Now let me tell you another peculiar
thing about Super Mega Worm - at the end of every game, it submits your highest
previous score to the Game Center, not your current score.  This means that it
stores your highscore somewhere, and I'm guessing that it reads that stored
score back into a signed integer. So, if you _were_ to cheat by the relatively
pedestrian means of modifying the saved score on your jailbroken phone, then
2^31-1 might well be the maximum score you could get. Then again, if the game
itself stores its score in a signed 32-bit int, you could get the same score
through perfect play, effectively beating the game. So, which is it in this
case? I'll leave that for you to decide.
a> 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689
/*
    ChibiOS/RT - Copyright (C) 2006,2007,2008,2009,2010,
                 2011 Giovanni Di Sirio.

    This file is part of ChibiOS/RT.

    ChibiOS/RT is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 3 of the License, or
    (at your option) any later version.

    ChibiOS/RT is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

/**
 * @file    usb.c
 * @brief   USB Driver code.
 *
 * @addtogroup USB
 * @{
 */

#include <string.h>

#include "ch.h"
#include "hal.h"
#include "usb.h"

#if HAL_USE_USB || defined(__DOXYGEN__)

/*===========================================================================*/
/* Driver exported variables.                                                */
/*===========================================================================*/

/*===========================================================================*/
/* Driver local variables.                                                   */
/*===========================================================================*/

static const uint8_t zero_status[] = {0x00, 0x00};
static const uint8_t active_status[] ={0x00, 0x00};
static const uint8_t halted_status[] = {0x01, 0x00};

/*===========================================================================*/
/* Driver local functions.                                                   */
/*===========================================================================*/

/**
 * @brief  SET ADDRESS transaction callback.
 *
 * @param[in] usbp      pointer to the @p USBDriver object
 */
static void set_address(USBDriver *usbp) {

  usbp->address = usbp->setup[2];
  usb_lld_set_address(usbp);
  _usb_isr_invoke_event_cb(usbp, USB_EVENT_ADDRESS);
  usbp->state = USB_SELECTED;
}

/**
 * @brief   Standard requests handler.
 * @details This is the standard requests default handler, most standard
 *          requests are handled here, the user can override the standard
 *          handling using the @p requests_hook_cb hook in the
 *          @p USBConfig structure.
 *
 * @param[in] usbp      pointer to the @p USBDriver object
 * @return              The request handling exit code.
 * @retval FALSE        Request not recognized by the handler or error.
 * @retval TRUE         Request handled.
 */
static bool_t default_handler(USBDriver *usbp) {
  const USBDescriptor *dp;

  /* Decoding the request.*/
  switch (((usbp->setup[0] & (USB_RTYPE_RECIPIENT_MASK |
                              USB_RTYPE_TYPE_MASK)) |
           (usbp->setup[1] << 8))) {
  case USB_RTYPE_RECIPIENT_DEVICE | (USB_REQ_GET_STATUS << 8):
    /* Just returns the current status word.*/
    usbSetupTransfer(usbp, (uint8_t *)&usbp->status, 2, NULL);
    return TRUE;
  case USB_RTYPE_RECIPIENT_DEVICE | (USB_REQ_CLEAR_FEATURE << 8):
    /* Only the DEVICE_REMOTE_WAKEUP is handled here, any other feature
       number is handled as an error.*/
    if (usbp->setup[2] == USB_FEATURE_DEVICE_REMOTE_WAKEUP) {
      usbp->status &= ~2;
      usbSetupTransfer(usbp, NULL, 0, NULL);
      return TRUE;
    }
    return FALSE;
  case USB_RTYPE_RECIPIENT_DEVICE | (USB_REQ_SET_FEATURE << 8):
    /* Only the DEVICE_REMOTE_WAKEUP is handled here, any other feature
       number is handled as an error.*/
    if (usbp->setup[2] == USB_FEATURE_DEVICE_REMOTE_WAKEUP) {
      usbp->status |= 2;
      usbSetupTransfer(usbp, NULL, 0, NULL);
      return TRUE;
    }
    return FALSE;
  case USB_RTYPE_RECIPIENT_DEVICE | (USB_REQ_SET_ADDRESS << 8):
    /* The SET_ADDRESS handling can be performed here or postponed after
       the status packed depending on the USB_SET_ADDRESS_MODE low
       driver setting.*/
#if USB_SET_ADDRESS_MODE == USB_EARLY_SET_ADDRESS
    if ((usbp->setup[0] == USB_RTYPE_RECIPIENT_DEVICE) &&
        (usbp->setup[1] == USB_REQ_SET_ADDRESS))
      set_address(usbp);
    usbSetupTransfer(usbp, NULL, 0, NULL);
#else
    usbSetupTransfer(usbp, NULL, 0, set_address);
#endif
    return TRUE;
  case USB_RTYPE_RECIPIENT_DEVICE | (USB_REQ_GET_DESCRIPTOR << 8):
    /* Handling descriptor requests from the host.*/
    dp = usbp->config->get_descriptor_cb(
           usbp, usbp->setup[3], usbp->setup[2],
           usb_lld_fetch_word(&usbp->setup[4]));
    if (dp == NULL)
      return FALSE;
    usbSetupTransfer(usbp, (uint8_t *)dp->ud_string, dp->ud_size, NULL);
    return TRUE;
  case USB_RTYPE_RECIPIENT_DEVICE | (USB_REQ_GET_CONFIGURATION << 8):
    /* Returning the last selected configuration.*/
    usbSetupTransfer(usbp, &usbp->configuration, 1, NULL);
    return TRUE;
  case USB_RTYPE_RECIPIENT_DEVICE | (USB_REQ_SET_CONFIGURATION << 8):
    /* Handling configuration selection from the host.*/
    usbp->configuration = usbp->setup[2];
    if (usbp->configuration == 0)
      usbp->state = USB_SELECTED;
    else
      usbp->state = USB_ACTIVE;
    _usb_isr_invoke_event_cb(usbp, USB_EVENT_CONFIGURED);
    usbSetupTransfer(usbp, NULL, 0, NULL);
    return TRUE;
  case USB_RTYPE_RECIPIENT_INTERFACE | (USB_REQ_GET_STATUS << 8):
  case USB_RTYPE_RECIPIENT_ENDPOINT | (USB_REQ_SYNCH_FRAME << 8):
    /* Just sending two zero bytes, the application can change the behavior
       using a hook..*/
    usbSetupTransfer(usbp, (uint8_t *)zero_status, 2, NULL);
    return TRUE;
  case USB_RTYPE_RECIPIENT_ENDPOINT | (USB_REQ_GET_STATUS << 8):
    /* Sending the EP status.*/
    if (usbp->setup[4] & 0x80) {
      switch (usb_lld_get_status_in(usbp, usbp->setup[4] & 0x0F)) {
      case EP_STATUS_STALLED:
        usbSetupTransfer(usbp, (uint8_t *)halted_status, 2, NULL);
        return TRUE;
      case EP_STATUS_ACTIVE:
        usbSetupTransfer(usbp, (uint8_t *)active_status, 2, NULL);
        return TRUE;
      default:
        return FALSE;
      }
    }
    else {
      switch (usb_lld_get_status_out(usbp, usbp->setup[4] & 0x0F)) {
      case EP_STATUS_STALLED:
        usbSetupTransfer(usbp, (uint8_t *)halted_status, 2, NULL);
        return TRUE;
      case EP_STATUS_ACTIVE:
        usbSetupTransfer(usbp, (uint8_t *)active_status, 2, NULL);
        return TRUE;
      default:
        return FALSE;
      }
    }
  case USB_RTYPE_RECIPIENT_ENDPOINT | (USB_REQ_CLEAR_FEATURE << 8):
    /* Only ENDPOINT_HALT is handled as feature.*/
    if (usbp->setup[2] != USB_FEATURE_ENDPOINT_HALT)
      return FALSE;
    /* Clearing the EP status, not valid for EP0, it is ignored in that case.*/
    if ((usbp->setup[4] & 0x0F) > 0) {
      if (usbp->setup[4] & 0x80)
        usb_lld_clear_in(usbp, usbp->setup[4] & 0x0F);
      else
        usb_lld_clear_out(usbp, usbp->setup[4] & 0x0F);
    }
    usbSetupTransfer(usbp, NULL, 0, NULL);
    return TRUE;
  case USB_RTYPE_RECIPIENT_ENDPOINT | (USB_REQ_SET_FEATURE << 8):
    /* Only ENDPOINT_HALT is handled as feature.*/
    if (usbp->setup[2] != USB_FEATURE_ENDPOINT_HALT)
      return FALSE;
    /* Stalling the EP, not valid for EP0, it is ignored in that case.*/
    if ((usbp->setup[4] & 0x0F) > 0) {
      if (usbp->setup[4] & 0x80)
        usb_lld_stall_in(usbp, usbp->setup[4] & 0x0F);
      else
        usb_lld_stall_out(usbp, usbp->setup[4] & 0x0F);
    }
    usbSetupTransfer(usbp, NULL, 0, NULL);
    return TRUE;
  case USB_RTYPE_RECIPIENT_DEVICE | (USB_REQ_SET_DESCRIPTOR << 8):
  case USB_RTYPE_RECIPIENT_INTERFACE | (USB_REQ_CLEAR_FEATURE << 8):
  case USB_RTYPE_RECIPIENT_INTERFACE | (USB_REQ_SET_FEATURE << 8):
  case USB_RTYPE_RECIPIENT_INTERFACE | (USB_REQ_GET_INTERFACE << 8):
  case USB_RTYPE_RECIPIENT_INTERFACE | (USB_REQ_SET_INTERFACE << 8):
    /* All the above requests are not handled here, if you need them then
       use the hook mechanism and provide handling.*/
  default:
    return FALSE;
  }
}

/*===========================================================================*/
/* Driver exported functions.                                                */
/*===========================================================================*/

/**
 * @brief   USB Driver initialization.
 * @note    This function is implicitly invoked by @p halInit(), there is
 *          no need to explicitly initialize the driver.
 *
 * @init
 */
void usbInit(void) {

  usb_lld_init();
}

/**
 * @brief   Initializes the standard part of a @p USBDriver structure.
 *
 * @param[out] usbp     pointer to the @p USBDriver object
 *
 * @init
 */
void usbObjectInit(USBDriver *usbp) {

  usbp->state        = USB_STOP;
  usbp->config       = NULL;
  usbp->param        = NULL;
  usbp->transmitting = 0;
  usbp->receiving    = 0;
}

/**
 * @brief   Configures and activates the USB peripheral.
 *
 * @param[in] usbp      pointer to the @p USBDriver object
 * @param[in] config    pointer to the @p USBConfig object
 *
 * @api
 */
void usbStart(USBDriver *usbp, const USBConfig *config) {
  unsigned i;

  chDbgCheck((usbp != NULL) && (config != NULL), "usbStart");

  chSysLock();
  chDbgAssert((usbp->state == USB_STOP) || (usbp->state == USB_READY),
              "usbStart(), #1", "invalid state");
  usbp->config = config;
  for (i = 0; i <= USB_MAX_ENDPOINTS; i++)
    usbp->epc[i] = NULL;
  usb_lld_start(usbp);
  usbp->state = USB_READY;
  chSysUnlock();
}

/**
 * @brief   Deactivates the USB peripheral.
 *
 * @param[in] usbp      pointer to the @p USBDriver object
 *
 * @api
 */
void usbStop(USBDriver *usbp) {

  chDbgCheck(usbp != NULL, "usbStop");

  chSysLock();
  chDbgAssert((usbp->state == USB_STOP) || (usbp->state == USB_READY),
              "usbStop(), #1", "invalid state");
  usb_lld_stop(usbp);
  usbp->state = USB_STOP;
  chSysUnlock();
}

/**
 * @brief   Enables an endpoint.
 * @details This function enables an endpoint, both IN and/or OUT directions
 *          depending on the configuration structure.
 * @note    This function must be invoked in response of a SET_CONFIGURATION
 *          or SET_INTERFACE message.
 *
 * @param[in] usbp      pointer to the @p USBDriver object
 * @param[in] ep        endpoint number
 * @param[in] epcp      the endpoint configuration
 *
 * @iclass
 */
void usbInitEndpointI(USBDriver *usbp, usbep_t ep,
                      const USBEndpointConfig *epcp) {

  chDbgAssert(usbp->state == USB_ACTIVE,
              "usbEnableEndpointI(), #1", "invalid state");
  chDbgAssert(usbp->epc[ep] != NULL,
              "usbEnableEndpointI(), #2", "already initialized");

  /* Logically enabling the endpoint in the USBDriver structure.*/
  if (!(epcp->ep_mode & USB_EP_MODE_PACKET)) {
    memset(epcp->in_state, 0, sizeof(USBInEndpointState));
    memset(epcp->out_state, 0, sizeof(USBOutEndpointState));
  }
  usbp->epc[ep] = epcp;

  /* Low level endpoint activation.*/
  usb_lld_init_endpoint(usbp, ep);
}

/**
 * @brief   Disables all the active endpoints.
 * @details This function disables all the active endpoints except the
 *          endpoint zero.
 * @note    This function must be invoked in response of a SET_CONFIGURATION
 *          message with configuration number zero.
 *
 * @param[in] usbp      pointer to the @p USBDriver object
 *
 * @iclass
 */
void usbDisableEndpointsI(USBDriver *usbp) {
  unsigned i;

  chDbgAssert(usbp->state == USB_SELECTED,
              "usbDisableEndpointsI(), #1", "invalid state");

  usbp->transmitting &= ~1;
  usbp->receiving    &= ~1;
  for (i = 1; i <= USB_MAX_ENDPOINTS; i++)
    usbp->epc[i] = NULL;

  /* Low level endpoints deactivation.*/
  usb_lld_disable_endpoints(usbp);
}

/**
 * @brief   Reads a packet from the dedicated packet buffer.
 * @pre     In order to use this function he endpoint must have been
 *          initialized in packet mode.
 * @post    The endpoint is ready to accept another packet.
 *
 * @param[in] usbp      pointer to the @p USBDriver object
 * @param[in] ep        endpoint number
 * @param[out] buf      buffer where to copy the packet data
 * @param[in] n         maximum number of bytes to copy. This value must
 *                      not exceed the maximum packet size for this endpoint.
 * @return              The received packet size regardless the specified
 *                      @p n parameter.
 * @retval USB_ENDPOINT_BUSY Endpoint busy receiving.
 * @retval 0            Zero size packet received.
 *
 * @iclass
 */
size_t usbReadPacketI(USBDriver *usbp, usbep_t ep,
                      uint8_t *buf, size_t n) {

  if (usbGetReceiveStatusI(usbp, ep))
    return USB_ENDPOINT_BUSY;

  usbp->receiving |= (1 << ep);
  return usb_lld_read_packet(usbp, ep, buf, n);;
}

/**
 * @brief   Writes a packet to the dedicated packet buffer.
 * @pre     In order to use this function he endpoint must have been
 *          initialized in packet mode.
 * @post    The endpoint is ready to transmit the packet.
 *
 * @param[in] usbp      pointer to the @p USBDriver object
 * @param[in] ep        endpoint number
 * @param[in] buf       buffer where to fetch the packet data
 * @param[in] n         maximum number of bytes to copy. This value must
 *                      not exceed the maximum packet size for this endpoint.
 * @return              The operation status.
 * @retval USB_ENDPOINT_BUSY Endpoint busy transmitting.
 * @retval 0            Operation complete.
 *
 * @iclass
 */
size_t usbWritePacketI(USBDriver *usbp, usbep_t ep,
                       const uint8_t *buf, size_t n) {

  if (usbGetTransmitStatusI(usbp, ep))
    return USB_ENDPOINT_BUSY;

  usbp->transmitting |= (1 << ep);
  usb_lld_write_packet(usbp, ep, buf, n);
  return 0;
}

/**
 * @brief   Starts a receive transaction on an OUT endpoint.
 * @pre     In order to use this function he endpoint must have been
 *          initialized in transaction mode.
 * @post    The endpoint callback is invoked when the transfer has been
 *          completed.
 *
 * @param[in] usbp      pointer to the @p USBDriver object
 * @param[in] ep        endpoint number
 * @param[out] buf      buffer where to copy the received data
 * @param[in] n         maximum number of bytes to copy
 * @return              The operation status.
 * @retval FALSE        Operation started successfully.
 * @retval TRUE         Endpoint busy, operation not started.
 *
 * @iclass
 */
bool_t usbStartReceiveI(USBDriver *usbp, usbep_t ep,
                        uint8_t *buf, size_t n) {

  if (usbGetReceiveStatusI(usbp, ep))
    return TRUE;

  usbp->receiving |= (1 << ep);
  usb_lld_start_out(usbp, ep, buf, n);
  return FALSE;
}

/**
 * @brief   Starts a transmit transaction on an IN endpoint.
 * @pre     In order to use this function he endpoint must have been
 *          initialized in transaction mode.
 * @post    The endpoint callback is invoked when the transfer has been
 *          completed.
 *
 * @param[in] usbp      pointer to the @p USBDriver object
 * @param[in] ep        endpoint number
 * @param[in] buf       buffer where to fetch the data to be transmitted
 * @param[in] n         maximum number of bytes to copy
 * @return              The operation status.
 * @retval FALSE        Operation started successfully.
 * @retval TRUE         Endpoint busy, operation not started.
 *
 * @iclass
 */
bool_t usbStartTransmitI(USBDriver *usbp, usbep_t ep,
                         const uint8_t *buf, size_t n) {

  if (usbGetTransmitStatusI(usbp, ep))
    return TRUE;

  usbp->transmitting |= (1 << ep);
  usb_lld_start_in(usbp, ep, buf, n);
  return FALSE;
}

/**
 * @brief   Stalls an OUT endpoint.
 *
 * @param[in] usbp      pointer to the @p USBDriver object
 * @param[in] ep        endpoint number
 * @return              The operation status.
 * @retval FALSE        Endpoint stalled.
 * @retval TRUE         Endpoint busy, not stalled.
 *
 * @iclass
 */
bool_t usbStallReceiveI(USBDriver *usbp, usbep_t ep) {

  if (usbGetReceiveStatusI(usbp, ep))
    return TRUE;

  usb_lld_stall_out(usbp, ep);
  return FALSE;
}

/**
 * @brief   Stalls an IN endpoint.
 *
 * @param[in] usbp      pointer to the @p USBDriver object
 * @param[in] ep        endpoint number
 * @return              The operation status.
 * @retval FALSE        Endpoint stalled.
 * @retval TRUE         Endpoint busy, not stalled.
 *
 * @iclass
 */
bool_t usbStallTransmitI(USBDriver *usbp, usbep_t ep) {

  if (usbGetTransmitStatusI(usbp, ep))
    return TRUE;

  usb_lld_stall_in(usbp, ep);
  return FALSE;
}

/**
 * @brief   USB reset routine.
 * @details This function must be invoked when an USB bus reset condition is
 *          detected.
 *
 * @param[in] usbp      pointer to the @p USBDriver object
 *
 * @notapi
 */
void _usb_reset(USBDriver *usbp) {
  unsigned i;

  usbp->state         = USB_READY;
  usbp->status        = 0;
  usbp->address       = 0;
  usbp->configuration = 0;
  usbp->transmitting  = 0;
  usbp->receiving     = 0;

  /* Invalidates all endpoints into the USBDriver structure.*/
  for (i = 0; i <= USB_MAX_ENDPOINTS; i++)
    usbp->epc[i] = NULL;

  /* EP0 state machine initialization.*/
  usbp->ep0state = USB_EP0_WAITING_SETUP;

  /* Low level reset.*/
  usb_lld_reset(usbp);
}

/**
 * @brief   Default EP0 SETUP callback.
 * @details This function is used by the low level driver as default handler
 *          for EP0 SETUP events.
 *
 * @param[in] usbp      pointer to the @p USBDriver object
 * @param[in] ep        endpoint number, always zero
 *
 * @notapi
 */
void _usb_ep0setup(USBDriver *usbp, usbep_t ep) {
  size_t max;

  usbp->ep0state = USB_EP0_WAITING_SETUP;
  usbReadSetup(usbp, ep, usbp->setup);

  /* First verify if the application has an handler installed for this
     request.*/
  if (!(usbp->config->requests_hook_cb) ||
      !(usbp->config->requests_hook_cb(usbp))) {
    /* Invoking the default handler, if this fails then stalls the
       endpoint zero as error.*/
    if (((usbp->setup[0] & USB_RTYPE_TYPE_MASK) != USB_RTYPE_TYPE_STD) ||
        !default_handler(usbp)) {
      /* Error response, the state machine goes into an error state, the low
         level layer will have to reset it to USB_EP0_WAITING_SETUP after
         receiving a SETUP packet.*/
      usb_lld_stall_in(usbp, 0);
      usb_lld_stall_out(usbp, 0);
      _usb_isr_invoke_event_cb(usbp, USB_EVENT_STALLED);
      usbp->ep0state = USB_EP0_ERROR;
    }
  }

  /* Transfer preparation. The request handler must have populated
     correctly the fields ep0next, ep0n and ep0endcb using the macro
     usbSetupTransfer().*/
  max = usb_lld_fetch_word(&usbp->setup[6]);
  /* The transfer size cannot exceed the specified amount.*/
  if (usbp->ep0n > max)
    usbp->ep0n = max;
  if ((usbp->setup[0] & USB_RTYPE_DIR_MASK) == USB_RTYPE_DIR_DEV2HOST) {
    /* IN phase.*/
    if (usbp->ep0n > 0) {
      /* Starts the transmit phase.*/
      usbp->ep0state = USB_EP0_TX;
      usb_lld_start_in(usbp, 0, usbp->ep0next, usbp->ep0n);
    }
    else {
      /* No transmission phase, directly receiving the zero sized status
         packet.*/
      usbp->ep0state = USB_EP0_WAITING_STS;
      usb_lld_start_out(usbp, 0, NULL, 0);
    }
  }
  else {
    /* OUT phase.*/
    if (usbp->ep0n > 0) {
      /* Starts the receive phase.*/
      usbp->ep0state = USB_EP0_RX;
      usb_lld_start_out(usbp, 0, usbp->ep0next, usbp->ep0n);
    }
    else {
      /* No receive phase, directly sending the zero sized status
         packet.*/
      usbp->ep0state = USB_EP0_SENDING_STS;
      usb_lld_start_in(usbp, 0, NULL, 0);
    }
  }
}

/**
 * @brief   Default EP0 IN callback.
 * @details This function is used by the low level driver as default handler
 *          for EP0 IN events.
 *
 * @param[in] usbp      pointer to the @p USBDriver object
 * @param[in] ep        endpoint number, always zero
 *
 * @notapi
 */
void _usb_ep0in(USBDriver *usbp, usbep_t ep) {
  size_t max;

  (void)ep;
  switch (usbp->ep0state) {
  case USB_EP0_TX:
    max = usb_lld_fetch_word(&usbp->setup[6]);
     /* If the transmitted size is less than the requested size and it is a
        multiple of the maximum packet size then a zero size packet must be
        transmitted.*/
     if ((usbp->ep0n < max) &&
         ((usbp->ep0n % usbp->epc[0]->in_maxsize) == 0)) {
       usb_lld_start_in(usbp, 0, NULL, 0);
       return;
     }

     /* Transmit phase over, receiving the zero sized status packet.*/
     usbp->ep0state = USB_EP0_WAITING_STS;
     usb_lld_start_out(usbp, 0, NULL, 0);
    return;
  case USB_EP0_SENDING_STS:
    /* Status packet sent, invoking the callback if defined.*/
    if (usbp->ep0endcb != NULL)
      usbp->ep0endcb(usbp);
    usbp->ep0state = USB_EP0_WAITING_SETUP;
    return;
  default:
    ;
  }
  /* Error response, the state machine goes into an error state, the low
     level layer will have to reset it to USB_EP0_WAITING_SETUP after
     receiving a SETUP packet.*/
  usb_lld_stall_in(usbp, 0);
  usb_lld_stall_out(usbp, 0);
  _usb_isr_invoke_event_cb(usbp, USB_EVENT_STALLED);
  usbp->ep0state = USB_EP0_ERROR;
}

/**
 * @brief   Default EP0 OUT callback.
 * @details This function is used by the low level driver as default handler
 *          for EP0 OUT events.
 *
 * @param[in] usbp      pointer to the @p USBDriver object
 * @param[in] ep        endpoint number, always zero
 *
 * @notapi
 */
void _usb_ep0out(USBDriver *usbp, usbep_t ep) {

  (void)ep;
  switch (usbp->ep0state) {
  case USB_EP0_RX:
    /* Receive phase over, sending the zero sized status packet.*/
    usbp->ep0state = USB_EP0_SENDING_STS;
    usb_lld_start_in(usbp, 0, NULL, 0);
    return;
  case USB_EP0_WAITING_STS:
    /* Status packet received, it must be zero sized, invoking the callback
       if defined.*/
    if (usbGetReceiveTransactionSizeI(usbp, 0) != 0)
      break;
    if (usbp->ep0endcb != NULL)
      usbp->ep0endcb(usbp);
    usbp->ep0state = USB_EP0_WAITING_SETUP;
    return;
  default:
    ;
  }
  /* Error response, the state machine goes into an error state, the low
     level layer will have to reset it to USB_EP0_WAITING_SETUP after
     receiving a SETUP packet.*/
  usb_lld_stall_in(usbp, 0);
  usb_lld_stall_out(usbp, 0);
  _usb_isr_invoke_event_cb(usbp, USB_EVENT_STALLED);
  usbp->ep0state = USB_EP0_ERROR;
}

#endif /* HAL_USE_USB */

/** @} */