<commit_before>package device

import (
	"fmt"
	"net"
	"os"

	"github.com/pkg/errors"

	"github.com/lxc/lxd/lxd/cluster"
	deviceConfig "github.com/lxc/lxd/lxd/device/config"
	"github.com/lxc/lxd/lxd/dnsmasq/dhcpalloc"
	"github.com/lxc/lxd/lxd/instance"
	"github.com/lxc/lxd/lxd/instance/instancetype"
	"github.com/lxc/lxd/lxd/network"
	"github.com/lxc/lxd/lxd/network/openvswitch"
	"github.com/lxc/lxd/lxd/revert"
	"github.com/lxc/lxd/lxd/util"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/api"
	log "github.com/lxc/lxd/shared/log15"
)

type nicOVN struct {
	deviceCommon

	network network.Network
}

// getIntegrationBridgeName returns the OVS integration bridge to use.
func (d *nicOVN) getIntegrationBridgeName() (string, error) {
	integrationBridge, err := cluster.ConfigGetString(d.state.Cluster, "network.ovn.integration_bridge")
	if err != nil {
		return "", errors.Wrapf(err, "Failed to get OVN integration bridge name")
	}

	return integrationBridge, nil
}

// validateConfig checks the supplied config for correctness.
func (d *nicOVN) validateConfig(instConf instance.ConfigReader) error {
	if !instanceSupported(instConf.Type(), instancetype.Container, instancetype.VM) {
		return ErrUnsupportedDevType
	}

	requiredFields := []string{
		"network",
	}

	optionalFields := []string{
		"name",
		"hwaddr",
		"host_name",
		"ipv4.address",
		"ipv6.address",
		"boot.priority",
	}

	// Lookup network settings and apply them to the device's config.
	n, err := network.LoadByName(d.state, d.config["network"])
	if err != nil {
		return errors.Wrapf(err, "Error loading network config for %q", d.config["network"])
	}

	if n.Status() == api.NetworkStatusPending {
		return fmt.Errorf("Specified network is not fully created")
	}

	if n.Type() != "ovn" {
		return fmt.Errorf("Specified network must be of type ovn")
	}

	d.network = n // Stored loaded instance for use by other functions.
	netConfig := d.network.Config()

	if d.config["ipv4.address"] != "" {
		// Check that DHCPv4 is enabled on parent network (needed to use static assigned IPs).
		if n.DHCPv4Subnet() == nil {
			return fmt.Errorf("Cannot specify %q when DHCP is disabled on network %q", "ipv4.address", d.config["network"])
		}

		_, subnet, err := net.ParseCIDR(netConfig["ipv4.address"])
		if err != nil {
			return errors.Wrapf(err, "Invalid network ipv4.address")
		}

		// Check the static IP supplied is valid for the linked network. It should be part of the
		// network's subnet, but not necessarily part of the dynamic allocation ranges.
		if !dhcpalloc.DHCPValidIP(subnet, nil, net.ParseIP(d.config["ipv4.address"])) {
			return fmt.Errorf("Device IP address %q not within network %q subnet", d.config["ipv4.address"], d.config["network"])
		}
	}

	if d.config["ipv6.address"] != "" {
		// Check that DHCPv6 is enabled on parent network (needed to use static assigned IPs).
		if n.DHCPv6Subnet() == nil || !shared.IsTrue(netConfig["ipv6.dhcp.stateful"]) {
			return fmt.Errorf("Cannot specify %q when DHCP or %q are disabled on network %q", "ipv6.address", "ipv6.dhcp.stateful", d.config["network"])
		}

		_, subnet, err := net.ParseCIDR(netConfig["ipv6.address"])
		if err != nil {
			return errors.Wrapf(err, "Invalid network ipv6.address")
		}

		// Check the static IP supplied is valid for the linked network. It should be part of the
		// network's subnet, but not necessarily part of the dynamic allocation ranges.
		if !dhcpalloc.DHCPValidIP(subnet, nil, net.ParseIP(d.config["ipv6.address"])) {
			return fmt.Errorf("Device IP address %q not within network %q subnet", d.config["ipv6.address"], d.config["network"])
		}
	}

	rules := nicValidationRules(requiredFields, optionalFields)

	// Now run normal validation.
	err = d.config.Validate(rules)
	if err != nil {
		return err
	}

	return nil
}

// validateEnvironment checks the runtime environment for correctness.
func (d *nicOVN) validateEnvironment() error {
	if d.inst.Type() == instancetype.Container && d.config["name"] == "" {
		return fmt.Errorf("Requires name property to start")
	}

	integrationBridge, err := d.getIntegrationBridgeName()
	if err != nil {
		return err
	}

	if !shared.PathExists(fmt.Sprintf("/sys/class/net/%s", integrationBridge)) {
		return fmt.Errorf("OVS integration bridge device %q doesn't exist", integrationBridge)
	}

	return nil
}

// CanHotPlug returns whether the device can be managed whilst the instance is running, it also
// returns a list of fields that can be updated without triggering a device remove & add.
func (d *nicOVN) CanHotPlug() (bool, []string) {
	return true, []string{}
}

// Add is run when a device is added to an instance whether or not the instance is running.
func (d *nicOVN) Add() error {
	return nil
}

// Start is run when the device is added to a running instance or instance is starting up.
func (d *nicOVN) Start() (*deviceConfig.RunConfig, error) {
	err := d.validateEnvironment()
	if err != nil {
		return nil, err
	}

	revert := revert.New()
	defer revert.Fail()

	saveData := make(map[string]string)
	saveData["host_name"] = d.config["host_name"]

	var peerName string

	// Create veth pair and configure the peer end with custom hwaddr and mtu if supplied.
	if d.inst.Type() == instancetype.Container {
		if saveData["host_name"] == "" {
			saveData["host_name"] = networkRandomDevName("veth")
		}
		peerName, err = networkCreateVethPair(saveData["host_name"], d.config)
	} else if d.inst.Type() == instancetype.VM {
		if saveData["host_name"] == "" {
			saveData["host_name"] = networkRandomDevName("tap")
		}
		peerName = saveData["host_name"] // VMs use the host_name to link to the TAP FD.
		err = networkCreateTap(saveData["host_name"], d.config)
	}

	if err != nil {
		return nil, err
	}

	revert.Add(func() { NetworkRemoveInterface(saveData["host_name"]) })

	// Populate device config with volatile fields if needed.
	networkVethFillFromVolatile(d.config, saveData)

	// Apply host-side limits.
	err = networkSetupHostVethLimits(d.config)
	if err != nil {
		return nil, err
	}

	// Disable IPv6 on host-side veth interface (prevents host-side interface getting link-local address)
	// which isn't needed because the host-side interface is connected to a bridge.
	err = util.SysctlSet(fmt.Sprintf("net/ipv6/conf/%s/disable_ipv6", saveData["host_name"]), "1")
	if err != nil && !os.IsNotExist(err) {
		return nil, err
	}

	mac, err := net.ParseMAC(d.config["hwaddr"])
	if err != nil {
		return nil, err
	}

	ips := []net.IP{}
	for _, key := range []string{"ipv4.address", "ipv6.address"} {
		if d.config[key] != "" {
			ip := net.ParseIP(d.config[key])
			if ip == nil {
				return nil, fmt.Errorf("Invalid %s value %q", key, d.config[key])
			}
			ips = append(ips, ip)
		}
	}

	// Add new OVN logical switch port for instance.
	logicalPortName, err := network.OVNInstanceDevicePortAdd(d.network, d.inst.ID(), d.name, mac, ips)
	if err != nil {
		return nil, err
	}

	revert.Add(func() { network.OVNInstanceDevicePortDelete(d.network, d.inst.ID(), d.name) })

	// Attach host side veth interface to bridge.
	integrationBridge, err := d.getIntegrationBridgeName()
	if err != nil {
		return nil, err
	}

	ovs := openvswitch.NewOVS()
	err = ovs.BridgePortAdd(integrationBridge, saveData["host_name"], true)
	if err != nil {
		return nil, err
	}

	revert.Add(func() { ovs.BridgePortDelete(integrationBridge, saveData["host_name"]) })

	// Link OVS port to OVN logical port.
	err = ovs.InterfaceAssociateOVNSwitchPort(saveData["host_name"], logicalPortName)
	if err != nil {
		return nil, err
	}

	// Attempt to disable router advertisement acceptance.
	err = util.SysctlSet(fmt.Sprintf("net/ipv6/conf/%s/accept_ra", saveData["host_name"]), "0")
	if err != nil && !os.IsNotExist(err) {
		return nil, err
	}

	// Attempt to disable IPv4 forwarding.
	err = util.SysctlSet(fmt.Sprintf("net/ipv4/conf/%s/forwarding", saveData["host_name"]), "0")
	if err != nil {
		return nil, err
	}

	err = d.volatileSet(saveData)
	if err != nil {
		return nil, err
	}

	runConf := deviceConfig.RunConfig{}
	runConf.NetworkInterface = []deviceConfig.RunConfigItem{
		{Key: "name", Value: d.config["name"]},
		{Key: "type", Value: "phys"},
		{Key: "flags", Value: "up"},
		{Key: "link", Value: peerName},
	}

	if d.inst.Type() == instancetype.VM {
		runConf.NetworkInterface = append(runConf.NetworkInterface,
			[]deviceConfig.RunConfigItem{
				{Key: "devName", Value: d.name},
				{Key: "hwaddr", Value: d.config["hwaddr"]},
			}...)
	}

	revert.Success()
	return &runConf, nil
}

// Update applies configuration changes to a started device.
func (d *nicOVN) Update(oldDevices deviceConfig.Devices, isRunning bool) error {
	oldConfig := oldDevices[d.name]

	v := d.volatileGet()

	// Populate device config with volatile fields if needed.
	networkVethFillFromVolatile(d.config, v)

	// If instance is running, apply host side limits and filters first before rebuilding
	// dnsmasq config below so that existing config can be used as part of the filter removal.
	if isRunning {
		err := d.validateEnvironment()
		if err != nil {
			return err
		}

		// Apply host-side limits.
		err = networkSetupHostVethLimits(d.config)
		if err != nil {
			return err
		}
	}

	// If an IPv6 address has changed, if the instance is running we should bounce the host-side
	// veth interface to give the instance a chance to detect the change and re-apply for an
	// updated lease with new IP address.
	if d.config["ipv6.address"] != oldConfig["ipv6.address"] && d.config["host_name"] != "" && shared.PathExists(fmt.Sprintf("/sys/class/net/%s", d.config["host_name"])) {
		_, err := shared.RunCommand("ip", "link", "set", d.config["host_name"], "down")
		if err != nil {
			return err
		}
		_, err = shared.RunCommand("ip", "link", "set", d.config["host_name"], "up")
		if err != nil {
			return err
		}
	}

	return nil
}

// Stop is run when the device is removed from the instance.
func (d *nicOVN) Stop() (*deviceConfig.RunConfig, error) {
	runConf := deviceConfig.RunConfig{
		PostHooks: []func() error{d.postStop},
	}

	err := network.OVNInstanceDevicePortDelete(d.network, d.inst.ID(), d.name)
	if err != nil {
		// Don't fail here as we still want the postStop hook to run to clean up the local veth pair.
		d.logger.Error("Failed to remove OVN device port", log.Ctx{"err": err})
	}

	return &runConf, nil
}

// postStop is run after the device is removed from the instance.
func (d *nicOVN) postStop() error {
	defer d.volatileSet(map[string]string{
		"host_name": "",
	})

	v := d.volatileGet()

	networkVethFillFromVolatile(d.config, v)

	if d.config["host_name"] != "" && shared.PathExists(fmt.Sprintf("/sys/class/net/%s", d.config["host_name"])) {
		integrationBridge, err := d.getIntegrationBridgeName()
		if err != nil {
			return err
		}

		ovs := openvswitch.NewOVS()

		// Detach host-side end of veth pair from bridge (required for openvswitch particularly).
		err = ovs.BridgePortDelete(integrationBridge, d.config["host_name"])
		if err != nil {
			return errors.Wrapf(err, "Failed to detach interface %q from %q", d.config["host_name"], integrationBridge)
		}

		// Removing host-side end of veth pair will delete the peer end too.
		err = NetworkRemoveInterface(d.config["host_name"])
		if err != nil {
			return errors.Wrapf(err, "Failed to remove interface %q", d.config["host_name"])
		}
	}

	return nil
}

// Remove is run when the device is removed from the instance or the instance is deleted.
func (d *nicOVN) Remove() error {
	return nil
}
<commit_msg>lxd/device/nic/ovn: Use parent network's bridge.mtu setting for setting device MTU<commit_after>package device

import (
	"fmt"
	"net"
	"os"

	"github.com/pkg/errors"

	"github.com/lxc/lxd/lxd/cluster"
	deviceConfig "github.com/lxc/lxd/lxd/device/config"
	"github.com/lxc/lxd/lxd/dnsmasq/dhcpalloc"
	"github.com/lxc/lxd/lxd/instance"
	"github.com/lxc/lxd/lxd/instance/instancetype"
	"github.com/lxc/lxd/lxd/network"
	"github.com/lxc/lxd/lxd/network/openvswitch"
	"github.com/lxc/lxd/lxd/revert"
	"github.com/lxc/lxd/lxd/util"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/api"
	log "github.com/lxc/lxd/shared/log15"
)

type nicOVN struct {
	deviceCommon

	network network.Network
}

// getIntegrationBridgeName returns the OVS integration bridge to use.
func (d *nicOVN) getIntegrationBridgeName() (string, error) {
	integrationBridge, err := cluster.ConfigGetString(d.state.Cluster, "network.ovn.integration_bridge")
	if err != nil {
		return "", errors.Wrapf(err, "Failed to get OVN integration bridge name")
	}

	return integrationBridge, nil
}

// validateConfig checks the supplied config for correctness.
func (d *nicOVN) validateConfig(instConf instance.ConfigReader) error {
	if !instanceSupported(instConf.Type(), instancetype.Container, instancetype.VM) {
		return ErrUnsupportedDevType
	}

	requiredFields := []string{
		"network",
	}

	optionalFields := []string{
		"name",
		"hwaddr",
		"host_name",
		"mtu",
		"ipv4.address",
		"ipv6.address",
		"boot.priority",
	}

	// Lookup network settings and apply them to the device's config.
	n, err := network.LoadByName(d.state, d.config["network"])
	if err != nil {
		return errors.Wrapf(err, "Error loading network config for %q", d.config["network"])
	}

	if n.Status() == api.NetworkStatusPending {
		return fmt.Errorf("Specified network is not fully created")
	}

	if n.Type() != "ovn" {
		return fmt.Errorf("Specified network must be of type ovn")
	}

	bannedKeys := []string{"mtu"}
	for _, bannedKey := range bannedKeys {
		if d.config[bannedKey] != "" {
			return fmt.Errorf("Cannot use %q property in conjunction with %q property", bannedKey, "network")
		}
	}

	d.network = n // Stored loaded instance for use by other functions.
	netConfig := d.network.Config()

	if d.config["ipv4.address"] != "" {
		// Check that DHCPv4 is enabled on parent network (needed to use static assigned IPs).
		if n.DHCPv4Subnet() == nil {
			return fmt.Errorf("Cannot specify %q when DHCP is disabled on network %q", "ipv4.address", d.config["network"])
		}

		_, subnet, err := net.ParseCIDR(netConfig["ipv4.address"])
		if err != nil {
			return errors.Wrapf(err, "Invalid network ipv4.address")
		}

		// Check the static IP supplied is valid for the linked network. It should be part of the
		// network's subnet, but not necessarily part of the dynamic allocation ranges.
		if !dhcpalloc.DHCPValidIP(subnet, nil, net.ParseIP(d.config["ipv4.address"])) {
			return fmt.Errorf("Device IP address %q not within network %q subnet", d.config["ipv4.address"], d.config["network"])
		}
	}

	if d.config["ipv6.address"] != "" {
		// Check that DHCPv6 is enabled on parent network (needed to use static assigned IPs).
		if n.DHCPv6Subnet() == nil || !shared.IsTrue(netConfig["ipv6.dhcp.stateful"]) {
			return fmt.Errorf("Cannot specify %q when DHCP or %q are disabled on network %q", "ipv6.address", "ipv6.dhcp.stateful", d.config["network"])
		}

		_, subnet, err := net.ParseCIDR(netConfig["ipv6.address"])
		if err != nil {
			return errors.Wrapf(err, "Invalid network ipv6.address")
		}

		// Check the static IP supplied is valid for the linked network. It should be part of the
		// network's subnet, but not necessarily part of the dynamic allocation ranges.
		if !dhcpalloc.DHCPValidIP(subnet, nil, net.ParseIP(d.config["ipv6.address"])) {
			return fmt.Errorf("Device IP address %q not within network %q subnet", d.config["ipv6.address"], d.config["network"])
		}
	}

	// Apply network level config options to device config before validation.
	mtu, err := network.OVNInstanceDeviceMTU(n)
	if err != nil {
		return err
	}

	d.config["mtu"] = fmt.Sprintf("%d", mtu)

	rules := nicValidationRules(requiredFields, optionalFields)

	// Now run normal validation.
	err = d.config.Validate(rules)
	if err != nil {
		return err
	}

	return nil
}

// validateEnvironment checks the runtime environment for correctness.
func (d *nicOVN) validateEnvironment() error {
	if d.inst.Type() == instancetype.Container && d.config["name"] == "" {
		return fmt.Errorf("Requires name property to start")
	}

	integrationBridge, err := d.getIntegrationBridgeName()
	if err != nil {
		return err
	}

	if !shared.PathExists(fmt.Sprintf("/sys/class/net/%s", integrationBridge)) {
		return fmt.Errorf("OVS integration bridge device %q doesn't exist", integrationBridge)
	}

	return nil
}

// CanHotPlug returns whether the device can be managed whilst the instance is running, it also
// returns a list of fields that can be updated without triggering a device remove & add.
func (d *nicOVN) CanHotPlug() (bool, []string) {
	return true, []string{}
}

// Add is run when a device is added to an instance whether or not the instance is running.
func (d *nicOVN) Add() error {
	return nil
}

// Start is run when the device is added to a running instance or instance is starting up.
func (d *nicOVN) Start() (*deviceConfig.RunConfig, error) {
	err := d.validateEnvironment()
	if err != nil {
		return nil, err
	}

	revert := revert.New()
	defer revert.Fail()

	saveData := make(map[string]string)
	saveData["host_name"] = d.config["host_name"]

	var peerName string

	// Create veth pair and configure the peer end with custom hwaddr and mtu if supplied.
	if d.inst.Type() == instancetype.Container {
		if saveData["host_name"] == "" {
			saveData["host_name"] = networkRandomDevName("veth")
		}
		peerName, err = networkCreateVethPair(saveData["host_name"], d.config)
	} else if d.inst.Type() == instancetype.VM {
		if saveData["host_name"] == "" {
			saveData["host_name"] = networkRandomDevName("tap")
		}
		peerName = saveData["host_name"] // VMs use the host_name to link to the TAP FD.
		err = networkCreateTap(saveData["host_name"], d.config)
	}

	if err != nil {
		return nil, err
	}

	revert.Add(func() { NetworkRemoveInterface(saveData["host_name"]) })

	// Populate device config with volatile fields if needed.
	networkVethFillFromVolatile(d.config, saveData)

	// Apply host-side limits.
	err = networkSetupHostVethLimits(d.config)
	if err != nil {
		return nil, err
	}

	// Disable IPv6 on host-side veth interface (prevents host-side interface getting link-local address)
	// which isn't needed because the host-side interface is connected to a bridge.
	err = util.SysctlSet(fmt.Sprintf("net/ipv6/conf/%s/disable_ipv6", saveData["host_name"]), "1")
	if err != nil && !os.IsNotExist(err) {
		return nil, err
	}

	mac, err := net.ParseMAC(d.config["hwaddr"])
	if err != nil {
		return nil, err
	}

	ips := []net.IP{}
	for _, key := range []string{"ipv4.address", "ipv6.address"} {
		if d.config[key] != "" {
			ip := net.ParseIP(d.config[key])
			if ip == nil {
				return nil, fmt.Errorf("Invalid %s value %q", key, d.config[key])
			}
			ips = append(ips, ip)
		}
	}

	// Add new OVN logical switch port for instance.
	logicalPortName, err := network.OVNInstanceDevicePortAdd(d.network, d.inst.ID(), d.name, mac, ips)
	if err != nil {
		return nil, err
	}

	revert.Add(func() { network.OVNInstanceDevicePortDelete(d.network, d.inst.ID(), d.name) })

	// Attach host side veth interface to bridge.
	integrationBridge, err := d.getIntegrationBridgeName()
	if err != nil {
		return nil, err
	}

	ovs := openvswitch.NewOVS()
	err = ovs.BridgePortAdd(integrationBridge, saveData["host_name"], true)
	if err != nil {
		return nil, err
	}

	revert.Add(func() { ovs.BridgePortDelete(integrationBridge, saveData["host_name"]) })

	// Link OVS port to OVN logical port.
	err = ovs.InterfaceAssociateOVNSwitchPort(saveData["host_name"], logicalPortName)
	if err != nil {
		return nil, err
	}

	// Attempt to disable router advertisement acceptance.
	err = util.SysctlSet(fmt.Sprintf("net/ipv6/conf/%s/accept_ra", saveData["host_name"]), "0")
	if err != nil && !os.IsNotExist(err) {
		return nil, err
	}

	// Attempt to disable IPv4 forwarding.
	err = util.SysctlSet(fmt.Sprintf("net/ipv4/conf/%s/forwarding", saveData["host_name"]), "0")
	if err != nil {
		return nil, err
	}

	err = d.volatileSet(saveData)
	if err != nil {
		return nil, err
	}

	runConf := deviceConfig.RunConfig{}
	runConf.NetworkInterface = []deviceConfig.RunConfigItem{
		{Key: "name", Value: d.config["name"]},
		{Key: "type", Value: "phys"},
		{Key: "flags", Value: "up"},
		{Key: "link", Value: peerName},
	}

	if d.inst.Type() == instancetype.VM {
		runConf.NetworkInterface = append(runConf.NetworkInterface,
			[]deviceConfig.RunConfigItem{
				{Key: "devName", Value: d.name},
				{Key: "hwaddr", Value: d.config["hwaddr"]},
			}...)
	}

	revert.Success()
	return &runConf, nil
}

// Update applies configuration changes to a started device.
func (d *nicOVN) Update(oldDevices deviceConfig.Devices, isRunning bool) error {
	oldConfig := oldDevices[d.name]

	v := d.volatileGet()

	// Populate device config with volatile fields if needed.
	networkVethFillFromVolatile(d.config, v)

	// If instance is running, apply host side limits and filters first before rebuilding
	// dnsmasq config below so that existing config can be used as part of the filter removal.
	if isRunning {
		err := d.validateEnvironment()
		if err != nil {
			return err
		}

		// Apply host-side limits.
		err = networkSetupHostVethLimits(d.config)
		if err != nil {
			return err
		}
	}

	// If an IPv6 address has changed, if the instance is running we should bounce the host-side
	// veth interface to give the instance a chance to detect the change and re-apply for an
	// updated lease with new IP address.
	if d.config["ipv6.address"] != oldConfig["ipv6.address"] && d.config["host_name"] != "" && shared.PathExists(fmt.Sprintf("/sys/class/net/%s", d.config["host_name"])) {
		_, err := shared.RunCommand("ip", "link", "set", d.config["host_name"], "down")
		if err != nil {
			return err
		}
		_, err = shared.RunCommand("ip", "link", "set", d.config["host_name"], "up")
		if err != nil {
			return err
		}
	}

	return nil
}

// Stop is run when the device is removed from the instance.
func (d *nicOVN) Stop() (*deviceConfig.RunConfig, error) {
	runConf := deviceConfig.RunConfig{
		PostHooks: []func() error{d.postStop},
	}

	err := network.OVNInstanceDevicePortDelete(d.network, d.inst.ID(), d.name)
	if err != nil {
		// Don't fail here as we still want the postStop hook to run to clean up the local veth pair.
		d.logger.Error("Failed to remove OVN device port", log.Ctx{"err": err})
	}

	return &runConf, nil
}

// postStop is run after the device is removed from the instance.
func (d *nicOVN) postStop() error {
	defer d.volatileSet(map[string]string{
		"host_name": "",
	})

	v := d.volatileGet()

	networkVethFillFromVolatile(d.config, v)

	if d.config["host_name"] != "" && shared.PathExists(fmt.Sprintf("/sys/class/net/%s", d.config["host_name"])) {
		integrationBridge, err := d.getIntegrationBridgeName()
		if err != nil {
			return err
		}

		ovs := openvswitch.NewOVS()

		// Detach host-side end of veth pair from bridge (required for openvswitch particularly).
		err = ovs.BridgePortDelete(integrationBridge, d.config["host_name"])
		if err != nil {
			return errors.Wrapf(err, "Failed to detach interface %q from %q", d.config["host_name"], integrationBridge)
		}

		// Removing host-side end of veth pair will delete the peer end too.
		err = NetworkRemoveInterface(d.config["host_name"])
		if err != nil {
			return errors.Wrapf(err, "Failed to remove interface %q", d.config["host_name"])
		}
	}

	return nil
}

// Remove is run when the device is removed from the instance or the instance is deleted.
func (d *nicOVN) Remove() error {
	return nil
}
<|endoftext|>


<commit_before>package mailmessage

import (
	"io"
	"net/mail"
)

func New(rdr io.Reader) {

}
<commit_msg>multipart reader<commit_after>package mailmessage

import (
	"bufio"
	"bytes"
	"crypto/rand"
	"encoding/base64"
	"encoding/hex"
	"errors"
	"io"
	"log"
	"net/mail"
	"os"
	"path"
	"strconv"
	"strings"
	"time"
)

const (
	CRLF               = "\r\n"
	MSG_MULTIPARTMIXED = iota
	MSG_MULTIPARTALTERNATIVE
	MSG_MESSAGE
)

type Message struct {
	Kind     int
	Header   mail.Header
	tempbody io.Reader
	Path     string
	File     *os.File
	Children []*Message
}

func (m *Message) Purge() {
	if m.File != nil {
		m.File.Close()
		m.File = nil
	}
	if len(m.Path) > 0 {
		os.Remove(m.Path)
	}
	if m.Children != nil {
		for k := range m.Children {
			m.Children[k].Purge()
		}
	}
}

func (m *Message) HTML() string {
	var rdr io.Reader
	if m.Kind == MSG_MULTIPARTALTERNATIVE {
		// find html
		for k := range m.Children {
			ct := m.Children[k].Header.Get("Content-Type")
			if strings.HasPrefix(ct, "text/html") {
				rdr = m.Children[k].File
				break
			}
		}
	} else if m.Kind == MSG_MESSAGE {
		ct := m.Header.Get("Content-Type")
		if strings.HasPrefix(ct, "text/html") {
			rdr = m.File
		}
	}
	if rdr == nil {
		return ""
	}
	var buffer bytes.Buffer
	io.Copy(&buffer, rdr)
	return buffer.String()
}

func (m *Message) Plaintext() string {
	var rdr io.Reader
	if m.Kind == MSG_MULTIPARTALTERNATIVE {
		// find text
		for k := range m.Children {
			ct := m.Children[k].Header.Get("Content-Type")
			if strings.HasPrefix(ct, "text/plain") {
				rdr = m.Children[k].File
				break
			}
		}
	} else if m.Kind == MSG_MESSAGE {
		ct := m.Header.Get("Content-Type")
		if strings.HasPrefix(ct, "text/plain") {
			rdr = m.File
		}
	}
	if rdr == nil {
		return ""
	}
	var buffer bytes.Buffer
	io.Copy(&buffer, rdr)
	return buffer.String()
}

func New(rdr *bufio.Reader) (*Message, error) {

	line, err := rdr.ReadString('\n')
	if err != nil {
		return nil, err
	}

	if strings.HasPrefix(line, "+OK") {
		log.Println("NEW MAIL", line[4:])
	} else if strings.HasPrefix(line, "-ERR") {
		return nil, errors.New(line[5:])
	} else {
		return nil, errors.New("Unknown pop3 server response `" + line + "`")
	}
	// save to a temporary file
	nowf := "msg_" + strconv.FormatInt(time.Now().Unix(), 10) + ".dat"
	fil0, err := os.OpenFile(path.Join(os.TempDir(), nowf), os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0666)

	if err != nil {
		return nil, err
	}

	for {
		line, err = rdr.ReadString('\n')
		if err != nil {
			return nil, err
		}
		if line == "."+CRLF {
			break
		}
		_, err = fil0.WriteString(line)
		if err != nil {
			return nil, err
		}
	}
	fil0.Seek(0, 0)
	mainm, err := mail.ReadMessage(fil0)
	if err != nil {
		return nil, err
	}
	contentType := mainm.Header.Get("Content-Type")
	if strings.HasPrefix(contentType, "multipart/mixed") {
		return multipartMessage(mainm, fil0)
	} else if strings.HasPrefix(contentType, "multipart/alternative") {
		return alternativeMessage(mainm, fil0)
	}
	return basicMessage(mainm, fil0)
}

func basicMessage(mainm *mail.Message, f *os.File) (*Message, error) {
	msg0 := &Message{}
	msg0.Header = mainm.Header
	msg0.File = f
	if f != nil {
		msg0.Path = f.Name()
	}
	msg0.Kind = MSG_MESSAGE
	msg0.tempbody = mainm.Body

	tf2n, tf2, _ := tempFile()
	io.Copy(tf2, msg0.tempbody)

	if f != nil {
		fn := f.Name()
		f.Close()
		os.Remove(fn)
	}
	tf2.Seek(0, 0)
	msg0.File = tf2
	msg0.Path = tf2n

	cte := msg0.Header.Get("Content-Transfer-Encoding")
	cte = strings.TrimSpace(cte)
	cte = strings.ToLower(cte)

	if cte == "base64" {
		bio := bufio.NewReader(msg0.File)
		tf3n, tf3, _ := tempFile()
		for {
			line, err := bio.ReadString('\n')
			if err != nil {
				break
			}
			if strings.HasSuffix(line, CRLF) {
				tf3.WriteString(line[:len(line)-2])
			} else {
				tf3.WriteString(line[:len(line)-1])
			}
		}
		tf3.Sync()
		tf3.Seek(0, 0)

		trdr := base64.NewDecoder(base64.StdEncoding, tf3)
		tf2.Seek(0, 0)
		tf2.Truncate(0)
		log.Println(io.Copy(tf2, trdr))
		tf2.Sync()
		tf2.Seek(0, 0)
		tf3.Close()
		os.Remove(tf3n)
	}

	return msg0, nil
}

func alternativeMessage(mainm *mail.Message, f *os.File) (*Message, error) {
	a, b := multipartMessage(mainm, f)
	if b != nil {
		return nil, b
	}
	a.Kind = MSG_MULTIPARTALTERNATIVE
	return a, b
}

func multipartMessage(mainm *mail.Message, f *os.File) (*Message, error) {
	ct := mainm.Header.Get("Content-Type")
	log.Println("it's multipart mixed", ct)
	boundary, err := getBoundary(ct)
	if err != nil {
		return nil, err
	}
	rdr := bufio.NewReader(mainm.Body)
	for {
		// read first boundary
		line, lerr := rdr.ReadString('\n')
		if lerr != nil {
			return nil, lerr
		}
		if strings.HasPrefix(line, "--"+boundary) {
			break
		}
	}

	msg0 := &Message{}
	msg0.File = f
	if f != nil {
		msg0.Path = f.Name()
	}
	msg0.Kind = MSG_MULTIPARTMIXED
	msg0.Header = mainm.Header
	msg0.Children = make([]*Message, 0)

	bound1 := "--" + boundary
	bound2 := "--" + boundary + "--"

	cont0 := true

	for cont0 {
		_, tf, err := tempFile()
		if err != nil {
			return nil, err
		}
		for {
			line, err := rdr.ReadString('\n')
			if err != nil {
				break
			}
			if strings.HasPrefix(line, bound2) {
				cont0 = false
				break
			} else if strings.HasPrefix(line, bound1) {
				break
			}
			tf.WriteString(line)
		}
		tf.Seek(0, 0)
		nmsg, err := mail.ReadMessage(tf)
		if err != nil {
			log.Println("nmsg, err := mail.ReadMessage(tf)", err)
			continue
		}
		var mmmsg *Message
		contentType := nmsg.Header.Get("Content-Type")
		if strings.HasPrefix(contentType, "multipart/mixed") {
			mmmsg, err = multipartMessage(nmsg, tf)
		} else if strings.HasPrefix(contentType, "multipart/alternative") {
			mmmsg, err = alternativeMessage(nmsg, tf)
		} else {
			mmmsg, err = basicMessage(nmsg, tf)
		}
		if err != nil {
			log.Println("err johnson", err)
			continue
		}
		msg0.Children = append(msg0.Children, mmmsg)
	}
	f.Close()
	os.Remove(msg0.Path)
	msg0.Path = ""
	msg0.File = nil
	return msg0, nil
}

func getBoundary(contentType string) (string, error) {
	strs := strings.Split(contentType, ";")
	for _, v := range strs {
		v = strings.TrimSpace(v)
		if strings.HasPrefix(v, "boundary=") {
			return v[9:], nil
		}
	}
	return "", errors.New("Boundary not found!")
}

var (
	tfi = 1
)

func tempFile() (string, *os.File, error) {
	tfi++
	bs := make([]byte, 6)
	rand.Read(bs)
	fn := hex.EncodeToString(bs) + "_" + strconv.FormatInt(time.Now().Unix(), 10) + "_" + strconv.Itoa(tfi) + ".dat"
	p0 := path.Join(os.TempDir(), fn)
	file, err := os.OpenFile(p0, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0666)
	return p0, file, err
}
<|endoftext|>


<commit_before>package hoard

import (
	"fmt"
	"io"

	"github.com/golang/protobuf/proto"
	"github.com/monax/hoard/v8/api"
	"github.com/monax/hoard/v8/grant"
	"github.com/monax/hoard/v8/reference"
	"github.com/monax/hoard/v8/stores"
)

// StreamingService provides the API implementation for Service without relying directly on the
// GRPC generated streaming types
type StreamingService struct {
	grantService      GrantService
	chunkSize         int
	useLinkRefInGrant func(refs []*reference.Ref) bool
}

// Create a streaming service that will re-buffer any plaintext data in blocks of chunkSize. useLinkRefInGrant is a
// predicate used to decide whether PutSeal should store a LINK ref in a grant rather than an array of refs. It is
// passed the refs so it may decide to dynamically use LINK refs based on the number and size of references
func NewStreamingService(grantService GrantService, chunkSize int, useLinkRefInGrant func(refs []*reference.Ref) bool) *StreamingService {
	return &StreamingService{
		grantService:      grantService,
		chunkSize:         chunkSize,
		useLinkRefInGrant: useLinkRefInGrant,
	}
}

func (service *StreamingService) PutSeal(sendAndClose func(*grant.Grant) error, recv func() (*api.PlaintextAndGrantSpec, error)) error {
	first, err := recv()
	if err != nil {
		return err
	}
	spec := first.GetGrantSpec()
	if spec == nil {
		return fmt.Errorf("grant spec expected in first message")
	}

	head := first.GetPlaintext().GetHead()

	var refs reference.Refs
	if head != nil {
		ref, err := headerReference(head, service.grantService.Put)
		if err != nil {
			return err
		}
		refs = append(refs, ref)
	}

	err = CopyChunked(
		func(chunk []byte) error {
			ref, err := service.grantService.Put(chunk, head.GetSalt())
			if err != nil {
				return err
			}
			refs = append(refs, ref)
			return nil
		},
		func() ([]byte, error) {
			if first != nil {
				body := first.GetPlaintext().GetBody()
				first = nil
				return body, nil
			}
			ptgs, err := recv()
			if err != nil {
				return nil, err
			}
			return ptgs.GetPlaintext().GetBody(), nil
		},
		service.chunkSize)

	// Should we store the refs themselves in hoard and seal a LINK ref into the grant
	if service.useLinkRefInGrant(refs) {
		ref, err := service.grantService.Put(refs.Plaintext(nil), head.GetSalt())
		if err != nil {
			return err
		}
		ref.Type = reference.Ref_LINK
		refs = reference.Refs{ref}
	}

	// Now send the grant
	grt, err := service.grantService.Seal(refs, spec)
	if err != nil {
		return err
	}

	return sendAndClose(grt)
}

// UnsealGet decrypts and gets plaintext associated with a grant
func (service *StreamingService) UnsealGet(grt *grant.Grant, send func(*api.Plaintext) error) error {
	refs, err := service.grantService.Unseal(grt)
	if err != nil {
		return err
	}

	for _, ref := range refs {
		data, err := service.grantService.Get(ref)
		if err != nil {
			return err
		}

		if err = decodePlaintext(data, ref.GetType(), service.grantService.Get, send); err != nil {
			return err
		}
	}
	return nil
}

// UnsealDelete gets the references stored in a grant and deletes them
func (service *StreamingService) UnsealDelete(grt *grant.Grant, send func(address *api.Address) error) error {
	refs, err := service.grantService.Unseal(grt)
	if err != nil {
		return err
	}

	for _, ref := range refs {
		err := service.grantService.Store().Delete(ref.Address)
		if err != nil {
			return err
		}
		if err = send(&api.Address{Address: ref.Address}); err != nil {
			return err
		}
	}
	return nil
}

// Put encrypted data in the store
func (service *StreamingService) Put(send func(*reference.Ref) error, recv func() (*api.Plaintext, error)) error {
	pt, err := recv()
	if err != nil {
		return err
	}

	head := pt.GetHead()
	if head != nil {
		ref, err := headerReference(head, service.grantService.Put)
		if err != nil {
			return err
		}
		err = send(ref)
		if err != nil {
			return err
		}
	}
	err = CopyChunked(
		func(chunk []byte) error {
			ref, err := service.grantService.Put(chunk, head.GetSalt())
			if err != nil {
				return err
			}
			return send(ref)
		},
		func() ([]byte, error) {
			// When Header is omitted first message should contain b
			if pt.GetBody() != nil {
				body := pt.Body
				pt = nil
				return body, nil
			}
			plaintext, err := recv()
			if err != nil {
				return nil, err
			}
			return plaintext.Body, nil
		},
		service.chunkSize)

	if err != nil {
		return fmt.Errorf("Put: could not put plaintexts: %w", err)
	}

	return nil
}

// Get decrypted data from the store
func (service *StreamingService) Get(send func(*api.Plaintext) error, recv func() (*reference.Ref, error)) error {
	for {
		ref, err := recv()
		if err != nil {
			if err == io.EOF {
				return nil
			}

			return err
		}

		data, err := service.grantService.Get(ref)
		if err != nil {
			return err
		}

		if err = decodePlaintext(data, ref.GetType(), service.grantService.Get, send); err != nil {
			return err
		}
	}
}

// Encrypt data and return ciphertext
func (service *StreamingService) Encrypt(send func(*api.ReferenceAndCiphertext) error, recv func() (*api.Plaintext, error)) error {
	first, err := recv()
	if err != nil {
		return err
	}
	head := first.GetHead()

	if head != nil {
		var encryptedData []byte
		ref, err := headerReference(head, func(data, salt []byte) (ref *reference.Ref, err error) {
			ref, encryptedData, err = service.grantService.Encrypt(data, salt)
			return ref, err
		})
		if err != nil {
			return fmt.Errorf("Encrypt: could not encode header: %w", err)
		}

		err = send(&api.ReferenceAndCiphertext{
			Reference: ref,
			Ciphertext: &api.Ciphertext{
				EncryptedData: encryptedData,
			},
		})
		if err != nil {
			return err
		}
	}

	var body []byte
	for {
		if first.GetBody() != nil {
			body = first.GetBody()
			first = nil
		} else {
			plaintext, err := recv()
			if err != nil {
				if err == io.EOF {
					return nil
				}
				return err
			}
			body = plaintext.GetBody()
		}

		ref, encryptedData, err := service.grantService.Encrypt(body, head.GetSalt())
		if err != nil {
			return err
		}

		if err = send(&api.ReferenceAndCiphertext{
			Reference: ref,
			Ciphertext: &api.Ciphertext{
				EncryptedData: encryptedData,
			},
		}); err != nil {
			return err
		}
	}
}

// Decrypt ciphertext and return plaintext
func (service *StreamingService) Decrypt(send func(*api.Plaintext) error, recv func() (*api.ReferenceAndCiphertext, error)) error {
	for {
		refAndCiphertext, err := recv()
		if err != nil {
			if err == io.EOF {
				return nil
			}

			return err
		}

		data, err := service.grantService.Decrypt(refAndCiphertext.Reference, refAndCiphertext.Ciphertext.EncryptedData)
		if err != nil {
			return err
		}

		if err = decodePlaintext(data, refAndCiphertext.Reference.GetType(), service.grantService.Get, send); err != nil {
			return err
		}
	}
}

// StorageServer

// Push ciphertext directly to store
func (service *StreamingService) Push(send func(*api.Address) error, recv func() (*api.Ciphertext, error)) error {
	for {
		ciphertext, err := recv()
		if err != nil {
			if err == io.EOF {
				return nil
			}

			return err
		}

		addr, err := service.grantService.Store().Put(ciphertext.EncryptedData)
		if err != nil {
			return err
		}

		if err = send(&api.Address{Address: addr}); err != nil {
			return err
		}
	}
}

// Pull gets ciphertext directly from the store
func (service *StreamingService) Pull(send func(*api.Ciphertext) error, recv func() (*api.Address, error)) error {
	for {
		addr, err := recv()
		if err != nil {
			if err == io.EOF {
				return nil
			}

			return err
		}

		data, err := service.grantService.Store().Get(addr.Address)
		if err != nil {
			return err
		}

		if err = send(&api.Ciphertext{EncryptedData: data}); err != nil {
			return err
		}
	}
}

// GrantServer

// Seal puts refs in a shareable grant
func (service *StreamingService) Seal(sendAndClose func(*grant.Grant) error, recv func() (*api.ReferenceAndGrantSpec, error)) error {
	var refs reference.Refs
	rgs, err := recv()
	if err != nil {
		return err
	}

	spec := rgs.GetGrantSpec()
	if spec == nil {
		return fmt.Errorf("grant spec expected in first message")
	}

	for {
		refAndSpec, err := recv()
		if err != nil {
			if err == io.EOF {
				break
			}
			return err
		}

		if s := refAndSpec.GrantSpec; s != nil {
			return fmt.Errorf("received multiple grant specs but there can be at most one")
		}

		refs = append(refs, refAndSpec.Reference)
	}

	grt, err := service.grantService.Seal(refs, spec)
	if err != nil {
		return err
	}

	return sendAndClose(grt)
}

// Unseal gets the refs stored in a grant
func (service *StreamingService) Unseal(grt *grant.Grant, send func(*reference.Ref) error) error {
	refs, err := service.grantService.Unseal(grt)
	if err != nil {
		return err
	}

	for _, ref := range refs {
		if err = send(ref); err != nil {
			return err
		}
	}

	return nil
}

// Reseal changes how the references in a grant are stored
func (service *StreamingService) Reseal(arg *api.GrantAndGrantSpec) (*grant.Grant, error) {
	refs, err := service.grantService.Unseal(arg.Grant)
	if err != nil {
		return nil, err
	}
	return service.grantService.Seal(refs, arg.GrantSpec)
}

func (service *StreamingService) Stat(address *api.Address) (*stores.StatInfo, error) {
	statInfo, err := service.grantService.Store().Stat(address.Address)
	if err != nil {
		return nil, err
	}
	// provide the address and the canonical location
	statInfo.Address = address.Address
	statInfo.Location = service.grantService.Store().Location(address.Address)
	return statInfo, nil
}

func (service *StreamingService) Delete(address []byte) error {
	return service.grantService.Store().Delete(address)
}

// Converts raw plaintext data to the Plaintext wrapper type
// In the case of a HEADER ref type the plaintext is deserialised using the header type
// In the case of a LINK ref type the supplied get function is used to fetch additional plaintext data which are themselves each decoded
// Otherwise the data is returned as Plaintext.Body
// The decoded plaintext(s) are then streamed as output via the supplied send function
func decodePlaintext(data []byte, refType reference.Ref_RefType, get func(*reference.Ref) ([]byte, error),
	send func(*api.Plaintext) error) error {

	switch refType {
	case reference.Ref_HEADER:
		head := new(api.Header)
		err := proto.Unmarshal(data, head)
		if err != nil {
			return err
		}
		return send(&api.Plaintext{Head: head})

	case reference.Ref_LINK:
		refs := reference.RepeatedFromPlaintext(data)
		for _, ref := range refs {
			data, err := get(ref)
			if err != nil {
				return err
			}
			err = decodePlaintext(data, ref.Type, get, send)
			if err != nil {
				return err
			}
		}
		return nil

	default:
		return send(&api.Plaintext{
			Body: data,
		})
	}
}

func headerReference(head *api.Header, getRef func(data, salt []byte) (*reference.Ref, error)) (*reference.Ref, error) {
	data, err := proto.Marshal(head)
	if err != nil {
		return nil, err
	}

	ref, err := getRef(data, head.GetSalt())
	if err != nil {
		return nil, err
	}
	ref.Type = reference.Ref_HEADER

	return ref, nil
}
<commit_msg>DRY up streaming in particular header handling<commit_after>package hoard

import (
	"fmt"
	"io"

	"github.com/golang/protobuf/proto"
	"github.com/monax/hoard/v8/api"
	"github.com/monax/hoard/v8/grant"
	"github.com/monax/hoard/v8/reference"
	"github.com/monax/hoard/v8/stores"
)

// StreamingService provides the API implementation for Service without relying directly on the
// GRPC generated streaming types
type StreamingService struct {
	grantService      GrantService
	chunkSize         int
	useLinkRefInGrant func(refs []*reference.Ref) bool
}

// Create a streaming service that will re-buffer any plaintext data in blocks of chunkSize. useLinkRefInGrant is a
// predicate used to decide whether PutSeal should store a LINK ref in a grant rather than an array of refs. It is
// passed the refs so it may decide to dynamically use LINK refs based on the number and size of references
func NewStreamingService(grantService GrantService, chunkSize int, useLinkRefInGrant func(refs []*reference.Ref) bool) *StreamingService {
	return &StreamingService{
		grantService:      grantService,
		chunkSize:         chunkSize,
		useLinkRefInGrant: useLinkRefInGrant,
	}
}

func (service *StreamingService) PutSeal(sendAndClose func(*grant.Grant) error, recv func() (*api.PlaintextAndGrantSpec, error)) error {
	first, err := recv()
	if err != nil {
		return err
	}

	spec := first.GetGrantSpec()
	if spec == nil {
		return fmt.Errorf("grant spec expected in first message")
	}

	head := first.GetPlaintext().GetHead()

	var refs reference.Refs

	err = encrypt(first.GetPlaintext(), service.put,
		func(ref *reference.Ref, encryptedData []byte) error {
			refs = append(refs, ref)
			return nil
		},
		func() (*api.Plaintext, error) {
			ptgs, err := recv()
			if err != nil {
				return nil, err
			}
			return ptgs.GetPlaintext(), nil
		}, service.chunkSize)

	// Should we store the refs themselves in hoard and seal a LINK ref into the grant
	if service.useLinkRefInGrant(refs) {
		ref, err := service.grantService.Put(refs.Plaintext(nil), head.GetSalt())
		if err != nil {
			return err
		}
		ref.Type = reference.Ref_LINK
		refs = reference.Refs{ref}
	}

	// Now send the grant
	grt, err := service.grantService.Seal(refs, spec)
	if err != nil {
		return err
	}

	return sendAndClose(grt)
}

// UnsealGet decrypts and gets plaintext associated with a grant
func (service *StreamingService) UnsealGet(grt *grant.Grant, send func(*api.Plaintext) error) error {
	refs, err := service.grantService.Unseal(grt)
	if err != nil {
		return err
	}

	for _, ref := range refs {
		data, err := service.grantService.Get(ref)
		if err != nil {
			return err
		}

		if err = decodePlaintext(data, ref.GetType(), service.grantService.Get, send); err != nil {
			return err
		}
	}
	return nil
}

// UnsealDelete gets the references stored in a grant and deletes them
func (service *StreamingService) UnsealDelete(grt *grant.Grant, send func(address *api.Address) error) error {
	refs, err := service.grantService.Unseal(grt)
	if err != nil {
		return err
	}

	for _, ref := range refs {
		err := service.grantService.Store().Delete(ref.Address)
		if err != nil {
			return err
		}
		if err = send(&api.Address{Address: ref.Address}); err != nil {
			return err
		}
	}
	return nil
}

// Put encrypted data in the store
func (service *StreamingService) Put(send func(*reference.Ref) error, recv func() (*api.Plaintext, error)) error {
	first, err := recv()
	if err != nil {
		return err
	}

	err = encrypt(first, service.put, func(ref *reference.Ref, _ []byte) error { return send(ref) },
		recv, service.chunkSize)

	if err != nil {
		return fmt.Errorf("Put: could not put plaintexts: %w", err)
	}

	return nil
}

// Get decrypted data from the store
func (service *StreamingService) Get(send func(*api.Plaintext) error, recv func() (*reference.Ref, error)) error {
	for {
		ref, err := recv()
		if err != nil {
			if err == io.EOF {
				return nil
			}

			return err
		}

		data, err := service.grantService.Get(ref)
		if err != nil {
			return err
		}

		if err = decodePlaintext(data, ref.GetType(), service.grantService.Get, send); err != nil {
			return err
		}
	}
}

// Encrypt data and return ciphertext
func (service *StreamingService) Encrypt(send func(*api.ReferenceAndCiphertext) error, recv func() (*api.Plaintext, error)) error {
	first, err := recv()
	if err != nil {
		return err
	}

	err = encrypt(first, service.grantService.Encrypt, func(ref *reference.Ref, encryptedData []byte) error {
		return send(&api.ReferenceAndCiphertext{
			Reference: ref,
			Ciphertext: &api.Ciphertext{
				EncryptedData: encryptedData,
			},
		})
	}, recv, service.chunkSize)

	if err != nil {
		return fmt.Errorf("Could not encrypt data: %w", err)
	}
	return nil
}

// Decrypt ciphertext and return plaintext
func (service *StreamingService) Decrypt(send func(*api.Plaintext) error, recv func() (*api.ReferenceAndCiphertext, error)) error {
	for {
		refAndCiphertext, err := recv()
		if err != nil {
			if err == io.EOF {
				return nil
			}

			return err
		}

		data, err := service.grantService.Decrypt(refAndCiphertext.Reference, refAndCiphertext.Ciphertext.EncryptedData)
		if err != nil {
			return err
		}

		if err = decodePlaintext(data, refAndCiphertext.Reference.GetType(), service.grantService.Get, send); err != nil {
			return err
		}
	}
}

// StorageServer

// Push ciphertext directly to store
func (service *StreamingService) Push(send func(*api.Address) error, recv func() (*api.Ciphertext, error)) error {
	for {
		ciphertext, err := recv()
		if err != nil {
			if err == io.EOF {
				return nil
			}

			return err
		}

		addr, err := service.grantService.Store().Put(ciphertext.EncryptedData)
		if err != nil {
			return err
		}

		if err = send(&api.Address{Address: addr}); err != nil {
			return err
		}
	}
}

// Pull gets ciphertext directly from the store
func (service *StreamingService) Pull(send func(*api.Ciphertext) error, recv func() (*api.Address, error)) error {
	for {
		addr, err := recv()
		if err != nil {
			if err == io.EOF {
				return nil
			}

			return err
		}

		data, err := service.grantService.Store().Get(addr.Address)
		if err != nil {
			return err
		}

		if err = send(&api.Ciphertext{EncryptedData: data}); err != nil {
			return err
		}
	}
}

// GrantServer

// Seal puts refs in a shareable grant
func (service *StreamingService) Seal(sendAndClose func(*grant.Grant) error, recv func() (*api.ReferenceAndGrantSpec, error)) error {
	var refs reference.Refs
	rgs, err := recv()
	if err != nil {
		return err
	}

	spec := rgs.GetGrantSpec()
	if spec == nil {
		return fmt.Errorf("grant spec expected in first message")
	}

	for {
		refAndSpec, err := recv()
		if err != nil {
			if err == io.EOF {
				break
			}
			return err
		}

		if s := refAndSpec.GrantSpec; s != nil {
			return fmt.Errorf("received multiple grant specs but there can be at most one")
		}

		refs = append(refs, refAndSpec.Reference)
	}

	grt, err := service.grantService.Seal(refs, spec)
	if err != nil {
		return err
	}

	return sendAndClose(grt)
}

// Unseal gets the refs stored in a grant
func (service *StreamingService) Unseal(grt *grant.Grant, send func(*reference.Ref) error) error {
	refs, err := service.grantService.Unseal(grt)
	if err != nil {
		return err
	}

	for _, ref := range refs {
		if err = send(ref); err != nil {
			return err
		}
	}

	return nil
}

// Reseal changes how the references in a grant are stored
func (service *StreamingService) Reseal(arg *api.GrantAndGrantSpec) (*grant.Grant, error) {
	refs, err := service.grantService.Unseal(arg.Grant)
	if err != nil {
		return nil, err
	}
	return service.grantService.Seal(refs, arg.GrantSpec)
}

func (service *StreamingService) Stat(address *api.Address) (*stores.StatInfo, error) {
	statInfo, err := service.grantService.Store().Stat(address.Address)
	if err != nil {
		return nil, err
	}
	// provide the address and the canonical location
	statInfo.Address = address.Address
	statInfo.Location = service.grantService.Store().Location(address.Address)
	return statInfo, nil
}

func (service *StreamingService) Delete(address []byte) error {
	return service.grantService.Store().Delete(address)
}

// Converts raw plaintext data to the Plaintext wrapper type
// In the case of a HEADER ref type the plaintext is deserialised using the header type
// In the case of a LINK ref type the supplied get function is used to fetch additional plaintext data which are themselves each decoded
// Otherwise the data is returned as Plaintext.Body
// The decoded plaintext(s) are then streamed as output via the supplied send function
func decodePlaintext(data []byte, refType reference.Ref_RefType, get func(*reference.Ref) ([]byte, error),
	send func(*api.Plaintext) error) error {

	switch refType {
	case reference.Ref_HEADER:
		head := new(api.Header)
		err := proto.Unmarshal(data, head)
		if err != nil {
			return err
		}
		return send(&api.Plaintext{Head: head})

	case reference.Ref_LINK:
		refs := reference.RepeatedFromPlaintext(data)
		for _, ref := range refs {
			data, err := get(ref)
			if err != nil {
				return err
			}
			err = decodePlaintext(data, ref.Type, get, send)
			if err != nil {
				return err
			}
		}
		return nil

	default:
		return send(&api.Plaintext{
			Body: data,
		})
	}
}

// Put wrapped with dummy 'encrypt' signature to help with reuse
func (service *StreamingService) put(data, salt []byte) (*reference.Ref, []byte, error) {
	ref, err := service.grantService.Put(data, salt)
	return ref, nil, err
}

// Abstracts common Plaintext input flow handling
func encrypt(first *api.Plaintext,
	encrypt func(data []byte, salt []byte) (ref *reference.Ref, encryptedData []byte, err error),
	send func(ref *reference.Ref, encryptedData []byte) error,
	recv func() (*api.Plaintext, error), chunkSize int) error {

	head := first.GetHead()
	if head != nil {
		data, err := proto.Marshal(head)
		if err != nil {
			return err
		}

		ref, encryptedData, err := encrypt(data, head.GetSalt())
		if err != nil {
			return err
		}
		ref.Type = reference.Ref_HEADER

		err = send(ref, encryptedData)
		if err != nil {
			return err
		}
	}
	return CopyChunked(
		func(chunk []byte) error {
			ref, encryptedData, err := encrypt(chunk, head.GetSalt())
			if err != nil {
				return err
			}
			return send(ref, encryptedData)
		},
		func() ([]byte, error) {
			// In case first message contains body, push that first
			if first.GetBody() != nil {
				body := first.GetBody()
				first = nil
				return body, nil
			}
			plaintext, err := recv()
			if err != nil {
				return nil, err
			}
			return plaintext.Body, nil
		},
		chunkSize)
}
<|endoftext|>


<commit_before>package strutils

import (
	"crypto/md5"
	"encoding/hex"
	"math/rand"
)

func NonceString() string {
	var bytes [32]byte
	var b int64
	for i := 0; i != 32; i++ {
		b = rand.Int63n(62)
		switch {
		case b < 10:
			b += 48
		case b < 36:
			b += 55
		default:
			b += 61
		}
		bytes[i] = byte(b)
	}
	return string(bytes[:32])
}

func MD5String(s string) string {
	h := md5.New()
	h.Write([]byte(s))
	return hex.EncodeToString(h.Sum(nil))
}
<commit_msg>MD5Bytes<commit_after>package strutils

import (
	"crypto/md5"
	"encoding/hex"
	"math/rand"
)

func NonceString() string {
	var bytes [32]byte
	var b int64
	for i := 0; i != 32; i++ {
		b = rand.Int63n(62)
		switch {
		case b < 10:
			b += 48
		case b < 36:
			b += 55
		default:
			b += 61
		}
		bytes[i] = byte(b)
	}
	return string(bytes[:32])
}

func MD5String(s string) string {
	h := md5.New()
	h.Write([]byte(s))
	return hex.EncodeToString(h.Sum(nil))
}

func MD5Bytes(b []byte) string {
	h := md5.New()
	h.Write(b)
	return hex.EncodeToString(h.Sum(nil))
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"log"
	"net/http"
	_ "net/http/pprof"
	"os"
	"os/signal"
	"syscall"
	"time"
)

var (
	signalChannel = make(chan os.Signal, 1)
)

func main() {
	measurements := make(chan Measurement, 100)
	config := GetConfig()

	mp := NewMultiPoller(measurements, config)

	signal.Notify(signalChannel, syscall.SIGINT)
	signal.Notify(signalChannel, syscall.SIGTERM)

	go func() {
		for sig := range signalChannel {
			mp.Exit()
			log.Fatal(Slog{"signal": sig, "finished": time.Now(), "duration": time.Since(config.Start)})
		}
	}()

	if config.ProfilePort != DEFAULT_PROFILE_PORT {
		go func() {
			log.Println(http.ListenAndServe("localhost:"+config.ProfilePort, nil))
		}()
	}

	ctx := Slog{"shh_start": true, "at": config.Start.Format(time.RFC3339Nano), "interval": config.Interval}
	fmt.Println(ctx)

	outputter, err := NewOutputter(config.Outputter, measurements, config)
	if err != nil {
		ctx.FatalError(err, "creating outputter")
	}
	outputter.Start()

	start := make(chan time.Time, 1)
	start <- time.Now()
	ticks := time.Tick(config.Interval)

	for {
		select {
		case tick := <-start:
			mp.Poll(tick)
			start = nil
		case tick := <-ticks:
			mp.Poll(tick)
		}
	}
}
<commit_msg>Add a --version command line flag so I can stop exploring ELF dumps<commit_after>package main

import (
	"flag"
	"fmt"
	"log"
	"net/http"
	_ "net/http/pprof"
	"os"
	"os/signal"
	"syscall"
	"time"
)

var (
	signalChannel = make(chan os.Signal, 1)
	versionFlag = flag.Bool("version", false, "Display version info and exit")
)

func main() {
	flag.Parse()

	if versionFlag {
		fmt.Println(VERSION)
		os.Exit(0)
	}

	measurements := make(chan Measurement, 100)
	config := GetConfig()

	mp := NewMultiPoller(measurements, config)

	signal.Notify(signalChannel, syscall.SIGINT)
	signal.Notify(signalChannel, syscall.SIGTERM)

	go func() {
		for sig := range signalChannel {
			mp.Exit()
			log.Fatal(Slog{"signal": sig, "finished": time.Now(), "duration": time.Since(config.Start)})
		}
	}()

	if config.ProfilePort != DEFAULT_PROFILE_PORT {
		go func() {
			log.Println(http.ListenAndServe("localhost:"+config.ProfilePort, nil))
		}()
	}

	ctx := Slog{"shh_start": true, "at": config.Start.Format(time.RFC3339Nano), "interval": config.Interval}
	fmt.Println(ctx)

	outputter, err := NewOutputter(config.Outputter, measurements, config)
	if err != nil {
		ctx.FatalError(err, "creating outputter")
	}
	outputter.Start()

	start := make(chan time.Time, 1)
	start <- time.Now()
	ticks := time.Tick(config.Interval)

	for {
		select {
		case tick := <-start:
			mp.Poll(tick)
			start = nil
		case tick := <-ticks:
			mp.Poll(tick)
		}
	}
}
<|endoftext|>


<commit_before>package svg

import (
	"encoding/xml"
	"errors"
	"fmt"
	"io"
	"strconv"
	"strings"

	mt "github.com/rustyoz/Mtransform"
)

// DrawingInstructionParser allow getting segments and drawing
// instructions from them. All SVG elements should implement this
// interface.
type DrawingInstructionParser interface {
	ParseDrawingInstructions() (chan *DrawingInstruction, chan error)
}

// Tuple is an X,Y coordinate
type Tuple [2]float64

// Svg represents an SVG file containing at least a top level group or a
// number of Paths
type Svg struct {
	Title        string  `xml:"title"`
	Groups       []Group `xml:"g"`
	ViewBox      string  `xml:"viewBox,attr"`
	Elements     []DrawingInstructionParser
	Name         string
	Transform    *mt.Transform
	scale        float64
	instructions chan *DrawingInstruction
	errors       chan error
	segments     chan Segment
}

// Group represents an SVG group (usually located in a 'g' XML element)
type Group struct {
	ID              string
	Stroke          string
	StrokeWidth     int32
	Fill            string
	FillRule        string
	Elements        []DrawingInstructionParser
	TransformString string
	Transform       *mt.Transform // row, column
	Parent          *Group
	Owner           *Svg
	instructions    chan *DrawingInstruction
	errors          chan error
	segments        chan Segment
}

// ParseDrawingInstructions implements the DrawingInstructionParser interface
//
// This method makes it easier to get all the drawing instructions.
func (g *Group) ParseDrawingInstructions() (chan *DrawingInstruction, chan error) {
	g.instructions = make(chan *DrawingInstruction, 100)
	g.errors = make(chan error, 100)

	go func() {
		defer close(g.instructions)
		defer close(g.errors)
		for _, e := range g.Elements {
			instrs, errs := e.ParseDrawingInstructions()
			go func() {
				for er := range errs {
					g.errors <- er
				}
			}()
			for is := range instrs {
				g.instructions <- is
			}
		}
	}()

	return g.instructions, g.errors
}

// UnmarshalXML implements the encoding.xml.Unmarshaler interface
func (g *Group) UnmarshalXML(decoder *xml.Decoder, start xml.StartElement) error {
	for _, attr := range start.Attr {
		switch attr.Name.Local {
		case "id":
			g.ID = attr.Value
		case "stroke":
			g.Stroke = attr.Value
		case "stroke-width":
			intValue, err := strconv.ParseInt(attr.Value, 10, 32)
			if err != nil {
				return err
			}
			g.StrokeWidth = int32(intValue)
		case "fill":
			g.Fill = attr.Value
		case "fill-rule":
			g.FillRule = attr.Value
		case "transform":
			g.TransformString = attr.Value
			t, err := parseTransform(g.TransformString)
			if err != nil {
				fmt.Println(err)
			}
			g.Transform = &t
		}
	}

	for {
		token, err := decoder.Token()
		if err != nil {
			return err
		}

		switch tok := token.(type) {
		case xml.StartElement:
			var elementStruct DrawingInstructionParser

			switch tok.Name.Local {
			case "g":
				elementStruct = &Group{Parent: g, Owner: g.Owner, Transform: mt.NewTransform()}
			case "rect":
				elementStruct = &Rect{group: g}
			case "circle":
				elementStruct = &Circle{group: g}
			case "path":
				elementStruct = &Path{group: g, StrokeWidth: float64(g.StrokeWidth), Stroke: &g.Stroke, Fill: &g.Fill}

			}

			if err = decoder.DecodeElement(elementStruct, &tok); err != nil {
				return fmt.Errorf("error decoding element of Group: %s", err)
			}
			g.Elements = append(g.Elements, elementStruct)

		case xml.EndElement:
			return nil
		}
	}
}

// ParseDrawingInstructions implements the DrawingInstructionParser interface
//
// This method makes it easier to get all the drawing instructions.
func (s *Svg) ParseDrawingInstructions() (chan *DrawingInstruction, chan error) {
	s.instructions = make(chan *DrawingInstruction, 100)
	s.errors = make(chan error, 100)

	go func() {
		var elecount int
		defer close(s.instructions)
		defer close(s.errors)
		for _, e := range s.Elements {
			elecount++
			instrs, errs := e.ParseDrawingInstructions()
			go func(count int) {
				for er := range errs {
					s.errors <- fmt.Errorf("error when parsing element nr. %d: %s", count, er)
				}
			}(elecount)

			for is := range instrs {
				s.instructions <- is
			}
		}

		for _, g := range s.Groups {
			instrs, errs := g.ParseDrawingInstructions()
			go func() {
				for er := range errs {
					g.errors <- er
				}
			}()
			for is := range instrs {
				s.instructions <- is
			}
		}
	}()

	return s.instructions, s.errors
}

// UnmarshalXML implements the encoding.xml.Unmarshaler interface
func (s *Svg) UnmarshalXML(decoder *xml.Decoder, start xml.StartElement) error {
	for {
		for _, attr := range start.Attr {
			if attr.Name.Local == "viewBox" {
				s.ViewBox = attr.Value
				break
			}
		}

		token, err := decoder.Token()
		if err != nil {
			return err
		}

		switch tok := token.(type) {
		case xml.StartElement:
			var dip DrawingInstructionParser

			switch tok.Name.Local {
			case "g":
				g := &Group{Owner: s, Transform: mt.NewTransform()}
				if err = decoder.DecodeElement(g, &tok); err != nil {
					return fmt.Errorf("error decoding group element within SVG struct: %s", err)
				}
				s.Groups = append(s.Groups, *g)
				continue
			case "rect":
				dip = &Rect{}
			case "circle":
				dip = &Circle{}
			case "path":
				dip = &Path{}

			default:
				continue
			}

			if err = decoder.DecodeElement(dip, &tok); err != nil {
				return fmt.Errorf("error decoding element of SVG struct: %s", err)
			}

			s.Elements = append(s.Elements, dip)

		case xml.EndElement:
			if tok.Name.Local == "svg" {
				return nil
			}
		}
	}
}

// ParseSvg parses an SVG string into an SVG struct
func ParseSvg(str string, name string, scale float64) (*Svg, error) {
	var svg Svg
	svg.Name = name
	svg.Transform = mt.NewTransform()
	if scale > 0 {
		svg.Transform.Scale(scale, scale)
		svg.scale = scale
	}
	if scale < 0 {
		svg.Transform.Scale(1.0/-scale, 1.0/-scale)
		svg.scale = 1.0 / -scale
	}

	err := xml.Unmarshal([]byte(str), &svg)
	if err != nil {
		return nil, fmt.Errorf("ParseSvg Error: %v", err)
	}
	fmt.Println(len(svg.Groups))
	for i := range svg.Groups {
		svg.Groups[i].SetOwner(&svg)
		if svg.Groups[i].Transform == nil {
			svg.Groups[i].Transform = mt.NewTransform()
		}
	}
	return &svg, nil
}

// ParseSvgFromReader parses an SVG struct from an io.Reader
func ParseSvgFromReader(r io.Reader, name string, scale float64) (*Svg, error) {
	var svg Svg
	svg.Name = name
	svg.Transform = mt.NewTransform()
	if scale > 0 {
		svg.Transform.Scale(scale, scale)
		svg.scale = scale
	}
	if scale < 0 {
		svg.Transform.Scale(1.0/-scale, 1.0/-scale)
		svg.scale = 1.0 / -scale
	}

	if err := xml.NewDecoder(r).Decode(&svg); err != nil {
		return nil, fmt.Errorf("ParseSvg Error: %v", err)
	}

	fmt.Println(len(svg.Groups))

	for i := range svg.Groups {
		svg.Groups[i].SetOwner(&svg)
		if svg.Groups[i].Transform == nil {
			svg.Groups[i].Transform = mt.NewTransform()
		}
	}
	return &svg, nil
}

// ViewBoxValues returns all the numerical values in the viewBox
// attribute.
func (s *Svg) ViewBoxValues() ([]float64, error) {
	var vals []float64

	if s.ViewBox == "" {
		return vals, errors.New("viewBox attribute is empty")
	}

	split := strings.Split(s.ViewBox, " ")

	for _, val := range split {
		ival, err := strconv.ParseFloat(val, 64)
		if err != nil {
			return vals, err
		}
		vals = append(vals, ival)
	}

	return vals, nil
}

// SetOwner sets the owner of a SVG Group
func (g *Group) SetOwner(svg *Svg) {
	g.Owner = svg
	for _, gn := range g.Elements {
		switch gn.(type) {
		case *Group:
			gn.(*Group).Owner = g.Owner
			gn.(*Group).SetOwner(svg)
		case *Path:
			gn.(*Path).group = g
		}
	}
}
<commit_msg>svg: remove debugging print statements<commit_after>package svg

import (
	"encoding/xml"
	"errors"
	"fmt"
	"io"
	"strconv"
	"strings"

	mt "github.com/rustyoz/Mtransform"
)

// DrawingInstructionParser allow getting segments and drawing
// instructions from them. All SVG elements should implement this
// interface.
type DrawingInstructionParser interface {
	ParseDrawingInstructions() (chan *DrawingInstruction, chan error)
}

// Tuple is an X,Y coordinate
type Tuple [2]float64

// Svg represents an SVG file containing at least a top level group or a
// number of Paths
type Svg struct {
	Title        string  `xml:"title"`
	Groups       []Group `xml:"g"`
	ViewBox      string  `xml:"viewBox,attr"`
	Elements     []DrawingInstructionParser
	Name         string
	Transform    *mt.Transform
	scale        float64
	instructions chan *DrawingInstruction
	errors       chan error
	segments     chan Segment
}

// Group represents an SVG group (usually located in a 'g' XML element)
type Group struct {
	ID              string
	Stroke          string
	StrokeWidth     int32
	Fill            string
	FillRule        string
	Elements        []DrawingInstructionParser
	TransformString string
	Transform       *mt.Transform // row, column
	Parent          *Group
	Owner           *Svg
	instructions    chan *DrawingInstruction
	errors          chan error
	segments        chan Segment
}

// ParseDrawingInstructions implements the DrawingInstructionParser interface
//
// This method makes it easier to get all the drawing instructions.
func (g *Group) ParseDrawingInstructions() (chan *DrawingInstruction, chan error) {
	g.instructions = make(chan *DrawingInstruction, 100)
	g.errors = make(chan error, 100)

	go func() {
		defer close(g.instructions)
		defer close(g.errors)
		for _, e := range g.Elements {
			instrs, errs := e.ParseDrawingInstructions()
			go func() {
				for er := range errs {
					g.errors <- er
				}
			}()
			for is := range instrs {
				g.instructions <- is
			}
		}
	}()

	return g.instructions, g.errors
}

// UnmarshalXML implements the encoding.xml.Unmarshaler interface
func (g *Group) UnmarshalXML(decoder *xml.Decoder, start xml.StartElement) error {
	for _, attr := range start.Attr {
		switch attr.Name.Local {
		case "id":
			g.ID = attr.Value
		case "stroke":
			g.Stroke = attr.Value
		case "stroke-width":
			intValue, err := strconv.ParseInt(attr.Value, 10, 32)
			if err != nil {
				return err
			}
			g.StrokeWidth = int32(intValue)
		case "fill":
			g.Fill = attr.Value
		case "fill-rule":
			g.FillRule = attr.Value
		case "transform":
			g.TransformString = attr.Value
			t, err := parseTransform(g.TransformString)
			if err != nil {
				fmt.Println(err)
			}
			g.Transform = &t
		}
	}

	for {
		token, err := decoder.Token()
		if err != nil {
			return err
		}

		switch tok := token.(type) {
		case xml.StartElement:
			var elementStruct DrawingInstructionParser

			switch tok.Name.Local {
			case "g":
				elementStruct = &Group{Parent: g, Owner: g.Owner, Transform: mt.NewTransform()}
			case "rect":
				elementStruct = &Rect{group: g}
			case "circle":
				elementStruct = &Circle{group: g}
			case "path":
				elementStruct = &Path{group: g, StrokeWidth: float64(g.StrokeWidth), Stroke: &g.Stroke, Fill: &g.Fill}

			}

			if err = decoder.DecodeElement(elementStruct, &tok); err != nil {
				return fmt.Errorf("error decoding element of Group: %s", err)
			}
			g.Elements = append(g.Elements, elementStruct)

		case xml.EndElement:
			return nil
		}
	}
}

// ParseDrawingInstructions implements the DrawingInstructionParser interface
//
// This method makes it easier to get all the drawing instructions.
func (s *Svg) ParseDrawingInstructions() (chan *DrawingInstruction, chan error) {
	s.instructions = make(chan *DrawingInstruction, 100)
	s.errors = make(chan error, 100)

	go func() {
		var elecount int
		defer close(s.instructions)
		defer close(s.errors)
		for _, e := range s.Elements {
			elecount++
			instrs, errs := e.ParseDrawingInstructions()
			go func(count int) {
				for er := range errs {
					s.errors <- fmt.Errorf("error when parsing element nr. %d: %s", count, er)
				}
			}(elecount)

			for is := range instrs {
				s.instructions <- is
			}
		}

		for _, g := range s.Groups {
			instrs, errs := g.ParseDrawingInstructions()
			go func() {
				for er := range errs {
					g.errors <- er
				}
			}()
			for is := range instrs {
				s.instructions <- is
			}
		}
	}()

	return s.instructions, s.errors
}

// UnmarshalXML implements the encoding.xml.Unmarshaler interface
func (s *Svg) UnmarshalXML(decoder *xml.Decoder, start xml.StartElement) error {
	for {
		for _, attr := range start.Attr {
			if attr.Name.Local == "viewBox" {
				s.ViewBox = attr.Value
				break
			}
		}

		token, err := decoder.Token()
		if err != nil {
			return err
		}

		switch tok := token.(type) {
		case xml.StartElement:
			var dip DrawingInstructionParser

			switch tok.Name.Local {
			case "g":
				g := &Group{Owner: s, Transform: mt.NewTransform()}
				if err = decoder.DecodeElement(g, &tok); err != nil {
					return fmt.Errorf("error decoding group element within SVG struct: %s", err)
				}
				s.Groups = append(s.Groups, *g)
				continue
			case "rect":
				dip = &Rect{}
			case "circle":
				dip = &Circle{}
			case "path":
				dip = &Path{}

			default:
				continue
			}

			if err = decoder.DecodeElement(dip, &tok); err != nil {
				return fmt.Errorf("error decoding element of SVG struct: %s", err)
			}

			s.Elements = append(s.Elements, dip)

		case xml.EndElement:
			if tok.Name.Local == "svg" {
				return nil
			}
		}
	}
}

// ParseSvg parses an SVG string into an SVG struct
func ParseSvg(str string, name string, scale float64) (*Svg, error) {
	var svg Svg
	svg.Name = name
	svg.Transform = mt.NewTransform()
	if scale > 0 {
		svg.Transform.Scale(scale, scale)
		svg.scale = scale
	}
	if scale < 0 {
		svg.Transform.Scale(1.0/-scale, 1.0/-scale)
		svg.scale = 1.0 / -scale
	}

	err := xml.Unmarshal([]byte(str), &svg)
	if err != nil {
		return nil, fmt.Errorf("ParseSvg Error: %v", err)
	}

	for i := range svg.Groups {
		svg.Groups[i].SetOwner(&svg)
		if svg.Groups[i].Transform == nil {
			svg.Groups[i].Transform = mt.NewTransform()
		}
	}
	return &svg, nil
}

// ParseSvgFromReader parses an SVG struct from an io.Reader
func ParseSvgFromReader(r io.Reader, name string, scale float64) (*Svg, error) {
	var svg Svg
	svg.Name = name
	svg.Transform = mt.NewTransform()
	if scale > 0 {
		svg.Transform.Scale(scale, scale)
		svg.scale = scale
	}
	if scale < 0 {
		svg.Transform.Scale(1.0/-scale, 1.0/-scale)
		svg.scale = 1.0 / -scale
	}

	if err := xml.NewDecoder(r).Decode(&svg); err != nil {
		return nil, fmt.Errorf("ParseSvg Error: %v", err)
	}

	for i := range svg.Groups {
		svg.Groups[i].SetOwner(&svg)
		if svg.Groups[i].Transform == nil {
			svg.Groups[i].Transform = mt.NewTransform()
		}
	}
	return &svg, nil
}

// ViewBoxValues returns all the numerical values in the viewBox
// attribute.
func (s *Svg) ViewBoxValues() ([]float64, error) {
	var vals []float64

	if s.ViewBox == "" {
		return vals, errors.New("viewBox attribute is empty")
	}

	split := strings.Split(s.ViewBox, " ")

	for _, val := range split {
		ival, err := strconv.ParseFloat(val, 64)
		if err != nil {
			return vals, err
		}
		vals = append(vals, ival)
	}

	return vals, nil
}

// SetOwner sets the owner of a SVG Group
func (g *Group) SetOwner(svg *Svg) {
	g.Owner = svg
	for _, gn := range g.Elements {
		switch gn.(type) {
		case *Group:
			gn.(*Group).Owner = g.Owner
			gn.(*Group).SetOwner(svg)
		case *Path:
			gn.(*Path).group = g
		}
	}
}
<|endoftext|>


<commit_before>// Copyright 2016 Albert Nigmatzianov. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package id3v2

import (
	"bufio"
	"errors"
	"io"
	"os"

	"github.com/bogem/id3v2/bwpool"
	"github.com/bogem/id3v2/util"
)

var ErrNoFile = errors.New("tag was not initialized with file")

// Tag stores all information about opened tag.
type Tag struct {
	// buf is used for util.WriteBytesSize in tag.WriteTo().
	buf []byte

	frames    map[string]Framer
	sequences map[string]*sequence

	reader io.Reader

	originalSize int64
	version      byte
}

// AddFrame adds f to tag with appropriate id. If id is "" or f is nil,
// AddFrame will not add it to tag.
//
// If you want to add attached picture, comment or unsynchronised lyrics/text
// transcription frames, better use AddAttachedPicture, AddCommentFrame
// or AddUnsynchronisedLyricsFrame methods respectively.
func (tag *Tag) AddFrame(id string, f Framer) {
	if id == "" || f == nil {
		return
	}

	if mustFrameBeInSequence(id) {
		if tag.sequences[id] == nil {
			tag.sequences[id] = newSequence()
		}
		tag.sequences[id].AddFrame(f)
	} else {
		tag.frames[id] = f
	}
}

// AddAttachedPicture adds a picture frame to tag.
func (tag *Tag) AddAttachedPicture(pf PictureFrame) {
	id := tag.CommonID("Attached picture")
	tag.AddFrame(id, pf)
}

// AddCommentFrame adds a comment frame to tag.
func (tag *Tag) AddCommentFrame(cf CommentFrame) {
	id := tag.CommonID("Comments")
	tag.AddFrame(id, cf)
}

// AddUnsynchronisedLyricsFrame adds an unsynchronised lyrics/text frame
// to tag.
func (tag *Tag) AddUnsynchronisedLyricsFrame(uslf UnsynchronisedLyricsFrame) {
	id := tag.CommonID("Unsynchronised lyrics/text transcription")
	tag.AddFrame(id, uslf)
}

// CommonID returns ID3v2.3 or ID3v2.4 (in appropriate to version of Tag) frame ID
// from given description.
// For example, CommonID("Language") will return "TLAN".
// If it can't find the ID with given description, it returns the description.
//
// All descriptions you can find in file common_ids.go
// or in id3 documentation (for fourth version: http://id3.org/id3v2.4.0-frames;
// for third version: http://id3.org/id3v2.3.0#Declared_ID3v2_frames).
func (tag *Tag) CommonID(description string) string {
	var ids map[string]string
	if tag.version == 3 {
		ids = V23CommonIDs
	} else {
		ids = V24CommonIDs
	}
	if id, ok := ids[description]; ok {
		return id
	}
	return description
}

// AllFrames returns map, that contains all frames in tag, that could be parsed.
// The key of this map is an ID of frame and value is an array of frames.
func (tag *Tag) AllFrames() map[string][]Framer {
	frames := make(map[string][]Framer)

	for id, f := range tag.frames {
		frames[id] = []Framer{f}
	}
	for id, sequence := range tag.sequences {
		frames[id] = sequence.Frames()
	}

	return frames
}

// DeleteAllFrames deletes all frames in tag.
func (tag *Tag) DeleteAllFrames() {
	if tag.frames == nil || len(tag.frames) > 0 {
		tag.frames = make(map[string]Framer)
	}
	if tag.sequences == nil || len(tag.sequences) > 0 {
		tag.sequences = make(map[string]*sequence)
	}
}

// DeleteFrames deletes frames in tag with given id.
func (tag *Tag) DeleteFrames(id string) {
	delete(tag.frames, id)
	delete(tag.sequences, id)
}

// Reset deletes all frames in tag and parses rd considering opts.
func (tag *Tag) Reset(rd io.Reader, opts Options) error {
	tag.DeleteAllFrames()
	return tag.parse(rd, opts)
}

// GetFrames returns frames with corresponding id.
// It returns nil if there is no frames with given id.
func (tag *Tag) GetFrames(id string) []Framer {
	if f, exists := tag.frames[id]; exists {
		return []Framer{f}
	} else if s, exists := tag.sequences[id]; exists {
		return s.Frames()
	}
	return nil
}

// GetLastFrame returns last frame from slice, that is returned from GetFrames function.
// GetLastFrame is suitable for frames, that can be only one in whole tag.
// For example, for text frames.
func (tag *Tag) GetLastFrame(id string) Framer {
	// Avoid an allocation of slice in GetFrames,
	// if there is anyway one frame.
	if f, exists := tag.frames[id]; exists {
		return f
	}

	fs := tag.GetFrames(id)
	if len(fs) == 0 {
		return nil
	}
	return fs[len(fs)-1]
}

// GetTextFrame returns text frame with corresponding id.
func (tag *Tag) GetTextFrame(id string) TextFrame {
	f := tag.GetLastFrame(id)
	if f == nil {
		return TextFrame{}
	}
	tf := f.(TextFrame)
	return tf
}

// Count returns the number of frames in tag.
func (tag *Tag) Count() int {
	n := len(tag.frames)
	for _, s := range tag.sequences {
		n += s.Count()
	}
	return n
}

// HasFrames checks if there is at least one frame in tag.
// It's much faster than tag.Count() > 0.
func (tag *Tag) HasFrames() bool {
	return len(tag.frames) > 0 || len(tag.sequences) > 0
}

func (tag *Tag) Title() string {
	f := tag.GetTextFrame(tag.CommonID("Title/Songname/Content description"))
	return f.Text
}

func (tag *Tag) SetTitle(title string) {
	tag.AddFrame(tag.CommonID("Title/Songname/Content description"), TextFrame{Encoding: ENUTF8, Text: title})
}

func (tag *Tag) Artist() string {
	f := tag.GetTextFrame(tag.CommonID("Lead artist/Lead performer/Soloist/Performing group"))
	return f.Text
}

func (tag *Tag) SetArtist(artist string) {
	tag.AddFrame(tag.CommonID("Lead artist/Lead performer/Soloist/Performing group"), TextFrame{Encoding: ENUTF8, Text: artist})
}

func (tag *Tag) Album() string {
	f := tag.GetTextFrame(tag.CommonID("Album/Movie/Show title"))
	return f.Text
}

func (tag *Tag) SetAlbum(album string) {
	tag.AddFrame(tag.CommonID("Album/Movie/Show title"), TextFrame{Encoding: ENUTF8, Text: album})
}

func (tag *Tag) Year() string {
	f := tag.GetTextFrame(tag.CommonID("Year"))
	return f.Text
}

func (tag *Tag) SetYear(year string) {
	tag.AddFrame(tag.CommonID("Year"), TextFrame{Encoding: ENUTF8, Text: year})
}

func (tag *Tag) Genre() string {
	f := tag.GetTextFrame(tag.CommonID("Content type"))
	return f.Text
}

func (tag *Tag) SetGenre(genre string) {
	tag.AddFrame(tag.CommonID("Content type"), TextFrame{Encoding: ENUTF8, Text: genre})
}

// iterateOverAllFrames iterates over every single frame in tag and calls
// f for them. It consumps no memory at all, unlike the tag.AllFrames().
// It returns error only if f returns error.
func (tag *Tag) iterateOverAllFrames(f func(id string, frame Framer) error) error {
	for id, frame := range tag.frames {
		if err := f(id, frame); err != nil {
			return err
		}
	}
	for id, sequence := range tag.sequences {
		for _, frame := range sequence.Frames() {
			if err := f(id, frame); err != nil {
				return err
			}
		}
	}
	return nil
}

// Size returns the size of all frames in bytes.
func (tag *Tag) Size() int {
	if !tag.HasFrames() {
		return 0
	}

	var n int
	n += tagHeaderSize // Add the size of tag header
	tag.iterateOverAllFrames(func(id string, f Framer) error {
		n += frameHeaderSize + f.Size() // Add the whole frame size
		return nil
	})

	return n
}

// Version returns current ID3v2 version of tag.
func (tag *Tag) Version() byte {
	return tag.version
}

// SetVersion sets given ID3v2 version to tag.
// If version is less than 3 or greater than 4, then this method will do nothing.
// If tag has some frames, which are deprecated or changed in given version,
// then to your notice you can delete, change or just stay them.
func (tag *Tag) SetVersion(version byte) {
	if version < 3 || version > 4 {
		return
	}
	tag.version = version
}

// Save writes tag to the file, if tag was opened with a file.
// If there are no frames in tag, Save will write
// only music part without any ID3v2 information.
// If tag was initiliazed not with file, it returns ErrNoFile.
func (tag *Tag) Save() error {
	file, ok := tag.reader.(*os.File)
	if !ok {
		return ErrNoFile
	}

	// Get original file mode.
	originalFile := file
	originalStat, err := originalFile.Stat()
	if err != nil {
		return err
	}

	// Create a temp file for mp3 file, which will contain new tag.
	name := file.Name() + "-id3v2"
	newFile, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE, originalStat.Mode())
	if err != nil {
		return err
	}

	// Make sure we clean up the temp file if it's still around.
	defer os.Remove(newFile.Name())

	// Write tag in new file.
	tagSize, err := tag.WriteTo(newFile)
	if err != nil {
		return err
	}

	// Seek to a music part of original file.
	if _, err = originalFile.Seek(tag.originalSize, os.SEEK_SET); err != nil {
		return err
	}

	// Write to new file the music part.
	if _, err = io.Copy(newFile, originalFile); err != nil {
		return err
	}

	// Close files to allow replacing.
	newFile.Close()
	originalFile.Close()

	// Replace original file with new file.
	if err = os.Rename(newFile.Name(), originalFile.Name()); err != nil {
		return err
	}

	// Set tag.reader to new file with original name.
	tag.reader, err = os.Open(originalFile.Name())
	if err != nil {
		return err
	}

	// Set tag.originalSize to new frames size.
	if tagSize > tagHeaderSize {
		tag.originalSize = tagSize - tagHeaderSize
	} else {
		tag.originalSize = 0
	}

	return nil
}

// WriteTo writes whole tag in w if there is at least one frame.
// It returns the number of bytes written and error during the write.
// It returns nil as error if the write was successful.
func (tag *Tag) WriteTo(w io.Writer) (n int64, err error) {
	if w == nil {
		return 0, errors.New("w is nil")
	}
	if len(tag.buf) < 4 {
		tag.buf = make([]byte, 4)
	}

	// Form size of frames
	framesSize := tag.Size() - tagHeaderSize
	if framesSize <= 0 {
		return 0, nil
	}

	// Write tag header.
	bw := bwpool.Get(w)
	defer bwpool.Put(bw)
	if err := writeTagHeader(bw, tag.buf, framesSize, tag.version); err != nil {
		return n, err
	}
	n += tagHeaderSize

	// Write frames.
	err = tag.iterateOverAllFrames(func(id string, f Framer) error {
		nn, err := writeFrame(bw, tag.buf, id, f)
		n += nn
		return err
	})
	if err != nil {
		return n, err
	}

	return n, bw.Flush()
}

// writeFrame writes whole frame to bw.
// buf is needed for util.WriteBytesSize in writeFrameHeader.
func writeFrame(bw *bufio.Writer, buf []byte, id string, frame Framer) (int64, error) {
	if err := writeFrameHeader(bw, buf, id, frame.Size()); err != nil {
		return 0, err
	}

	frameSize, err := frame.WriteTo(bw)
	return frameHeaderSize + frameSize, err
}

// writeFrameHeader writes frame header with id and frameSize to bw.
// buf is needed for util.WriteBytesSize.
func writeFrameHeader(bw *bufio.Writer, buf []byte, id string, frameSize int) error {
	if err := util.WriteBytesSize(buf, frameSize); err != nil {
		return err
	}

	// ID
	if _, err := bw.WriteString(id); err != nil {
		return err
	}

	// Size
	if _, err := bw.Write(buf); err != nil {
		return err
	}

	// Flags
	if _, err := bw.Write([]byte{0, 0}); err != nil {
		return err
	}
	return nil
}

// Close closes tag's file, if tag was opened with a file.
// If tag was initiliazed not with file, it returns ErrNoFile.
func (tag *Tag) Close() error {
	file, ok := tag.reader.(*os.File)
	if !ok {
		return ErrNoFile
	}
	return file.Close()
}
<commit_msg>Fix typo in tag.WriteTo()<commit_after>// Copyright 2016 Albert Nigmatzianov. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package id3v2

import (
	"bufio"
	"errors"
	"io"
	"os"

	"github.com/bogem/id3v2/bwpool"
	"github.com/bogem/id3v2/util"
)

var ErrNoFile = errors.New("tag was not initialized with file")

// Tag stores all information about opened tag.
type Tag struct {
	// buf is used for util.WriteBytesSize in tag.WriteTo().
	buf []byte

	frames    map[string]Framer
	sequences map[string]*sequence

	reader io.Reader

	originalSize int64
	version      byte
}

// AddFrame adds f to tag with appropriate id. If id is "" or f is nil,
// AddFrame will not add it to tag.
//
// If you want to add attached picture, comment or unsynchronised lyrics/text
// transcription frames, better use AddAttachedPicture, AddCommentFrame
// or AddUnsynchronisedLyricsFrame methods respectively.
func (tag *Tag) AddFrame(id string, f Framer) {
	if id == "" || f == nil {
		return
	}

	if mustFrameBeInSequence(id) {
		if tag.sequences[id] == nil {
			tag.sequences[id] = newSequence()
		}
		tag.sequences[id].AddFrame(f)
	} else {
		tag.frames[id] = f
	}
}

// AddAttachedPicture adds a picture frame to tag.
func (tag *Tag) AddAttachedPicture(pf PictureFrame) {
	id := tag.CommonID("Attached picture")
	tag.AddFrame(id, pf)
}

// AddCommentFrame adds a comment frame to tag.
func (tag *Tag) AddCommentFrame(cf CommentFrame) {
	id := tag.CommonID("Comments")
	tag.AddFrame(id, cf)
}

// AddUnsynchronisedLyricsFrame adds an unsynchronised lyrics/text frame
// to tag.
func (tag *Tag) AddUnsynchronisedLyricsFrame(uslf UnsynchronisedLyricsFrame) {
	id := tag.CommonID("Unsynchronised lyrics/text transcription")
	tag.AddFrame(id, uslf)
}

// CommonID returns ID3v2.3 or ID3v2.4 (in appropriate to version of Tag) frame ID
// from given description.
// For example, CommonID("Language") will return "TLAN".
// If it can't find the ID with given description, it returns the description.
//
// All descriptions you can find in file common_ids.go
// or in id3 documentation (for fourth version: http://id3.org/id3v2.4.0-frames;
// for third version: http://id3.org/id3v2.3.0#Declared_ID3v2_frames).
func (tag *Tag) CommonID(description string) string {
	var ids map[string]string
	if tag.version == 3 {
		ids = V23CommonIDs
	} else {
		ids = V24CommonIDs
	}
	if id, ok := ids[description]; ok {
		return id
	}
	return description
}

// AllFrames returns map, that contains all frames in tag, that could be parsed.
// The key of this map is an ID of frame and value is an array of frames.
func (tag *Tag) AllFrames() map[string][]Framer {
	frames := make(map[string][]Framer)

	for id, f := range tag.frames {
		frames[id] = []Framer{f}
	}
	for id, sequence := range tag.sequences {
		frames[id] = sequence.Frames()
	}

	return frames
}

// DeleteAllFrames deletes all frames in tag.
func (tag *Tag) DeleteAllFrames() {
	if tag.frames == nil || len(tag.frames) > 0 {
		tag.frames = make(map[string]Framer)
	}
	if tag.sequences == nil || len(tag.sequences) > 0 {
		tag.sequences = make(map[string]*sequence)
	}
}

// DeleteFrames deletes frames in tag with given id.
func (tag *Tag) DeleteFrames(id string) {
	delete(tag.frames, id)
	delete(tag.sequences, id)
}

// Reset deletes all frames in tag and parses rd considering opts.
func (tag *Tag) Reset(rd io.Reader, opts Options) error {
	tag.DeleteAllFrames()
	return tag.parse(rd, opts)
}

// GetFrames returns frames with corresponding id.
// It returns nil if there is no frames with given id.
func (tag *Tag) GetFrames(id string) []Framer {
	if f, exists := tag.frames[id]; exists {
		return []Framer{f}
	} else if s, exists := tag.sequences[id]; exists {
		return s.Frames()
	}
	return nil
}

// GetLastFrame returns last frame from slice, that is returned from GetFrames function.
// GetLastFrame is suitable for frames, that can be only one in whole tag.
// For example, for text frames.
func (tag *Tag) GetLastFrame(id string) Framer {
	// Avoid an allocation of slice in GetFrames,
	// if there is anyway one frame.
	if f, exists := tag.frames[id]; exists {
		return f
	}

	fs := tag.GetFrames(id)
	if len(fs) == 0 {
		return nil
	}
	return fs[len(fs)-1]
}

// GetTextFrame returns text frame with corresponding id.
func (tag *Tag) GetTextFrame(id string) TextFrame {
	f := tag.GetLastFrame(id)
	if f == nil {
		return TextFrame{}
	}
	tf := f.(TextFrame)
	return tf
}

// Count returns the number of frames in tag.
func (tag *Tag) Count() int {
	n := len(tag.frames)
	for _, s := range tag.sequences {
		n += s.Count()
	}
	return n
}

// HasFrames checks if there is at least one frame in tag.
// It's much faster than tag.Count() > 0.
func (tag *Tag) HasFrames() bool {
	return len(tag.frames) > 0 || len(tag.sequences) > 0
}

func (tag *Tag) Title() string {
	f := tag.GetTextFrame(tag.CommonID("Title/Songname/Content description"))
	return f.Text
}

func (tag *Tag) SetTitle(title string) {
	tag.AddFrame(tag.CommonID("Title/Songname/Content description"), TextFrame{Encoding: ENUTF8, Text: title})
}

func (tag *Tag) Artist() string {
	f := tag.GetTextFrame(tag.CommonID("Lead artist/Lead performer/Soloist/Performing group"))
	return f.Text
}

func (tag *Tag) SetArtist(artist string) {
	tag.AddFrame(tag.CommonID("Lead artist/Lead performer/Soloist/Performing group"), TextFrame{Encoding: ENUTF8, Text: artist})
}

func (tag *Tag) Album() string {
	f := tag.GetTextFrame(tag.CommonID("Album/Movie/Show title"))
	return f.Text
}

func (tag *Tag) SetAlbum(album string) {
	tag.AddFrame(tag.CommonID("Album/Movie/Show title"), TextFrame{Encoding: ENUTF8, Text: album})
}

func (tag *Tag) Year() string {
	f := tag.GetTextFrame(tag.CommonID("Year"))
	return f.Text
}

func (tag *Tag) SetYear(year string) {
	tag.AddFrame(tag.CommonID("Year"), TextFrame{Encoding: ENUTF8, Text: year})
}

func (tag *Tag) Genre() string {
	f := tag.GetTextFrame(tag.CommonID("Content type"))
	return f.Text
}

func (tag *Tag) SetGenre(genre string) {
	tag.AddFrame(tag.CommonID("Content type"), TextFrame{Encoding: ENUTF8, Text: genre})
}

// iterateOverAllFrames iterates over every single frame in tag and calls
// f for them. It consumps no memory at all, unlike the tag.AllFrames().
// It returns error only if f returns error.
func (tag *Tag) iterateOverAllFrames(f func(id string, frame Framer) error) error {
	for id, frame := range tag.frames {
		if err := f(id, frame); err != nil {
			return err
		}
	}
	for id, sequence := range tag.sequences {
		for _, frame := range sequence.Frames() {
			if err := f(id, frame); err != nil {
				return err
			}
		}
	}
	return nil
}

// Size returns the size of all frames in bytes.
func (tag *Tag) Size() int {
	if !tag.HasFrames() {
		return 0
	}

	var n int
	n += tagHeaderSize // Add the size of tag header
	tag.iterateOverAllFrames(func(id string, f Framer) error {
		n += frameHeaderSize + f.Size() // Add the whole frame size
		return nil
	})

	return n
}

// Version returns current ID3v2 version of tag.
func (tag *Tag) Version() byte {
	return tag.version
}

// SetVersion sets given ID3v2 version to tag.
// If version is less than 3 or greater than 4, then this method will do nothing.
// If tag has some frames, which are deprecated or changed in given version,
// then to your notice you can delete, change or just stay them.
func (tag *Tag) SetVersion(version byte) {
	if version < 3 || version > 4 {
		return
	}
	tag.version = version
}

// Save writes tag to the file, if tag was opened with a file.
// If there are no frames in tag, Save will write
// only music part without any ID3v2 information.
// If tag was initiliazed not with file, it returns ErrNoFile.
func (tag *Tag) Save() error {
	file, ok := tag.reader.(*os.File)
	if !ok {
		return ErrNoFile
	}

	// Get original file mode.
	originalFile := file
	originalStat, err := originalFile.Stat()
	if err != nil {
		return err
	}

	// Create a temp file for mp3 file, which will contain new tag.
	name := file.Name() + "-id3v2"
	newFile, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE, originalStat.Mode())
	if err != nil {
		return err
	}

	// Make sure we clean up the temp file if it's still around.
	defer os.Remove(newFile.Name())

	// Write tag in new file.
	tagSize, err := tag.WriteTo(newFile)
	if err != nil {
		return err
	}

	// Seek to a music part of original file.
	if _, err = originalFile.Seek(tag.originalSize, os.SEEK_SET); err != nil {
		return err
	}

	// Write to new file the music part.
	if _, err = io.Copy(newFile, originalFile); err != nil {
		return err
	}

	// Close files to allow replacing.
	newFile.Close()
	originalFile.Close()

	// Replace original file with new file.
	if err = os.Rename(newFile.Name(), originalFile.Name()); err != nil {
		return err
	}

	// Set tag.reader to new file with original name.
	tag.reader, err = os.Open(originalFile.Name())
	if err != nil {
		return err
	}

	// Set tag.originalSize to new frames size.
	if tagSize > tagHeaderSize {
		tag.originalSize = tagSize - tagHeaderSize
	} else {
		tag.originalSize = 0
	}

	return nil
}

// WriteTo writes whole tag in w if there is at least one frame.
// It returns the number of bytes written and error during the write.
// It returns nil as error if the write was successful.
func (tag *Tag) WriteTo(w io.Writer) (n int64, err error) {
	if w == nil {
		return 0, errors.New("w is nil")
	}
	if len(tag.buf) < 4 {
		tag.buf = make([]byte, 4)
	}

	// Count size of frames.
	framesSize := tag.Size() - tagHeaderSize
	if framesSize <= 0 {
		return 0, nil
	}

	// Write tag header.
	bw := bwpool.Get(w)
	defer bwpool.Put(bw)
	if err := writeTagHeader(bw, tag.buf, framesSize, tag.version); err != nil {
		return n, err
	}
	n += tagHeaderSize

	// Write frames.
	err = tag.iterateOverAllFrames(func(id string, f Framer) error {
		nn, err := writeFrame(bw, tag.buf, id, f)
		n += nn
		return err
	})
	if err != nil {
		return n, err
	}

	return n, bw.Flush()
}

// writeFrame writes whole frame to bw.
// buf is needed for util.WriteBytesSize in writeFrameHeader.
func writeFrame(bw *bufio.Writer, buf []byte, id string, frame Framer) (int64, error) {
	if err := writeFrameHeader(bw, buf, id, frame.Size()); err != nil {
		return 0, err
	}

	frameSize, err := frame.WriteTo(bw)
	return frameHeaderSize + frameSize, err
}

// writeFrameHeader writes frame header with id and frameSize to bw.
// buf is needed for util.WriteBytesSize.
func writeFrameHeader(bw *bufio.Writer, buf []byte, id string, frameSize int) error {
	if err := util.WriteBytesSize(buf, frameSize); err != nil {
		return err
	}

	// ID
	if _, err := bw.WriteString(id); err != nil {
		return err
	}

	// Size
	if _, err := bw.Write(buf); err != nil {
		return err
	}

	// Flags
	if _, err := bw.Write([]byte{0, 0}); err != nil {
		return err
	}
	return nil
}

// Close closes tag's file, if tag was opened with a file.
// If tag was initiliazed not with file, it returns ErrNoFile.
func (tag *Tag) Close() error {
	file, ok := tag.reader.(*os.File)
	if !ok {
		return ErrNoFile
	}
	return file.Close()
}
<|endoftext|>