Add VXLAN, Switch and PCI to 1.19.2 version

This commit is contained in:
Nadja Reitzenstein
2024-03-24 17:52:47 +01:00
46 changed files with 2260 additions and 1 deletions

View File

@@ -77,7 +77,7 @@ repositories {
}
dependencies {
minecraft "net.minecraftforge:forge:1.19.2-43.2.10"
minecraft "net.minecraftforge:forge:${minecraft_version}-${forge_version}"
annotationProcessor "org.spongepowered:mixin:0.8.5:processor"
// Specify the libs embedded in the library mod explicitly for local development, where
@@ -90,6 +90,7 @@ dependencies {
compileOnly "li.cil.sedna:sedna-buildroot:0.0.8"
}
implementation "curse.maven:sedna-511276:3885542"
minecraftLibrary "org.apache.commons:commons-collections4:4.4"
implementation fg.deobf("curse.maven:markdownmanual-502485:4306669")
implementation fg.deobf("curse.maven:architectury-api-419699:4521273")
@@ -186,6 +187,7 @@ jar {
"Implementation-Vendor" : "Sangar",
"Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"),
"MixinConfigs" : "mixins.oc2.json",
"ContainedDeps" : "commons-collections4-4.4.jar"
])
}
}

View File

@@ -6,6 +6,7 @@ import li.cil.oc2.common.bus.device.rpc.RPCMethodParameterTypeAdapters;
import li.cil.oc2.common.integration.IMC;
import li.cil.oc2.common.network.Network;
import li.cil.oc2.common.util.ServerScheduler;
import li.cil.oc2.common.vxlan.TunnelManager;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
@@ -16,5 +17,6 @@ public final class CommonSetup {
Network.initialize();
RPCMethodParameterTypeAdapters.initialize();
ServerScheduler.initialize();
TunnelManager.initialize();
}
}

View File

@@ -20,6 +20,8 @@ public final class Config {
@Path("energy.blocks") public static int chargerEnergyStorage = 10000;
@Path("energy.blocks") public static int projectorEnergyPerTick = 20;
@Path("energy.blocks") public static int projectorEnergyStorage = 2000;
@Path("energy.blocks") public static int cardCageEnergyPerTick = 20;
@Path("energy.blocks") public static int cardCageEnergyStorage = 2000;
@Path("energy.entities") public static int robotEnergyPerTick = 5;
@Path("energy.entities") public static int robotEnergyStorage = 750000;
@@ -42,6 +44,12 @@ public final class Config {
@Path("admin.virtual_network") public static int ethernetFrameTimeToLive = 12;
@Path("admin.virtual_network") public static int hubEthernetFramesPerTick = 32;
@Path("vxlan") public static boolean enable = true;
@Path("vxlan") public static String remoteHost = "::1";
@Path("vxlan") public static int remotePort = 4789;
@Path("vxlan") public static String bindHost = "::1";
@Path("vxlan") public static int bindPort = 4789;
public static boolean computersUseEnergy() {
return computerEnergyPerTick > 0 && computerEnergyStorage > 0;
}
@@ -54,6 +62,10 @@ public final class Config {
return projectorEnergyStorage > 0 && projectorEnergyPerTick > 0;
}
public static boolean cardCagesUseEnergy() {
return cardCageEnergyStorage > 0 && cardCageEnergyPerTick > 0;
}
public static boolean robotsUseEnergy() {
return robotEnergyPerTick > 0 && robotEnergyStorage > 0;
}

View File

@@ -65,8 +65,10 @@ public final class ConfigManager {
static {
PARSERS.put(int.class, ConfigManager::parseIntField);
PARSERS.put(short.class, ConfigManager::parseShortField);
PARSERS.put(long.class, ConfigManager::parseLongField);
PARSERS.put(double.class, ConfigManager::parseDoubleField);
PARSERS.put(boolean.class, ConfigManager::parseBooleanField);
PARSERS.put(String.class, ConfigManager::parseStringField);
PARSERS.put(UUID.class, ConfigManager::parseUUIDField);
PARSERS.put(ResourceLocation.class, ConfigManager::parseResourceLocationField);
@@ -134,6 +136,16 @@ public final class ConfigManager {
return new ConfigFieldPair<>(field, configValue);
}
private static ConfigFieldPair<?> parseShortField(final Object instance, final Field field, final String path, final ForgeConfigSpec.Builder builder) throws IllegalAccessException {
final short defaultValue = field.getShort(instance);
final short minValue = (short) Math.max(getMin(field), Short.MIN_VALUE);
final short maxValue = (short) Math.min(getMax(field), Short.MAX_VALUE);
final ForgeConfigSpec.IntValue configValue = builder.defineInRange(path, defaultValue, minValue, maxValue);
return new ConfigFieldPair<>(field, configValue);
}
private static ConfigFieldPair<?> parseLongField(final Object instance, final Field field, final String path, final ForgeConfigSpec.Builder builder) throws IllegalAccessException {
final long defaultValue = field.getLong(instance);
final long minValue = (long) Math.max(getMin(field), Long.MIN_VALUE);
@@ -170,6 +182,14 @@ public final class ConfigManager {
return new ConfigFieldPair<>(field, configValue, UUID::fromString);
}
private static ConfigFieldPair<?> parseBooleanField(final Object instance, final Field field, final String path, final ForgeConfigSpec.Builder builder) throws IllegalAccessException {
final boolean defaultValue = (boolean) field.get(instance);
final ForgeConfigSpec.ConfigValue<Boolean> configValue = builder.define(path, defaultValue);
return new ConfigFieldPair<>(field, configValue);
}
private static ConfigFieldPair<?> parseResourceLocationField(final Object instance, final Field field, final String path, final ForgeConfigSpec.Builder builder) throws IllegalAccessException {
final ResourceLocation defaultValue = (ResourceLocation) field.get(instance);

View File

@@ -55,6 +55,7 @@ public final class Main {
ProviderRegistry.initialize();
DeviceTypes.initialize();
BlockDeviceDataRegistry.initialize();
FirmwareRegistry.initialize();

View File

@@ -3,9 +3,11 @@
package li.cil.oc2.common.block;
import li.cil.oc2.api.API;
import li.cil.oc2.common.blockentity.VxlanBlockEntity;
import li.cil.oc2.common.util.RegistryUtils;
import net.minecraft.world.level.block.Block;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.ForgeRegistries;
import net.minecraftforge.registries.RegistryObject;
@@ -24,8 +26,12 @@ public final class Blocks {
public static final RegistryObject<KeyboardBlock> KEYBOARD = BLOCKS.register("keyboard", KeyboardBlock::new);
public static final RegistryObject<NetworkConnectorBlock> NETWORK_CONNECTOR = BLOCKS.register("network_connector", NetworkConnectorBlock::new);
public static final RegistryObject<NetworkHubBlock> NETWORK_HUB = BLOCKS.register("network_hub", NetworkHubBlock::new);
public static final RegistryObject<NetworkSwitchBlock> NETWORK_SWITCH = BLOCKS.register("network_switch", NetworkSwitchBlock::new);
public static final RegistryObject<ProjectorBlock> PROJECTOR = BLOCKS.register("projector", ProjectorBlock::new);
public static final RegistryObject<RedstoneInterfaceBlock> REDSTONE_INTERFACE = BLOCKS.register("redstone_interface", RedstoneInterfaceBlock::new);
public static final RegistryObject<VxlanBlock> VXLAN_HUB = BLOCKS.register("vxlan_hub", VxlanBlock::new);
public static final RegistryObject<PciCardCageBlock> PCI_CARD_CAGE = BLOCKS.register("pci_card_cage", PciCardCageBlock::new);
///////////////////////////////////////////////////////////////////

View File

@@ -0,0 +1,61 @@
package li.cil.oc2.common.block;
import li.cil.oc2.common.blockentity.BlockEntities;
import li.cil.oc2.common.blockentity.NetworkHubBlockEntity;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.EntityBlock;
import net.minecraft.world.level.block.HorizontalDirectionalBlock;
import net.minecraft.world.level.block.SoundType;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.material.Material;
import javax.annotation.Nullable;
public final class NetworkSwitchBlock extends HorizontalDirectionalBlock implements EntityBlock {
public NetworkSwitchBlock() {
super(Properties
.of(Material.METAL)
.sound(SoundType.METAL)
.strength(1.5f, 6.0f));
registerDefaultState(getStateDefinition().any().setValue(FACING, Direction.NORTH));
}
///////////////////////////////////////////////////////////////////
@Override
public BlockState getStateForPlacement(final BlockPlaceContext context) {
return super.defaultBlockState().setValue(FACING, context.getHorizontalDirection().getOpposite());
}
@SuppressWarnings("deprecation")
@Override
public void neighborChanged(final BlockState state, final Level level, final BlockPos pos, final Block changedBlock, final BlockPos changedBlockPos, final boolean isMoving) {
final BlockEntity blockEntity = level.getBlockEntity(pos);
if (blockEntity instanceof final NetworkHubBlockEntity networkHub) {
networkHub.handleNeighborChanged();
}
}
///////////////////////////////////////////////////////////////////
// EntityBlock
@Nullable
@Override
public BlockEntity newBlockEntity(final BlockPos pos, final BlockState state) {
return BlockEntities.NETWORK_SWITCH.get().create(pos, state);
}
///////////////////////////////////////////////////////////////////
@Override
protected void createBlockStateDefinition(final StateDefinition.Builder<Block, BlockState> builder) {
super.createBlockStateDefinition(builder);
builder.add(FACING);
}
}

View File

@@ -0,0 +1,100 @@
/* SPDX-License-Identifier: MIT */
package li.cil.oc2.common.block;
import li.cil.oc2.common.Config;
import li.cil.oc2.common.blockentity.BlockEntities;
import li.cil.oc2.common.blockentity.TickableBlockEntity;
import li.cil.oc2.common.util.VoxelShapeUtils;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.EntityBlock;
import net.minecraft.world.level.block.HorizontalDirectionalBlock;
import net.minecraft.world.level.block.SoundType;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityTicker;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.world.level.material.Material;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
import javax.annotation.Nullable;
public final class PciCardCageBlock extends HorizontalDirectionalBlock implements EntityBlock, EnergyConsumingBlock {
public static final BooleanProperty LIT = BlockStateProperties.LIT;
// We bake the visual indents on the front and sides into the collision shape, to prevent stuff being
// placeable on those sides, such as network connectors, torches, etc.
private static final VoxelShape NEG_Z_SHAPE = Shapes.join(Shapes.block(), Shapes.or(
Shapes.box(0 / 16f, 2 / 16f, 2 / 16f, 1 / 16f, 6 / 16f, 14 / 16f),
Shapes.box(15 / 16f, 2 / 16f, 2 / 16f, 16 / 16f, 6 / 16f, 14 / 16f),
Shapes.box(4 / 16f, 4 / 16f, 0 / 16f, 12 / 16f, 12 / 16f, 2 / 16f)
), (a, b) -> a && !b);
private static final VoxelShape NEG_X_SHAPE = VoxelShapeUtils.rotateHorizontalClockwise(NEG_Z_SHAPE);
private static final VoxelShape POS_Z_SHAPE = VoxelShapeUtils.rotateHorizontalClockwise(NEG_X_SHAPE);
private static final VoxelShape POS_X_SHAPE = VoxelShapeUtils.rotateHorizontalClockwise(POS_Z_SHAPE);
public PciCardCageBlock() {
super(Properties
.of(Material.METAL)
.sound(SoundType.METAL)
.lightLevel(state -> state.getValue(LIT) ? 8 : 0)
.strength(1.5f, 6.0f));
registerDefaultState(getStateDefinition().any()
.setValue(FACING, Direction.NORTH)
.setValue(LIT, false));
}
///////////////////////////////////////////////////////////////////
@Override
public int getEnergyConsumption() {
if (Config.cardCagesUseEnergy()) {
return Config.cardCageEnergyPerTick;
} else {
return 0;
}
}
@Override
public BlockEntity newBlockEntity(final BlockPos pos, final BlockState state) {
return BlockEntities.PCI_CARD_CAGE.get().create(pos, state);
}
@Nullable
@Override
public <T extends BlockEntity> BlockEntityTicker<T> getTicker(final Level level, final BlockState state, final BlockEntityType<T> type) {
return TickableBlockEntity.createServerTicker(level, type, BlockEntities.PROJECTOR.get());
}
@SuppressWarnings("deprecation")
@Override
public VoxelShape getShape(final BlockState state, final BlockGetter level, final BlockPos blockPos, final CollisionContext context) {
return switch (state.getValue(FACING)) {
case NORTH -> NEG_Z_SHAPE;
case SOUTH -> POS_Z_SHAPE;
case WEST -> NEG_X_SHAPE;
default -> POS_X_SHAPE;
};
}
@Override
public BlockState getStateForPlacement(final BlockPlaceContext context) {
return super.defaultBlockState().setValue(FACING, context.getHorizontalDirection().getOpposite());
}
///////////////////////////////////////////////////////////////////
protected void createBlockStateDefinition(final StateDefinition.Builder<Block, BlockState> builder) {
builder.add(FACING, LIT);
}
}

View File

@@ -0,0 +1,70 @@
package li.cil.oc2.common.block;
import li.cil.oc2.common.blockentity.BlockEntities;
import li.cil.oc2.common.blockentity.TickableBlockEntity;
import li.cil.oc2.common.blockentity.VxlanBlockEntity;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.EntityBlock;
import net.minecraft.world.level.block.HorizontalDirectionalBlock;
import net.minecraft.world.level.block.SoundType;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityTicker;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.material.Material;
import javax.annotation.Nullable;
public final class VxlanBlock extends HorizontalDirectionalBlock implements EntityBlock {
public VxlanBlock() {
super(Properties
.of(Material.METAL)
.sound(SoundType.METAL)
.strength(1.5f, 6.0f));
registerDefaultState(getStateDefinition().any().setValue(FACING, Direction.NORTH));
}
///////////////////////////////////////////////////////////////////
@Override
public BlockState getStateForPlacement(final BlockPlaceContext context) {
return super.defaultBlockState().setValue(FACING, context.getHorizontalDirection().getOpposite());
}
@SuppressWarnings("deprecation")
@Override
public void neighborChanged(final BlockState state, final Level level, final BlockPos pos, final Block changedBlock, final BlockPos changedBlockPos, final boolean isMoving) {
final BlockEntity blockEntity = level.getBlockEntity(pos);
if (blockEntity instanceof final VxlanBlockEntity vxlanEntity) {
vxlanEntity.handleNeighborChanged();
}
}
///////////////////////////////////////////////////////////////////
// EntityBlock
@Nullable
@Override
public BlockEntity newBlockEntity(final BlockPos pos, final BlockState state) {
return BlockEntities.VXLAN_HUB.get().create(pos, state);
}
@Nullable
@Override
public <T extends BlockEntity> BlockEntityTicker<T> getTicker(final Level level, final BlockState state, final BlockEntityType<T> type) {
return TickableBlockEntity.createServerTicker(level, type, BlockEntities.VXLAN_HUB.get());
}
///////////////////////////////////////////////////////////////////
@Override
protected void createBlockStateDefinition(final StateDefinition.Builder<Block, BlockState> builder) {
super.createBlockStateDefinition(builder);
builder.add(FACING);
}
}

View File

@@ -27,8 +27,12 @@ public final class BlockEntities {
public static final RegistryObject<BlockEntityType<KeyboardBlockEntity>> KEYBOARD = register(Blocks.KEYBOARD, KeyboardBlockEntity::new);
public static final RegistryObject<BlockEntityType<NetworkConnectorBlockEntity>> NETWORK_CONNECTOR = register(Blocks.NETWORK_CONNECTOR, NetworkConnectorBlockEntity::new);
public static final RegistryObject<BlockEntityType<NetworkHubBlockEntity>> NETWORK_HUB = register(Blocks.NETWORK_HUB, NetworkHubBlockEntity::new);
public static final RegistryObject<BlockEntityType<NetworkSwitchBlockEntity>> NETWORK_SWITCH = register(Blocks.NETWORK_SWITCH, NetworkSwitchBlockEntity::new);
public static final RegistryObject<BlockEntityType<ProjectorBlockEntity>> PROJECTOR = register(Blocks.PROJECTOR, ProjectorBlockEntity::new);
public static final RegistryObject<BlockEntityType<RedstoneInterfaceBlockEntity>> REDSTONE_INTERFACE = register(Blocks.REDSTONE_INTERFACE, RedstoneInterfaceBlockEntity::new);
public static final RegistryObject<BlockEntityType<VxlanBlockEntity>> VXLAN_HUB = register(Blocks.VXLAN_HUB, VxlanBlockEntity::new);
public static final RegistryObject<BlockEntityType<PciCardCageBlockEntity>> PCI_CARD_CAGE = register(Blocks.PCI_CARD_CAGE, PciCardCageBlockEntity::new);
///////////////////////////////////////////////////////////////////

View File

@@ -0,0 +1,498 @@
package li.cil.oc2.common.blockentity;
import java.util.*;
import java.util.stream.Collectors;
import com.google.gson.internal.LinkedTreeMap;
import com.mojang.datafixers.util.Pair;
import li.cil.oc2.api.bus.device.object.Callback;
import li.cil.oc2.api.bus.device.object.DocumentedDevice;
import li.cil.oc2.api.bus.device.object.NamedDevice;
import li.cil.oc2.api.capabilities.NetworkInterface;
import li.cil.oc2.common.Constants;
import li.cil.oc2.common.block.NetworkSwitchBlock;
import li.cil.oc2.common.blockentity.BlockEntities;
import li.cil.oc2.common.blockentity.NetworkHubBlockEntity;
import li.cil.oc2.common.capabilities.Capabilities;
import li.cil.oc2.common.util.LazyOptionalUtils;
import li.cil.oc2.common.util.LevelUtils;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.*;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraftforge.common.util.LazyOptional;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
public final class NetworkSwitchBlockEntity extends ModBlockEntity implements NamedDevice, DocumentedDevice, NetworkInterface, TickableBlockEntity {
private final String GET_LINK_STATE = "getLinkState";
private final String GET_HOST_TABLE = "getHostTable";
private final String GET_PORT_CONFIG = "getPortConfig";
private final String SET_PORT_CONFIG = "setPortConfig";
private final long HOST_TTL = 20 * 60 * 2;
private final int TTL_COST = 1;
private final Map<Long, HostEntry> hostTable = new HashMap<>();
private final PortSettings[] portSettings = new PortSettings[Constants.BLOCK_FACE_COUNT];
private int tickCount = 0;
private final NetworkInterface[] adjacentBlockInterfaces = new NetworkInterface[Constants.BLOCK_FACE_COUNT];
private boolean haveAdjacentBlocksChanged = true;
public NetworkSwitchBlockEntity(final BlockPos pos, final BlockState state) {
super(BlockEntities.NETWORK_SWITCH.get(), pos, state);
for (int i = 0; i < portSettings.length; i++) {
portSettings[i] = new PortSettings();
}
}
public void writeEthernetFrame(final NetworkInterface source, byte[] frame, final int timeToLive) {
validateAdjacentBlocks();
long tickTime = getLevel().getGameTime();
long destMac = macToLong(frame, 0);
long srcMac = macToLong(frame, 6);
short vlan = getVLAN(frame);
Optional<Integer> optSide = sideReverseLookup(source);
if (!optSide.isPresent()) {
return;
}
int side = optSide.get();
if (hostTable.size() <= 256) {
hostTable.put(srcMac, new HostEntry(side, tickTime));
}
PortSettings ingressSettings = portSettings[side];
SwitchLog log = new SwitchLog(vlan, side, srcMac, destMac);
if (vlan == 0) {
// Untagged packet
Pair<Short, byte[]> pair = removeVLANTag(frame); // Remove tag in case there is a 0-tag
frame = pair.getSecond();
if (ingressSettings.untagged != 0) {
frame = addVLANTag(frame, ingressSettings.untagged);
vlan = ingressSettings.untagged;
}
} else {
if (!(ingressSettings.trunkAll || ingressSettings.tagged.contains(vlan))) {
// drop packet with disallowed vlan
log.drop("Tag not allowed for ingress");
return;
}
}
HostEntry host = hostTable.get(destMac);
if (host != null) {
if (host.iface == side && !ingressSettings.hairpin) {
// if packet is to same port and hairpin is disabled, drop
log.drop("hairpin disabled");
return;
}
writeToSide(frame, host.iface, vlan, log, timeToLive);
} else {
log.flood();
for (int i = 0; i < Constants.BLOCK_FACE_COUNT; i++) {
if (i != side) {
writeToSide(frame, i, vlan, log, timeToLive);
}
}
}
}
@Override
public byte[] readEthernetFrame() {
return null;
}
private void writeToSide(byte[] frame, int side, short vlan, SwitchLog log, int timeToLive) {
log.egressPort(side);
NetworkInterface iface = adjacentBlockInterfaces[side];
if (iface != null) {
PortSettings egressSettings = portSettings[side];
if (egressSettings.untagged != 0 && vlan == 0) {
log.drop("inner tag untagged");
return;
}
if (egressSettings.untagged == vlan) {
Pair<Short, byte[]> pair = removeVLANTag(frame);
frame = pair.getSecond();
log.egressVlan = 0;
} else if (!(egressSettings.trunkAll || egressSettings.tagged.contains(vlan))) {
// Drop packets with wrong tag
log.drop("Tag not allowed for egress");
return;
} else {
log.egressVlan = vlan;
}
log.emit();
iface.writeEthernetFrame(this, frame, timeToLive - TTL_COST);
}
}
private long macToLong(final byte[] mac, int offset) {
long ret = 0;
for (int i = 0; i < 6; i++) {
ret |= ((((long) mac[i + offset]) & 0xff) << (i * 8));
}
return ret;
}
@Override
public void clientTick()
{
return;
}
@Override
public void serverTick() {
if (level == null) {
return;
}
if (tickCount++ % 20 == 0) {
long threshold = getLevel().getGameTime() - HOST_TTL;
if (threshold < 0) {
return;
}
hostTable.entrySet().removeIf(e -> e.getValue().timestamp < threshold);
}
}
@Override
public void getDeviceDocumentation(final DeviceVisitor visitor) {
visitor.visitCallback(GET_HOST_TABLE)
.description("Returns the MAC address table of the switch")
.returnValueDescription("The MAC table. For each host the mac address, the age (in ticks) and the face is returned");
}
@Override
public Collection<String> getDeviceTypeNames() {
return singletonList("switch");
}
@Override
public void saveAdditional(final CompoundTag tag) {
super.saveAdditional(tag);
ListTag hosts = new ListTag();
for (Map.Entry<Long, HostEntry> host : hostTable.entrySet()) {
CompoundTag thisHost = new CompoundTag();
thisHost.put("mac", LongTag.valueOf(host.getKey()));
thisHost.put("side", IntTag.valueOf(host.getValue().iface));
thisHost.put("timestamp", LongTag.valueOf(host.getValue().timestamp));
hosts.add(thisHost);
}
tag.put("hosts", hosts);
ListTag ports = new ListTag();
for (PortSettings myPort : portSettings) {
CompoundTag port = new CompoundTag();
myPort.save(port);
ports.add(port);
}
tag.put("ports", ports);
}
@Override
public void load(final CompoundTag tag) {
super.load(tag);
Tag hosts = tag.get("hosts");
if (hosts != null) {
for (Tag host_ : ((ListTag) hosts)) {
CompoundTag host = (CompoundTag) host_;
hostTable.put(
host.getLong("mac"),
new HostEntry(
tag.getInt("side"),
tag.getLong("timestamp")
)
);
}
}
Tag ports = tag.get("ports");
if (ports != null) {
int i = 0;
for (Tag port : ((ListTag) ports)) {
portSettings[i++] = PortSettings.load((CompoundTag) port);
}
}
}
@Callback(name = GET_HOST_TABLE)
public List<LuaHostEntry> getHostTable() {
long now = getLevel().getGameTime();
return hostTable
.entrySet()
.stream()
.map(e -> new LuaHostEntry(macLongToString(e.getKey()), now - e.getValue().timestamp, e.getValue().iface))
.collect(Collectors.toList());
}
@Callback(name = GET_PORT_CONFIG, synchronize = false)
public PortSettings[] getPortSettings() {
return portSettings;
}
@Callback(name = SET_PORT_CONFIG)
public void setPortSettings(List<LinkedTreeMap> settings) {
int max = Math.min(portSettings.length, settings.size());
for (int i = 0; i < max; i++) {
portSettings[i].untagged = ((Double) settings.get(i).get("untagged")).shortValue();
}
}
@Callback(name = GET_LINK_STATE)
public boolean[] getLinkState() {
validateAdjacentBlocks();
boolean[] sides = new boolean[Constants.BLOCK_FACE_COUNT];
for (int i = 0; i < Constants.BLOCK_FACE_COUNT; i++) {
sides[i] = adjacentBlockInterfaces[i] != null;
}
return sides;
}
private Optional<Integer> sideReverseLookup(NetworkInterface iface) {
for (int i = 0; i < Constants.BLOCK_FACE_COUNT; i++) {
if (iface == adjacentBlockInterfaces[i]) {
return Optional.of(i);
}
}
return Optional.empty();
}
private static String macLongToString(long mac) {
StringBuilder ret = new StringBuilder();
for (int i = 0; i < 6; i++) {
if (i != 0) {
ret.append(":");
}
ret.append(String.format("%02x", (mac >> (i * 8)) & 0xff));
}
return ret.toString();
}
private byte[] addVLANTag(byte[] packet, short tag) {
if (tag != 0) {
byte[] ret = new byte[packet.length + 4];
copyBytes(packet, ret, 0, 0, 12); // Copy Ethernet Header
copyBytes(packet, ret, 12, 16, packet.length - 12);
ret[12] = (byte) 0x81;
ret[13] = (byte) 0x00;
ret[14] = (byte) ((tag >> 8) & 0x0f);
ret[15] = (byte) (tag & 0xff);
return ret;
} else {
return packet;
}
}
private short getVLAN(byte[] packet) {
if (packet[12] == ((byte) 0x81) && packet[13] == 0x00) {
return (short) (packet[15] | ((((short) packet[14]) & 0x0f) << 8));
} else {
return (short) 0;
}
}
private Pair<Short, byte[]> removeVLANTag(byte[] packet) {
if (packet[12] == ((byte) 0x81) && packet[13] == 0x00) {
byte[] ret = new byte[packet.length - 4];
copyBytes(packet, ret, 0, 0, 12); // Copy Ethernet Header
copyBytes(packet, ret, 16, 12, packet.length - 16); // Copy payload
short tag = (short) (packet[15] | ((((short) packet[14]) & 0x0f) << 8)); // Extract vlan tag
return new Pair<>(tag, ret);
} else {
return new Pair<>((short) 0, packet);
}
}
private void copyBytes(byte[] input, byte[] output, int inputOffset, int outputOffset, int length) {
for (int i = 0; i < length; i++) {
output[outputOffset + i] = input[inputOffset + i];
}
}
private void validateAdjacentBlocks() {
if (isRemoved() || !haveAdjacentBlocksChanged) {
return;
}
for (final Direction side : Constants.DIRECTIONS) {
adjacentBlockInterfaces[side.get3DDataValue()] = null;
}
haveAdjacentBlocksChanged = false;
if (level == null || level.isClientSide()) {
return;
}
final BlockPos pos = getBlockPos();
for (final Direction side : Constants.DIRECTIONS) {
final BlockEntity neighborBlockEntity = LevelUtils.getBlockEntityIfChunkExists(level, pos.relative(side));
if (neighborBlockEntity != null) {
final LazyOptional<NetworkInterface> optional = neighborBlockEntity.getCapability(Capabilities.networkInterface(), side.getOpposite());
optional.ifPresent(adjacentInterface -> {
adjacentBlockInterfaces[side.get3DDataValue()] = adjacentInterface;
LazyOptionalUtils.addWeakListener(optional, this, (hub, unused) -> hub.handleNeighborChanged());
});
}
}
}
private void handleNeighborChanged() {
haveAdjacentBlocksChanged = true;
}
private static class HostEntry {
public int iface;
public long timestamp;
public HostEntry(int iface, long timestamp) {
this.iface = iface;
this.timestamp = timestamp;
}
}
public static class LuaHostEntry {
public String mac;
public long age;
public int side;
public LuaHostEntry(String mac, long age, int iface) {
this.mac = mac;
this.age = age;
this.side = iface;
}
}
private static class PortSettings {
/**
* The VLAN that is both PVID and untagged vlan. It will be removed on egress and added on ingress. If set to 0
* this port is put on the global untagged vlan. The global untagged vlan can ever only be used as an untagged vlan
*/
public short untagged;
/**
* A list of tagged vlans that will be accepted on both ingress and egress. 0 (the global untagged vlan) is not a valid
* value
*/
public List<Short> tagged;
/**
* If enabled, packets entering on this port may also leave via this port again
*/
public boolean hairpin;
/**
* If this is set, tagged will be ignored. Instead all tagged vlans will be accepted. untagged will still be honored
*/
public boolean trunkAll;
public PortSettings(final short untagged, final List<Short> tagged, final boolean hairpin, final boolean trunkAll) {
this.untagged = untagged;
this.tagged = tagged;
this.hairpin = hairpin;
this.trunkAll = trunkAll;
}
/**
* Default configuration of an unmanaged switch, which just forwards all tagged vlans as well as the untagged vlan
* straight through
*/
public PortSettings() {
this((short) 0, emptyList(), false, true);
}
public void save(final CompoundTag tag) {
tag.put("untagged", ShortTag.valueOf(untagged));
tag.put("tagged", new IntArrayTag(tagged.stream().map(s -> (int) s).collect(Collectors.toList())));
tag.put("hairpin", ByteTag.valueOf(hairpin));
tag.put("trunkAll", ByteTag.valueOf(trunkAll));
}
public static PortSettings load(final CompoundTag tag) {
short untagged = tag.getShort("untagged");
List<Short> tagged = Arrays.stream(tag.getIntArray("tagged"))
.mapToObj(i -> (short) i)
.collect(Collectors.toList());
boolean hairpin = tag.getBoolean("hairpin");
boolean trunkAll = tag.getBoolean("trunkAll");
return new PortSettings(untagged, tagged, hairpin, trunkAll);
}
}
private static class SwitchLog {
private static final boolean ENABLED = true;
private short ingressVlan = 0;
private short egressVlan = 0;
private int ingressSide = 0;
private final long srcMac;
private final long destMac;
private Integer egressSide = null;
public SwitchLog(short ingressVlan, int ingressSide, long srcMac, long destMac) {
this.ingressVlan = ingressVlan;
this.ingressSide = ingressSide;
this.srcMac = srcMac;
this.destMac = destMac;
}
public void egressPort(int side) {
egressSide = side;
}
public void drop(String reason) {
if (!ENABLED) return;
String inMac = NetworkSwitchBlockEntity.macLongToString(srcMac);
String outMac = NetworkSwitchBlockEntity.macLongToString(destMac);
if (egressSide == null) {
System.out.printf(
"Switch Packet %s (Port %s, VLAN %s) -> %s drop (%s)\n",
inMac,
ingressSide,
ingressVlan,
outMac,
reason
);
} else {
System.out.printf(
"Switch Packet %s (Port %s, VLAN %s) -> %s (Port %s) drop (%s)\n",
inMac,
ingressSide,
ingressVlan,
outMac,
egressSide,
reason
);
}
}
public void emit() {
if (!ENABLED) return;
String inMac = NetworkSwitchBlockEntity.macLongToString(srcMac);
String outMac = NetworkSwitchBlockEntity.macLongToString(destMac);
System.out.printf(
"Switch Packet %s (Port %s, VLAN %s) -> %s (Port %s, VLAN %s)\n",
inMac,
ingressSide,
ingressVlan,
outMac,
egressSide,
egressVlan
);
}
public void flood() {
if (!ENABLED) return;
String inMac = NetworkSwitchBlockEntity.macLongToString(srcMac);
String outMac = NetworkSwitchBlockEntity.macLongToString(destMac);
System.out.printf(
"Switch Packet %s (Port %s, VLAN %s) -> %s flood\n",
inMac,
ingressSide,
ingressVlan,
outMac
);
}
}
}

View File

@@ -0,0 +1,122 @@
/* SPDX-License-Identifier: MIT */
package li.cil.oc2.common.blockentity;
import li.cil.oc2.common.Config;
import li.cil.oc2.common.block.PciCardCageBlock;
import li.cil.oc2.common.bus.device.vm.block.PciCardCageDevice;
import li.cil.oc2.common.capabilities.Capabilities;
import li.cil.oc2.common.energy.FixedEnergyStorage;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.level.block.state.BlockState;
import javax.annotation.Nullable;
public final class PciCardCageBlockEntity extends ModBlockEntity implements TickableBlockEntity {
private static final String ENERGY_TAG_NAME = "energy";
private static final String HAS_ENERGY_TAG_NAME = "has_energy";
///////////////////////////////////////////////////////////////
private final PciCardCageDevice cardCageDevice = new PciCardCageDevice(this, this::handleMountedChanged);
private boolean isMounted, hasEnergy;
private final FixedEnergyStorage energy = new FixedEnergyStorage(Config.cardCageEnergyStorage);
///////////////////////////////////////////////////////////////
public PciCardCageBlockEntity(final BlockPos pos, final BlockState state) {
super(BlockEntities.PCI_CARD_CAGE.get(), pos, state);
}
///////////////////////////////////////////////////////////////
private void handleMountedChanged(final boolean value) {
}
public boolean hasEnergy() {
return hasEnergy;
}
@Override
public void serverTick() {
if (!isMounted) {
return;
}
final boolean isPowered;
if (Config.cardCagesUseEnergy()) {
isPowered = energy.extractEnergy(Config.cardCageEnergyPerTick, true) >= Config.cardCageEnergyPerTick;
if (isPowered) {
energy.extractEnergy(Config.cardCageEnergyPerTick, false);
}
} else {
isPowered = true;
}
}
@Override
public CompoundTag getUpdateTag() {
final CompoundTag tag = super.getUpdateTag();
tag.putBoolean(HAS_ENERGY_TAG_NAME, hasEnergy);
return tag;
}
@Override
public void handleUpdateTag(final CompoundTag tag) {
super.handleUpdateTag(tag);
hasEnergy = tag.getBoolean(HAS_ENERGY_TAG_NAME);
}
@Override
protected void saveAdditional(final CompoundTag tag) {
super.saveAdditional(tag);
tag.put(ENERGY_TAG_NAME, energy.serializeNBT());
}
@Override
public void load(final CompoundTag tag) {
super.load(tag);
energy.deserializeNBT(tag.getCompound(ENERGY_TAG_NAME));
}
@SuppressWarnings("deprecation")
@Override
public void setBlockState(final BlockState state) {
super.setBlockState(state);
}
///////////////////////////////////////////////////////////////
@Override
protected void collectCapabilities(final CapabilityCollector collector, @Nullable final Direction direction) {
if (Config.cardCagesUseEnergy()) {
collector.offer(Capabilities.energyStorage(), energy);
}
if (direction == getBlockState().getValue(PciCardCageBlock.FACING).getOpposite()) {
collector.offer(Capabilities.device(), cardCageDevice);
}
}
///////////////////////////////////////////////////////////////
}

View File

@@ -0,0 +1,175 @@
package li.cil.oc2.common.blockentity;
import li.cil.oc2.api.capabilities.NetworkInterface;
import li.cil.oc2.common.Config;
import li.cil.oc2.common.Constants;
import li.cil.oc2.common.capabilities.Capabilities;
import li.cil.oc2.common.util.LazyOptionalUtils;
import li.cil.oc2.common.util.LevelUtils;
import li.cil.oc2.common.vxlan.TunnelManager;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraftforge.common.util.LazyOptional;
import org.apache.commons.collections4.QueueUtils;
import org.apache.commons.collections4.queue.CircularFifoQueue;
import javax.annotation.Nullable;
import java.util.Arrays;
import java.util.Objects;
import java.util.Queue;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.stream.Stream;
public final class VxlanBlockEntity extends ModBlockEntity implements NetworkInterface, TickableBlockEntity {
private static final int TTL_COST = 1;
//private int vti = ((int) (Math.random() * Integer.MAX_VALUE)) & 0x00ff_ffff;
private int vti = 1000;
private int frameCount;
private long lastGameTime;
private final Queue<byte[]> packetQueue = new ArrayBlockingQueue<byte[]>(32);
///////////////////////////////////////////////////////////////////
// Each face and the default TunnelInterface connecting to the outernet
private final NetworkInterface[] adjacentBlockInterfaces = new NetworkInterface[Constants.BLOCK_FACE_COUNT + 1];
private boolean haveAdjacentBlocksChanged = true;
///////////////////////////////////////////////////////////////////
public VxlanBlockEntity(final BlockPos pos, final BlockState state) {
super(BlockEntities.VXLAN_HUB.get(), pos, state);
}
///////////////////////////////////////////////////////////////////
public void handleNeighborChanged() {
haveAdjacentBlocksChanged = true;
}
@Override
public byte[] readEthernetFrame() {
return null;
}
@Override
public void writeEthernetFrame(final NetworkInterface source, final byte[] frame, final int timeToLive) {
if (level == null) {
return;
}
final long gameTime = level.getGameTime();
if (gameTime > lastGameTime) {
lastGameTime = gameTime;
frameCount = 1;
} else if (frameCount > Config.hubEthernetFramesPerTick) {
return;
} else {
frameCount++;
}
getAdjacentInterfaces().forEach(adjacentInterface -> {
if (adjacentInterface != source) {
adjacentInterface.writeEthernetFrame(this, frame, timeToLive - TTL_COST);
}
});
}
@Override
public void serverTick() {
if (level == null) {
return;
}
if (adjacentBlockInterfaces[0] != null) {
// CircularFifoQueue isn't thread-safe, so we have to synchronize on it.
synchronized (packetQueue) {
packetQueue.forEach(packet -> writeEthernetFrame(adjacentBlockInterfaces[0], packet, 255));
packetQueue.clear();
}
} else {
System.out.printf("VXLAN block is unregistered upstream: VTI=%d\n", vti);
}
}
///////////////////////////////////////////////////////////////////
@Override
public void load(CompoundTag tag) {
super.load(tag);
if (level != null && !level.isClientSide() && tag.contains("vti")) {
vti = tag.getInt("vti");
}
}
@Override
public void saveAdditional(CompoundTag tag) {
super.saveAdditional(tag);
if (level != null && !level.isClientSide()) {
tag.putInt("vti", vti);
}
}
@Override
protected void onUnload(final boolean isRemove) {
if (level != null && !level.isClientSide()) {
adjacentBlockInterfaces[0] = null;
TunnelManager.instance().unregisterVti(vti);
}
super.onUnload(isRemove);
}
@Override
public void loadServer() {
adjacentBlockInterfaces[0] = TunnelManager.instance().registerVti(vti, this.packetQueue);
}
///////////////////////////////////////////////////////////////////
@Override
protected void collectCapabilities(final CapabilityCollector collector, @Nullable final Direction direction) {
collector.offer(Capabilities.networkInterface(), this);
}
///////////////////////////////////////////////////////////////////
private Stream<NetworkInterface> getAdjacentInterfaces() {
validateAdjacentBlocks();
return Arrays.stream(adjacentBlockInterfaces).filter(Objects::nonNull);
}
private void validateAdjacentBlocks() {
if (isRemoved() || !haveAdjacentBlocksChanged) {
return;
}
for (final Direction side : Constants.DIRECTIONS) {
adjacentBlockInterfaces[side.get3DDataValue() + 1] = null;
}
haveAdjacentBlocksChanged = false;
if (level == null || level.isClientSide()) {
return;
}
final BlockPos pos = getBlockPos();
for (final Direction side : Constants.DIRECTIONS) {
final BlockEntity neighborBlockEntity = LevelUtils.getBlockEntityIfChunkExists(level, pos.relative(side));
if (neighborBlockEntity != null) {
final LazyOptional<NetworkInterface> optional = neighborBlockEntity.getCapability(Capabilities.networkInterface(), side.getOpposite());
optional.ifPresent(adjacentInterface -> {
adjacentBlockInterfaces[side.get3DDataValue() + 1] = adjacentInterface;
LazyOptionalUtils.addWeakListener(optional, this, (hub, unused) -> hub.handleNeighborChanged());
});
}
}
}
}

View File

@@ -0,0 +1,139 @@
/* SPDX-License-Identifier: MIT */
package li.cil.oc2.common.bus.device.vm.block;
import it.unimi.dsi.fastutil.booleans.BooleanConsumer;
import li.cil.oc2.api.bus.device.vm.VMDevice;
import li.cil.oc2.api.bus.device.vm.VMDeviceLoadResult;
import li.cil.oc2.api.bus.device.vm.context.VMContext;
import li.cil.oc2.common.Constants;
import li.cil.oc2.common.bus.device.util.IdentityProxy;
import li.cil.oc2.common.bus.device.util.OptionalAddress;
import li.cil.oc2.common.serialization.BlobStorage;
import li.cil.oc2.common.util.NBTTagIds;
import li.cil.oc2.common.vm.device.PciRootPortDevice;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.level.block.entity.BlockEntity;
import javax.annotation.Nullable;
import java.io.IOException;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.util.UUID;
public final class PciCardCageDevice extends IdentityProxy<BlockEntity> implements VMDevice {
private static final String ADDRESS_TAG_NAME = "address";
private static final String BLOB_HANDLE_TAG_NAME = "blob";
public static final int BUS_COUNT = 16;
public static final int WINDOW_SIZE = 16 * 1024 * 1024;
///////////////////////////////////////////////////////////////
private final BooleanConsumer onMountedChanged;
@Nullable private PciRootPortDevice device;
///////////////////////////////////////////////////////////////
private final OptionalAddress address = new OptionalAddress();
@Nullable private UUID blobHandle;
///////////////////////////////////////////////////////////////
public PciCardCageDevice(final BlockEntity identity, final BooleanConsumer onMountedChanged) {
super(identity);
this.onMountedChanged = onMountedChanged;
}
///////////////////////////////////////////////////////////////
@Override
public VMDeviceLoadResult mount(final VMContext context) {
if (!allocateDevice(context)) {
return VMDeviceLoadResult.fail();
}
assert device != null;
if (!address.claim(context, device)) {
return VMDeviceLoadResult.fail();
}
onMountedChanged.accept(true);
return VMDeviceLoadResult.success();
}
@Override
public void unmount() {
final PciRootPortDevice pciRootPortDevice = device;
device = null;
if (pciRootPortDevice != null) {
pciRootPortDevice.close();
}
if (blobHandle != null) {
BlobStorage.close(blobHandle);
}
onMountedChanged.accept(false);
}
@Override
public void dispose() {
if (blobHandle != null) {
BlobStorage.delete(blobHandle);
blobHandle = null;
}
address.clear();
}
@Override
public CompoundTag serializeNBT() {
final CompoundTag tag = new CompoundTag();
if (blobHandle != null) {
tag.putUUID(BLOB_HANDLE_TAG_NAME, blobHandle);
}
if (address.isPresent()) {
tag.putLong(ADDRESS_TAG_NAME, address.getAsLong());
}
return tag;
}
@Override
public void deserializeNBT(final CompoundTag tag) {
if (tag.hasUUID(BLOB_HANDLE_TAG_NAME)) {
blobHandle = tag.getUUID(BLOB_HANDLE_TAG_NAME);
}
if (tag.contains(ADDRESS_TAG_NAME, NBTTagIds.TAG_LONG)) {
address.set(tag.getLong(ADDRESS_TAG_NAME));
}
}
///////////////////////////////////////////////////////////////
private boolean allocateDevice(final VMContext context) {
if (!context.getMemoryAllocator().claimMemory(Constants.PAGE_SIZE)) {
return false;
}
try {
device = createPciRootPortDevice();
} catch (final IOException e) {
return false;
}
return true;
}
private PciRootPortDevice createPciRootPortDevice() throws IOException {
blobHandle = BlobStorage.validateHandle(blobHandle);
final FileChannel channel = BlobStorage.getOrOpen(blobHandle);
final MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, WINDOW_SIZE * 2);
return new PciRootPortDevice(BUS_COUNT, WINDOW_SIZE, buffer);
}
}

View File

@@ -34,8 +34,11 @@ public final class Items {
public static final RegistryObject<Item> KEYBOARD = register(Blocks.KEYBOARD);
public static final RegistryObject<Item> NETWORK_CONNECTOR = register(Blocks.NETWORK_CONNECTOR);
public static final RegistryObject<Item> NETWORK_HUB = register(Blocks.NETWORK_HUB);
public static final RegistryObject<Item> NETWORK_SWITCH = register(Blocks.NETWORK_SWITCH);
public static final RegistryObject<Item> PROJECTOR = register(Blocks.PROJECTOR);
public static final RegistryObject<Item> REDSTONE_INTERFACE = register(Blocks.REDSTONE_INTERFACE);
public static final RegistryObject<Item> VXLAN_HUB = register(Blocks.VXLAN_HUB);
public static final RegistryObject<Item> PCI_CARD_CAGE = register(Blocks.PCI_CARD_CAGE);
///////////////////////////////////////////////////////////////////

View File

@@ -0,0 +1,101 @@
/* SPDX-License-Identifier: MIT */
package li.cil.oc2.common.vm.device;
import li.cil.sedna.api.device.MemoryMappedDevice;
import li.cil.sedna.api.memory.MemoryAccessException;
import li.cil.sedna.utils.DirectByteBufferUtils;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public final class PciRootPortDevice implements MemoryMappedDevice {
///////////////////////////////////////////////////////////////
private final ByteBuffer buffer;
private int length;
///////////////////////////////////////////////////////////////
public PciRootPortDevice(final int bus_count, final int window_size, final ByteBuffer buffer) {
length = window_size * 2;
if (buffer.capacity() < length) {
throw new IllegalArgumentException("Buffer too small.");
}
this.buffer = buffer.order(ByteOrder.LITTLE_ENDIAN);
this.buffer.putInt(0, 0x12345678);
this.buffer.putInt(4, 2);
this.buffer.putInt(8, 0xFF000000);
this.buffer.putInt(12, 0x00000101);
this.buffer.putInt(0x10, 0x00000000);
this.buffer.putInt(0x2C, 0x12345678);
}
///////////////////////////////////////////////////////////////
public void close() {
synchronized (buffer) {
length = 0;
DirectByteBufferUtils.release(buffer);
}
}
public boolean hasChanges() {
return false;
}
@Override
public int getLength() {
return length;
}
@Override
public long load(final int offset, final int sizeLog2) throws MemoryAccessException {
if (offset >= 0 && offset <= length - (1 << sizeLog2)) {
System.out.println(String.format("PCI config read: %x %x", offset, sizeLog2));
if (offset == 0x10) {
long res = buffer.getInt(offset);
System.out.println(String.format(" 00:00.0 BAR0 read %x", res));
res = res & 0xFFFFF000L;
System.out.println(String.format("Clipped 00:00.0 BAR0 read to %x", res));
return res;
}
return switch (sizeLog2) {
case 0 -> buffer.get(offset);
case 1 -> buffer.getShort(offset);
case 2 -> buffer.getInt(offset);
case 3 -> buffer.getLong(offset);
default -> throw new IllegalArgumentException();
};
} else {
return 0;
}
}
@Override
public void store(final int offset, final long value, final int sizeLog2) throws MemoryAccessException {
if (offset >= 0 && offset <= length - (1 << sizeLog2)) {
System.out.println(String.format("PCI config write: %x %x %x", offset, value, sizeLog2));
switch (sizeLog2) {
case 0 -> buffer.put(offset, (byte) value);
case 1 -> buffer.putShort(offset, (short) value);
case 2 -> buffer.putInt(offset, (int) value);
case 3 -> buffer.putLong(offset, value);
default -> throw new IllegalArgumentException();
}
}
}
///////////////////////////////////////////////////////////////
}

View File

@@ -2,11 +2,13 @@
package li.cil.oc2.common.vm.provider;
import li.cil.oc2.common.vm.device.PciRootPortDevice;
import li.cil.oc2.common.vm.device.SimpleFramebufferDevice;
import li.cil.sedna.devicetree.DeviceTreeRegistry;
public final class DeviceTreeProviders {
public static void initialize() {
DeviceTreeRegistry.putProvider(SimpleFramebufferDevice.class, new SimpleFramebufferDeviceProvider());
DeviceTreeRegistry.putProvider(PciRootPortDevice.class, new PciRootPortDeviceProvider());
}
}

View File

@@ -0,0 +1,48 @@
/* SPDX-License-Identifier: MIT */
package li.cil.oc2.common.vm.provider;
import li.cil.oc2.common.vm.device.PciRootPortDevice;
import li.cil.sedna.api.device.Device;
import li.cil.sedna.api.device.MemoryMappedDevice;
import li.cil.sedna.api.devicetree.DeviceNames;
import li.cil.sedna.api.devicetree.DevicePropertyNames;
import li.cil.sedna.api.devicetree.DeviceTree;
import li.cil.sedna.api.devicetree.DeviceTreeProvider;
import li.cil.sedna.api.memory.MappedMemoryRange;
import li.cil.sedna.api.memory.MemoryMap;
import java.util.Optional;
public final class PciRootPortDeviceProvider implements DeviceTreeProvider {
@Override
public Optional<String> getName(final Device device) {
return Optional.of("pci");
}
@Override
public Optional<DeviceTree> createNode(final DeviceTree root, final MemoryMap memoryMap, final Device device, final String deviceName) {
final Optional<MappedMemoryRange> range = memoryMap.getMemoryRange((MemoryMappedDevice) device);
return range.map(r -> {
final DeviceTree pci = root.find("/pci");
return pci.getChild(deviceName, r.address());
});
}
@Override
public void visit(final DeviceTree node, final MemoryMap memoryMap, final Device device) {
final PciRootPortDevice pr = (PciRootPortDevice) device;
final Optional<MappedMemoryRange> range = memoryMap.getMemoryRange((MemoryMappedDevice) device);
node
.addProp(DevicePropertyNames.COMPATIBLE, "pci-host-cam-generic")
.addProp(DevicePropertyNames.DEVICE_TYPE, DeviceNames.PCI)
.addProp(DevicePropertyNames.NUM_ADDRESS_CELLS,3)
.addProp(DevicePropertyNames.NUM_SIZE_CELLS, 2)
.addProp("bus-range", 0, 1)
//.addProp("linux,pci-probe-only", 1)
.addProp(DevicePropertyNames.RANGES,
// type pci.hi pci.lo cpu.hi cpu.lo len.hi len.lo
0x02000000, 0x00000000, 0x40000000, 0x00000000, 0x40000000, 0x00000000, 0x20000000); //
}
}

View File

@@ -0,0 +1,162 @@
package li.cil.oc2.common.vxlan;
import li.cil.oc2.api.capabilities.NetworkInterface;
import li.cil.oc2.common.Config;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.net.*;
import java.util.HashMap;
import java.util.Queue;
public class TunnelManager {
private static final Logger LOGGER = LogManager.getLogger();
private final HashMap<Integer, TunnelInterface> tunnels = new HashMap<>();
private DatagramSocket socket;
private static TunnelManager INSTANCE;
private final InetAddress remoteHost;
private final short remotePort;
private final InetAddress bindHost;
private final short bindPort;
public TunnelManager(InetAddress bindHost, short bindPort, InetAddress remoteHost, short remotePort) throws SocketException {
this.remoteHost = remoteHost;
this.remotePort = remotePort;
this.bindHost = bindHost;
this.bindPort = bindPort;
}
public static void initialize() {
LOGGER.info("Initializing outernet tunnel manager");
try {
INSTANCE = new TunnelManager(
InetAddress.getByName(Config.bindHost), (short) Config.bindPort,
InetAddress.getByName(Config.remoteHost), (short) Config.remotePort
);
} catch (SocketException | UnknownHostException e) {
LOGGER.error("Failed to bind to configured address: " + e.getMessage());
LOGGER.error(e);
}
if (Config.enable) {
Thread bgThread = new Thread(() -> {
try {
INSTANCE.listen();
} catch (IOException e) {
LOGGER.error(e);
}
});
bgThread.setName("VXLAN Background Thread");
bgThread.start();
}
}
public void listen() throws IOException {
LOGGER.printf(Level.INFO, "Binding %s:%s\n", bindHost, bindPort);
if (Config.enable) {
socket = new DatagramSocket(bindPort, bindHost);
} else {
return;
}
LOGGER.printf(Level.INFO, "Bind successful: connected=%s bound=%s\n", socket.isConnected(), socket.isBound());
byte[] buffer = new byte[65535];
// TODO shut this thread down more cleanly on server shutdown?
//noinspection InfiniteLoopStatement
while (true) {
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
socket.receive(packet);
if (packet.getLength() < 8) {
continue;
}
byte flags = packet.getData()[0];
int vni = (packet.getData()[6] & 0xFF )
| ( ( packet.getData()[5] & 0xFF ) << 8 )
| ( ( packet.getData()[4] & 0xFF ) << 16 );
if ((flags & 0x08) != 0x08) {
continue;
}
LOGGER.debug("recv on vti " + vni);
TunnelInterface iface = tunnels.get(vni);
if (iface != null) {
byte[] inner = new byte[packet.getLength() - 8];
System.arraycopy(packet.getData(), 8, inner, 0, packet.getLength() - 8);
// CircularFifoQueue isn't thread-safe, so we have to synchronize on it.
synchronized (iface.packetQueue) {
iface.packetQueue.offer(inner);
}
}
}
}
public static TunnelManager instance() {
return INSTANCE;
}
public void sendToOuternet(int vti, byte[] payload) {
if (socket != null) {
byte[] buffer = new byte[payload.length + 8];
System.arraycopy(payload, 0, buffer, 8, payload.length);
buffer[0] = 0x08;
buffer[4] = (byte) ((vti >> 16) & 0xff);
buffer[5] = (byte) ((vti >> 8) & 0xff);
buffer[6] = (byte) (vti & 0xff);
DatagramPacket packet = new DatagramPacket(buffer, buffer.length, this.remoteHost, this.remotePort);
try {
socket.send(packet);
} catch (IOException e) {
LOGGER.error(e);
}
} else {
LOGGER.error("No socket in TunnelManager\n");
}
}
public NetworkInterface registerVti(int vti, Queue<byte[]> packetQueue) {
TunnelInterface tuniface = new TunnelInterface(vti, packetQueue);
tunnels.put(vti, tuniface);
return tuniface;
}
public void unregisterVti(int vti) {
tunnels.remove(vti);
}
public class TunnelInterface implements NetworkInterface {
final Queue<byte[]> packetQueue;
private final int vti;
public TunnelInterface(int vti, Queue<byte[]> packetQueue) {
this.vti = vti;
this.packetQueue = packetQueue;
}
@Override
public byte[] readEthernetFrame() {
return null;
}
@Override
public void writeEthernetFrame(final @NotNull NetworkInterface source, final byte @NotNull [] frame, final int timeToLive) {
TunnelManager.this.sendToOuternet(vti, frame);
}
}
}

View File

@@ -29,6 +29,8 @@ public final class ModBlockStateProvider extends BlockStateProvider {
private static final ResourceLocation NETWORK_HUB_MODEL = new ResourceLocation(API.MOD_ID, "block/network_hub");
private static final ResourceLocation PROJECTOR_MODEL = new ResourceLocation(API.MOD_ID, "block/projector");
private static final ResourceLocation REDSTONE_INTERFACE_MODEL = new ResourceLocation(API.MOD_ID, "block/redstone_interface");
private static final ResourceLocation PCI_CARD_CAGE_MODEL = new ResourceLocation(API.MOD_ID, "block/pci_card_cage");
public ModBlockStateProvider(final DataGenerator generator, final ExistingFileHelper existingFileHelper) {
super(generator, API.MOD_ID, existingFileHelper);
@@ -55,8 +57,10 @@ public final class ModBlockStateProvider extends BlockStateProvider {
.end()
.end();
horizontalBlock(Blocks.NETWORK_HUB, Items.NETWORK_HUB, NETWORK_HUB_MODEL);
horizontalBlock(Blocks.NETWORK_SWITCH, Items.NETWORK_SWITCH, NETWORK_HUB_MODEL);
horizontalBlock(Blocks.PROJECTOR, Items.PROJECTOR, PROJECTOR_MODEL);
horizontalBlock(Blocks.REDSTONE_INTERFACE, Items.REDSTONE_INTERFACE, REDSTONE_INTERFACE_MODEL);
horizontalBlock(Blocks.PCI_CARD_CAGE, Items.PCI_CARD_CAGE, PCI_CARD_CAGE_MODEL);
registerCableStates();
}

View File

@@ -0,0 +1 @@
Maven-Artifact: org.apache.commons:commons-collections4:4.4

View File

@@ -0,0 +1,19 @@
{
"variants": {
"facing=north": {
"model": "oc2:block/network_switch"
},
"facing=south": {
"model": "oc2:block/network_switch",
"y": 180
},
"facing=west": {
"model": "oc2:block/network_switch",
"y": 270
},
"facing=east": {
"model": "oc2:block/network_switch",
"y": 90
}
}
}

View File

@@ -0,0 +1,34 @@
{
"variants": {
"facing=north,lit=false": {
"model": "oc2:block/pci_card_cage"
},
"facing=south,lit=false": {
"model": "oc2:block/pci_card_cage",
"y": 180
},
"facing=west,lit=false": {
"model": "oc2:block/pci_card_cage",
"y": 270
},
"facing=east,lit=false": {
"model": "oc2:block/pci_card_cage",
"y": 90
},
"facing=north,lit=true": {
"model": "oc2:block/pci_card_cage"
},
"facing=south,lit=true": {
"model": "oc2:block/pci_card_cage",
"y": 180
},
"facing=west,lit=true": {
"model": "oc2:block/pci_card_cage",
"y": 270
},
"facing=east,lit=true": {
"model": "oc2:block/pci_card_cage",
"y": 90
}
}
}

View File

@@ -0,0 +1,19 @@
{
"variants": {
"facing=north": {
"model": "oc2:block/vxlan_hub"
},
"facing=south": {
"model": "oc2:block/vxlan_hub",
"y": 180
},
"facing=west": {
"model": "oc2:block/vxlan_hub",
"y": 270
},
"facing=east": {
"model": "oc2:block/vxlan_hub",
"y": 90
}
}
}

View File

@@ -0,0 +1,293 @@
{
"parent": "block/block",
"textures": {
"particle": "oc2:block/network_switch/network_switch_atlas0",
"atlas0": "oc2:block/network_switch/network_switch_atlas0",
"atlas1": "oc2:block/network_switch/network_switch_atlas1",
"atlas2": "oc2:block/network_switch/network_switch_atlas2",
"atlas3": "oc2:block/network_switch/network_switch_atlas3"
},
"elements": [
{
"from": [0, 0, 10],
"to": [16, 1, 16],
"faces": {
"north": {"uv": [0, 3, 8, 3.5], "texture": "#atlas0"},
"east": {"uv": [8, 7, 11, 7.5], "texture": "#atlas1", "cullface": "east"},
"south": {"uv": [0, 3.5, 8, 4], "texture": "#atlas0", "cullface": "south"},
"west": {"uv": [8, 7.5, 11, 8], "texture": "#atlas1", "cullface": "west"},
"down": {"uv": [0, 0, 8, 3], "texture": "#atlas0", "cullface": "down"}
}
},
{
"from": [0, 0, 6],
"to": [6, 1, 10],
"faces": {
"north": {"uv": [11, 7.5, 14, 8], "texture": "#atlas1"},
"east": {"uv": [14, 7.5, 16, 8], "texture": "#atlas1"},
"south": {"uv": [11, 7, 14, 7.5], "texture": "#atlas1"},
"west": {"uv": [14, 7, 16, 7.5], "texture": "#atlas1", "cullface": "west"},
"down": {"uv": [0, 0, 3, 2], "texture": "#atlas3", "cullface": "down"}
}
},
{
"from": [10, 0, 6],
"to": [16, 1, 10],
"faces": {
"north": {"uv": [0, 4, 3, 4.5], "texture": "#atlas3"},
"east": {"uv": [9, 14.5, 11, 15], "texture": "#atlas3", "cullface": "east"},
"south": {"uv": [0, 4.5, 3, 5], "texture": "#atlas3"},
"west": {"uv": [11, 14.5, 13, 15], "texture": "#atlas3"},
"down": {"uv": [0, 2, 3, 4], "texture": "#atlas3", "cullface": "down"}
}
},
{
"from": [0, 0, 0],
"to": [16, 1, 6],
"faces": {
"north": {"uv": [0, 7, 8, 7.5], "texture": "#atlas0", "cullface": "north"},
"east": {"uv": [0, 5, 3, 5.5], "texture": "#atlas3", "cullface": "east"},
"south": {"uv": [0, 7.5, 8, 8], "texture": "#atlas0"},
"west": {"uv": [0, 5.5, 3, 6], "texture": "#atlas3", "cullface": "west"},
"down": {"uv": [0, 4, 8, 7], "texture": "#atlas0", "cullface": "down"}
}
},
{
"from": [0, 1, 0],
"to": [16, 6, 16],
"faces": {
"north": {"uv": [0, 13, 8, 15.5], "texture": "#atlas0", "cullface": "north"},
"east": {"uv": [0, 8, 8, 10.5], "texture": "#atlas0", "cullface": "east"},
"south": {"uv": [8, 13, 16, 15.5], "texture": "#atlas0", "cullface": "south"},
"west": {"uv": [0, 10.5, 8, 13], "texture": "#atlas0", "cullface": "west"},
"up": {"uv": [0, 0, 8, 8], "texture": "#atlas1"},
"down": {"uv": [0, 8, 8, 16], "texture": "#atlas1"}
}
},
{
"from": [0, 6, 15],
"to": [6, 10, 16],
"faces": {
"east": {"uv": [15, 0, 15.5, 2], "texture": "#atlas2"},
"south": {"uv": [0, 7, 3, 9], "texture": "#atlas3", "cullface": "south"},
"west": {"uv": [15.5, 0, 16, 2], "texture": "#atlas2", "cullface": "west"},
"up": {"uv": [0, 6, 3, 6.5], "texture": "#atlas3"},
"down": {"uv": [0, 6.5, 3, 7], "texture": "#atlas3"}
}
},
{
"from": [10, 6, 15],
"to": [16, 10, 16],
"faces": {
"east": {"uv": [15, 2, 15.5, 4], "texture": "#atlas2", "cullface": "east"},
"south": {"uv": [0, 10, 3, 12], "texture": "#atlas3", "cullface": "south"},
"west": {"uv": [15.5, 2, 16, 4], "texture": "#atlas2"},
"up": {"uv": [0, 9, 3, 9.5], "texture": "#atlas3"},
"down": {"uv": [0, 9.5, 3, 10], "texture": "#atlas3"}
}
},
{
"from": [1, 6, 6],
"to": [15, 10, 10],
"faces": {
"north": {"uv": [8, 4, 15, 6], "texture": "#atlas2"},
"east": {"uv": [13, 10, 15, 12], "texture": "#atlas3"},
"south": {"uv": [8, 6, 15, 8], "texture": "#atlas2"},
"west": {"uv": [3, 7, 5, 9], "texture": "#atlas3"},
"up": {"uv": [8, 0, 15, 2], "texture": "#atlas2"},
"down": {"uv": [8, 2, 15, 4], "texture": "#atlas2"}
}
},
{
"from": [0, 6, 1],
"to": [16, 10, 6],
"faces": {
"north": {"uv": [8, 13, 16, 15], "texture": "#atlas1"},
"east": {"uv": [8, 10, 10.5, 12], "texture": "#atlas3", "cullface": "east"},
"south": {"uv": [8, 0, 16, 2], "texture": "#atlas1"},
"west": {"uv": [10.5, 10, 13, 12], "texture": "#atlas3", "cullface": "west"},
"up": {"uv": [8, 8, 16, 10.5], "texture": "#atlas1"},
"down": {"uv": [8, 10.5, 16, 13], "texture": "#atlas1"}
}
},
{
"from": [0, 6, 0],
"to": [6, 10, 1],
"faces": {
"north": {"uv": [0, 13, 3, 15], "texture": "#atlas3", "cullface": "north"},
"east": {"uv": [15, 4, 15.5, 6], "texture": "#atlas2"},
"west": {"uv": [15.5, 4, 16, 6], "texture": "#atlas2", "cullface": "west"},
"up": {"uv": [0, 12, 3, 12.5], "texture": "#atlas3"},
"down": {"uv": [0, 12.5, 3, 13], "texture": "#atlas3"}
}
},
{
"from": [10, 6, 0],
"to": [16, 10, 1],
"faces": {
"north": {"uv": [3, 13, 6, 15], "texture": "#atlas3", "cullface": "north"},
"east": {"uv": [15, 6, 15.5, 8], "texture": "#atlas2", "cullface": "east"},
"west": {"uv": [15.5, 6, 16, 8], "texture": "#atlas2"},
"up": {"uv": [0, 15, 3, 15.5], "texture": "#atlas3"},
"down": {"uv": [0, 15.5, 3, 16], "texture": "#atlas3"}
}
},
{
"from": [0, 10, 0],
"to": [16, 15, 16],
"faces": {
"north": {"uv": [8, 8, 16, 10.5], "texture": "#atlas2", "cullface": "north"},
"east": {"uv": [8, 2, 16, 4.5], "texture": "#atlas1", "cullface": "east"},
"south": {"uv": [8, 10.5, 16, 13], "texture": "#atlas2", "cullface": "south"},
"west": {"uv": [8, 4.5, 16, 7], "texture": "#atlas1", "cullface": "west"},
"up": {"uv": [0, 0, 8, 8], "texture": "#atlas2"},
"down": {"uv": [0, 8, 8, 16], "texture": "#atlas2"}
}
},
{
"from": [0, 15, 13],
"to": [16, 16, 16],
"faces": {
"north": {"uv": [0, 15.5, 8, 16], "texture": "#atlas0"},
"east": {"uv": [11, 13, 12.5, 13.5], "texture": "#atlas3", "cullface": "east"},
"south": {"uv": [8, 15.5, 16, 16], "texture": "#atlas0", "cullface": "south"},
"west": {"uv": [12.5, 13, 14, 13.5], "texture": "#atlas3", "cullface": "west"},
"up": {"uv": [8, 13, 16, 14.5], "texture": "#atlas2", "cullface": "up"}
}
},
{
"from": [0, 15, 12],
"to": [6, 16, 13],
"faces": {
"north": {"uv": [6, 15.5, 9, 16], "texture": "#atlas3"},
"east": {"uv": [15.5, 13, 16, 13.5], "texture": "#atlas3"},
"south": {"uv": [9, 15.5, 12, 16], "texture": "#atlas3"},
"west": {"uv": [13.5, 12.5, 14, 13], "texture": "#atlas3", "cullface": "west"},
"up": {"uv": [3, 15.5, 6, 16], "texture": "#atlas3", "cullface": "up"}
}
},
{
"from": [10, 15, 12],
"to": [16, 16, 13],
"faces": {
"north": {"uv": [3, 15, 6, 15.5], "texture": "#atlas3"},
"east": {"uv": [14, 12.5, 14.5, 13], "texture": "#atlas3", "cullface": "east"},
"south": {"uv": [6, 15, 9, 15.5], "texture": "#atlas3"},
"west": {"uv": [14.5, 12.5, 15, 13], "texture": "#atlas3"},
"up": {"uv": [12, 15.5, 15, 16], "texture": "#atlas3", "cullface": "up"}
}
},
{
"from": [0, 15, 10],
"to": [16, 16, 12],
"faces": {
"north": {"uv": [8, 7.5, 16, 8], "texture": "#atlas0"},
"east": {"uv": [15, 15.5, 16, 16], "texture": "#atlas3", "cullface": "east"},
"south": {"uv": [8, 7, 16, 7.5], "texture": "#atlas0"},
"west": {"uv": [15, 15, 16, 15.5], "texture": "#atlas3", "cullface": "west"},
"up": {"uv": [8, 6, 16, 7], "texture": "#atlas0", "cullface": "up"}
}
},
{
"from": [0, 15, 6],
"to": [3, 16, 10],
"faces": {
"north": {"uv": [14, 13, 15.5, 13.5], "texture": "#atlas3"},
"east": {"uv": [13, 14.5, 15, 15], "texture": "#atlas3"},
"south": {"uv": [3, 12.5, 4.5, 13], "texture": "#atlas3"},
"west": {"uv": [9, 14, 11, 14.5], "texture": "#atlas3", "cullface": "west"},
"up": {"uv": [3, 0, 4.5, 2], "texture": "#atlas3", "cullface": "up"}
}
},
{
"from": [4, 15, 6],
"to": [6, 16, 10],
"faces": {
"north": {"uv": [15, 14.5, 16, 15], "texture": "#atlas3"},
"east": {"uv": [11, 14, 13, 14.5], "texture": "#atlas3"},
"south": {"uv": [15, 14, 16, 14.5], "texture": "#atlas3"},
"west": {"uv": [13, 14, 15, 14.5], "texture": "#atlas3"},
"up": {"uv": [4.5, 0, 5.5, 2], "texture": "#atlas3", "cullface": "up"}
}
},
{
"from": [10, 15, 6],
"to": [12, 16, 10],
"faces": {
"north": {"uv": [15, 13.5, 16, 14], "texture": "#atlas3"},
"east": {"uv": [9, 13.5, 11, 14], "texture": "#atlas3"},
"south": {"uv": [10.5, 12.5, 11.5, 13], "texture": "#atlas3"},
"west": {"uv": [11, 13.5, 13, 14], "texture": "#atlas3"},
"up": {"uv": [5.5, 0, 6.5, 2], "texture": "#atlas3", "cullface": "up"}
}
},
{
"from": [13, 15, 6],
"to": [16, 16, 10],
"faces": {
"north": {"uv": [4.5, 12.5, 6, 13], "texture": "#atlas3"},
"east": {"uv": [13, 13.5, 15, 14], "texture": "#atlas3", "cullface": "east"},
"south": {"uv": [6, 12.5, 7.5, 13], "texture": "#atlas3"},
"west": {"uv": [9, 13, 11, 13.5], "texture": "#atlas3"},
"up": {"uv": [6.5, 0, 8, 2], "texture": "#atlas3", "cullface": "up"}
}
},
{
"from": [0, 15, 4],
"to": [16, 16, 6],
"faces": {
"north": {"uv": [8, 3.5, 16, 4], "texture": "#atlas0"},
"east": {"uv": [11.5, 12.5, 12.5, 13], "texture": "#atlas3", "cullface": "east"},
"south": {"uv": [8, 3, 16, 3.5], "texture": "#atlas0"},
"west": {"uv": [12.5, 12.5, 13.5, 13], "texture": "#atlas3", "cullface": "west"},
"up": {"uv": [8, 2, 16, 3], "texture": "#atlas0", "cullface": "up"}
}
},
{
"from": [0, 15, 3],
"to": [6, 16, 4],
"faces": {
"north": {"uv": [12, 15, 15, 15.5], "texture": "#atlas3"},
"east": {"uv": [15, 12.5, 15.5, 13], "texture": "#atlas3"},
"south": {"uv": [6, 13, 9, 13.5], "texture": "#atlas3"},
"west": {"uv": [15.5, 12.5, 16, 13], "texture": "#atlas3", "cullface": "west"},
"up": {"uv": [9, 15, 12, 15.5], "texture": "#atlas3", "cullface": "up"}
}
},
{
"from": [10, 15, 3],
"to": [16, 16, 4],
"faces": {
"north": {"uv": [6, 14, 9, 14.5], "texture": "#atlas3"},
"east": {"uv": [3, 12, 3.5, 12.5], "texture": "#atlas3", "cullface": "east"},
"south": {"uv": [6, 14.5, 9, 15], "texture": "#atlas3"},
"west": {"uv": [3.5, 12, 4, 12.5], "texture": "#atlas3"},
"up": {"uv": [6, 13.5, 9, 14], "texture": "#atlas3", "cullface": "up"}
}
},
{
"from": [0, 15, 0],
"to": [16, 16, 3],
"faces": {
"north": {"uv": [8, 15, 16, 15.5], "texture": "#atlas1", "cullface": "north"},
"east": {"uv": [7.5, 12.5, 9, 13], "texture": "#atlas3", "cullface": "east"},
"south": {"uv": [8, 15.5, 16, 16], "texture": "#atlas1"},
"west": {"uv": [9, 12.5, 10.5, 13], "texture": "#atlas3", "cullface": "west"},
"up": {"uv": [8, 14.5, 16, 16], "texture": "#atlas2", "cullface": "up"}
}
},
{
"from": [0, 6, 10],
"to": [16, 10, 15],
"faces": {
"north": {"uv": [8, 4, 16, 6], "texture": "#atlas0"},
"east": {"uv": [3, 10, 5.5, 12], "texture": "#atlas3", "cullface": "east"},
"south": {"uv": [8, 0, 16, 2], "texture": "#atlas0"},
"west": {"uv": [5.5, 10, 8, 12], "texture": "#atlas3", "cullface": "west"},
"up": {"uv": [8, 10.5, 16, 13], "texture": "#atlas0"},
"down": {"uv": [8, 8, 16, 10.5], "texture": "#atlas0"}
}
}
],
"display": {}
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,292 @@
{
"parent": "block/block",
"textures": {
"particle": "oc2:block/vxlan_hub/vxlan_hub_atlas0",
"atlas0": "oc2:block/vxlan_hub/vxlan_hub_atlas0",
"atlas1": "oc2:block/vxlan_hub/vxlan_hub_atlas1",
"atlas2": "oc2:block/vxlan_hub/vxlan_hub_atlas2",
"atlas3": "oc2:block/vxlan_hub/vxlan_hub_atlas3"
},
"elements": [
{
"from": [0, 0, 10],
"to": [16, 1, 16],
"faces": {
"north": {"uv": [0, 3, 8, 3.5], "texture": "#atlas0"},
"east": {"uv": [8, 7, 11, 7.5], "texture": "#atlas1", "cullface": "east"},
"south": {"uv": [0, 3.5, 8, 4], "texture": "#atlas0", "cullface": "south"},
"west": {"uv": [8, 7.5, 11, 8], "texture": "#atlas1", "cullface": "west"},
"down": {"uv": [0, 0, 8, 3], "texture": "#atlas0", "cullface": "down"}
}
},
{
"from": [0, 0, 6],
"to": [6, 1, 10],
"faces": {
"north": {"uv": [11, 7.5, 14, 8], "texture": "#atlas1"},
"east": {"uv": [14, 7.5, 16, 8], "texture": "#atlas1"},
"south": {"uv": [11, 7, 14, 7.5], "texture": "#atlas1"},
"west": {"uv": [14, 7, 16, 7.5], "texture": "#atlas1", "cullface": "west"},
"down": {"uv": [0, 0, 3, 2], "texture": "#atlas3", "cullface": "down"}
}
},
{
"from": [10, 0, 6],
"to": [16, 1, 10],
"faces": {
"north": {"uv": [0, 4, 3, 4.5], "texture": "#atlas3"},
"east": {"uv": [9, 14.5, 11, 15], "texture": "#atlas3", "cullface": "east"},
"south": {"uv": [0, 4.5, 3, 5], "texture": "#atlas3"},
"west": {"uv": [11, 14.5, 13, 15], "texture": "#atlas3"},
"down": {"uv": [0, 2, 3, 4], "texture": "#atlas3", "cullface": "down"}
}
},
{
"from": [0, 0, 0],
"to": [16, 1, 6],
"faces": {
"north": {"uv": [0, 7, 8, 7.5], "texture": "#atlas0", "cullface": "north"},
"east": {"uv": [0, 5, 3, 5.5], "texture": "#atlas3", "cullface": "east"},
"south": {"uv": [0, 7.5, 8, 8], "texture": "#atlas0"},
"west": {"uv": [0, 5.5, 3, 6], "texture": "#atlas3", "cullface": "west"},
"down": {"uv": [0, 4, 8, 7], "texture": "#atlas0", "cullface": "down"}
}
},
{
"from": [0, 1, 0],
"to": [16, 6, 16],
"faces": {
"north": {"uv": [0, 13, 8, 15.5], "texture": "#atlas0", "cullface": "north"},
"east": {"uv": [0, 8, 8, 10.5], "texture": "#atlas0", "cullface": "east"},
"south": {"uv": [8, 13, 16, 15.5], "texture": "#atlas0", "cullface": "south"},
"west": {"uv": [0, 10.5, 8, 13], "texture": "#atlas0", "cullface": "west"},
"up": {"uv": [0, 0, 8, 8], "texture": "#atlas1"},
"down": {"uv": [0, 8, 8, 16], "texture": "#atlas1"}
}
},
{
"from": [0, 6, 15],
"to": [6, 10, 16],
"faces": {
"east": {"uv": [15, 0, 15.5, 2], "texture": "#atlas2"},
"south": {"uv": [0, 7, 3, 9], "texture": "#atlas3", "cullface": "south"},
"west": {"uv": [15.5, 0, 16, 2], "texture": "#atlas2", "cullface": "west"},
"up": {"uv": [0, 6, 3, 6.5], "texture": "#atlas3"},
"down": {"uv": [0, 6.5, 3, 7], "texture": "#atlas3"}
}
},
{
"from": [10, 6, 15],
"to": [16, 10, 16],
"faces": {
"east": {"uv": [15, 2, 15.5, 4], "texture": "#atlas2", "cullface": "east"},
"south": {"uv": [0, 10, 3, 12], "texture": "#atlas3", "cullface": "south"},
"west": {"uv": [15.5, 2, 16, 4], "texture": "#atlas2"},
"up": {"uv": [0, 9, 3, 9.5], "texture": "#atlas3"},
"down": {"uv": [0, 9.5, 3, 10], "texture": "#atlas3"}
}
},
{
"from": [0, 6, 10],
"to": [16, 10, 15],
"faces": {
"north": {"uv": [8, 4, 16, 6], "texture": "#atlas0"},
"east": {"uv": [3, 10, 5.5, 12], "texture": "#atlas3", "cullface": "east"},
"south": {"uv": [8, 0, 16, 2], "texture": "#atlas0"},
"west": {"uv": [5.5, 10, 8, 12], "texture": "#atlas3", "cullface": "west"},
"up": {"uv": [8, 10.5, 16, 13], "texture": "#atlas0"},
"down": {"uv": [8, 8, 16, 10.5], "texture": "#atlas0"}
}
},
{
"from": [1, 6, 6],
"to": [15, 10, 10],
"faces": {
"north": {"uv": [8, 4, 15, 6], "texture": "#atlas2"},
"east": {"uv": [13, 10, 15, 12], "texture": "#atlas3"},
"south": {"uv": [8, 6, 15, 8], "texture": "#atlas2"},
"west": {"uv": [3, 7, 5, 9], "texture": "#atlas3"},
"up": {"uv": [8, 0, 15, 2], "texture": "#atlas2"},
"down": {"uv": [8, 2, 15, 4], "texture": "#atlas2"}
}
},
{
"from": [0, 6, 1],
"to": [16, 10, 6],
"faces": {
"north": {"uv": [8, 13, 16, 15], "texture": "#atlas1"},
"east": {"uv": [8, 10, 10.5, 12], "texture": "#atlas3", "cullface": "east"},
"south": {"uv": [8, 0, 16, 2], "texture": "#atlas1"},
"west": {"uv": [10.5, 10, 13, 12], "texture": "#atlas3", "cullface": "west"},
"up": {"uv": [8, 8, 16, 10.5], "texture": "#atlas1"},
"down": {"uv": [8, 10.5, 16, 13], "texture": "#atlas1"}
}
},
{
"from": [0, 6, 0],
"to": [6, 10, 1],
"faces": {
"north": {"uv": [0, 13, 3, 15], "texture": "#atlas3", "cullface": "north"},
"east": {"uv": [15, 4, 15.5, 6], "texture": "#atlas2"},
"west": {"uv": [15.5, 4, 16, 6], "texture": "#atlas2", "cullface": "west"},
"up": {"uv": [0, 12, 3, 12.5], "texture": "#atlas3"},
"down": {"uv": [0, 12.5, 3, 13], "texture": "#atlas3"}
}
},
{
"from": [10, 6, 0],
"to": [16, 10, 1],
"faces": {
"north": {"uv": [3, 13, 6, 15], "texture": "#atlas3", "cullface": "north"},
"east": {"uv": [15, 6, 15.5, 8], "texture": "#atlas2", "cullface": "east"},
"west": {"uv": [15.5, 6, 16, 8], "texture": "#atlas2"},
"up": {"uv": [0, 15, 3, 15.5], "texture": "#atlas3"},
"down": {"uv": [0, 15.5, 3, 16], "texture": "#atlas3"}
}
},
{
"from": [0, 10, 0],
"to": [16, 15, 16],
"faces": {
"north": {"uv": [8, 8, 16, 10.5], "texture": "#atlas2", "cullface": "north"},
"east": {"uv": [8, 2, 16, 4.5], "texture": "#atlas1", "cullface": "east"},
"south": {"uv": [8, 10.5, 16, 13], "texture": "#atlas2", "cullface": "south"},
"west": {"uv": [8, 4.5, 16, 7], "texture": "#atlas1", "cullface": "west"},
"up": {"uv": [0, 0, 8, 8], "texture": "#atlas2"},
"down": {"uv": [0, 8, 8, 16], "texture": "#atlas2"}
}
},
{
"from": [0, 15, 13],
"to": [16, 16, 16],
"faces": {
"north": {"uv": [0, 15.5, 8, 16], "texture": "#atlas0"},
"east": {"uv": [11, 13, 12.5, 13.5], "texture": "#atlas3", "cullface": "east"},
"south": {"uv": [8, 15.5, 16, 16], "texture": "#atlas0", "cullface": "south"},
"west": {"uv": [12.5, 13, 14, 13.5], "texture": "#atlas3", "cullface": "west"},
"up": {"uv": [8, 13, 16, 14.5], "texture": "#atlas2", "cullface": "up"}
}
},
{
"from": [0, 15, 12],
"to": [6, 16, 13],
"faces": {
"north": {"uv": [6, 15.5, 9, 16], "texture": "#atlas3"},
"east": {"uv": [15.5, 13, 16, 13.5], "texture": "#atlas3"},
"south": {"uv": [9, 15.5, 12, 16], "texture": "#atlas3"},
"west": {"uv": [13.5, 12.5, 14, 13], "texture": "#atlas3", "cullface": "west"},
"up": {"uv": [3, 15.5, 6, 16], "texture": "#atlas3", "cullface": "up"}
}
},
{
"from": [10, 15, 12],
"to": [16, 16, 13],
"faces": {
"north": {"uv": [3, 15, 6, 15.5], "texture": "#atlas3"},
"east": {"uv": [14, 12.5, 14.5, 13], "texture": "#atlas3", "cullface": "east"},
"south": {"uv": [6, 15, 9, 15.5], "texture": "#atlas3"},
"west": {"uv": [14.5, 12.5, 15, 13], "texture": "#atlas3"},
"up": {"uv": [12, 15.5, 15, 16], "texture": "#atlas3", "cullface": "up"}
}
},
{
"from": [0, 15, 10],
"to": [16, 16, 12],
"faces": {
"north": {"uv": [8, 7.5, 16, 8], "texture": "#atlas0"},
"east": {"uv": [15, 15.5, 16, 16], "texture": "#atlas3", "cullface": "east"},
"south": {"uv": [8, 7, 16, 7.5], "texture": "#atlas0"},
"west": {"uv": [15, 15, 16, 15.5], "texture": "#atlas3", "cullface": "west"},
"up": {"uv": [8, 6, 16, 7], "texture": "#atlas0", "cullface": "up"}
}
},
{
"from": [0, 15, 6],
"to": [3, 16, 10],
"faces": {
"north": {"uv": [14, 13, 15.5, 13.5], "texture": "#atlas3"},
"east": {"uv": [13, 14.5, 15, 15], "texture": "#atlas3"},
"south": {"uv": [3, 12.5, 4.5, 13], "texture": "#atlas3"},
"west": {"uv": [9, 14, 11, 14.5], "texture": "#atlas3", "cullface": "west"},
"up": {"uv": [3, 0, 4.5, 2], "texture": "#atlas3", "cullface": "up"}
}
},
{
"from": [4, 15, 6],
"to": [6, 16, 10],
"faces": {
"north": {"uv": [15, 14.5, 16, 15], "texture": "#atlas3"},
"east": {"uv": [11, 14, 13, 14.5], "texture": "#atlas3"},
"south": {"uv": [15, 14, 16, 14.5], "texture": "#atlas3"},
"west": {"uv": [13, 14, 15, 14.5], "texture": "#atlas3"},
"up": {"uv": [4.5, 0, 5.5, 2], "texture": "#atlas3", "cullface": "up"}
}
},
{
"from": [10, 15, 6],
"to": [12, 16, 10],
"faces": {
"north": {"uv": [15, 13.5, 16, 14], "texture": "#atlas3"},
"east": {"uv": [9, 13.5, 11, 14], "texture": "#atlas3"},
"south": {"uv": [10.5, 12.5, 11.5, 13], "texture": "#atlas3"},
"west": {"uv": [11, 13.5, 13, 14], "texture": "#atlas3"},
"up": {"uv": [5.5, 0, 6.5, 2], "texture": "#atlas3", "cullface": "up"}
}
},
{
"from": [13, 15, 6],
"to": [16, 16, 10],
"faces": {
"north": {"uv": [4.5, 12.5, 6, 13], "texture": "#atlas3"},
"east": {"uv": [13, 13.5, 15, 14], "texture": "#atlas3", "cullface": "east"},
"south": {"uv": [6, 12.5, 7.5, 13], "texture": "#atlas3"},
"west": {"uv": [9, 13, 11, 13.5], "texture": "#atlas3"},
"up": {"uv": [6.5, 0, 8, 2], "texture": "#atlas3", "cullface": "up"}
}
},
{
"from": [0, 15, 4],
"to": [16, 16, 6],
"faces": {
"north": {"uv": [8, 3.5, 16, 4], "texture": "#atlas0"},
"east": {"uv": [11.5, 12.5, 12.5, 13], "texture": "#atlas3", "cullface": "east"},
"south": {"uv": [8, 3, 16, 3.5], "texture": "#atlas0"},
"west": {"uv": [12.5, 12.5, 13.5, 13], "texture": "#atlas3", "cullface": "west"},
"up": {"uv": [8, 2, 16, 3], "texture": "#atlas0", "cullface": "up"}
}
},
{
"from": [0, 15, 3],
"to": [6, 16, 4],
"faces": {
"north": {"uv": [12, 15, 15, 15.5], "texture": "#atlas3"},
"east": {"uv": [15, 12.5, 15.5, 13], "texture": "#atlas3"},
"south": {"uv": [6, 13, 9, 13.5], "texture": "#atlas3"},
"west": {"uv": [15.5, 12.5, 16, 13], "texture": "#atlas3", "cullface": "west"},
"up": {"uv": [9, 15, 12, 15.5], "texture": "#atlas3", "cullface": "up"}
}
},
{
"from": [10, 15, 3],
"to": [16, 16, 4],
"faces": {
"north": {"uv": [6, 14, 9, 14.5], "texture": "#atlas3"},
"east": {"uv": [3, 12, 3.5, 12.5], "texture": "#atlas3", "cullface": "east"},
"south": {"uv": [6, 14.5, 9, 15], "texture": "#atlas3"},
"west": {"uv": [3.5, 12, 4, 12.5], "texture": "#atlas3"},
"up": {"uv": [6, 13.5, 9, 14], "texture": "#atlas3", "cullface": "up"}
}
},
{
"from": [0, 15, 0],
"to": [16, 16, 3],
"faces": {
"north": {"uv": [8, 15, 16, 15.5], "texture": "#atlas1", "cullface": "north"},
"east": {"uv": [7.5, 12.5, 9, 13], "texture": "#atlas3", "cullface": "east"},
"south": {"uv": [8, 15.5, 16, 16], "texture": "#atlas1"},
"west": {"uv": [9, 12.5, 10.5, 13], "texture": "#atlas3", "cullface": "west"},
"up": {"uv": [8, 14.5, 16, 16], "texture": "#atlas2", "cullface": "up"}
}
}
]
}

View File

@@ -0,0 +1,3 @@
{
"parent": "oc2:block/network_switch"
}

View File

@@ -0,0 +1,3 @@
{
"parent": "oc2:block/pci_card_cage"
}

View File

@@ -0,0 +1,3 @@
{
"parent": "oc2:block/vxlan_hub"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 628 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,59 @@
#!/usr/bin/lua
function usage()
print("Usage:")
print(" swconfig show_hosts")
print(" swconfig show_ports")
print(" swconfig set_port <port> untagged <vid>")
print(" swconfig set_port <port> trunk_all (on|off)")
end
if not arg[1] then
usage()
return
end
local cjson = require("cjson").new()
local devbus = require('devices')
local switch = devbus:find("switch")
if not switch then
print("No switch found")
return
end
if arg[1] == "show_hosts" then
host_table = switch:getHostTable()
for _, v in ipairs(host_table) do
print(v.mac .. " : " .. v.side .. ", Age: " .. v.age)
end
elseif arg[1] == "show_ports" then
local link_state = switch:getLinkState()
for i, port in ipairs(switch:getPortConfig()) do
print("Port #" .. (i - 1) .. " " .. (link_state[i] and "UP" or "DOWN"))
print(" Untagged VLAN: " .. port.untagged)
print(" Tagged: " .. table.concat(port.tagged, ", "))
print(" Hairpin: " .. (port.hairpin and "on" or "off"))
print(" Trunk All: " .. (port.trunk_all and "on" or "off"))
end
elseif arg[1] == "set_port" then
if #arg < 4 then
usage()
return
end
local config = switch:getPortConfig()
local port = config[tonumber(arg[2]) + 1]
if not port then
print("Invalid Port Number")
return
end
if arg[3] == "untagged" then
port.untagged = tonumber(arg[4])
end
switch:setPortConfig(config)
else
usage()
return
end