static int atusb_get_and_conf_chip(struct atusb *atusb)
{
	struct usb_device *usb_dev = atusb->usb_dev;
	u8 man_id_0, man_id_1, part_num, version_num;
	const char *chip;
	struct ieee802154_hw *hw = atusb->hw;

	man_id_0 = atusb_read_reg(atusb, RG_MAN_ID_0);
	man_id_1 = atusb_read_reg(atusb, RG_MAN_ID_1);
	part_num = atusb_read_reg(atusb, RG_PART_NUM);
	version_num = atusb_read_reg(atusb, RG_VERSION_NUM);

	if (atusb->err)
		return atusb->err;

	hw->flags = IEEE802154_HW_TX_OMIT_CKSUM | IEEE802154_HW_AFILT |
		    IEEE802154_HW_PROMISCUOUS | IEEE802154_HW_CSMA_PARAMS;

	hw->phy->flags = WPAN_PHY_FLAG_TXPOWER | WPAN_PHY_FLAG_CCA_ED_LEVEL |
			 WPAN_PHY_FLAG_CCA_MODE;

	hw->phy->supported.cca_modes = BIT(NL802154_CCA_ENERGY) |
				       BIT(NL802154_CCA_CARRIER) |
				       BIT(NL802154_CCA_ENERGY_CARRIER);
	hw->phy->supported.cca_opts = BIT(NL802154_CCA_OPT_ENERGY_CARRIER_AND) |
				      BIT(NL802154_CCA_OPT_ENERGY_CARRIER_OR);

	hw->phy->cca.mode = NL802154_CCA_ENERGY;

	hw->phy->current_page = 0;

	if ((man_id_1 << 8 | man_id_0) != ATUSB_JEDEC_ATMEL) {
		dev_err(&usb_dev->dev,
			"non-Atmel transceiver xxxx%02x%02x\n",
			man_id_1, man_id_0);
		goto fail;
	}

	switch (part_num) {
	case 2:
		chip = "AT86RF230";
		atusb->hw->phy->supported.channels[0] = 0x7FFF800;
		atusb->hw->phy->current_channel = 11;	/* reset default */
		atusb->hw->phy->symbol_duration = 16;
		atusb->hw->phy->supported.tx_powers = atusb_powers;
		atusb->hw->phy->supported.tx_powers_size = ARRAY_SIZE(atusb_powers);
		hw->phy->supported.cca_ed_levels = atusb_ed_levels;
		hw->phy->supported.cca_ed_levels_size = ARRAY_SIZE(atusb_ed_levels);
		break;
	case 3:
		chip = "AT86RF231";
		atusb->hw->phy->supported.channels[0] = 0x7FFF800;
		atusb->hw->phy->current_channel = 11;	/* reset default */
		atusb->hw->phy->symbol_duration = 16;
		atusb->hw->phy->supported.tx_powers = atusb_powers;
		atusb->hw->phy->supported.tx_powers_size = ARRAY_SIZE(atusb_powers);
		hw->phy->supported.cca_ed_levels = atusb_ed_levels;
		hw->phy->supported.cca_ed_levels_size = ARRAY_SIZE(atusb_ed_levels);
		break;
	case 7:
		chip = "AT86RF212";
		atusb->hw->flags |= IEEE802154_HW_LBT;
		atusb->hw->phy->supported.channels[0] = 0x00007FF;
		atusb->hw->phy->supported.channels[2] = 0x00007FF;
		atusb->hw->phy->current_channel = 5;
		atusb->hw->phy->symbol_duration = 25;
		atusb->hw->phy->supported.lbt = NL802154_SUPPORTED_BOOL_BOTH;
		atusb->hw->phy->supported.tx_powers = at86rf212_powers;
		atusb->hw->phy->supported.tx_powers_size = ARRAY_SIZE(at86rf212_powers);
		atusb->hw->phy->supported.cca_ed_levels = at86rf212_ed_levels_100;
		atusb->hw->phy->supported.cca_ed_levels_size = ARRAY_SIZE(at86rf212_ed_levels_100);
		break;
	default:
		dev_err(&usb_dev->dev,
			"unexpected transceiver, part 0x%02x version 0x%02x\n",
			part_num, version_num);
		goto fail;
	}

	hw->phy->transmit_power = hw->phy->supported.tx_powers[0];
	hw->phy->cca_ed_level = hw->phy->supported.cca_ed_levels[7];

	dev_info(&usb_dev->dev, "ATUSB: %s version %d\n", chip, version_num);

	return 0;

fail:
	atusb->err = -ENODEV;
	return -ENODEV;
}


static int atusb_write_subreg(struct atusb *atusb, u8 reg, u8 mask,
			      u8 shift, u8 value)
{
	struct usb_device *usb_dev = atusb->usb_dev;
	u8 orig, tmp;
	int ret = 0;

	dev_dbg(&usb_dev->dev, "%s: 0x%02x <- 0x%02x\n", __func__, reg, value);

	orig = atusb_read_reg(atusb, reg);

	/* Write the value only into that part of the register which is allowed
	 * by the mask. All other bits stay as before.
	 */
	tmp = orig & ~mask;
	tmp |= (value << shift) & mask;

	if (tmp != orig)
		ret = atusb_write_reg(atusb, reg, tmp);

	return ret;
}

ieee802154: atusb: fix use-after-free at disconnect

The disconnect callback was accessing the hardware-descriptor private
data after having having freed it.

Fixes: 7490b008d123 ("ieee802154: add support for atusb transceiver")
Cc: stable <stable@vger.kernel.org>     # 4.2
Cc: Alexander Aring <alex.aring@gmail.com>
Reported-by: syzbot+f4509a9138a1472e7e80@syzkaller.appspotmail.com
Signed-off-by: Johan Hovold <johan@kernel.org>
Signed-off-by: Stefan Schmidt <stefan@datenfreihafen.org>


static void atusb_free_urbs(struct atusb *atusb)
{
	struct urb *urb;

	while (1) {
		urb = usb_get_from_anchor(&atusb->idle_urbs);
		if (!urb)
			break;
		kfree_skb(urb->context);
		usb_free_urb(urb);
	}
}


static int atusb_set_extended_addr(struct atusb *atusb)
{
	struct usb_device *usb_dev = atusb->usb_dev;
	unsigned char *buffer;
	__le64 extended_addr;
	u64 addr;
	int ret;

	/* Firmware versions before 0.3 do not support the EUI64_READ command.
	 * Just use a random address and be done.
	 */
	if (atusb->fw_ver_maj == 0 && atusb->fw_ver_min < 3) {
		ieee802154_random_extended_addr(&atusb->hw->phy->perm_extended_addr);
		return 0;
	}

	buffer = kmalloc(IEEE802154_EXTENDED_ADDR_LEN, GFP_KERNEL);
	if (!buffer)
		return -ENOMEM;

	/* Firmware is new enough so we fetch the address from EEPROM */
	ret = atusb_control_msg(atusb, usb_rcvctrlpipe(usb_dev, 0),
				ATUSB_EUI64_READ, ATUSB_REQ_FROM_DEV, 0, 0,
				buffer, IEEE802154_EXTENDED_ADDR_LEN, 1000);
	if (ret < 0) {
		dev_err(&usb_dev->dev, "failed to fetch extended address, random address set\n");
		ieee802154_random_extended_addr(&atusb->hw->phy->perm_extended_addr);
		kfree(buffer);
		return ret;
	}

	memcpy(&extended_addr, buffer, IEEE802154_EXTENDED_ADDR_LEN);
	/* Check if read address is not empty and the unicast bit is set correctly */
	if (!ieee802154_is_valid_extended_unicast_addr(extended_addr)) {
		dev_info(&usb_dev->dev, "no permanent extended address found, random address set\n");
		ieee802154_random_extended_addr(&atusb->hw->phy->perm_extended_addr);
	} else {
		atusb->hw->phy->perm_extended_addr = extended_addr;
		addr = swab64((__force u64)atusb->hw->phy->perm_extended_addr);
		dev_info(&usb_dev->dev, "Read permanent extended address %8phC from device\n",
			 &addr);
	}

	kfree(buffer);
	return ret;
}


static int atusb_write_reg(struct atusb *atusb, u8 reg, u8 value)
{
	struct usb_device *usb_dev = atusb->usb_dev;

	dev_dbg(&usb_dev->dev, "%s: 0x%02x <- 0x%02x\n", __func__, reg, value);
	return atusb_control_msg(atusb, usb_sndctrlpipe(usb_dev, 0),
				 ATUSB_REG_WRITE, ATUSB_REQ_TO_DEV,
				 value, reg, NULL, 0, 1000);
}


static int atusb_alloc_urbs(struct atusb *atusb, int n)
{
	struct urb *urb;

	while (n) {
		urb = usb_alloc_urb(0, GFP_KERNEL);
		if (!urb) {
			atusb_free_urbs(atusb);
			return -ENOMEM;
		}
		usb_anchor_urb(urb, &atusb->idle_urbs);
		n--;
	}
	return 0;
}


static void atusb_stop(struct ieee802154_hw *hw)
{
	struct atusb *atusb = hw->priv;
	struct usb_device *usb_dev = atusb->usb_dev;

	dev_dbg(&usb_dev->dev, "%s\n", __func__);
	usb_kill_anchored_urbs(&atusb->idle_urbs);
	atusb_command(atusb, ATUSB_RX_MODE, 0);
	atusb_get_and_clear_error(atusb);
}


static int atusb_read_subreg(struct atusb *lp,
			     unsigned int addr, unsigned int mask,
			     unsigned int shift)
{
	int rc;

	rc = atusb_read_reg(lp, addr);
	rc = (rc & mask) >> shift;

	return rc;
}


static int hulusb_set_channel(struct ieee802154_hw *hw, u8 page, u8 channel)
{
	int rc;
	int rssi_base_val;

	struct atusb *lp = hw->priv;

	if (channel == 0)
		rc = atusb_write_subreg(lp, SR_SUB_MODE, 0);
	else
		rc = atusb_write_subreg(lp, SR_SUB_MODE, 1);
	if (rc < 0)
		return rc;

	if (page == 0) {
		rc = atusb_write_subreg(lp, SR_BPSK_QPSK, 0);
		rssi_base_val = -100;
	} else {
		rc = atusb_write_subreg(lp, SR_BPSK_QPSK, 1);
		rssi_base_val = -98;
	}
	if (rc < 0)
		return rc;

	rc = hulusb_set_cca_ed_level(lp, rssi_base_val);
	if (rc < 0)
		return rc;

	/* This sets the symbol_duration according frequency on the 212.
	 * TODO move this handling while set channel and page in cfg802154.
	 * We can do that, this timings are according 802.15.4 standard.
	 * If we do that in cfg802154, this is a more generic calculation.
	 *
	 * This should also protected from ifs_timer. Means cancel timer and
	 * init with a new value. For now, this is okay.
	 */
	if (channel == 0) {
		if (page == 0) {
			/* SUB:0 and BPSK:0 -> BPSK-20 */
			lp->hw->phy->symbol_duration = 50;
		} else {
			/* SUB:1 and BPSK:0 -> BPSK-40 */
			lp->hw->phy->symbol_duration = 25;
		}
	} else {
		if (page == 0)
			/* SUB:0 and BPSK:1 -> OQPSK-100/200/400 */
			lp->hw->phy->symbol_duration = 40;
		else
			/* SUB:1 and BPSK:1 -> OQPSK-250/500/1000 */
			lp->hw->phy->symbol_duration = 16;
	}

	lp->hw->phy->lifs_period = IEEE802154_LIFS_PERIOD *
				   lp->hw->phy->symbol_duration;
	lp->hw->phy->sifs_period = IEEE802154_SIFS_PERIOD *
				   lp->hw->phy->symbol_duration;

	return atusb_write_subreg(lp, SR_CHANNEL, channel);
}


static int atusb_channel(struct ieee802154_hw *hw, u8 page, u8 channel)
{
	struct atusb *atusb = hw->priv;
	int ret = -ENOTSUPP;

	if (atusb->data) {
		ret = atusb->data->set_channel(hw, page, channel);
		/* @@@ ugly synchronization */
		msleep(atusb->data->t_channel_switch);
	}

	return ret;
}


atusb_set_frame_retries(struct ieee802154_hw *hw, s8 retries)
{
	struct atusb *atusb = hw->priv;

	return atusb_write_subreg(atusb, SR_MAX_FRAME_RETRIES, retries);
}


static int atusb_control_msg(struct atusb *atusb, unsigned int pipe,
			     __u8 request, __u8 requesttype,
			     __u16 value, __u16 index,
			     void *data, __u16 size, int timeout)
{
	struct usb_device *usb_dev = atusb->usb_dev;
	int ret;

	if (atusb->err)
		return atusb->err;

	ret = usb_control_msg(usb_dev, pipe, request, requesttype,
			      value, index, data, size, timeout);
	if (ret < 0) {
		atusb->err = ret;
		dev_err(&usb_dev->dev,
			"%s: req 0x%02x val 0x%x idx 0x%x, error %d\n",
			__func__, request, value, index, ret);
	}
	return ret;
}


hulusb_set_txpower(struct ieee802154_hw *hw, s32 mbm)
{
	u32 i;

	for (i = 0; i < hw->phy->supported.tx_powers_size; i++) {
		if (hw->phy->supported.tx_powers[i] == mbm)
			return atusb_write_subreg(hw->priv, SR_TX_PWR_212, i);
	}

	return -EINVAL;
}


static void atusb_disconnect(struct usb_interface *interface)
{
	struct atusb *atusb = usb_get_intfdata(interface);

	dev_dbg(&atusb->usb_dev->dev, "%s\n", __func__);

	atusb->shutdown = 1;
	cancel_delayed_work_sync(&atusb->work);

	usb_kill_anchored_urbs(&atusb->rx_urbs);
	atusb_free_urbs(atusb);
	usb_kill_urb(atusb->tx_urb);
	usb_free_urb(atusb->tx_urb);

	ieee802154_unregister_hw(atusb->hw);

	usb_put_dev(atusb->usb_dev);

	ieee802154_free_hw(atusb->hw);

	usb_set_intfdata(interface, NULL);

	pr_debug("%s done\n", __func__);
}


static int atusb_get_and_show_build(struct atusb *atusb)
{
	struct usb_device *usb_dev = atusb->usb_dev;
	char *build;
	int ret;

	build = kmalloc(ATUSB_BUILD_SIZE + 1, GFP_KERNEL);
	if (!build)
		return -ENOMEM;

	ret = atusb_control_msg(atusb, usb_rcvctrlpipe(usb_dev, 0),
				ATUSB_BUILD, ATUSB_REQ_FROM_DEV, 0, 0,
				build, ATUSB_BUILD_SIZE, 1000);
	if (ret >= 0) {
		build[ret] = 0;
		dev_info(&usb_dev->dev, "Firmware: build %s\n", build);
	}

	kfree(build);
	return ret;
}


static void atusb_work_urbs(struct work_struct *work)
{
	struct atusb *atusb =
	    container_of(to_delayed_work(work), struct atusb, work);
	struct usb_device *usb_dev = atusb->usb_dev;
	struct urb *urb;
	int ret;

	if (atusb->shutdown)
		return;

	do {
		urb = usb_get_from_anchor(&atusb->idle_urbs);
		if (!urb)
			return;
		ret = atusb_submit_rx_urb(atusb, urb);
	} while (!ret);

	usb_anchor_urb(urb, &atusb->idle_urbs);
	dev_warn_ratelimited(&usb_dev->dev,
			     "atusb_in: can't allocate/submit URB (%d)\n", ret);
	schedule_delayed_work(&atusb->work,
			      msecs_to_jiffies(ATUSB_ALLOC_DELAY_MS) + 1);
}


static void atusb_in_good(struct urb *urb)
{
	struct usb_device *usb_dev = urb->dev;
	struct sk_buff *skb = urb->context;
	struct atusb *atusb = SKB_ATUSB(skb);
	u8 len, lqi;

	if (!urb->actual_length) {
		dev_dbg(&usb_dev->dev, "atusb_in: zero-sized URB ?\n");
		return;
	}

	len = *skb->data;

	if (urb->actual_length == 1) {
		atusb_tx_done(atusb, len);
		return;
	}

	if (len + 1 > urb->actual_length - 1) {
		dev_dbg(&usb_dev->dev, "atusb_in: frame len %d+1 > URB %u-1\n",
			len, urb->actual_length);
		return;
	}

	if (!ieee802154_is_valid_psdu_len(len)) {
		dev_dbg(&usb_dev->dev, "atusb_in: frame corrupted\n");
		return;
	}

	lqi = skb->data[len + 1];
	dev_dbg(&usb_dev->dev, "atusb_in: rx len %d lqi 0x%02x\n", len, lqi);
	skb_pull(skb, 1);	/* remove PHR */
	skb_trim(skb, len);	/* get payload only */
	ieee802154_rx_irqsafe(atusb->hw, skb, lqi);
	urb->context = NULL;	/* skb is gone */
}


atusb_set_txpower(struct ieee802154_hw *hw, s32 mbm)
{
	struct atusb *atusb = hw->priv;
	u32 i;

	for (i = 0; i < hw->phy->supported.tx_powers_size; i++) {
		if (hw->phy->supported.tx_powers[i] == mbm)
			return atusb_write_subreg(atusb, SR_TX_PWR_23X, i);
	}

	return -EINVAL;
}


static int atusb_get_and_clear_error(struct atusb *atusb)
{
	int err = atusb->err;

	atusb->err = 0;
	return err;
}


static void atusb_in(struct urb *urb)
{
	struct usb_device *usb_dev = urb->dev;
	struct sk_buff *skb = urb->context;
	struct atusb *atusb = SKB_ATUSB(skb);

	dev_dbg(&usb_dev->dev, "%s: status %d len %d\n", __func__,
		urb->status, urb->actual_length);
	if (urb->status) {
		if (urb->status == -ENOENT) { /* being killed */
			kfree_skb(skb);
			urb->context = NULL;
			return;
		}
		dev_dbg(&usb_dev->dev, "%s: URB error %d\n", __func__, urb->status);
	} else {
		atusb_in_good(urb);
	}

	usb_anchor_urb(urb, &atusb->idle_urbs);
	if (!atusb->shutdown)
		schedule_delayed_work(&atusb->work, 0);
}


atusb_txpower(struct ieee802154_hw *hw, s32 mbm)
{
	struct atusb *atusb = hw->priv;

	if (atusb->data)
		return atusb->data->set_txpower(hw, mbm);
	else
		return -ENOTSUPP;
}


static int atusb_command(struct atusb *atusb, u8 cmd, u8 arg)
{
	struct usb_device *usb_dev = atusb->usb_dev;

	dev_dbg(&usb_dev->dev, "%s: cmd = 0x%x\n", __func__, cmd);
	return atusb_control_msg(atusb, usb_sndctrlpipe(usb_dev, 0),
				 cmd, ATUSB_REQ_TO_DEV, arg, 0, NULL, 0, 1000);
}


static void atusb_tx_done(struct atusb *atusb, u8 seq)
{
	struct usb_device *usb_dev = atusb->usb_dev;
	u8 expect = atusb->tx_ack_seq;

	dev_dbg(&usb_dev->dev, "%s (0x%02x/0x%02x)\n", __func__, seq, expect);
	if (seq == expect) {
		/* TODO check for ifs handling in firmware */
		ieee802154_xmit_complete(atusb->hw, atusb->tx_skb, false);
	} else {
		/* TODO I experience this case when atusb has a tx complete
		 * irq before probing, we should fix the firmware it's an
		 * unlikely case now that seq == expect is then true, but can
		 * happen and fail with a tx_skb = NULL;
		 */
		ieee802154_wake_queue(atusb->hw);
		if (atusb->tx_skb)
			dev_kfree_skb_irq(atusb->tx_skb);
	}
}


static int atusb_ed(struct ieee802154_hw *hw, u8 *level)
{
	WARN_ON(!level);
	*level = 0xbe;
	return 0;
}


static int atusb_submit_rx_urb(struct atusb *atusb, struct urb *urb)
{
	struct usb_device *usb_dev = atusb->usb_dev;
	struct sk_buff *skb = urb->context;
	int ret;

	if (!skb) {
		skb = alloc_skb(MAX_RX_XFER, GFP_KERNEL);
		if (!skb) {
			dev_warn_ratelimited(&usb_dev->dev,
					     "atusb_in: can't allocate skb\n");
			return -ENOMEM;
		}
		skb_put(skb, MAX_RX_XFER);
		SKB_ATUSB(skb) = atusb;
	}

	usb_fill_bulk_urb(urb, usb_dev, usb_rcvbulkpipe(usb_dev, 1),
			  skb->data, MAX_RX_XFER, atusb_in, skb);
	usb_anchor_urb(urb, &atusb->rx_urbs);

	ret = usb_submit_urb(urb, GFP_KERNEL);
	if (ret) {
		usb_unanchor_urb(urb);
		kfree_skb(skb);
		urb->context = NULL;
	}
	return ret;
}


atusb_set_csma_params(struct ieee802154_hw *hw, u8 min_be, u8 max_be, u8 retries)
{
	struct atusb *atusb = hw->priv;
	int ret;

	ret = atusb_write_subreg(atusb, SR_MIN_BE, min_be);
	if (ret)
		return ret;

	ret = atusb_write_subreg(atusb, SR_MAX_BE, max_be);
	if (ret)
		return ret;

	return atusb_write_subreg(atusb, SR_MAX_CSMA_RETRIES, retries);
}


hulusb_set_lbt(struct ieee802154_hw *hw, bool on)
{
	struct atusb *atusb = hw->priv;

	return atusb_write_subreg(atusb, SR_CSMA_LBT_MODE, on);
}


atusb_set_cca_ed_level(struct ieee802154_hw *hw, s32 mbm)
{
	struct atusb *atusb = hw->priv;
	u32 i;

	for (i = 0; i < hw->phy->supported.cca_ed_levels_size; i++) {
		if (hw->phy->supported.cca_ed_levels[i] == mbm)
			return atusb_write_subreg(atusb, SR_CCA_ED_THRES, i);
	}

	return -EINVAL;
}


static int atusb_get_and_show_revision(struct atusb *atusb)
{
	struct usb_device *usb_dev = atusb->usb_dev;
	char *hw_name;
	unsigned char *buffer;
	int ret;

	buffer = kmalloc(3, GFP_KERNEL);
	if (!buffer)
		return -ENOMEM;

	/* Get a couple of the ATMega Firmware values */
	ret = atusb_control_msg(atusb, usb_rcvctrlpipe(usb_dev, 0),
				ATUSB_ID, ATUSB_REQ_FROM_DEV, 0, 0,
				buffer, 3, 1000);
	if (ret >= 0) {
		atusb->fw_ver_maj = buffer[0];
		atusb->fw_ver_min = buffer[1];
		atusb->fw_hw_type = buffer[2];

		switch (atusb->fw_hw_type) {
		case ATUSB_HW_TYPE_100813:
		case ATUSB_HW_TYPE_101216:
		case ATUSB_HW_TYPE_110131:
			hw_name = "ATUSB";
			atusb->data = &atusb_chip_data;
			break;
		case ATUSB_HW_TYPE_RZUSB:
			hw_name = "RZUSB";
			atusb->data = &atusb_chip_data;
			break;
		case ATUSB_HW_TYPE_HULUSB:
			hw_name = "HULUSB";
			atusb->data = &hulusb_chip_data;
			break;
		default:
			hw_name = "UNKNOWN";
			atusb->err = -ENOTSUPP;
			ret = -ENOTSUPP;
			break;
		}

		dev_info(&usb_dev->dev,
			 "Firmware: major: %u, minor: %u, hardware type: %s (%d)\n",
			 atusb->fw_ver_maj, atusb->fw_ver_min, hw_name,
			 atusb->fw_hw_type);
	}
	if (atusb->fw_ver_maj == 0 && atusb->fw_ver_min < 2) {
		dev_info(&usb_dev->dev,
			 "Firmware version (%u.%u) predates our first public release.",
			 atusb->fw_ver_maj, atusb->fw_ver_min);
		dev_info(&usb_dev->dev, "Please update to version 0.2 or newer");
	}

	kfree(buffer);
	return ret;
}


static int hulusb_set_cca_ed_level(struct atusb *lp, int rssi_base_val)
{
	unsigned int cca_ed_thres;

	cca_ed_thres = atusb_read_subreg(lp, SR_CCA_ED_THRES);

	switch (rssi_base_val) {
	case -98:
		lp->hw->phy->supported.cca_ed_levels = at86rf212_ed_levels_98;
		lp->hw->phy->supported.cca_ed_levels_size = ARRAY_SIZE(at86rf212_ed_levels_98);
		lp->hw->phy->cca_ed_level = at86rf212_ed_levels_98[cca_ed_thres];
		break;
	case -100:
		lp->hw->phy->supported.cca_ed_levels = at86rf212_ed_levels_100;
		lp->hw->phy->supported.cca_ed_levels_size = ARRAY_SIZE(at86rf212_ed_levels_100);
		lp->hw->phy->cca_ed_level = at86rf212_ed_levels_100[cca_ed_thres];
		break;
	default:
		WARN_ON(1);
	}

	return 0;
}


static int atusb_xmit(struct ieee802154_hw *hw, struct sk_buff *skb)
{
	struct atusb *atusb = hw->priv;
	struct usb_device *usb_dev = atusb->usb_dev;
	int ret;

	dev_dbg(&usb_dev->dev, "%s (%d)\n", __func__, skb->len);
	atusb->tx_skb = skb;
	atusb->tx_ack_seq++;
	atusb->tx_dr.wIndex = cpu_to_le16(atusb->tx_ack_seq);
	atusb->tx_dr.wLength = cpu_to_le16(skb->len);

	usb_fill_control_urb(atusb->tx_urb, usb_dev,
			     usb_sndctrlpipe(usb_dev, 0),
			     (unsigned char *)&atusb->tx_dr, skb->data,
			     skb->len, atusb_xmit_complete, NULL);
	ret = usb_submit_urb(atusb->tx_urb, GFP_ATOMIC);
	dev_dbg(&usb_dev->dev, "%s done (%d)\n", __func__, ret);
	return ret;
}


atusb_set_promiscuous_mode(struct ieee802154_hw *hw, const bool on)
{
	struct atusb *atusb = hw->priv;
	int ret;

	if (on) {
		ret = atusb_write_subreg(atusb, SR_AACK_DIS_ACK, 1);
		if (ret < 0)
			return ret;

		ret = atusb_write_subreg(atusb, SR_AACK_PROM_MODE, 1);
		if (ret < 0)
			return ret;
	} else {
		ret = atusb_write_subreg(atusb, SR_AACK_PROM_MODE, 0);
		if (ret < 0)
			return ret;

		ret = atusb_write_subreg(atusb, SR_AACK_DIS_ACK, 0);
		if (ret < 0)
			return ret;
	}

	return 0;
}


static int atusb_set_hw_addr_filt(struct ieee802154_hw *hw,
				  struct ieee802154_hw_addr_filt *filt,
				  unsigned long changed)
{
	struct atusb *atusb = hw->priv;
	struct device *dev = &atusb->usb_dev->dev;

	if (changed & IEEE802154_AFILT_SADDR_CHANGED) {
		u16 addr = le16_to_cpu(filt->short_addr);

		dev_vdbg(dev, "%s called for saddr\n", __func__);
		atusb_write_reg(atusb, RG_SHORT_ADDR_0, addr);
		atusb_write_reg(atusb, RG_SHORT_ADDR_1, addr >> 8);
	}

	if (changed & IEEE802154_AFILT_PANID_CHANGED) {
		u16 pan = le16_to_cpu(filt->pan_id);

		dev_vdbg(dev, "%s called for pan id\n", __func__);
		atusb_write_reg(atusb, RG_PAN_ID_0, pan);
		atusb_write_reg(atusb, RG_PAN_ID_1, pan >> 8);
	}

	if (changed & IEEE802154_AFILT_IEEEADDR_CHANGED) {
		u8 i, addr[IEEE802154_EXTENDED_ADDR_LEN];

		memcpy(addr, &filt->ieee_addr, IEEE802154_EXTENDED_ADDR_LEN);
		dev_vdbg(dev, "%s called for IEEE addr\n", __func__);
		for (i = 0; i < 8; i++)
			atusb_write_reg(atusb, RG_IEEE_ADDR_0 + i, addr[i]);
	}

	if (changed & IEEE802154_AFILT_PANC_CHANGED) {
		dev_vdbg(dev, "%s called for panc change\n", __func__);
		if (filt->pan_coord)
			atusb_write_subreg(atusb, SR_AACK_I_AM_COORD, 1);
		else
			atusb_write_subreg(atusb, SR_AACK_I_AM_COORD, 0);
	}

	return atusb_get_and_clear_error(atusb);
}


atusb_set_cca_mode(struct ieee802154_hw *hw, const struct wpan_phy_cca *cca)
{
	struct atusb *atusb = hw->priv;
	u8 val;

	/* mapping 802.15.4 to driver spec */
	switch (cca->mode) {
	case NL802154_CCA_ENERGY:
		val = 1;
		break;
	case NL802154_CCA_CARRIER:
		val = 2;
		break;
	case NL802154_CCA_ENERGY_CARRIER:
		switch (cca->opt) {
		case NL802154_CCA_OPT_ENERGY_CARRIER_AND:
			val = 3;
			break;
		case NL802154_CCA_OPT_ENERGY_CARRIER_OR:
			val = 0;
			break;
		default:
			return -EINVAL;
		}
		break;
	default:
		return -EINVAL;
	}

	return atusb_write_subreg(atusb, SR_CCA_MODE, val);
}


static int atusb_read_reg(struct atusb *atusb, u8 reg)
{
	struct usb_device *usb_dev = atusb->usb_dev;
	int ret;
	u8 *buffer;
	u8 value;

	buffer = kmalloc(1, GFP_KERNEL);
	if (!buffer)
		return -ENOMEM;

	dev_dbg(&usb_dev->dev, "%s: reg = 0x%x\n", __func__, reg);
	ret = atusb_control_msg(atusb, usb_rcvctrlpipe(usb_dev, 0),
				ATUSB_REG_READ, ATUSB_REQ_FROM_DEV,
				0, reg, buffer, 1, 1000);

	if (ret >= 0) {
		value = buffer[0];
		kfree(buffer);
		return value;
	} else {
		kfree(buffer);
		return ret;
	}
}


static MOBI_RET mobi_parse_index_entry(MOBIIndx *indx, const MOBIIdxt idxt, const MOBITagx *tagx, const MOBIOrdt *ordt, MOBIBuffer *buf, const size_t curr_number) {
    if (indx == NULL) {
        debug_print("%s", "INDX structure not initialized\n");
        return MOBI_INIT_FAILED;
    }
    const size_t entry_offset = indx->entries_count;
    const size_t entry_length = idxt.offsets[curr_number + 1] - idxt.offsets[curr_number];
    mobi_buffer_setpos(buf, idxt.offsets[curr_number]);
    size_t entry_number = curr_number + entry_offset;
    if (entry_number >= indx->total_entries_count) {
        debug_print("Entry number beyond array: %zu\n", entry_number);
        return MOBI_DATA_CORRUPT;
    }
    /* save original record maxlen */
    const size_t buf_maxlen = buf->maxlen;
    if (buf->offset + entry_length >= buf_maxlen) {
        debug_print("Entry length too long: %zu\n", entry_length);
        return MOBI_DATA_CORRUPT;
    }
    buf->maxlen = buf->offset + entry_length;
    size_t label_length = mobi_buffer_get8(buf);
    if (label_length > entry_length) {
        debug_print("Label length too long: %zu\n", label_length);
        return MOBI_DATA_CORRUPT;
    }
    char text[INDX_LABEL_SIZEMAX + 1];
    /* FIXME: what is ORDT1 for? */
    if (ordt->ordt2) {
        label_length = mobi_getstring_ordt(ordt, buf, (unsigned char*) text, label_length);
    } else {
        label_length = mobi_indx_get_label((unsigned char*) text, buf, label_length, indx->ligt_entries_count);
        if (buf->error != MOBI_SUCCESS) {
            debug_print("Buffer error reading label: %d\n", buf->error);
            return MOBI_DATA_CORRUPT;
        }
    }
    indx->entries[entry_number].label = malloc(label_length + 1);
    if (indx->entries[entry_number].label == NULL) {
        debug_print("Memory allocation failed (%zu bytes)\n", label_length);
        return MOBI_MALLOC_FAILED;
    }
    strncpy(indx->entries[entry_number].label, text, label_length + 1);
    //debug_print("tag label[%zu]: %s\n", entry_number, indx->entries[entry_number].label);
    unsigned char *control_bytes;
    control_bytes = buf->data + buf->offset;
    mobi_buffer_seek(buf, (int) tagx->control_byte_count);
    indx->entries[entry_number].tags_count = 0;
    indx->entries[entry_number].tags = NULL;
    if (tagx->tags_count > 0) {
        typedef struct {
            uint8_t tag;
            uint8_t tag_value_count;
            uint32_t value_count;
            uint32_t value_bytes;
        } MOBIPtagx;
        MOBIPtagx *ptagx = malloc(tagx->tags_count * sizeof(MOBIPtagx));
        if (ptagx == NULL) {
            debug_print("Memory allocation failed (%zu bytes)\n", tagx->tags_count * sizeof(MOBIPtagx));
            return MOBI_MALLOC_FAILED;
        }
        uint32_t ptagx_count = 0;
        size_t len;
        size_t i = 0;
        while (i < tagx->tags_count) {
            if (tagx->tags[i].control_byte == 1) {
                control_bytes++;
                i++;
                continue;
            }
            uint32_t value = control_bytes[0] & tagx->tags[i].bitmask;
            if (value != 0) {
                /* FIXME: is it safe to use MOBI_NOTSET? */
                uint32_t value_count = MOBI_NOTSET;
                uint32_t value_bytes = MOBI_NOTSET;
                /* all bits of masked value are set */
                if (value == tagx->tags[i].bitmask) {
                    /* more than 1 bit set */
                    if (mobi_bitcount(tagx->tags[i].bitmask) > 1) {
                        /* read value bytes from entry */
                        len = 0;
                        value_bytes = mobi_buffer_get_varlen(buf, &len);
                    } else {
                        value_count = 1;
                    }
                } else {
                    uint8_t mask = tagx->tags[i].bitmask;
                    while ((mask & 1) == 0) {
                        mask >>= 1;
                        value >>= 1;
                    }
                    value_count = value;
                }
                ptagx[ptagx_count].tag = tagx->tags[i].tag;
                ptagx[ptagx_count].tag_value_count = tagx->tags[i].values_count;
                ptagx[ptagx_count].value_count = value_count;
                ptagx[ptagx_count].value_bytes = value_bytes;
                ptagx_count++;
            }
            i++;
        }
        indx->entries[entry_number].tags = malloc(tagx->tags_count * sizeof(MOBIIndexTag));
        if (indx->entries[entry_number].tags == NULL) {
            debug_print("Memory allocation failed (%zu bytes)\n", tagx->tags_count * sizeof(MOBIIndexTag));
            free(ptagx);
            return MOBI_MALLOC_FAILED;
        }
        i = 0;
        while (i < ptagx_count) {
            uint32_t tagvalues_count = 0;
            /* FIXME: is it safe to use MOBI_NOTSET? */
            /* value count is set */
            uint32_t tagvalues[INDX_TAGVALUES_MAX];
            if (ptagx[i].value_count != MOBI_NOTSET) {
                size_t count = ptagx[i].value_count * ptagx[i].tag_value_count;
                while (count-- && tagvalues_count < INDX_TAGVALUES_MAX) {
                    len = 0;
                    const uint32_t value_bytes = mobi_buffer_get_varlen(buf, &len);
                    tagvalues[tagvalues_count++] = value_bytes;
                }
            /* value count is not set */
            } else {
                /* read value_bytes bytes */
                len = 0;
                while (len < ptagx[i].value_bytes && tagvalues_count < INDX_TAGVALUES_MAX) {
                    const uint32_t value_bytes = mobi_buffer_get_varlen(buf, &len);
                    tagvalues[tagvalues_count++] = value_bytes;
                }
            }
            if (tagvalues_count) {
                const size_t arr_size = tagvalues_count * sizeof(*indx->entries[entry_number].tags[i].tagvalues);
                indx->entries[entry_number].tags[i].tagvalues = malloc(arr_size);
                if (indx->entries[entry_number].tags[i].tagvalues == NULL) {
                    debug_print("Memory allocation failed (%zu bytes)\n", arr_size);
                    free(ptagx);
                    return MOBI_MALLOC_FAILED;
                }
                memcpy(indx->entries[entry_number].tags[i].tagvalues, tagvalues, arr_size);
            } else {
                indx->entries[entry_number].tags[i].tagvalues = NULL;
            }
            indx->entries[entry_number].tags[i].tagid = ptagx[i].tag;
            indx->entries[entry_number].tags[i].tagvalues_count = tagvalues_count;
            indx->entries[entry_number].tags_count++;
            i++;
        }
        free(ptagx);
    }
    /* restore buffer maxlen */
    buf->maxlen = buf_maxlen;
    return MOBI_SUCCESS;
}

Fix: index entry label not being zero-terminated with corrupt input


static void carve_deps_at_address(RzDyldCache *cache, cache_img_t *img, HtPU *path_to_idx, ut64 address, int *deps, bool printing) {
	ut64 pa = va2pa(address, cache->n_maps, cache->maps, cache->buf, 0, NULL, NULL);
	if (pa == UT64_MAX) {
		return;
	}
	struct MACH0_(mach_header) mh;
	if (rz_buf_fread_at(cache->buf, pa, (ut8 *)&mh, "8i", 1) != sizeof(struct MACH0_(mach_header))) {
		return;
	}
	if (mh.magic != MH_MAGIC_64 || mh.sizeofcmds == 0) {
		return;
	}
	ut64 cmds_at = pa + sizeof(struct MACH0_(mach_header));
	ut8 *cmds = malloc(mh.sizeofcmds + 1);
	if (!cmds || rz_buf_read_at(cache->buf, cmds_at, cmds, mh.sizeofcmds) != mh.sizeofcmds) {
		goto beach;
	}
	cmds[mh.sizeofcmds] = 0;
	ut8 *cursor = cmds;
	ut8 *end = cmds + mh.sizeofcmds;
	while (cursor < end) {
		ut32 cmd = rz_read_le32(cursor);
		ut32 cmdsize = rz_read_le32(cursor + sizeof(ut32));
		if (cmd == LC_LOAD_DYLIB ||
			cmd == LC_LOAD_WEAK_DYLIB ||
			cmd == LC_REEXPORT_DYLIB ||
			cmd == LC_LOAD_UPWARD_DYLIB) {
			bool found;
			if (cursor + 24 >= end) {
				break;
			}
			const char *key = (const char *)cursor + 24;
			size_t dep_index = (size_t)ht_pu_find(path_to_idx, key, &found);
			if (!found || dep_index >= cache->hdr->imagesCount) {
				RZ_LOG_WARN("alien dep '%s'\n", key);
				continue;
			}
			deps[dep_index]++;
			if (printing) {
				RZ_LOG_INFO("-> %s\n", key);
			}
		}
		cursor += cmdsize;
	}

beach:
	free(cmds);
}

Fix oob write in dyldcache

When the individual n_slide_infos were too high, the sum would overflow
and too few entries would be allocated.


RZ_API void rz_dyldcache_symbols_from_locsym(RzDyldCache *cache, RzDyldBinImage *bin, RzList *symbols, SetU *hash) {
	RzDyldLocSym *locsym = cache->locsym;
	if (!locsym) {
		return;
	}

	if (bin->nlist_start_index >= locsym->nlists_count ||
		bin->nlist_start_index + bin->nlist_count > locsym->nlists_count) {
		RZ_LOG_ERROR("dyldcache: malformed local symbol entry\n");
		return;
	}

	ut64 nlists_size = sizeof(struct MACH0_(nlist)) * bin->nlist_count;
	struct MACH0_(nlist) *nlists = RZ_NEWS0(struct MACH0_(nlist), bin->nlist_count);
	if (!nlists) {
		return;
	}
	ut64 nlists_offset = locsym->local_symbols_offset + locsym->nlists_offset +
		bin->nlist_start_index * sizeof(struct MACH0_(nlist));
	if (rz_buf_fread_at(cache->buf, nlists_offset, (ut8 *)nlists, "iccsl", bin->nlist_count) != nlists_size) {
		free(nlists);
		return;
	}

	ut32 j;
	for (j = 0; j != bin->nlist_count; j++) {
		struct MACH0_(nlist) *nlist = &nlists[j];
		if (set_u_contains(hash, (ut64)nlist->n_value)) {
			continue;
		}
		set_u_add(hash, (ut64)nlist->n_value);
		if (nlist->n_strx >= locsym->strings_size) {
			continue;
		}
		RzBinSymbol *sym = RZ_NEW0(RzBinSymbol);
		if (!sym) {
			break;
		}
		sym->type = "LOCAL";
		sym->vaddr = nlist->n_value;
		ut64 slide = rz_dyldcache_get_slide(cache);
		sym->paddr = va2pa(nlist->n_value, cache->n_maps, cache->maps, cache->buf, slide, NULL, NULL);

		char *symstr = rz_buf_get_string(cache->buf, locsym->local_symbols_offset + locsym->strings_offset + nlist->n_strx);
		if (symstr) {
			sym->name = symstr;
		} else {
			static ut32 k = 0;
			sym->name = rz_str_newf("unk_local%d", k++);
		}

		rz_list_append(symbols, sym);
	}

	free(nlists);
}


static void rebase_info2_free(RzDyldRebaseInfo2 *rebase_info) {
	if (!rebase_info) {
		return;
	}
	free(rebase_info->page_starts);
	free(rebase_info->page_extras);
	free(rebase_info);
}


RZ_API objc_cache_opt_info *rz_dyldcache_get_objc_opt_info(RzBinFile *bf, RzDyldCache *cache) {
	objc_cache_opt_info *result = NULL;
	RzListIter *iter;
	RzDyldBinImage *bin;
	rz_list_foreach (cache->bins, iter, bin) {
		if (strcmp(bin->file, "lib/libobjc.A.dylib")) {
			continue;
		}

		struct MACH0_(opts_t) opts = { 0 };
		opts.verbose = bf->rbin->verbose;
		opts.header_at = bin->header_at;

		struct MACH0_(obj_t) *mach0 = MACH0_(new_buf)(cache->buf, &opts);
		if (!mach0) {
			goto beach;
		}

		struct section_t *sections = NULL;
		if (!(sections = MACH0_(get_sections)(mach0))) {
			MACH0_(mach0_free)
			(mach0);
			goto beach;
		}

		int i;
		ut64 scoffs_offset = 0;
		ut64 scoffs_size = 0;
		ut64 slide = rz_dyldcache_get_slide(cache);
		for (i = 0; !sections[i].last; i++) {
			if (sections[i].size == 0) {
				continue;
			}
			if (strstr(sections[i].name, "__objc_scoffs")) {
				scoffs_offset = va2pa(sections[i].addr, cache->n_maps, cache->maps, cache->buf, slide, NULL, NULL);
				scoffs_size = sections[i].size;
				break;
			}
		}

		MACH0_(mach0_free)
		(mach0);
		RZ_FREE(sections);

		if (!scoffs_offset || scoffs_size < 40) {
			break;
		}
		ut64 check;
		if (!rz_buf_read_le64_at(cache->buf, scoffs_offset, &check) || check != 2) {
			break;
		}
		ut64 sel_string_base;
		if (!rz_buf_read_le64_at(cache->buf, scoffs_offset + 8, &sel_string_base)) {
			break;
		}
		ut64 sel_string_end;
		if (!rz_buf_read_le64_at(cache->buf, scoffs_offset + 16, &sel_string_end) || sel_string_end == sel_string_base) {
			break;
		}
		result = RZ_NEW0(objc_cache_opt_info);
		if (!result) {
			break;
		}
		result->sel_string_base = sel_string_base;
	}
beach:
	return result;
}


static cache_img_t *read_cache_images(RzBuffer *cache_buf, cache_hdr_t *hdr, ut64 hdr_offset) {
	if (!cache_buf || !hdr) {
		return NULL;
	}
	if (!hdr->imagesCount || !hdr->imagesOffset || hdr->imagesOffset == UT32_MAX || hdr->imagesCount == UT32_MAX) {
		return NULL;
	}

	ut64 size = sizeof(cache_img_t) * hdr->imagesCount;
	cache_img_t *images = RZ_NEWS0(cache_img_t, hdr->imagesCount);
	if (!images) {
		return NULL;
	}

	if (rz_buf_fread_at(cache_buf, hdr->imagesOffset, (ut8 *)images, "3l2i", hdr->imagesCount) != size) {
		RZ_FREE(images);
		return NULL;
	}

	if (hdr_offset) {
		ut32 i;
		for (i = 0; i < hdr->imagesCount; i++) {
			cache_img_t *img = &images[i];
			img->pathFileOffset += hdr_offset;
		}
	}

	return images;
}


static ut64 estimate_slide(RzDyldCache *cache, ut64 value_mask, ut64 value_add) {
	ut64 slide = 0;
	if (cache->n_hdr > 1) {
		return slide;
	}
	ut64 *classlist = malloc(64);
	if (!classlist) {
		goto beach;
	}

	RzListIter *iter;
	RzDyldBinImage *bin;
	rz_list_foreach (cache->bins, iter, bin) {
		bool found_sample = false;

		struct MACH0_(opts_t) opts = { 0 };
		opts.header_at = bin->header_at;

		struct MACH0_(obj_t) *mach0 = MACH0_(new_buf)(cache->buf, &opts);
		if (!mach0) {
			goto beach;
		}

		struct section_t *sections = NULL;
		if (!(sections = MACH0_(get_sections)(mach0))) {
			MACH0_(mach0_free)
			(mach0);
			goto beach;
		}

		int i;
		int incomplete = 2;
		int classlist_idx = 0, data_idx = 0;
		for (i = 0; !sections[i].last && incomplete; i++) {
			if (sections[i].size == 0) {
				continue;
			}
			if (strstr(sections[i].name, "__objc_classlist")) {
				incomplete--;
				classlist_idx = i;
				continue;
			}
			if (strstr(sections[i].name, "__objc_data")) {
				incomplete--;
				data_idx = i;
				continue;
			}
		}

		if (incomplete) {
			goto next_bin;
		}

		int classlist_sample_size = RZ_MIN(64, sections[classlist_idx].size);
		int n_classes = classlist_sample_size / 8;
		ut64 sect_offset = sections[classlist_idx].offset + bin->hdr_offset;

		if (rz_buf_fread_at(cache->buf, sect_offset, (ut8 *)classlist, "l", n_classes) < classlist_sample_size) {
			goto next_bin;
		}

		ut64 data_addr = sections[data_idx].addr;
		ut64 data_tail = data_addr & 0xfff;
		ut64 data_tail_end = (data_addr + sections[data_idx].size) & 0xfff;
		for (i = 0; i < n_classes; i++) {
			ut64 cl_addr = (classlist[i] & value_mask) + value_add;
			ut64 cl_tail = cl_addr & 0xfff;
			if (cl_tail >= data_tail && cl_tail < data_tail_end) {
				ut64 off = cl_tail - data_tail;
				slide = ((cl_addr - off) & value_mask) - (data_addr & value_mask);
				found_sample = true;
				break;
			}
		}

	next_bin:
		MACH0_(mach0_free)
		(mach0);
		free(sections);

		if (found_sample) {
			break;
		}
	}

beach:
	free(classlist);
	return slide;
}


static void free_bin(RzDyldBinImage *bin) {
	if (!bin) {
		return;
	}
	free(bin->file);
	free(bin);
}


RZ_API ut64 rz_dyldcache_get_slide(RzDyldCache *cache) {
	rz_return_val_if_fail(cache, 0);
	if (!cache->rebase_infos || !cache->rebase_infos->length) {
		return 0;
	}

	size_t i;
	for (i = 0; i < cache->rebase_infos->length; i++) {
		if (cache->rebase_infos->entries[i].info) {
			return cache->rebase_infos->entries[i].info->slide;
		}
	}

	return 0;
}


static void rebase_info3_free(RzDyldRebaseInfo3 *rebase_info) {
	if (!rebase_info) {
		return;
	}
	free(rebase_info->page_starts);
	free(rebase_info);
}


RZ_API ut64 rz_dyldcache_va2pa(RzDyldCache *cache, uint64_t vaddr, ut32 *offset, ut32 *left) {
	rz_return_val_if_fail(cache, UT64_MAX);
	ut64 slide = rz_dyldcache_get_slide(cache);
	ut64 res = va2pa(vaddr, cache->n_maps, cache->maps, cache->buf, slide, offset, left);
	if (res == UT64_MAX) {
		res = 0;
	}
	return res;
}


static void rebase_info1_free(RzDyldRebaseInfo1 *rebase_info) {
	if (!rebase_info) {
		return;
	}
	free(rebase_info->toc);
	free(rebase_info->entries);
	free(rebase_info);
}


static int string_contains(const void *a, const void *b) {
	return !strstr((const char *)a, (const char *)b);
}


static void populate_cache_maps(RzDyldCache *cache) {
	rz_return_if_fail(cache && cache->buf);

	ut32 i;
	ut32 n_maps = 0;
	ut64 max_count = 0;
	for (i = 0; i < cache->n_hdr; i++) {
		cache_hdr_t *hdr = &cache->hdr[i];
		if (!hdr->mappingCount || !hdr->mappingOffset) {
			continue;
		}
		max_count = RZ_MAX(hdr->mappingCount, max_count);
		n_maps += hdr->mappingCount;
	}

	if (n_maps < 1 || n_maps < max_count /* overflow */) {
		cache->maps = NULL;
		cache->n_maps = 0;
		return;
	}

	cache->maps_index = RZ_NEWS0(ut32, cache->n_hdr);
	if (!cache->maps_index) {
		return;
	}
	cache_map_t *maps = RZ_NEWS0(cache_map_t, n_maps);

	ut32 next_map = 0;
	ut32 last_idx = UT32_MAX;
	ut64 max_address = 0;
	for (i = 0; i < cache->n_hdr; i++) {
		cache_hdr_t *hdr = &cache->hdr[i];
		cache->maps_index[i] = next_map;

		if (!hdr->mappingCount || !hdr->mappingOffset) {
			continue;
		}
		ut64 size = sizeof(cache_map_t) * hdr->mappingCount;
		if (rz_buf_fread_at(cache->buf, hdr->mappingOffset, (ut8 *)&maps[next_map], "3l2i", hdr->mappingCount) != size) {
			continue;
		}
		ut32 j;
		ut64 hdr_offset = cache->hdr_offset[i];
		for (j = 0; j < hdr->mappingCount; j++) {
			cache_map_t *map = &maps[next_map + j];
			map->fileOffset += hdr_offset;
			if (map->address > max_address) {
				last_idx = i;
				max_address = map->address;
			}
		}
		next_map += hdr->mappingCount;
	}

	cache->maps = maps;
	cache->n_maps = next_map;
	if (last_idx == UT32_MAX) {
		cache->symbols_off_base = 0;
	} else {
		cache->symbols_off_base = cache->hdr_offset[last_idx];
	}
}


static cache_imgxtr_t *read_cache_imgextra(RzBuffer *cache_buf, cache_hdr_t *hdr, cache_accel_t *accel) {
	if (!cache_buf || !hdr || !hdr->imagesCount || !accel || !accel->imageExtrasCount || !accel->imagesExtrasOffset) {
		return NULL;
	}

	ut64 size = sizeof(cache_imgxtr_t) * accel->imageExtrasCount;
	cache_imgxtr_t *images = RZ_NEWS0(cache_imgxtr_t, accel->imageExtrasCount);
	if (!images) {
		return NULL;
	}

	if (rz_buf_fread_at(cache_buf, accel->imagesExtrasOffset, (ut8 *)images, "ll4i", accel->imageExtrasCount) != size) {
		RZ_FREE(images);
		return NULL;
	}

	return images;
}


static char *get_lib_name(RzBuffer *cache_buf, cache_img_t *img) {
	char file[256];
	char *lib_name = file;
	if (rz_buf_read_at(cache_buf, img->pathFileOffset, (ut8 *)&file, sizeof(file)) == sizeof(file)) {
		file[255] = 0;
		return strdup(lib_name);
	}
	return strdup("FAIL");
}


RZ_API void rz_dyldcache_free(RzDyldCache *cache) {
	if (!cache) {
		return;
	}

	rz_list_free(cache->bins);
	cache->bins = NULL;
	rz_buf_free(cache->buf);
	cache->buf = NULL;
	if (cache->rebase_infos) {
		int i;
		for (i = 0; i < cache->rebase_infos->length; i++) {
			rebase_info_free(cache->rebase_infos->entries[i].info);
			cache->rebase_infos->entries[i].info = NULL;
		}
		RZ_FREE(cache->rebase_infos->entries);
		RZ_FREE(cache->rebase_infos);
	}
	free(cache->hdr);
	free(cache->maps);
	free(cache->maps_index);
	free(cache->hdr_offset);
	free(cache->accel);
	free(cache->locsym);
	free(cache->oi);
	free(cache);
}


static RzDyldRebaseInfos *get_rebase_infos(RzDyldCache *cache) {
	RzDyldRebaseInfos *result = RZ_NEW0(RzDyldRebaseInfos);
	if (!result) {
		return NULL;
	}

	if (!cache->hdr->slideInfoOffset || !cache->hdr->slideInfoSize) {
		size_t total_slide_infos = 0;
		ut32 n_slide_infos[MAX_N_HDR];

		ut32 i;
		for (i = 0; i < cache->n_hdr && i < MAX_N_HDR; i++) {
			ut64 hdr_offset = cache->hdr_offset[i];
			if (!rz_buf_read_le32_at(cache->buf, 0x13c + hdr_offset, &n_slide_infos[i])) {
				goto beach;
			}
			ut32 total = total_slide_infos + n_slide_infos[i];
			if (total < total_slide_infos) {
				// overflow
				goto beach;
			}
			total_slide_infos = total;
		}

		if (!total_slide_infos) {
			goto beach;
		}

		RzDyldRebaseInfosEntry *infos = RZ_NEWS0(RzDyldRebaseInfosEntry, total_slide_infos);
		if (!infos) {
			goto beach;
		}

		ut32 k = 0;
		for (i = 0; i < cache->n_hdr && i < MAX_N_HDR; i++) {
			ut64 hdr_offset = cache->hdr_offset[i];
			if (!n_slide_infos[i]) {
				continue;
			}
			ut32 sio;
			if (!rz_buf_read_le32_at(cache->buf, 0x138 + hdr_offset, &sio)) {
				continue;
			}
			ut64 slide_infos_offset = sio;
			if (!slide_infos_offset) {
				continue;
			}
			slide_infos_offset += hdr_offset;

			ut32 j;
			RzDyldRebaseInfo *prev_info = NULL;
			for (j = 0; j < n_slide_infos[i]; j++) {
				ut64 offset = slide_infos_offset + j * sizeof(cache_mapping_slide);
				cache_mapping_slide entry;
				if (rz_buf_fread_at(cache->buf, offset, (ut8 *)&entry, "6lii", 1) != sizeof(cache_mapping_slide)) {
					break;
				}

				if (entry.slideInfoOffset && entry.slideInfoSize) {
					infos[k].start = entry.fileOffset + hdr_offset;
					infos[k].end = infos[k].start + entry.size;
					ut64 slide = prev_info ? prev_info->slide : UT64_MAX;
					infos[k].info = get_rebase_info(cache, entry.slideInfoOffset + hdr_offset, entry.slideInfoSize, entry.fileOffset + hdr_offset, slide);
					prev_info = infos[k].info;
					k++;
				}
			}
		}

		if (!k) {
			free(infos);
			goto beach;
		}

		if (k < total_slide_infos) {
			RzDyldRebaseInfosEntry *pruned_infos = RZ_NEWS0(RzDyldRebaseInfosEntry, k);
			if (!pruned_infos) {
				free(infos);
				goto beach;
			}

			memcpy(pruned_infos, infos, sizeof(RzDyldRebaseInfosEntry) * k);
			free(infos);
			infos = pruned_infos;
		}

		result->entries = infos;
		result->length = k;
		return result;
	}

	if (cache->hdr->mappingCount > 1) {
		RzDyldRebaseInfosEntry *infos = RZ_NEWS0(RzDyldRebaseInfosEntry, 1);
		if (!infos) {
			goto beach;
		}

		infos[0].start = cache->maps[1].fileOffset;
		infos[0].end = infos[0].start + cache->maps[1].size;
		infos[0].info = get_rebase_info(cache, cache->hdr->slideInfoOffset, cache->hdr->slideInfoSize, infos[0].start, UT64_MAX);

		result->entries = infos;
		result->length = 1;
		return result;
	}

beach:
	free(result);
	return NULL;
}


static ut64 va2pa(uint64_t addr, ut32 n_maps, cache_map_t *maps, RzBuffer *cache_buf, ut64 slide, ut32 *offset, ut32 *left) {
	ut64 res = UT64_MAX;
	ut32 i;

	addr -= slide;

	for (i = 0; i < n_maps; i++) {
		if (addr >= maps[i].address && addr < maps[i].address + maps[i].size) {
			res = maps[i].fileOffset + addr - maps[i].address;
			if (offset) {
				*offset = addr - maps[i].address;
			}
			if (left) {
				*left = maps[i].size - (addr - maps[i].address);
			}
			break;
		}
	}

	return res;
}


static void rebase_info_free(RzDyldRebaseInfo *rebase_info) {
	if (!rebase_info) {
		return;
	}

	RZ_FREE(rebase_info->one_page_buf);

	ut8 version = rebase_info->version;

	if (version == 1) {
		rebase_info1_free((RzDyldRebaseInfo1 *)rebase_info);
	} else if (version == 2 || version == 4) {
		rebase_info2_free((RzDyldRebaseInfo2 *)rebase_info);
	} else if (version == 3) {
		rebase_info3_free((RzDyldRebaseInfo3 *)rebase_info);
	} else {
		free(rebase_info);
	}
}


static HtPU *create_path_to_index(RzBuffer *cache_buf, cache_img_t *img, cache_hdr_t *hdr) {
	HtPU *path_to_idx = ht_pu_new0();
	if (!path_to_idx) {
		return NULL;
	}
	for (size_t i = 0; i != hdr->imagesCount; i++) {
		char file[256];
		if (rz_buf_read_at(cache_buf, img[i].pathFileOffset, (ut8 *)&file, sizeof(file)) != sizeof(file)) {
			continue;
		}
		file[255] = 0;
		ht_pu_insert(path_to_idx, file, (ut64)i);
	}

	return path_to_idx;
}


static cache_hdr_t *read_cache_header(RzBuffer *cache_buf, ut64 offset) {
	if (!cache_buf) {
		return NULL;
	}

	cache_hdr_t *hdr = RZ_NEW0(cache_hdr_t);
	if (!hdr) {
		return NULL;
	}

	ut64 size = sizeof(cache_hdr_t);
	if (rz_buf_fread_at(cache_buf, offset, (ut8 *)hdr, "16c4i7l16clii4l", 1) != size) {
		free(hdr);
		return NULL;
	}
	if (!rz_dyldcache_check_magic(hdr->magic)) {
		free(hdr);
		return NULL;
	}

	if (!hdr->imagesCount && !hdr->imagesOffset) {
		if (!rz_buf_read_le32_at(cache_buf, 0x1c0 + offset, &hdr->imagesOffset) || !rz_buf_read_le32_at(cache_buf, 0x1c4 + offset, &hdr->imagesCount)) {
			free(hdr);
			return NULL;
		}
	}
	return hdr;
}


RZ_API bool rz_dyldcache_check_magic(const char *magic) {
	return !strcmp(magic, "dyld_v1   arm64") || !strcmp(magic, "dyld_v1  arm64e") || !strcmp(magic, "dyld_v1  x86_64") || !strcmp(magic, "dyld_v1 x86_64h");
}


RZ_API RzDyldCache *rz_dyldcache_new_buf(RzBuffer *buf) {
	RzDyldCache *cache = RZ_NEW0(RzDyldCache);
	if (!cache) {
		return NULL;
	}
	memcpy(cache->magic, "dyldcac", 7);
	cache->buf = rz_buf_ref(buf);
	populate_cache_headers(cache);
	if (!cache->hdr) {
		goto cupertino;
	}
	populate_cache_maps(cache);
	if (!cache->maps) {
		goto cupertino;
	}
	cache->accel = read_cache_accel(cache->buf, cache->hdr, cache->maps);
	cache->bins = create_cache_bins(cache);
	if (!cache->bins) {
		goto cupertino;
	}
	cache->locsym = rz_dyld_locsym_new(cache);
	cache->rebase_infos = get_rebase_infos(cache);
	return cache;
cupertino:
	rz_dyldcache_free(cache);
	return NULL;
}


static void populate_cache_headers(RzDyldCache *cache) {
	cache->n_hdr = 0;
	RzList *hdrs = rz_list_newf(NULL);
	if (!hdrs) {
		return;
	}

	cache_hdr_t *h;
	ut64 offsets[MAX_N_HDR];
	ut64 offset = 0;
	do {
		offsets[cache->n_hdr] = offset;
		h = read_cache_header(cache->buf, offset);
		if (!h) {
			break;
		}
		rz_list_append(hdrs, h);

		ut64 size = h->codeSignatureOffset + h->codeSignatureSize;

#define SHIFT_MAYBE(x) \
	if (x) { \
		x += offset; \
	}

		SHIFT_MAYBE(h->mappingOffset);
		SHIFT_MAYBE(h->imagesOffset);
		SHIFT_MAYBE(h->codeSignatureOffset);
		SHIFT_MAYBE(h->slideInfoOffset);
		SHIFT_MAYBE(h->localSymbolsOffset);
		SHIFT_MAYBE(h->branchPoolsOffset);
		SHIFT_MAYBE(h->imagesTextOffset);

		offset += size;
		cache->n_hdr++;
	} while (cache->n_hdr < MAX_N_HDR);

	if (!cache->n_hdr) {
		goto beach;
	}

	cache->hdr = RZ_NEWS0(cache_hdr_t, cache->n_hdr);
	if (!cache->hdr) {
		cache->n_hdr = 0;
		goto beach;
	}

	cache->hdr_offset = RZ_NEWS0(ut64, cache->n_hdr);
	if (!cache->hdr_offset) {
		cache->n_hdr = 0;
		RZ_FREE(cache->hdr);
		goto beach;
	}

	memcpy(cache->hdr_offset, offsets, cache->n_hdr * sizeof(ut64));

	ut32 i = 0;
	RzListIter *iter;
	cache_hdr_t *item;
	rz_list_foreach (hdrs, iter, item) {
		if (i >= cache->n_hdr) {
			break;
		}
		memcpy(&cache->hdr[i++], item, sizeof(cache_hdr_t));
	}

beach:
	rz_list_free(hdrs);
}


static void match_bin_entries(RzDyldCache *cache, void *entries) {
	rz_return_if_fail(cache && cache->bins && entries);

	cache_img_t *imgs = read_cache_images(cache->buf, cache->hdr, 0);
	if (!imgs) {
		return;
	}

	RzDyldBinImage *bin = NULL;
	RzListIter *it = rz_list_iterator(cache->bins);

	bool has_large_entries = cache->n_hdr > 1;

	ut32 i;
	for (i = 0; i < cache->hdr->imagesCount; i++) {
		cache_img_t *img = &imgs[i];
		if (!it) {
			break;
		}
		bin = it->data;
		if (!bin) {
			break;
		}
		if (bin && bin->va == img->address) {
			if (has_large_entries) {
				cache_locsym_entry_large_t *e = &((cache_locsym_entry_large_t *)entries)[i];
				bin->nlist_start_index = e->nlistStartIndex;
				bin->nlist_count = e->nlistCount;
			} else {
				cache_locsym_entry_t *e = &((cache_locsym_entry_t *)entries)[i];
				bin->nlist_start_index = e->nlistStartIndex;
				bin->nlist_count = e->nlistCount;
			}
			it = it->n;
		}
	}

	RZ_FREE(imgs);
}


static RzDyldLocSym *rz_dyld_locsym_new(RzDyldCache *cache) {
	rz_return_val_if_fail(cache && cache->buf, NULL);

	ut32 i;
	for (i = 0; i < cache->n_hdr; i++) {
		cache_hdr_t *hdr = &cache->hdr[i];
		if (!hdr || !hdr->localSymbolsSize || !hdr->localSymbolsOffset) {
			continue;
		}

		cache_locsym_info_t *info = NULL;
		void *entries = NULL;

		ut64 info_size = sizeof(cache_locsym_info_t);
		info = RZ_NEW0(cache_locsym_info_t);
		if (!info) {
			goto beach;
		}
		if (rz_buf_fread_at(cache->buf, hdr->localSymbolsOffset, (ut8 *)info, "6i", 1) != info_size) {
			RZ_LOG_ERROR("Cannot read cache_locsym_info_t from header\n");
			goto beach;
		}
		if (info->entriesCount != cache->hdr->imagesCount) {
			RZ_LOG_ERROR("The number of entries count differs from cache header image count\n");
			goto beach;
		}

		bool has_large_entries = cache->n_hdr > 1;
		if (has_large_entries) {
			ut64 entries_size = sizeof(cache_locsym_entry_large_t) * info->entriesCount;
			cache_locsym_entry_large_t *large_entries = RZ_NEWS0(cache_locsym_entry_large_t, info->entriesCount);
			if (!large_entries) {
				goto beach;
			}
			if (rz_buf_fread_at(cache->buf, hdr->localSymbolsOffset + info->entriesOffset, (ut8 *)large_entries, "lii",
				    info->entriesCount) != entries_size) {
				RZ_LOG_ERROR("Cannot read cache_locsym_entry_large_t\n");
				goto beach;
			}
			entries = large_entries;
		} else {
			ut64 entries_size = sizeof(cache_locsym_entry_t) * info->entriesCount;
			cache_locsym_entry_t *regular_entries = RZ_NEWS0(cache_locsym_entry_t, info->entriesCount);
			if (!regular_entries) {
				goto beach;
			}
			if (rz_buf_fread_at(cache->buf, hdr->localSymbolsOffset + info->entriesOffset, (ut8 *)regular_entries, "iii",
				    info->entriesCount) != entries_size) {
				RZ_LOG_ERROR("Cannot read cache_locsym_entry_t\n");
				goto beach;
			}
			entries = regular_entries;
		}
		RzDyldLocSym *locsym = RZ_NEW0(RzDyldLocSym);
		if (!locsym) {
			goto beach;
		}

		match_bin_entries(cache, entries);

		locsym->local_symbols_offset = hdr->localSymbolsOffset;
		locsym->nlists_offset = info->nlistOffset;
		locsym->nlists_count = info->nlistCount;
		locsym->strings_offset = info->stringsOffset;
		locsym->strings_size = info->stringsSize;

		free(info);
		free(entries);

		return locsym;

	beach:
		free(info);
		free(entries);

		RZ_LOG_ERROR("dyldcache: malformed local symbols metadata\n");
		break;
	}
	return NULL;
}


static RzList *create_cache_bins(RzDyldCache *cache) {
	RzList *bins = rz_list_newf((RzListFree)free_bin);
	if (!bins) {
		return NULL;
	}

	char *target_libs = NULL;
	RzList *target_lib_names = NULL;
	int *deps = NULL;
	target_libs = rz_sys_getenv("RZ_DYLDCACHE_FILTER");
	if (target_libs) {
		target_lib_names = rz_str_split_list(target_libs, ":", 0);
		if (!target_lib_names) {
			rz_list_free(bins);
			return NULL;
		}
		deps = RZ_NEWS0(int, cache->hdr->imagesCount);
		if (!deps) {
			rz_list_free(bins);
			rz_list_free(target_lib_names);
			return NULL;
		}
	}

	ut32 i;
	for (i = 0; i < cache->n_hdr; i++) {
		cache_hdr_t *hdr = &cache->hdr[i];
		ut64 hdr_offset = cache->hdr_offset[i];
		ut64 symbols_off = cache->symbols_off_base - hdr_offset;
		ut32 maps_index = cache->maps_index[i];
		cache_img_t *img = read_cache_images(cache->buf, hdr, hdr_offset);
		if (!img) {
			goto next;
		}

		ut32 j;
		ut16 *depArray = NULL;
		cache_imgxtr_t *extras = NULL;
		if (target_libs) {
			HtPU *path_to_idx = NULL;
			if (cache->accel) {
				depArray = RZ_NEWS0(ut16, cache->accel->depListCount);
				if (!depArray) {
					goto next;
				}

				if (rz_buf_fread_at(cache->buf, cache->accel->depListOffset, (ut8 *)depArray, "s", cache->accel->depListCount) != cache->accel->depListCount * 2) {
					goto next;
				}

				extras = read_cache_imgextra(cache->buf, hdr, cache->accel);
				if (!extras) {
					goto next;
				}
			} else {
				path_to_idx = create_path_to_index(cache->buf, img, hdr);
			}

			for (j = 0; j < hdr->imagesCount; j++) {
				bool printing = !deps[j];
				char *lib_name = get_lib_name(cache->buf, &img[j]);
				if (!lib_name) {
					break;
				}
				if (strstr(lib_name, "libobjc.A.dylib")) {
					deps[j]++;
				}
				if (!rz_list_find(target_lib_names, lib_name, string_contains)) {
					RZ_FREE(lib_name);
					continue;
				}
				if (printing) {
					RZ_LOG_INFO("FILTER: %s\n", lib_name);
				}
				RZ_FREE(lib_name);
				deps[j]++;

				if (extras && depArray) {
					ut32 k;
					for (k = extras[j].dependentsStartArrayIndex; depArray[k] != 0xffff; k++) {
						ut16 dep_index = depArray[k] & 0x7fff;
						deps[dep_index]++;

						char *dep_name = get_lib_name(cache->buf, &img[dep_index]);
						if (!dep_name) {
							break;
						}
						if (printing) {
							RZ_LOG_INFO("-> %s\n", dep_name);
						}
						free(dep_name);
					}
				} else if (path_to_idx) {
					carve_deps_at_address(cache, img, path_to_idx, img[j].address, deps, printing);
				}
			}

			ht_pu_free(path_to_idx);
			RZ_FREE(depArray);
			RZ_FREE(extras);
		}

		for (j = 0; j < hdr->imagesCount; j++) {
			if (deps && !deps[j]) {
				continue;
			}
			ut64 pa = va2pa(img[j].address, hdr->mappingCount, &cache->maps[maps_index], cache->buf, 0, NULL, NULL);
			if (pa == UT64_MAX) {
				continue;
			}
			ut8 magicbytes[4];
			rz_buf_read_at(cache->buf, pa, magicbytes, 4);
			int magic = rz_read_le32(magicbytes);
			switch (magic) {
			case MH_MAGIC_64: {
				char file[256];
				RzDyldBinImage *bin = RZ_NEW0(RzDyldBinImage);
				if (!bin) {
					goto next;
				}
				bin->header_at = pa;
				bin->hdr_offset = hdr_offset;
				bin->symbols_off = symbols_off;
				bin->va = img[j].address;
				if (rz_buf_read_at(cache->buf, img[j].pathFileOffset, (ut8 *)&file, sizeof(file)) == sizeof(file)) {
					file[255] = 0;
					char *last_slash = strrchr(file, '/');
					if (last_slash && *last_slash) {
						if (last_slash > file) {
							char *scan = last_slash - 1;
							while (scan > file && *scan != '/') {
								scan--;
							}
							if (*scan == '/') {
								bin->file = strdup(scan + 1);
							} else {
								bin->file = strdup(last_slash + 1);
							}
						} else {
							bin->file = strdup(last_slash + 1);
						}
					} else {
						bin->file = strdup(file);
					}
				}
				rz_list_append(bins, bin);
				break;
			}
			default:
				RZ_LOG_WARN("Unknown sub-bin\n");
				break;
			}
		}
	next:
		RZ_FREE(depArray);
		RZ_FREE(extras);
		RZ_FREE(img);
	}
	if (rz_list_empty(bins)) {
		rz_list_free(bins);
		bins = NULL;
	}
	RZ_FREE(deps);
	RZ_FREE(target_libs);
	rz_list_free(target_lib_names);
	return bins;
}


static cache_accel_t *read_cache_accel(RzBuffer *cache_buf, cache_hdr_t *hdr, cache_map_t *maps) {
	if (!cache_buf || !hdr || !hdr->accelerateInfoSize || !hdr->accelerateInfoAddr) {
		return NULL;
	}

	ut64 offset = va2pa(hdr->accelerateInfoAddr, hdr->mappingCount, maps, cache_buf, 0, NULL, NULL);
	if (!offset) {
		return NULL;
	}

	ut64 size = sizeof(cache_accel_t);
	cache_accel_t *accel = RZ_NEW0(cache_accel_t);
	if (!accel) {
		return NULL;
	}

	if (rz_buf_fread_at(cache_buf, offset, (ut8 *)accel, "16il", 1) != size) {
		RZ_FREE(accel);
		return NULL;
	}

	accel->imagesExtrasOffset += offset;
	accel->bottomUpListOffset += offset;
	accel->dylibTrieOffset += offset;
	accel->initializersOffset += offset;
	accel->dofSectionsOffset += offset;
	accel->reExportListOffset += offset;
	accel->depListOffset += offset;
	accel->rangeTableOffset += offset;

	return accel;
}


static RzDyldRebaseInfo *get_rebase_info(RzDyldCache *cache, ut64 slideInfoOffset, ut64 slideInfoSize, ut64 start_of_data, ut64 slide) {
	ut8 *tmp_buf_1 = NULL;
	ut8 *tmp_buf_2 = NULL;
	ut8 *one_page_buf = NULL;
	RzBuffer *cache_buf = cache->buf;

	ut64 offset = slideInfoOffset;
	ut32 slide_info_version = 0;
	if (rz_buf_read_at(cache_buf, offset, (ut8 *)&slide_info_version, 4) != 4) {
		return NULL;
	}

	if (slide_info_version == 3) {
		cache_slide3_t slide_info;
		ut64 size = sizeof(cache_slide3_t);
		if (rz_buf_fread_at(cache_buf, offset, (ut8 *)&slide_info, "4i1l", 1) < 20) {
			return NULL;
		}

		ut64 page_starts_offset = offset + size;
		ut64 page_starts_size = slide_info.page_starts_count * 2;

		if (page_starts_size + size > slideInfoSize) {
			return NULL;
		}

		if (page_starts_size > 0) {
			tmp_buf_1 = malloc(page_starts_size);
			if (!tmp_buf_1) {
				goto beach;
			}
			if (rz_buf_fread_at(cache_buf, page_starts_offset, tmp_buf_1, "s", slide_info.page_starts_count) != page_starts_size) {
				goto beach;
			}
		}

		if (slide_info.page_size > 0) {
			one_page_buf = malloc(slide_info.page_size);
			if (!one_page_buf) {
				goto beach;
			}
		}

		RzDyldRebaseInfo3 *rebase_info = RZ_NEW0(RzDyldRebaseInfo3);
		if (!rebase_info) {
			goto beach;
		}

		rebase_info->version = 3;
		rebase_info->delta_mask = 0x3ff8000000000000ULL;
		rebase_info->delta_shift = 51;
		rebase_info->start_of_data = start_of_data;
		rebase_info->page_starts = (ut16 *)tmp_buf_1;
		rebase_info->page_starts_count = slide_info.page_starts_count;
		rebase_info->auth_value_add = slide_info.auth_value_add;
		rebase_info->page_size = slide_info.page_size;
		rebase_info->one_page_buf = one_page_buf;
		if (slide == UT64_MAX) {
			rebase_info->slide = estimate_slide(cache, 0x7ffffffffffffULL, 0);
			if (rebase_info->slide) {
				RZ_LOG_INFO("dyldcache is slid: 0x%" PFMT64x "\n", rebase_info->slide);
			}
		} else {
			rebase_info->slide = slide;
		}

		return (RzDyldRebaseInfo *)rebase_info;
	} else if (slide_info_version == 2 || slide_info_version == 4) {
		cache_slide2_t slide_info;
		ut64 size = sizeof(cache_slide2_t);
		if (rz_buf_fread_at(cache_buf, offset, (ut8 *)&slide_info, "6i2l", 1) != size) {
			return NULL;
		}

		if (slide_info.page_starts_offset == 0 ||
			slide_info.page_starts_offset > slideInfoSize ||
			slide_info.page_starts_offset + slide_info.page_starts_count * 2 > slideInfoSize) {
			return NULL;
		}

		if (slide_info.page_extras_offset == 0 ||
			slide_info.page_extras_offset > slideInfoSize ||
			slide_info.page_extras_offset + slide_info.page_extras_count * 2 > slideInfoSize) {
			return NULL;
		}

		if (slide_info.page_starts_count > 0) {
			ut64 size = slide_info.page_starts_count * 2;
			ut64 at = slideInfoOffset + slide_info.page_starts_offset;
			tmp_buf_1 = malloc(size);
			if (!tmp_buf_1) {
				goto beach;
			}
			if (rz_buf_fread_at(cache_buf, at, tmp_buf_1, "s", slide_info.page_starts_count) != size) {
				goto beach;
			}
		}

		if (slide_info.page_extras_count > 0) {
			ut64 size = slide_info.page_extras_count * 2;
			ut64 at = slideInfoOffset + slide_info.page_extras_offset;
			tmp_buf_2 = malloc(size);
			if (!tmp_buf_2) {
				goto beach;
			}
			if (rz_buf_fread_at(cache_buf, at, tmp_buf_2, "s", slide_info.page_extras_count) != size) {
				goto beach;
			}
		}

		if (slide_info.page_size > 0) {
			one_page_buf = malloc(slide_info.page_size);
			if (!one_page_buf) {
				goto beach;
			}
		}

		RzDyldRebaseInfo2 *rebase_info = RZ_NEW0(RzDyldRebaseInfo2);
		if (!rebase_info) {
			goto beach;
		}

		rebase_info->version = slide_info_version;
		rebase_info->start_of_data = start_of_data;
		rebase_info->page_starts = (ut16 *)tmp_buf_1;
		rebase_info->page_starts_count = slide_info.page_starts_count;
		rebase_info->page_extras = (ut16 *)tmp_buf_2;
		rebase_info->page_extras_count = slide_info.page_extras_count;
		rebase_info->value_add = slide_info.value_add;
		rebase_info->delta_mask = slide_info.delta_mask;
		rebase_info->value_mask = ~rebase_info->delta_mask;
		rebase_info->delta_shift = dumb_ctzll(rebase_info->delta_mask) - 2;
		rebase_info->page_size = slide_info.page_size;
		rebase_info->one_page_buf = one_page_buf;
		if (slide == UT64_MAX) {
			rebase_info->slide = estimate_slide(cache, rebase_info->value_mask, rebase_info->value_add);
			if (rebase_info->slide) {
				RZ_LOG_INFO("dyldcache is slid: 0x%" PFMT64x "\n", rebase_info->slide);
			}
		} else {
			rebase_info->slide = slide;
		}

		return (RzDyldRebaseInfo *)rebase_info;
	} else if (slide_info_version == 1) {
		cache_slide1_t slide_info;
		ut64 size = sizeof(cache_slide1_t);
		if (rz_buf_fread_at(cache_buf, offset, (ut8 *)&slide_info, "6i", 1) != size) {
			return NULL;
		}

		if (slide_info.toc_offset == 0 ||
			slide_info.toc_offset > slideInfoSize ||
			slide_info.toc_offset + slide_info.toc_count * 2 > slideInfoSize) {
			return NULL;
		}

		if (slide_info.entries_offset == 0 ||
			slide_info.entries_offset > slideInfoSize ||
			slide_info.entries_offset + slide_info.entries_count * slide_info.entries_size > slideInfoSize) {
			return NULL;
		}

		if (slide_info.toc_count > 0) {
			ut64 size = slide_info.toc_count * 2;
			ut64 at = slideInfoOffset + slide_info.toc_offset;
			tmp_buf_1 = malloc(size);
			if (!tmp_buf_1) {
				goto beach;
			}
			if (rz_buf_fread_at(cache_buf, at, tmp_buf_1, "s", slide_info.toc_count) != size) {
				goto beach;
			}
		}

		if (slide_info.entries_count > 0) {
			ut64 size = (ut64)slide_info.entries_count * (ut64)slide_info.entries_size;
			ut64 at = slideInfoOffset + slide_info.entries_offset;
			tmp_buf_2 = malloc(size);
			if (!tmp_buf_2) {
				goto beach;
			}
			if (rz_buf_read_at(cache_buf, at, tmp_buf_2, size) != size) {
				goto beach;
			}
		}

		one_page_buf = malloc(4096);
		if (!one_page_buf) {
			goto beach;
		}

		RzDyldRebaseInfo1 *rebase_info = RZ_NEW0(RzDyldRebaseInfo1);
		if (!rebase_info) {
			goto beach;
		}

		rebase_info->version = 1;
		rebase_info->start_of_data = start_of_data;
		rebase_info->one_page_buf = one_page_buf;
		rebase_info->page_size = 4096;
		rebase_info->toc = (ut16 *)tmp_buf_1;
		rebase_info->toc_count = slide_info.toc_count;
		rebase_info->entries = tmp_buf_2;
		rebase_info->entries_size = slide_info.entries_size;
		if (slide == UT64_MAX) {
			rebase_info->slide = estimate_slide(cache, UT64_MAX, 0);
			if (rebase_info->slide) {
				RZ_LOG_INFO("dyldcache is slid: 0x%" PFMT64x "\n", rebase_info->slide);
			}
		} else {
			rebase_info->slide = slide;
		}

		return (RzDyldRebaseInfo *)rebase_info;
	} else {
		RZ_LOG_ERROR("Unsupported slide info version %d\n", slide_info_version);
		return NULL;
	}

beach:
	free(tmp_buf_1);
	free(tmp_buf_2);
	free(one_page_buf);
	return NULL;
}


    byte* RemoteIo::mmap(bool /*isWriteable*/)
    {
        size_t nRealData = 0 ;
        if ( !bigBlock_ ) {
            size_t blockSize = p_->blockSize_;
            size_t blocks = (p_->size_ + blockSize -1)/blockSize ;
            bigBlock_   = new byte[blocks*blockSize] ;
            for ( size_t block = 0 ; block < blocks ; block ++ ) {
                void* p = p_->blocksMap_[block].getData();
                if  ( p ) {
                    nRealData += blockSize ;
                    memcpy(bigBlock_+(block*blockSize),p,blockSize);
                }
            }
#ifdef DEBUG
            std::cerr << "RemoteIo::mmap nRealData = " << nRealData << std::endl;
#endif
		}

        return bigBlock_;
    }

Fix https://github.com/Exiv2/exiv2/issues/55


    SshIo::SshImpl::SshImpl(const std::wstring& wurl, size_t blockSize):Impl(wurl, blockSize)
    {
        std::string url;
        url.assign(wurl.begin(), wurl.end());
        path_ = url;

        hostInfo_ = Exiv2::Uri::Parse(url);
        Exiv2::Uri::Decode(hostInfo_);

        // remove / at the beginning of the path
        if (hostInfo_.Path[0] == '/') {
            hostInfo_.Path = hostInfo_.Path.substr(1);
        }
        ssh_ = new SSH(hostInfo_.Host, hostInfo_.Username, hostInfo_.Password, hostInfo_.Port);

        if (protocol_ == pSftp) {
            ssh_->getFileSftp(hostInfo_.Path, fileHandler_);
            if (fileHandler_ == NULL) throw Error(1, "Unable to open the file");
        } else {
            fileHandler_ = NULL;
        }
    }


    void HttpIo::HttpImpl::getDataByRange(long lowBlock, long highBlock, std::string& response)
    {
        Exiv2::Dictionary responseDic;
        Exiv2::Dictionary request;
        request["server"] = hostInfo_.Host;
        request["page"  ] = hostInfo_.Path;
        if (hostInfo_.Port != "") request["port"] = hostInfo_.Port;
        request["verb"]   = "GET";
        std::string errors;
        if (lowBlock > -1 && highBlock > -1) {
            std::stringstream ss;
            ss << "Range: bytes=" << lowBlock * blockSize_  << "-" << ((highBlock + 1) * blockSize_ - 1) << "\r\n";
            request["header"] = ss.str();
        }

        long serverCode = (long)http(request, responseDic, errors);
        if (serverCode < 0 || serverCode >= 400 || errors.compare("") != 0) {
            throw Error(55, "Server", serverCode);
        }
        response = responseDic["body"];
    }


    std::wstring ReplaceStringInPlace(std::wstring subject, const std::wstring& search,
                          const std::wstring& replace) {
        std::wstring::size_type pos = 0;
        while((pos = subject.find(search, pos)) != std::wstring::npos) {
             subject.replace(pos, search.length(), replace);
             pos += replace.length();
        }
        return subject;
    }


    void MemIo::transfer(BasicIo& src)
    {
        MemIo *memIo = dynamic_cast<MemIo*>(&src);
        if (memIo) {
            // Optimization if src is another instance of MemIo
            if (p_->isMalloced_) {
                std::free(p_->data_);
            }
            p_->idx_ = 0;
            p_->data_ = memIo->p_->data_;
            p_->size_ = memIo->p_->size_;
            p_->isMalloced_ = memIo->p_->isMalloced_;
            memIo->p_->idx_ = 0;
            memIo->p_->data_ = 0;
            memIo->p_->size_ = 0;
            memIo->p_->isMalloced_ = false;
        }
        else {
            // Generic reopen to reset position to start
            if (src.open() != 0) {
                throw Error(9, src.path(), strError());
            }
            p_->idx_ = 0;
            write(src);
            src.close();
        }
        if (error() || src.error()) throw Error(19, strError());
    }


    long HttpIo::HttpImpl::getFileLength()
    {
        Exiv2::Dictionary response;
        Exiv2::Dictionary request;
        std::string errors;
        request["server"] = hostInfo_.Host;
        request["page"  ] = hostInfo_.Path;
        if (hostInfo_.Port != "") request["port"] = hostInfo_.Port;
        request["verb"]   = "HEAD";
        long serverCode = (long)http(request, response, errors);
        if (serverCode < 0 || serverCode >= 400 || errors.compare("") != 0) {
            throw Error(55, "Server", serverCode);
        }

        Exiv2::Dictionary_i lengthIter = response.find("Content-Length");
        return (lengthIter == response.end()) ? -1 : atol((lengthIter->second).c_str());
    }


    CurlIo::CurlImpl::CurlImpl(const std::wstring& wurl, size_t blockSize):Impl(wurl, blockSize)
    {
        std::string url;
        url.assign(wurl.begin(), wurl.end());
        path_ = url;

        // init curl pointer
        curl_ = curl_easy_init();
        if(!curl_) {
            throw Error(1, "Uable to init libcurl.");
        }

        // The default block size for FTP is much larger than other protocols
        // the reason is that getDataByRange() in FTP always creates the new connection,
        // so we need the large block size to reduce the overhead of creating the connection.
        if (blockSize_ == 0) {
            blockSize_ = protocol_ == pFtp ? 102400 : 1024;
        }
    }


    long writeFile(const DataBuf& buf, const std::wstring& wpath)
    {
        FileIo file(wpath);
        if (file.open("wb") != 0) {
            throw WError(10, wpath, "wb", strError().c_str());
        }
        return file.write(buf.pData_, buf.size_);
    }


    long CurlIo::write(const byte* data, long wcount)
    {
        if (p_->protocol_ == pHttp || p_->protocol_ == pHttps) {
            return RemoteIo::write(data, wcount);
        } else {
            throw Error(1, "doesnt support write for this protocol.");
        }
    }


    long SshIo::SshImpl::getFileLength()
    {
        long length = 0;
        if (protocol_ == pSftp) { // sftp
            sftp_attributes attributes = sftp_fstat(fileHandler_);
            length = (long)attributes->size;
        } else { // ssh
            std::string response;
            //std::string cmd = "stat -c %s " + hostInfo_.Path;
            std::string cmd = "declare -a x=($(ls -alt " + hostInfo_.Path + ")); echo ${x[4]}";
            if (ssh_->runCommand(cmd, &response) != 0) {
                throw Error(1, "Unable to get file length.");
            } else {
                length = atol(response.c_str());
                if (length == 0) {
                    throw Error(1, "File is empty or not found.");
                }
            }
        }
        return length;
    }


    long RemoteIo::read(byte* buf, long rcount)
    {
        assert(p_->isMalloced_);
        if (p_->eof_) return 0;
        p_->totalRead_ += rcount;

        size_t allow     = EXV_MIN(rcount, (long)( p_->size_ - p_->idx_));
        size_t lowBlock  =  p_->idx_         /p_->blockSize_;
        size_t highBlock = (p_->idx_ + allow)/p_->blockSize_;

        // connect to the remote machine & populate the blocks just in time.
        p_->populateBlocks(lowBlock, highBlock);
        byte* fakeData = (byte*) std::calloc(p_->blockSize_, sizeof(byte));
        if (!fakeData) {
            throw Error(1, "Unable to allocate data");
        }

        size_t iBlock = lowBlock;
        size_t startPos = p_->idx_ - lowBlock*p_->blockSize_;
        size_t totalRead = 0;
        do {
            byte* data = p_->blocksMap_[iBlock++].getData();
            if (data == NULL) data = fakeData;
            size_t blockR = EXV_MIN(allow, p_->blockSize_ - startPos);
            std::memcpy(&buf[totalRead], &data[startPos], blockR);
            totalRead += blockR;
            startPos = 0;
            allow -= blockR;
        } while(allow);

        if (fakeData) std::free(fakeData);

        p_->idx_ += (long) totalRead;
        p_->eof_ = (p_->idx_ == (long) p_->size_);

        return (long) totalRead;
    }


    MemIo::~MemIo()
    {
        if (p_->isMalloced_) {
            std::free(p_->data_);
        }
        delete p_;
    }


    void SshIo::SshImpl::getDataByRange(long lowBlock, long highBlock, std::string& response)
    {
        if (protocol_ == pSftp) {
            if (sftp_seek(fileHandler_, (uint32_t) (lowBlock * blockSize_)) < 0) throw Error(1, "SFTP: unable to sftp_seek");
            size_t buffSize = (highBlock - lowBlock + 1) * blockSize_;
            char* buffer = new char[buffSize];
            long nBytes = (long) sftp_read(fileHandler_, buffer, buffSize);
            if (nBytes < 0) throw Error(1, "SFTP: unable to sftp_read");
            response.assign(buffer, buffSize);
            delete[] buffer;
        } else {
            std::stringstream ss;
            if (lowBlock > -1 && highBlock > -1) {
                ss  << "dd if=" << hostInfo_.Path
                    << " ibs=" << blockSize_
                    << " skip=" << lowBlock
                    << " count=" << (highBlock - lowBlock) + 1<< " 2>/dev/null";
            } else {
                ss  << "dd if=" << hostInfo_.Path
                    << " ibs=" << blockSize_
                    << " 2>/dev/null";
            }
            std::string cmd = ss.str();
            if (ssh_->runCommand(cmd, &response) != 0) {
                throw Error(1, "Unable to get data by range.");
            }
        }
    }


    void CurlIo::CurlImpl::getDataByRange(long lowBlock, long highBlock, std::string& response)
    {
        curl_easy_reset(curl_); // reset all options
        curl_easy_setopt(curl_, CURLOPT_URL, path_.c_str());
        curl_easy_setopt(curl_, CURLOPT_NOPROGRESS, 1L); // no progress meter please
        curl_easy_setopt(curl_, CURLOPT_WRITEFUNCTION, curlWriter);
        curl_easy_setopt(curl_, CURLOPT_WRITEDATA, &response);
        curl_easy_setopt(curl_, CURLOPT_SSL_VERIFYPEER, 0L);
        curl_easy_setopt(curl_, CURLOPT_CONNECTTIMEOUT, timeout_);
        curl_easy_setopt(curl_, CURLOPT_SSL_VERIFYHOST, 0L);

        //curl_easy_setopt(curl_, CURLOPT_VERBOSE, 1); // debugging mode

        if (lowBlock > -1 && highBlock> -1) {
            std::stringstream ss;
            ss << lowBlock * blockSize_  << "-" << ((highBlock + 1) * blockSize_ - 1);
            std::string range = ss.str();
            curl_easy_setopt(curl_, CURLOPT_RANGE, range.c_str());
        }

        /* Perform the request, res will get the return code */
        CURLcode res = curl_easy_perform(curl_);

        if(res != CURLE_OK) {
            throw Error(1, curl_easy_strerror(res));
        } else {
            long serverCode;
            curl_easy_getinfo (curl_, CURLINFO_RESPONSE_CODE, &serverCode); // get code
            if (serverCode >= 400 || serverCode < 0) {
                throw Error(55, "Server", serverCode);
            }
        }
    }


    CurlIo::CurlImpl::CurlImpl(const std::string& url, size_t blockSize):Impl(url, blockSize)
    {
        // init curl pointer
        curl_ = curl_easy_init();
        if(!curl_) {
            throw Error(1, "Uable to init libcurl.");
        }

        // The default block size for FTP is much larger than other protocols
        // the reason is that getDataByRange() in FTP always creates the new connection,
        // so we need the large block size to reduce the overhead of creating the connection.
        if (blockSize_ == 0) {
            blockSize_ = protocol_ == pFtp ? 102400 : 1024;
        }

        std::string timeout = getEnv(envTIMEOUT);
        timeout_ = atol(timeout.c_str());
        if (timeout_ == 0) {
            throw Error(1, "Timeout Environmental Variable must be a positive integer.");
        }
    }


    byte* FileIo::mmap(bool isWriteable)
    {
        assert(p_->fp_ != 0);
        if (munmap() != 0) {
#ifdef EXV_UNICODE_PATH
            if (p_->wpMode_ == Impl::wpUnicode) {
                throw WError(2, wpath(), strError().c_str(), "munmap");
            }
            else
#endif
            {
                throw Error(2, path(), strError(), "munmap");
            }
        }
        p_->mappedLength_ = size();
        p_->isWriteable_ = isWriteable;
        if (p_->isWriteable_ && p_->switchMode(Impl::opWrite) != 0) {
#ifdef EXV_UNICODE_PATH
            if (p_->wpMode_ == Impl::wpUnicode) {
                throw WError(16, wpath(), strError().c_str());
            }
            else
#endif
            {
                throw Error(16, path(), strError());
            }
        }
#if defined EXV_HAVE_MMAP && defined EXV_HAVE_MUNMAP
        int prot = PROT_READ;
        if (p_->isWriteable_) {
            prot |= PROT_WRITE;
        }
        void* rc = ::mmap(0, p_->mappedLength_, prot, MAP_SHARED, fileno(p_->fp_), 0);
        if (MAP_FAILED == rc) {
#ifdef EXV_UNICODE_PATH
            if (p_->wpMode_ == Impl::wpUnicode) {
                throw WError(2, wpath(), strError().c_str(), "mmap");
            }
            else
#endif
            {
                throw Error(2, path(), strError(), "mmap");
            }
        }
        p_->pMappedArea_ = static_cast<byte*>(rc);

#elif defined WIN32 && !defined __CYGWIN__
        // Windows implementation

        // TODO: An attempt to map a file with a length of 0 (zero) fails with
        // an error code of ERROR_FILE_INVALID.
        // Applications should test for files with a length of 0 (zero) and
        // reject those files.

        DWORD dwAccess = FILE_MAP_READ;
        DWORD flProtect = PAGE_READONLY;
        if (isWriteable) {
            dwAccess = FILE_MAP_WRITE;
            flProtect = PAGE_READWRITE;
        }
        HANDLE hPh = GetCurrentProcess();
        HANDLE hFd = (HANDLE)_get_osfhandle(fileno(p_->fp_));
        if (hFd == INVALID_HANDLE_VALUE) {
#ifdef EXV_UNICODE_PATH
            if (p_->wpMode_ == Impl::wpUnicode) {
                throw WError(2, wpath(), "MSG1", "_get_osfhandle");
            }
            else
#endif
            {
                throw Error(2, path(), "MSG1", "_get_osfhandle");
            }
        }
        if (!DuplicateHandle(hPh, hFd, hPh, &p_->hFile_, 0, false, DUPLICATE_SAME_ACCESS)) {
#ifdef EXV_UNICODE_PATH
            if (p_->wpMode_ == Impl::wpUnicode) {
                throw WError(2, wpath(), "MSG2", "DuplicateHandle");
            }
            else
#endif
            {
                throw Error(2, path(), "MSG2", "DuplicateHandle");
            }
        }
        p_->hMap_ = CreateFileMapping(p_->hFile_, 0, flProtect, 0, (DWORD) p_->mappedLength_, 0);
        if (p_->hMap_ == 0 ) {
#ifdef EXV_UNICODE_PATH
            if (p_->wpMode_ == Impl::wpUnicode) {
                throw WError(2, wpath(), "MSG3", "CreateFileMapping");
            }
            else
#endif
            {
                throw Error(2, path(), "MSG3", "CreateFileMapping");
            }
        }
        void* rc = MapViewOfFile(p_->hMap_, dwAccess, 0, 0, 0);
        if (rc == 0) {
#ifdef EXV_UNICODE_PATH
            if (p_->wpMode_ == Impl::wpUnicode) {
                throw WError(2, wpath(), "MSG4", "CreateFileMapping");
            }
            else
#endif
            {
                throw Error(2, path(), "MSG4", "CreateFileMapping");
            }
        }
        p_->pMappedArea_ = static_cast<byte*>(rc);
#else
        // Workaround for platforms without mmap: Read the file into memory
        DataBuf buf(static_cast<long>(p_->mappedLength_));
        if (read(buf.pData_, buf.size_) != buf.size_) {
#ifdef EXV_UNICODE_PATH
            if (p_->wpMode_ == Impl::wpUnicode) {
                throw WError(2, wpath(), strError().c_str(), "FileIo::read");
            }
            else
#endif
            {
                throw Error(2, path(), strError(), "FileIo::read");
            }
        }
        if (error()) {
#ifdef EXV_UNICODE_PATH
            if (p_->wpMode_ == Impl::wpUnicode) {
                throw WError(2, wpath(), strError().c_str(), "FileIo::mmap");
            }
            else
#endif
            {
                throw Error(2, path(), strError(), "FileIo::mmap");
            }
        }
        p_->pMappedArea_ = buf.release().first;
        p_->isMalloced_ = true;
#endif
        return p_->pMappedArea_;
    }


    int RemoteIo::close()
    {
        if (p_->isMalloced_) {
            p_->eof_ = false;
            p_->idx_ = 0;
        }
#ifdef DEBUG
        std::cerr << "RemoteIo::close totalRead_ = " << p_->totalRead_ << std::endl;
#endif
		if ( bigBlock_ ) {
			delete [] bigBlock_;
			bigBlock_=NULL;
		}
        return 0;
    }


    long CurlIo::write(BasicIo& src)
    {
        if (p_->protocol_ == pHttp || p_->protocol_ == pHttps) {
            return RemoteIo::write(src);
        } else {
            throw Error(1, "doesnt support write for this protocol.");
        }
    }


    void XPathIo::ReadDataUri(const std::string& path) {
        size_t base64Pos = path.find("base64,");
        if (base64Pos == std::string::npos)
            throw Error(1, "No base64 data");

        std::string data = path.substr(base64Pos+7);
        char* decodeData = new char[data.length()];
        long size = base64decode(data.c_str(), decodeData, data.length());
        if (size > 0)
            write((byte*)decodeData, size);
        else
            throw Error(1, "Unable to decode base 64.");
        delete[] decodeData;
    }


    std::string XPathIo::writeDataToFile(const std::string& orgPath) {
        Protocol prot = fileProtocol(orgPath);

        // generating the name for temp file.
        std::time_t timestamp = std::time(NULL);
        std::stringstream ss;
        ss << timestamp << XPathIo::TEMP_FILE_EXT;
        std::string path = ss.str();
        std::ofstream fs(path.c_str(), std::ios::out | std::ios::binary | std::ios::trunc);

        if (prot == pStdin) {
            if (isatty(fileno(stdin)))
                throw Error(53);
#if defined(_MSC_VER) || defined(__MINGW__)
            // convert stdin to binary
            if (_setmode(_fileno(stdin), _O_BINARY) == -1)
                throw Error(54);
#endif
            // read stdin and write to the temp file.
            char readBuf[100*1024];
            std::streamsize readBufSize = 0;
            do {
                std::cin.read(readBuf, sizeof(readBuf));
                readBufSize = std::cin.gcount();
                if (readBufSize > 0) {
                    fs.write (readBuf, readBufSize);
                }
            } while(readBufSize);
        } else if (prot == pDataUri) {
            // read data uri and write to the temp file.
            size_t base64Pos = orgPath.find("base64,");
            if (base64Pos == std::string::npos)
                throw Error(1, "No base64 data");

            std::string data = orgPath.substr(base64Pos+7);
            char* decodeData = new char[data.length()];
            long size = base64decode(data.c_str(), decodeData, data.length());
            if (size > 0)
                fs.write(decodeData, size);
            else
                throw Error(1, "Unable to decode base 64.");
            delete[] decodeData;
        }

        fs.close();
        return path;
    }


    void MemIo::Impl::reserve(long wcount)
    {
        long need = wcount + idx_;

        if (!isMalloced_) {
            // Minimum size for 1st block is 32kB
            long size  = EXV_MAX(32768 * (1 + need / 32768), size_);
            byte* data = (byte*)std::malloc(size);
            std::memcpy(data, data_, size_);
            data_ = data;
            sizeAlloced_ = size;
            isMalloced_ = true;
        }

        if (need > size_) {
            if (need > sizeAlloced_) {
                // Allocate in blocks of 32kB
                long want = 32768 * (1 + need / 32768);
                data_ = (byte*)std::realloc(data_, want);
                sizeAlloced_ = want;
                isMalloced_ = true;
            }
            size_ = need;
        }
    }


    void Image::printIFDStructure(BasicIo& io, std::ostream& out, Exiv2::PrintStructureOption option,uint32_t start,bool bSwap,char c,int depth)
    {
        depth++;
        bool bFirst  = true  ;

        // buffer
        const size_t dirSize = 32;
        DataBuf  dir(dirSize);
        bool bPrint = option == kpsBasic || option == kpsRecursive;

        do {
            // Read top of directory
            io.seek(start,BasicIo::beg);
            io.read(dir.pData_, 2);
            uint16_t   dirLength = byteSwap2(dir,0,bSwap);

            bool tooBig = dirLength > 500;
            if ( tooBig ) throw Error(55);

            if ( bFirst && bPrint ) {
                out << Internal::indent(depth) << Internal::stringFormat("STRUCTURE OF TIFF FILE (%c%c): ",c,c) << io.path() << std::endl;
                if ( tooBig ) out << Internal::indent(depth) << "dirLength = " << dirLength << std::endl;
            }

            // Read the dictionary
            for ( int i = 0 ; i < dirLength ; i ++ ) {
                if ( bFirst && bPrint ) {
                    out << Internal::indent(depth)
                        << " address |    tag                              |     "
                        << " type |    count |    offset | value\n";
                }
                bFirst = false;

                io.read(dir.pData_, 12);
                uint16_t tag    = byteSwap2(dir,0,bSwap);
                uint16_t type   = byteSwap2(dir,2,bSwap);
                uint32_t count  = byteSwap4(dir,4,bSwap);
                uint32_t offset = byteSwap4(dir,8,bSwap);

                // Break for unknown tag types else we may segfault.
                if ( !typeValid(type) ) {
                    std::cerr << "invalid type value detected in Image::printIFDStructure:  " << type << std::endl;
                    start = 0; // break from do loop
                    throw Error(56);
                    break; // break from for loop
                }

                std::string sp  = "" ; // output spacer

                //prepare to print the value
                uint32_t kount  = isPrintXMP(tag,option) ? count // haul in all the data
                                : isPrintICC(tag,option) ? count // ditto
                                : isStringType(type)     ? (count > 32 ? 32 : count) // restrict long arrays
                                : count > 5              ? 5
                                : count
                                ;
                uint32_t pad    = isStringType(type) ? 1 : 0;
                uint32_t size   = isStringType(type) ? 1
                                : is2ByteType(type)  ? 2
                                : is4ByteType(type)  ? 4
                                : is8ByteType(type)  ? 8
                                : 1
                                ;

                // if ( offset > io.size() ) offset = 0; // Denial of service?

                // #55 memory allocation crash test/data/POC8
                long long allocate = (long long) (size*count + pad+20);
                if ( allocate > (long long) io.size() ) {
                    throw Error(57);
                }
                DataBuf  buf(allocate);  // allocate a buffer
                std::memcpy(buf.pData_,dir.pData_+8,4);  // copy dir[8:11] into buffer (short strings)
                const bool bOffsetIsPointer = count*size > 4;

                if ( bOffsetIsPointer ) {         // read into buffer
                    size_t   restore = io.tell();  // save
                    io.seek(offset,BasicIo::beg);  // position
                    io.read(buf.pData_,count*size);// read
                    io.seek(restore,BasicIo::beg); // restore
                }

                if ( bPrint ) {
                    const uint32_t address = start + 2 + i*12 ;
                    const std::string offsetString = bOffsetIsPointer?
                        Internal::stringFormat("%10u", offset):
                        "";

                    out << Internal::indent(depth)
                    << Internal::stringFormat("%8u | %#06x %-28s |%10s |%9u |%10s | "
                                              ,address,tag,tagName(tag).c_str(),typeName(type),count,offsetString.c_str());
                    if ( isShortType(type) ){
                        for ( size_t k = 0 ; k < kount ; k++ ) {
                            out << sp << byteSwap2(buf,k*size,bSwap);
                            sp = " ";
                        }
                    } else if ( isLongType(type) ){
                        for ( size_t k = 0 ; k < kount ; k++ ) {
                            out << sp << byteSwap4(buf,k*size,bSwap);
                            sp = " ";
                        }

                    } else if ( isRationalType(type) ){
                        for ( size_t k = 0 ; k < kount ; k++ ) {
                            uint32_t a = byteSwap4(buf,k*size+0,bSwap);
                            uint32_t b = byteSwap4(buf,k*size+4,bSwap);
                            out << sp << a << "/" << b;
                            sp = " ";
                        }
                    } else if ( isStringType(type) ) {
                        out << sp << Internal::binaryToString(buf, kount);
                    }

                    sp = kount == count ? "" : " ...";
                    out << sp << std::endl;

                    if ( option == kpsRecursive && (tag == 0x8769 /* ExifTag */ || tag == 0x014a/*SubIFDs*/  || type == tiffIfd) ) {
                        for ( size_t k = 0 ; k < count ; k++ ) {
                            size_t   restore = io.tell();
                            uint32_t offset = byteSwap4(buf,k*size,bSwap);
                            printIFDStructure(io,out,option,offset,bSwap,c,depth);
                            io.seek(restore,BasicIo::beg);
                        }
                    } else if ( option == kpsRecursive && tag == 0x83bb /* IPTCNAA */ ) {
                        size_t   restore = io.tell();  // save
                        io.seek(offset,BasicIo::beg);  // position
                        byte* bytes=new byte[count] ;  // allocate memory
                        io.read(bytes,count)        ;  // read
                        io.seek(restore,BasicIo::beg); // restore
                        IptcData::printStructure(out,bytes,count,depth);
                        delete[] bytes;                // free
                    }  else if ( option == kpsRecursive && tag == 0x927c /* MakerNote */ && count > 10) {
                        size_t   restore = io.tell();  // save

                        uint32_t jump= 10           ;
                        byte     bytes[20]          ;
                        const char* chars = (const char*) &bytes[0] ;
                        io.seek(offset,BasicIo::beg);  // position
                        io.read(bytes,jump    )     ;  // read
                        bytes[jump]=0               ;
                        if ( ::strcmp("Nikon",chars) == 0 ) {
                            // tag is an embedded tiff
                            byte* bytes=new byte[count-jump] ;  // allocate memory
                            io.read(bytes,count-jump)        ;  // read
                            MemIo memIo(bytes,count-jump)    ;  // create a file
                            printTiffStructure(memIo,out,option,depth);
                            delete[] bytes                   ;  // free
                        } else {
                            // tag is an IFD
                            io.seek(0,BasicIo::beg);  // position
                            printIFDStructure(io,out,option,offset,bSwap,c,depth);
                        }

                        io.seek(restore,BasicIo::beg); // restore
                    }
                }

                if ( isPrintXMP(tag,option) ) {
                    buf.pData_[count]=0;
                    out << (char*) buf.pData_;
                }
                if ( isPrintICC(tag,option) ) {
                    out.write((const char*)buf.pData_,count);
                }
            }
            if ( start ) {
                io.read(dir.pData_, 4);
                start = tooBig ? 0 : byteSwap4(dir,0,bSwap);
            }
        } while (start) ;

        if ( bPrint ) {
            out << Internal::indent(depth) << "END " << io.path() << std::endl;
        }
        out.flush();
        depth--;
    }


    void HttpIo::HttpImpl::writeRemote(const byte* data, size_t size, long from, long to)
    {
        std::string scriptPath(getEnv(envHTTPPOST));
        if (scriptPath == "") {
            throw Error(1, "Please set the path of the server script to handle http post data to EXIV2_HTTP_POST environmental variable.");
        }

        // standadize the path without "/" at the beginning.
        std::size_t protocolIndex = scriptPath.find("://");
        if (protocolIndex == std::string::npos && scriptPath[0] != '/') {
            scriptPath = "/" + scriptPath;
        }

        Exiv2::Dictionary response;
        Exiv2::Dictionary request;
        std::string errors;

        Uri scriptUri = Exiv2::Uri::Parse(scriptPath);
        request["server"] = scriptUri.Host == "" ? hostInfo_.Host : scriptUri.Host;
        if (scriptUri.Port != "") request["port"] = scriptUri.Port;
        request["page"] = scriptUri.Path;
        request["verb"] = "POST";

        // encode base64
        size_t encodeLength = ((size + 2) / 3) * 4 + 1;
        char* encodeData = new char[encodeLength];
        base64encode(data, size, encodeData, encodeLength);
        // url encode
        char* urlencodeData = urlencode(encodeData);
        delete[] encodeData;

        std::stringstream ss;
        ss << "path="   << hostInfo_.Path << "&"
           << "from="   << from           << "&"
           << "to="     << to             << "&"
           << "data="   << urlencodeData;
        std::string postData = ss.str();
        delete[] urlencodeData;

        // create the header
        ss.str("");
        ss << "Content-Length: " << postData.length()  << "\n"
           << "Content-Type: application/x-www-form-urlencoded\n"
           << "\n" << postData << "\r\n";
        request["header"] = ss.str();

        int serverCode = http(request, response, errors);
        if (serverCode < 0 || serverCode >= 400 || errors.compare("") != 0) {
            throw Error(55, "Server", serverCode);
        }
    }


    FileIo::~FileIo()
    {
        close();
        delete p_;
    }


    size_t RemoteIo::Impl::populateBlocks(size_t lowBlock, size_t highBlock)
    {
        assert(isMalloced_);

        // optimize: ignore all true blocks on left & right sides.
        while(!blocksMap_[lowBlock].isNone()  && lowBlock  < highBlock) lowBlock++;
        while(!blocksMap_[highBlock].isNone() && highBlock > lowBlock)  highBlock--;

        size_t rcount = 0;
        if (blocksMap_[highBlock].isNone())
        {
            std::string data;
            getDataByRange( (long) lowBlock, (long) highBlock, data);
            rcount = (size_t)data.length();
            if (rcount == 0) {
                throw Error(1, "Data By Range is empty. Please check the permission.");
            }
            byte* source = (byte*)data.c_str();
            size_t remain = rcount, totalRead = 0;
            size_t iBlock = (rcount == size_) ? 0 : lowBlock;

            while (remain) {
                size_t allow = EXV_MIN(remain, blockSize_);
                blocksMap_[iBlock].populate(&source[totalRead], allow);
                remain -= allow;
                totalRead += allow;
                iBlock++;
            }
        }

        return rcount;
    }


    int FileIo::seek(long offset, Position pos)
    {
        assert(p_->fp_ != 0);

        int fileSeek = 0;
        switch (pos) {
        case BasicIo::cur: fileSeek = SEEK_CUR; break;
        case BasicIo::beg: fileSeek = SEEK_SET; break;
        case BasicIo::end: fileSeek = SEEK_END; break;
        }

        if (p_->switchMode(Impl::opSeek) != 0) return 1;
        return std::fseek(p_->fp_, offset, fileSeek);
    }


    void SshIo::SshImpl::writeRemote(const byte* data, size_t size, long from, long to)
    {
        if (protocol_ == pSftp) throw Error(1, "not support SFTP write access.");

        //printf("ssh update size=%ld from=%ld to=%ld\n", (long)size, from, to);
        assert(isMalloced_);

        std::string tempFile = hostInfo_.Path + ".exiv2tmp";
        std::string response;
        std::stringstream ss;
        // copy the head (byte 0 to byte fromByte) of original file to filepath.exiv2tmp
        ss  << "head -c " << from
            << " "   << hostInfo_.Path
            << " > " << tempFile;
        std::string cmd = ss.str();
        if (ssh_->runCommand(cmd, &response) != 0) {
            throw Error(1, "SSH: Unable to cope the head of file to temp");
        }

        // upload the data (the byte ranges which are different between the original
        // file and the new file) to filepath.exiv2datatemp
        if (ssh_->scp(hostInfo_.Path + ".exiv2datatemp", data, size) != 0) {
            throw Error(1, "SSH: Unable to copy file");
        }

        // concatenate the filepath.exiv2datatemp to filepath.exiv2tmp
        cmd = "cat " + hostInfo_.Path + ".exiv2datatemp >> " + tempFile;
        if (ssh_->runCommand(cmd, &response) != 0) {
            throw Error(1, "SSH: Unable to copy the rest");
        }

        // copy the tail (from byte toByte to the end of file) of original file to filepath.exiv2tmp
        ss.str("");
        ss  << "tail -c+" << (to + 1)
            << " "   << hostInfo_.Path
            << " >> "   << tempFile;
        cmd = ss.str();
        if (ssh_->runCommand(cmd, &response) != 0) {
            throw Error(1, "SSH: Unable to copy the rest");
        }

        // replace the original file with filepath.exiv2tmp
        cmd = "mv " + tempFile + " " + hostInfo_.Path;
        if (ssh_->runCommand(cmd, &response) != 0) {
            throw Error(1, "SSH: Unable to copy the rest");
        }

        // remove filepath.exiv2datatemp
        cmd = "rm " + hostInfo_.Path + ".exiv2datatemp";
        if (ssh_->runCommand(cmd, &response) != 0) {
            throw Error(1, "SSH: Unable to copy the rest");
        }
    }


    DataBuf FileIo::read(long rcount)
    {
        assert(p_->fp_ != 0);
        if ( (size_t) rcount > size() ) throw Error(57);
        DataBuf buf(rcount);
        long readCount = read(buf.pData_, buf.size_);
        buf.size_ = readCount;
        return buf;
    }